using System.Collections.Generic; using System.IO; using System.Threading.Tasks; using LibHac.Ncm; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Hosting.Internal; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Moq; using NUnit.Framework; using TinfoilVibeServer.Models; using TinfoilVibeServer.Services; using TinfoilVibeServer.Utilities; namespace TinfoilVibeServerTest.Tests { [TestFixture] public class SnapshotServiceTests { private Mock> _loggerMock; private SnapshotService _service; private Mock _nspExtractorMock; private Mock _archiveHander; private Mock> _mockOptions; private SnapshotOptions _options; private MemoryCache _memoryCache; [SetUp] public void SetUp() { _mockOptions = new Mock>(); _options = new SnapshotOptions() { RomExtensions = [".nsp"], RootDirectories = ["TestData/ROMS"], SnapshotFile = "TestData/snapshot.json", SnapshotBackupFile = "TestData/snapshot.bak" }; /*// ensure ROM test directory has test files removed foreach (var file in Directory.GetFiles("TestData/ROMS")) { File.Delete(file); }*/ _mockOptions.Setup(m => m.CurrentValue).Returns(_options); _loggerMock = new Mock>(); _archiveHander = new Mock(); _nspExtractorMock = new Mock(); var hostEnv = new Mock(); var memoryCacheOptions = Options.Create(new MemoryCacheOptions()); _memoryCache = new MemoryCache(memoryCacheOptions); _nspExtractorMock.Setup(extractor => extractor.ExtractHashFromStream(It.IsAny())).Returns("HASH"); _nspExtractorMock.Setup(extractor => extractor.ExtractFromStream(It.IsAny())).Returns( new NcaMetadataWithHash(titleId: "0000000000000000","0000000000000000", version: 1, ContentMetaType.Application, "HASH")); //Settings.RootDirs = new List { "TestData/Root1", "TestData/Root2" }; _service = new SnapshotService(_memoryCache, _mockOptions.Object, _nspExtractorMock.Object, _archiveHander.Object, _loggerMock.Object, hostEnv.Object); } [TearDown] public void TearDown() { _service.Dispose(); _memoryCache.Dispose(); } [Test] public async Task BuildSnapshot_WhenFilesChanged_ShouldPersist() { // Arrange var initialHash = _service.GetSnapshot()?.Hash; var rebuilding = false; var rebuilt = false; CancellationTokenSource snapshotRebuilding = new(); _service.SnapshotRebuilding += (sender, args) => { rebuilding = true; snapshotRebuilding.Cancel(); }; CancellationTokenSource snapshotPersisting = new(); _service.SnapshotRebuilt+= (sender, args) => { rebuilt = true; snapshotPersisting.Cancel(); // Assert var newHash = _service.GetSnapshot()?.Hash; Assert.That(newHash, Is.Not.EqualTo(initialHash)); }; Timer timer = new(state => { snapshotPersisting.Cancel(); snapshotRebuilding.Cancel(); }, null, 20*1000, 0); await File.WriteAllTextAsync(_options.SnapshotFile, "[]", snapshotPersisting.Token); // Add a file to Root1 var newFile = Path.Combine(_options.RootDirectories.First(), "new.nsp"); // Act await File.WriteAllTextAsync(newFile,"TEST"); Task.Delay(4000).Wait(); try { while (_memoryCache.Count > 0) { Task.Delay(200).Wait(snapshotRebuilding.Token); } } catch (OperationCanceledException) { Assert.That(rebuilding, Is.True); } try { while (_memoryCache.Count > 0) { Task.Delay(200).Wait(snapshotPersisting.Token); } } catch (OperationCanceledException e) { Assert.That(rebuilt, Is.True); } } [Test] public async Task BuildSnapshot_NoChange_ShouldNotPersist() { // Act _service.BuildSnapshotAsync(); // Act again – snapshot should be identical _service.BuildSnapshotAsync(); // Assert _loggerMock.Verify( l => l.Log( LogLevel.Information, It.IsAny(), It.Is((v, t) => v.ToString().Contains("persisting new snapshot")), null, It.IsAny>()), Times.Never); } } }