diff --git a/src/AltGen.API.Tests/AltGen.API.Tests.csproj b/src/AltGen.API.Tests/AltGen.API.Tests.csproj
index 4684afd..303cf5b 100644
--- a/src/AltGen.API.Tests/AltGen.API.Tests.csproj
+++ b/src/AltGen.API.Tests/AltGen.API.Tests.csproj
@@ -16,7 +16,7 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
all
-
+
diff --git a/src/AltGen.Console.Tests/AltGen.Console.Tests.csproj b/src/AltGen.Console.Tests/AltGen.Console.Tests.csproj
index 401e66c..5ab1b28 100644
--- a/src/AltGen.Console.Tests/AltGen.Console.Tests.csproj
+++ b/src/AltGen.Console.Tests/AltGen.Console.Tests.csproj
@@ -8,18 +8,40 @@
-
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
-
+
+
+
+ true
+ ./TestResults/Coverage/
+ cobertura
+ [AltGen.Console]*
+ **/Program.cs
+
+
+
+
+
+
+
diff --git a/src/AltGen.Console.Tests/Unit/AddConfigCommandTests.cs b/src/AltGen.Console.Tests/Unit/AddConfigCommandTests.cs
index a7af256..f81dc7d 100644
--- a/src/AltGen.Console.Tests/Unit/AddConfigCommandTests.cs
+++ b/src/AltGen.Console.Tests/Unit/AddConfigCommandTests.cs
@@ -11,6 +11,33 @@ public class AddConfigCommandTests : IDisposable
_sut = new AddConfigCommand(_testConsole, _mockSettingsManager.Object);
}
+ [Fact]
+ public void Validate_WhenProviderIsNotSupported_ItShouldReturnError()
+ {
+ var commandSettings = new AddConfigCommand.Settings()
+ {
+ Provider = "unsupported",
+ Key = "key",
+ };
+
+ var result = commandSettings.Validate();
+
+ result.Successful.Should().BeFalse();
+ }
+
+ [Fact]
+ public void Validate_WhenProviderIsSupported_ItShouldReturnSuccess()
+ {
+ var commandSettings = new AddConfigCommand.Settings()
+ {
+ Provider = "gemini",
+ Key = "key",
+ };
+
+ var result = commandSettings.Validate();
+
+ result.Successful.Should().BeTrue();
+ }
[Fact]
public async Task ExecuteAsync_WhenSettingsDoNotExist_ItShouldCreateSettings()
diff --git a/src/AltGen.Console.Tests/Unit/AppSettingsTests.cs b/src/AltGen.Console.Tests/Unit/AppSettingsTests.cs
index e50e9ed..ef4dbd5 100644
--- a/src/AltGen.Console.Tests/Unit/AppSettingsTests.cs
+++ b/src/AltGen.Console.Tests/Unit/AppSettingsTests.cs
@@ -62,6 +62,28 @@ public class AppSettingsTests
]));
}
+ [Fact]
+ public void AddOrUpdateProvider_WhenExistingProvider_ItShouldAddNewProvider()
+ {
+ var providerSettings = new ProviderSettings("claude", "key", false);
+ var existingProviderSettings = new ProviderSettings("gemini", "key", false);
+
+ var commandSettings = new AddConfigCommand.Settings()
+ {
+ Provider = providerSettings.Provider,
+ Key = providerSettings.Key,
+ Default = providerSettings.Default,
+ };
+
+ var appSettings = new AppSettings([existingProviderSettings]);
+ var updatedAppSettings = appSettings.AddOrUpdateProvider(commandSettings);
+
+ updatedAppSettings.Should().BeEquivalentTo(new AppSettings([
+ providerSettings,
+ existingProviderSettings,
+ ]));
+ }
+
[Fact]
public void RemoveProvider_WhenProviderExists_ItShouldRemoveProvider()
{
diff --git a/src/AltGen.Console.Tests/Unit/GenerateCommandTests.cs b/src/AltGen.Console.Tests/Unit/GenerateCommandTests.cs
index 61905b8..ae5201d 100644
--- a/src/AltGen.Console.Tests/Unit/GenerateCommandTests.cs
+++ b/src/AltGen.Console.Tests/Unit/GenerateCommandTests.cs
@@ -1,6 +1,324 @@
+using System.Collections;
+
namespace AltGen.Console.Tests.Unit;
-public class GenerateCommandTests
+public class GenerateCommandTests : IDisposable
{
+ readonly TestConsole _testConsole = new();
+ readonly Mock _mockFileSystem = new();
+ readonly Mock _mockAltGenService = new();
+ readonly Mock _mockSettingsManager = new();
+ readonly GenerateCommand _sut;
+ public GenerateCommandTests()
+ {
+ _sut = new GenerateCommand(
+ _testConsole,
+ _mockFileSystem.Object,
+ _mockAltGenService.Object,
+ _mockSettingsManager.Object
+ );
+ }
+
+ [Fact]
+ public void Validate_WhenPathDoesNotExist_ItShouldReturnError()
+ {
+ _mockFileSystem
+ .Setup(static x => x.File.Exists(It.IsAny()))
+ .Returns(false);
+
+ var settings = new GenerateCommand.Settings(_mockFileSystem.Object)
+ {
+ Path = "C:/path/to/image.jpg"
+ };
+
+ var result = settings.Validate();
+
+ result.Successful.Should().BeFalse();
+ }
+
+ [Fact]
+ public void Validate_WhenPathIsNotAnImage_ItShouldReturnError()
+ {
+ _mockFileSystem
+ .Setup(static x => x.File.Exists(It.IsAny()))
+ .Returns(true);
+
+ _mockFileSystem
+ .Setup(static x => x.Path.GetExtension(It.IsAny()))
+ .Returns(".txt");
+
+ var settings = new GenerateCommand.Settings(_mockFileSystem.Object)
+ {
+ Path = "C:/path/to/image.txt"
+ };
+
+ var result = settings.Validate();
+
+ result.Successful.Should().BeFalse();
+ }
+
+ [Fact]
+ public void Validate_WhenPathIsValidImage_ItShouldReturnSuccess()
+ {
+ _mockFileSystem
+ .Setup(static x => x.File.Exists(It.IsAny()))
+ .Returns(true);
+
+ _mockFileSystem
+ .Setup(static x => x.Path.GetExtension(It.IsAny()))
+ .Returns(".jpg");
+
+ var settings = new GenerateCommand.Settings(_mockFileSystem.Object)
+ {
+ Path = "C:/path/to/image.jpg"
+ };
+
+ var result = settings.Validate();
+
+ result.Successful.Should().BeTrue();
+ }
+
+ [Fact]
+ public async Task ExecuteAsync_WhenProviderIsNotProvided_ItShouldThrowException()
+ {
+ _mockSettingsManager
+ .Setup(static x => x.GetAppSettingsAsync())
+ .ReturnsAsync(new AppSettings([]));
+
+ var settings = new GenerateCommand.Settings(_mockFileSystem.Object);
+
+ var action = async () => await _sut.ExecuteAsync(null!, settings);
+
+ await action.Should().ThrowAsync();
+ }
+
+ [Fact]
+ public async Task ExecuteAsync_WhenProviderIsProvidedOnCommandLine_ItShouldUseThatProvider()
+ {
+ _mockSettingsManager
+ .Setup(static x => x.GetAppSettingsAsync())
+ .ReturnsAsync(new AppSettings([]));
+
+ _mockFileSystem
+ .Setup(static x => x.Path.GetFileName(It.IsAny()))
+ .Returns("image.jpg");
+
+ _mockFileSystem
+ .Setup(static x => x.File.ReadAllBytesAsync(It.IsAny(), default))
+ .ReturnsAsync([0x00]);
+
+ _mockFileSystem
+ .Setup(static x => x.Path.GetExtension(It.IsAny()))
+ .Returns(".jpg");
+
+ _mockAltGenService
+ .Setup(static x => x.GenerateAltTextAsync(It.IsAny()))
+ .ReturnsAsync("alt text");
+
+ var settings = new GenerateCommand.Settings(_mockFileSystem.Object)
+ {
+ Provider = "gemini",
+ Key = "key",
+ Path = "C:/path/to/image.jpg"
+ };
+
+ var result = await _sut.ExecuteAsync(null!, settings);
+
+ result.Should().Be(0);
+
+ _mockAltGenService.Verify(
+ static x => x.GenerateAltTextAsync(It.Is(
+ static x => x.Provider == "gemini" &&
+ x.ProviderKey == "key" &&
+ x.FileName == "image.jpg" &&
+ x.Image.Length == 1 &&
+ x.ContentType == "image/jpeg"
+ )),
+ Times.Once
+ );
+ }
+
+ [Fact]
+ public async Task ExecuteAsync_WhenProviderIsNotProvidedOnCommandLine_ItShouldUseDefaultProvider()
+ {
+ _mockSettingsManager
+ .Setup(static x => x.GetAppSettingsAsync())
+ .ReturnsAsync(new AppSettings([
+ new("gemini", "key", true)
+ ]));
+
+ _mockFileSystem
+ .Setup(static x => x.Path.GetFileName(It.IsAny()))
+ .Returns("image.jpg");
+
+ _mockFileSystem
+ .Setup(static x => x.File.ReadAllBytesAsync(It.IsAny(), default))
+ .ReturnsAsync([0x00]);
+
+ _mockFileSystem
+ .Setup(static x => x.Path.GetExtension(It.IsAny()))
+ .Returns(".jpg");
+
+ _mockAltGenService
+ .Setup(static x => x.GenerateAltTextAsync(It.IsAny()))
+ .ReturnsAsync("alt text");
+
+ var settings = new GenerateCommand.Settings(_mockFileSystem.Object)
+ {
+ Key = "key",
+ Path = "C:/path/to/image.jpg"
+ };
+
+ var result = await _sut.ExecuteAsync(null!, settings);
+
+ result.Should().Be(0);
+
+ _mockAltGenService.Verify(
+ static x => x.GenerateAltTextAsync(It.Is(
+ static x => x.Provider == "gemini" &&
+ x.ProviderKey == "key" &&
+ x.FileName == "image.jpg" &&
+ x.Image.Length == 1 &&
+ x.ContentType == "image/jpeg"
+ )),
+ Times.Once
+ );
+ }
+
+ [Fact]
+ public async Task ExecuteAsync_WhenProviderIsNotSupported_ItShouldThrowException()
+ {
+ _mockSettingsManager
+ .Setup(static x => x.GetAppSettingsAsync())
+ .ReturnsAsync(new AppSettings([]));
+
+ var settings = new GenerateCommand.Settings(_mockFileSystem.Object)
+ {
+ Provider = "unsupported",
+ Key = "key",
+ Path = "C:/path/to/image.jpg"
+ };
+
+ var action = async () => await _sut.ExecuteAsync(null!, settings);
+
+ await action.Should().ThrowAsync();
+ }
+
+ [Fact]
+ public async Task ExecuteAsync_WhenNoKeyIsProvided_ItShouldThrowException()
+ {
+ _mockSettingsManager
+ .Setup(static x => x.GetAppSettingsAsync())
+ .ReturnsAsync(new AppSettings([]));
+
+ var settings = new GenerateCommand.Settings(_mockFileSystem.Object)
+ {
+ Provider = "gemini",
+ Path = "C:/path/to/image.jpg"
+ };
+
+ var action = async () => await _sut.ExecuteAsync(null!, settings);
+
+ await action.Should().ThrowAsync();
+ }
+
+ [Fact]
+ public async Task ExecuteAsync_WhenKeyIsProvidedOnCommandLine_ItShouldUseThatKey()
+ {
+ _mockSettingsManager
+ .Setup(static x => x.GetAppSettingsAsync())
+ .ReturnsAsync(new AppSettings([]));
+
+ _mockFileSystem
+ .Setup(static x => x.Path.GetFileName(It.IsAny()))
+ .Returns("image.jpg");
+
+ _mockFileSystem
+ .Setup(static x => x.File.ReadAllBytesAsync(It.IsAny(), default))
+ .ReturnsAsync([0x00]);
+
+ _mockFileSystem
+ .Setup(static x => x.Path.GetExtension(It.IsAny()))
+ .Returns(".jpg");
+
+ _mockAltGenService
+ .Setup(static x => x.GenerateAltTextAsync(It.IsAny()))
+ .ReturnsAsync("alt text");
+
+ var settings = new GenerateCommand.Settings(_mockFileSystem.Object)
+ {
+ Provider = "gemini",
+ Key = "key",
+ Path = "C:/path/to/image.jpg"
+ };
+
+ var result = await _sut.ExecuteAsync(null!, settings);
+
+ result.Should().Be(0);
+
+ _mockAltGenService.Verify(
+ static x => x.GenerateAltTextAsync(It.Is(
+ static x => x.Provider == "gemini" &&
+ x.ProviderKey == "key" &&
+ x.FileName == "image.jpg" &&
+ x.Image.Length == 1 &&
+ x.ContentType == "image/jpeg"
+ )),
+ Times.Once
+ );
+ }
+
+ [Fact]
+ public async Task ExecuteAsync_WhenNoKeyIsProvidedOnCommandLine_ItShouldUseDefaultKey()
+ {
+ _mockSettingsManager
+ .Setup(static x => x.GetAppSettingsAsync())
+ .ReturnsAsync(new AppSettings([
+ new("gemini", "key", true)
+ ]));
+
+ _mockFileSystem
+ .Setup(static x => x.Path.GetFileName(It.IsAny()))
+ .Returns("image.jpg");
+
+ _mockFileSystem
+ .Setup(static x => x.File.ReadAllBytesAsync(It.IsAny(), default))
+ .ReturnsAsync([0x00]);
+
+ _mockFileSystem
+ .Setup(static x => x.Path.GetExtension(It.IsAny()))
+ .Returns(".jpg");
+
+ _mockAltGenService
+ .Setup(static x => x.GenerateAltTextAsync(It.IsAny()))
+ .ReturnsAsync("alt text");
+
+ var settings = new GenerateCommand.Settings(_mockFileSystem.Object)
+ {
+ Provider = "gemini",
+ Path = "C:/path/to/image.jpg"
+ };
+
+ var result = await _sut.ExecuteAsync(null!, settings);
+
+ result.Should().Be(0);
+
+ _mockAltGenService.Verify(
+ static x => x.GenerateAltTextAsync(It.Is(
+ static x => x.Provider == "gemini" &&
+ x.ProviderKey == "key" &&
+ x.FileName == "image.jpg" &&
+ x.Image.Length == 1 &&
+ x.ContentType == "image/jpeg"
+ )),
+ Times.Once
+ );
+ }
+
+ public void Dispose()
+ {
+ _testConsole.Dispose();
+ GC.SuppressFinalize(this);
+ }
}
\ No newline at end of file
diff --git a/src/AltGen.Console.Tests/Unit/HostBuilderExtensionsTests.cs b/src/AltGen.Console.Tests/Unit/HostBuilderExtensionsTests.cs
new file mode 100644
index 0000000..c126d99
--- /dev/null
+++ b/src/AltGen.Console.Tests/Unit/HostBuilderExtensionsTests.cs
@@ -0,0 +1,14 @@
+namespace AltGen.Console.Tests.Unit;
+
+public class HostBuilderExtensionsTests
+{
+ [Fact]
+ public void BuildApp_WhenCalled_ItShouldBuildApp()
+ {
+ var hostBuilder = new HostBuilder();
+
+ var app = hostBuilder.BuildApp();
+
+ app.Should().NotBeNull();
+ }
+}
\ No newline at end of file
diff --git a/src/AltGen.Console.Tests/Unit/TypeRegistrarTests.cs b/src/AltGen.Console.Tests/Unit/TypeRegistrarTests.cs
new file mode 100644
index 0000000..5269540
--- /dev/null
+++ b/src/AltGen.Console.Tests/Unit/TypeRegistrarTests.cs
@@ -0,0 +1,74 @@
+namespace AltGen.Console.Tests.Unit;
+
+public class TypeRegistrarTests
+{
+ readonly Mock _mockBuilder = new();
+ readonly TypeRegistrar _sut;
+
+ public TypeRegistrarTests()
+ {
+ _sut = new TypeRegistrar(_mockBuilder.Object);
+ }
+
+ [Fact]
+ public void Register_WhenCalled_ItShouldRegisterService()
+ {
+ var service = typeof(IService);
+ var implementation = typeof(Implementation);
+
+ _sut.Register(service, implementation);
+
+ _mockBuilder.Verify(static x => x.ConfigureServices(It.IsAny>()), Times.Once);
+ }
+
+ [Fact]
+ public void RegisterInstance_WhenCalled_ItShouldRegisterService()
+ {
+ var service = typeof(IService);
+ var implementation = new Implementation();
+
+ _sut.RegisterInstance(service, implementation);
+
+ _mockBuilder.Verify(static x => x.ConfigureServices(It.IsAny>()), Times.Once);
+ }
+
+ [Fact]
+ public void RegisterLazy_WhenCalled_ItShouldRegisterService()
+ {
+ var service = typeof(IService);
+
+ static Implementation Func()
+ {
+ return new Implementation();
+ }
+
+ _sut.RegisterLazy(service, Func);
+
+ _mockBuilder.Verify(static x => x.ConfigureServices(It.IsAny>()), Times.Once);
+ }
+
+ [Fact]
+ public void RegisterLazy_WhenCalledAndFuncIsNull_ItShouldThrowArgumentNullException()
+ {
+ var service = typeof(IService);
+
+ var act = () => _sut.RegisterLazy(service, null!);
+
+ act.Should().Throw();
+ }
+
+ [Fact]
+ public void Build_WhenCalled_ItShouldReturnTypeResolver()
+ {
+ _mockBuilder.Setup(static x => x.Build()).Returns(Mock.Of());
+
+ var actual = _sut.Build();
+
+ actual.Should().BeOfType();
+
+ _mockBuilder.Verify(static x => x.Build(), Times.Once);
+ }
+
+ interface IService { }
+ class Implementation : IService { }
+}
\ No newline at end of file
diff --git a/src/AltGen.Console.Tests/Unit/TypeResolverTests.cs b/src/AltGen.Console.Tests/Unit/TypeResolverTests.cs
new file mode 100644
index 0000000..ed23cb4
--- /dev/null
+++ b/src/AltGen.Console.Tests/Unit/TypeResolverTests.cs
@@ -0,0 +1,55 @@
+namespace AltGen.Console.Tests.Unit;
+
+public class TypeResolverTests
+{
+ readonly Mock _mockHost = new();
+
+ [Fact]
+ public void Constructor_WhenCalledWithNullHost_ItShouldThrowArgumentNullException()
+ {
+ var act = static () => new TypeResolver(null!);
+
+ act.Should().Throw();
+ }
+
+ [Fact]
+ public void Resolve_WhenCalledWithNullType_ItShouldReturnNull()
+ {
+ var sut = new TypeResolver(_mockHost.Object);
+
+ var result = sut.Resolve(null);
+
+ result.Should().BeNull();
+ }
+
+ [Fact]
+ public void Resolve_WhenCalledWithValidType_ItShouldReturnService()
+ {
+ var sut = new TypeResolver(_mockHost.Object);
+ var service = typeof(IService);
+
+ _mockHost
+ .Setup(x => x.Services.GetService(service))
+ .Returns(new Implementation());
+
+ var result = sut.Resolve(service);
+
+ result.Should().NotBeNull();
+
+ _mockHost.Verify(x => x.Services.GetService(service), Times.Once);
+ }
+
+ [Fact]
+ public void Dispose_WhenCalled_ItShouldDisposeHost()
+ {
+ var sut = new TypeResolver(_mockHost.Object);
+
+ sut.Dispose();
+
+ _mockHost.Verify(static x => x.Dispose(), Times.Once);
+ }
+
+ interface IService { }
+
+ class Implementation : IService { }
+}
\ No newline at end of file
diff --git a/src/AltGen.Console.Tests/Usings.cs b/src/AltGen.Console.Tests/Usings.cs
index 83582f6..73aaf61 100644
--- a/src/AltGen.Console.Tests/Usings.cs
+++ b/src/AltGen.Console.Tests/Usings.cs
@@ -5,11 +5,11 @@ global using AltGen.Console.Common;
global using AltGen.Console.Config;
global using AltGen.Console.Generate;
-global using FluentAssertions;
+global using Microsoft.Extensions.DependencyInjection;
+global using Microsoft.Extensions.Hosting;
global using Moq;
global using RichardSzalay.MockHttp;
-global using Spectre.Console;
-global using Spectre.Console.Testing;
+global using Spectre.Console.Testing;
\ No newline at end of file
diff --git a/src/AltGen.Console/Generate/GenerateCommand.cs b/src/AltGen.Console/Generate/GenerateCommand.cs
index c076088..5f918d0 100644
--- a/src/AltGen.Console/Generate/GenerateCommand.cs
+++ b/src/AltGen.Console/Generate/GenerateCommand.cs
@@ -40,7 +40,6 @@ sealed class GenerateCommand(
public override ValidationResult Validate()
{
-
var pathExists = _fileSystem.File.Exists(Path);
if (pathExists is false)
diff --git a/src/AltGen.Console/Usings.cs b/src/AltGen.Console/Usings.cs
index 0782a80..2e74070 100644
--- a/src/AltGen.Console/Usings.cs
+++ b/src/AltGen.Console/Usings.cs
@@ -1,4 +1,5 @@
global using System.ComponentModel;
+global using System.Diagnostics.CodeAnalysis;
global using System.IO.Abstractions;
global using System.Net.Http.Headers;
global using System.Text.Json;
@@ -13,4 +14,4 @@ global using Microsoft.Extensions.Hosting;
global using Microsoft.Extensions.Logging;
global using Spectre.Console;
-global using Spectre.Console.Cli;
\ No newline at end of file
+global using Spectre.Console.Cli;