using System; using System.IO; using System.IO.Abstractions; using FluentAssertions; using Newtonsoft.Json; using NSubstitute; using NUnit.Framework; using Serilog; using Trash.Cache; using Trash.Config; namespace Trash.Tests.Cache { [TestFixture] [Parallelizable(ParallelScope.All)] public class ServiceCacheTest { private class Context { public Context(IFileSystem? fs = null) { Filesystem = fs ?? Substitute.For(); StoragePath = Substitute.For(); ServiceConfig = Substitute.For(); Cache = new ServiceCache(Filesystem, StoragePath, ServiceConfig, Substitute.For()); } public ServiceCache Cache { get; } public IServiceConfiguration ServiceConfig { get; } public ICacheStoragePath StoragePath { get; } public IFileSystem Filesystem { get; } } private class ObjectWithoutAttribute { } private const string ValidObjectName = "azAZ_09"; [CacheObjectName(ValidObjectName)] private class ObjectWithAttribute { public string TestValue { get; init; } = ""; } [CacheObjectName("invalid+name")] private class ObjectWithAttributeInvalidChars { } [Test] public void Load_NoFileExists_ReturnsNull() { var ctx = new Context(); ctx.Filesystem.File.Exists(Arg.Any()).Returns(false); var result = ctx.Cache.Load(); result.Should().BeNull(); } [Test] public void Load_WithAttribute_ParsesCorrectly() { var ctx = new Context(); ctx.StoragePath.Path.Returns("testpath"); dynamic testJson = new {TestValue = "Foo"}; ctx.Filesystem.File.Exists(Arg.Any()).Returns(true); ctx.Filesystem.File.ReadAllText(Arg.Any()) .Returns(_ => JsonConvert.SerializeObject(testJson)); var obj = ctx.Cache.Load(); obj.Should().NotBeNull(); obj!.TestValue.Should().Be("Foo"); ctx.Filesystem.File.Received().ReadAllText(Path.Combine("testpath", "c59d1c81", $"{ValidObjectName}.json")); } [Test] public void Load_WithAttributeInvalidName_ThrowsException() { var ctx = new Context(); Action act = () => ctx.Cache.Load(); act.Should() .Throw() .WithMessage("*'invalid+name' has unacceptable characters*"); } [Test] public void Load_WithoutAttribute_Throws() { var ctx = new Context(); Action act = () => ctx.Cache.Load(); act.Should() .Throw() .WithMessage("CacheObjectNameAttribute is missing*"); } [Test] public void Save_WithAttribute_ParsesCorrectly() { var ctx = new Context(); ctx.StoragePath.Path.Returns("testpath"); ctx.Cache.Save(new ObjectWithAttribute {TestValue = "Foo"}); var expectedParentDirectory = Path.Combine("testpath", "c59d1c81"); ctx.Filesystem.Directory.Received().CreateDirectory(expectedParentDirectory); dynamic expectedJson = new {TestValue = "Foo"}; var expectedPath = Path.Combine(expectedParentDirectory, $"{ValidObjectName}.json"); ctx.Filesystem.File.Received() .WriteAllText(expectedPath, JsonConvert.SerializeObject(expectedJson, Formatting.Indented)); } [Test] public void Save_WithAttributeInvalidName_ThrowsException() { var ctx = new Context(); Action act = () => ctx.Cache.Save(new ObjectWithAttributeInvalidChars()); act.Should() .Throw() .WithMessage("*'invalid+name' has unacceptable characters*"); } [Test] public void Save_WithoutAttribute_Throws() { var ctx = new Context(); Action act = () => ctx.Cache.Save(new ObjectWithoutAttribute()); act.Should() .Throw() .WithMessage("CacheObjectNameAttribute is missing*"); } [Test] public void When_cache_file_is_empty_do_not_throw() { var ctx = new Context(); ctx.Filesystem.File.Exists(Arg.Any()).Returns(true); ctx.Filesystem.File.ReadAllText(Arg.Any()) .Returns(_ => ""); Action act = () => ctx.Cache.Load(); act.Should().NotThrow(); } } }