UI/logic updates, tests added, backend updated
This commit is contained in:
124
Seasoned.Tests/RecipeControllerTests.cs
Normal file
124
Seasoned.Tests/RecipeControllerTests.cs
Normal file
@@ -0,0 +1,124 @@
|
||||
using Moq;
|
||||
using Xunit;
|
||||
using FluentAssertions;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Seasoned.Backend.Controllers;
|
||||
using Seasoned.Backend.Services;
|
||||
using Seasoned.Backend.DTOs;
|
||||
using Seasoned.Backend.Data;
|
||||
using Seasoned.Backend.Models;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace Seasoned.Tests;
|
||||
|
||||
public class RecipeControllerTests
|
||||
{
|
||||
private readonly Mock<IRecipeService> _mockService;
|
||||
private readonly ApplicationDbContext _context;
|
||||
private readonly RecipeController _controller;
|
||||
private readonly string _testUserId = "chef-123";
|
||||
|
||||
public RecipeControllerTests()
|
||||
{
|
||||
_mockService = new Mock<IRecipeService>();
|
||||
|
||||
// Setup InMemory Postgres replacement
|
||||
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
|
||||
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
|
||||
.Options;
|
||||
_context = new ApplicationDbContext(options);
|
||||
|
||||
_controller = new RecipeController(_mockService.Object, _context);
|
||||
|
||||
// Mock the User Identity (User.FindFirstValue(ClaimTypes.NameIdentifier))
|
||||
var user = new ClaimsPrincipal(new ClaimsIdentity(new Claim[]
|
||||
{
|
||||
new Claim(ClaimTypes.NameIdentifier, _testUserId),
|
||||
}, "mock"));
|
||||
|
||||
_controller.ControllerContext = new ControllerContext()
|
||||
{
|
||||
HttpContext = new DefaultHttpContext() { User = user }
|
||||
};
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UploadRecipe_ReturnsOk_WithParsedDataAndImage()
|
||||
{
|
||||
// Arrange
|
||||
var fakeRecipe = new RecipeResponseDto
|
||||
{
|
||||
Title = "Roasted Garlic Pasta",
|
||||
ImageUrl = "data:image/png;base64,header_captured_image"
|
||||
};
|
||||
|
||||
_mockService.Setup(s => s.ParseRecipeImageAsync(It.IsAny<IFormFile>()))
|
||||
.ReturnsAsync(fakeRecipe);
|
||||
|
||||
var file = CreateMockFile("test-recipe.jpg");
|
||||
|
||||
// Act
|
||||
var result = await _controller.UploadRecipe(file);
|
||||
|
||||
// Assert
|
||||
var okResult = result.Result as OkObjectResult;
|
||||
okResult.Should().NotBeNull();
|
||||
var returned = okResult!.Value as RecipeResponseDto;
|
||||
returned!.Title.Should().Be("Roasted Garlic Pasta");
|
||||
returned.ImageUrl.Should().Contain("base64");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SaveRecipe_PersistsToPostgres_ForCurrentUser()
|
||||
{
|
||||
// Arrange
|
||||
var dto = new RecipeResponseDto
|
||||
{
|
||||
Title = "Jenkins Special Brew",
|
||||
Ingredients = new List<string> { "Coffee", "Code" },
|
||||
ImageUrl = "base64-string"
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = await _controller.SaveRecipe(dto);
|
||||
|
||||
// Assert
|
||||
var okResult = result as OkResult; // Or OkObjectResult based on your Controller return
|
||||
var savedRecipe = await _context.Recipes.FirstOrDefaultAsync(r => r.Title == "Jenkins Special Brew");
|
||||
|
||||
savedRecipe.Should().NotBeNull();
|
||||
savedRecipe!.UserId.Should().Be(_testUserId);
|
||||
savedRecipe.ImageUrl.Should().Be("base64-string");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ConsultChef_ReturnsChefResponse()
|
||||
{
|
||||
// Arrange
|
||||
var request = new ChatRequestDto { Prompt = "How do I sear a steak?" };
|
||||
var expectedResponse = new ChefConsultResponseDto { Reply = "High heat, my friend!" };
|
||||
|
||||
_mockService.Setup(s => s.ConsultChefAsync(It.IsAny<string>()))
|
||||
.ReturnsAsync(expectedResponse);
|
||||
|
||||
// Act
|
||||
var result = await _controller.Consult(request);
|
||||
|
||||
// Assert
|
||||
var okResult = result as OkObjectResult;
|
||||
var response = okResult!.Value as ChefConsultResponseDto;
|
||||
response!.Reply.Should().Be("High heat, my friend!");
|
||||
}
|
||||
|
||||
private IFormFile CreateMockFile(string fileName)
|
||||
{
|
||||
var ms = new MemoryStream();
|
||||
var writer = new StreamWriter(ms);
|
||||
writer.Write("fake image binary data");
|
||||
writer.Flush();
|
||||
ms.Position = 0;
|
||||
return new FormFile(ms, 0, ms.Length, "image", fileName);
|
||||
}
|
||||
}
|
||||
70
Seasoned.Tests/RecipeServiceTests.cs
Normal file
70
Seasoned.Tests/RecipeServiceTests.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
using Moq;
|
||||
using Xunit;
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Seasoned.Backend.Services;
|
||||
using Seasoned.Backend.DTOs;
|
||||
using System.Text;
|
||||
|
||||
namespace Seasoned.Tests;
|
||||
|
||||
public class RecipeServiceTests
|
||||
{
|
||||
private readonly Mock<IConfiguration> _mockConfig;
|
||||
private readonly RecipeService _service;
|
||||
|
||||
public RecipeServiceTests()
|
||||
{
|
||||
_mockConfig = new Mock<IConfiguration>();
|
||||
// Mock the API Key retrieval
|
||||
_mockConfig.Setup(c => c["GEMINI_API_KEY"]).Returns("fake-api-key-123");
|
||||
|
||||
_service = new RecipeService(_mockConfig.Object);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ThrowsException_WhenApiKeyIsMissing()
|
||||
{
|
||||
// Arrange
|
||||
var emptyConfig = new Mock<IConfiguration>();
|
||||
emptyConfig.Setup(c => c["GEMINI_API_KEY"]).Returns((string?)null);
|
||||
|
||||
// Act
|
||||
Action act = () => new RecipeService(emptyConfig.Object);
|
||||
|
||||
// Assert
|
||||
act.Should().Throw<ArgumentNullException>().WithMessage("*API Key missing*");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ParseRecipeImageAsync_ReturnsError_WhenImageIsEmpty()
|
||||
{
|
||||
// Arrange: Create an empty file
|
||||
var fileMock = new Mock<IFormFile>();
|
||||
fileMock.Setup(f => f.Length).Returns(0);
|
||||
|
||||
// Act & Assert
|
||||
var result = await _service.ParseRecipeImageAsync(fileMock.Object);
|
||||
|
||||
result.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseRecipeImageAsync_AttachesCorrectBase64Header()
|
||||
{
|
||||
// Arrange
|
||||
var content = "fake-image-data";
|
||||
var fileName = "test.png";
|
||||
var ms = new MemoryStream(Encoding.UTF8.GetBytes(content));
|
||||
var mockFile = new FormFile(ms, 0, ms.Length, "image", fileName)
|
||||
{
|
||||
Headers = new HeaderDictionary(),
|
||||
ContentType = "image/png"
|
||||
};
|
||||
|
||||
var resultImageUrl = $"data:{mockFile.ContentType};base64,{Convert.ToBase64String(ms.ToArray())}";
|
||||
|
||||
resultImageUrl.Should().StartWith("data:image/png;base64,");
|
||||
}
|
||||
}
|
||||
@@ -8,20 +8,24 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
|
||||
<_Parameter1>Seasoned.Tests</_Parameter1>
|
||||
</AssemblyAttribute>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.2" />
|
||||
<PackageReference Include="FluentAssertions" Version="8.8.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||
<PackageReference Include="Moq" Version="4.20.72" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="9.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Seasoned.Backend\Seasoned.Backend.csproj" />
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Seasoned.Backend\Seasoned.Backend.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
using Moq;
|
||||
using Xunit;
|
||||
using FluentAssertions;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Seasoned.Backend.Controllers;
|
||||
using Seasoned.Backend.Services;
|
||||
using Seasoned.Backend.DTOs;
|
||||
|
||||
namespace Seasoned.Tests;
|
||||
|
||||
public class RecipeControllerTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task ParseRecipe_ReturnsOk_WhenImageIsValid()
|
||||
{
|
||||
// 1. Arrange: Create a "Fake" Service
|
||||
var mockService = new Mock<IRecipeService>();
|
||||
var fakeRecipe = new RecipeResponseDto { Title = "Test Recipe" };
|
||||
|
||||
mockService.Setup(s => s.ParseImageAsync(It.IsAny<IFormFile>()))
|
||||
.ReturnsAsync(fakeRecipe);
|
||||
|
||||
var controller = new RecipeController(mockService.Object);
|
||||
|
||||
// Create a fake image file
|
||||
var content = "fake image content";
|
||||
var fileName = "test.jpg";
|
||||
var ms = new MemoryStream();
|
||||
var writer = new StreamWriter(ms);
|
||||
writer.Write(content);
|
||||
writer.Flush();
|
||||
ms.Position = 0;
|
||||
var mockFile = new FormFile(ms, 0, ms.Length, "id_from_form", fileName);
|
||||
|
||||
// 2. Act: Call the Controller
|
||||
var result = await controller.ParseRecipe(mockFile);
|
||||
|
||||
// 3. Assert: Check the result
|
||||
var okResult = result as OkObjectResult;
|
||||
okResult.Should().NotBeNull();
|
||||
okResult!.StatusCode.Should().Be(200);
|
||||
|
||||
var returnedRecipe = okResult.Value as RecipeResponseDto;
|
||||
returnedRecipe!.Title.Should().Be("Test Recipe");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user