Added tests, need to go through them manually to check if they make sense and are correct later

This commit is contained in:
moarten
2026-04-19 20:55:26 +02:00
parent 11bff0de63
commit 3a1071dadc
10 changed files with 862 additions and 3 deletions

View File

@@ -0,0 +1,157 @@
using System.Net;
using System.Net.Http.Json;
using FluentAssertions;
using Lutra.API.IntegrationTests.Infrastructure;
using Lutra.Application.Supermarkten;
using Lutra.Domain.Entities;
namespace Lutra.API.IntegrationTests.Controllers;
public class SupermarktenControllerTests(LutraApiFactory factory)
: IntegrationTestBase(factory)
{
// ── GET /api/supermarkten ─────────────────────────────────────────────────
[Fact]
public async Task Get_ReturnsOk_WithEmptyList_WhenNoDataExists()
{
var response = await Client.GetAsync("/api/supermarkten");
response.StatusCode.Should().Be(HttpStatusCode.OK);
var body = await response.Content.ReadFromJsonAsync<GetSupermarkten.Response>();
body.Should().NotBeNull();
body!.Supermarkten.Should().BeEmpty();
}
[Fact]
public async Task Get_ReturnsSeededSupermarkt()
{
await SeedAsync(new Supermarkt
{
Id = Guid.NewGuid(), Naam = "Albert Heijn",
CreatedAt = DateTime.UtcNow, ModifiedAt = DateTime.UtcNow
});
var response = await Client.GetAsync("/api/supermarkten");
response.StatusCode.Should().Be(HttpStatusCode.OK);
var body = await response.Content.ReadFromJsonAsync<GetSupermarkten.Response>();
body!.Supermarkten.Should().HaveCount(1);
body.Supermarkten.First().Naam.Should().Be("Albert Heijn");
}
[Fact]
public async Task Get_ReturnsAllSeededSupermarkten()
{
await SeedAsync(new Supermarkt
{
Id = Guid.NewGuid(), Naam = "Albert Heijn",
CreatedAt = DateTime.UtcNow, ModifiedAt = DateTime.UtcNow
});
await SeedAsync(new Supermarkt
{
Id = Guid.NewGuid(), Naam = "Jumbo",
CreatedAt = DateTime.UtcNow, ModifiedAt = DateTime.UtcNow
});
await SeedAsync(new Supermarkt
{
Id = Guid.NewGuid(), Naam = "Picnic",
CreatedAt = DateTime.UtcNow, ModifiedAt = DateTime.UtcNow
});
var response = await Client.GetAsync("/api/supermarkten");
response.StatusCode.Should().Be(HttpStatusCode.OK);
var body = await response.Content.ReadFromJsonAsync<GetSupermarkten.Response>();
body!.Supermarkten.Should().HaveCount(3);
}
// ── GET /api/supermarkten — pagination ────────────────────────────────────
[Fact]
public async Task Get_Pagination_ReturnsCorrectPage()
{
await SeedAsync(new Supermarkt
{
Id = Guid.NewGuid(), Naam = "Albert Heijn",
CreatedAt = DateTime.UtcNow, ModifiedAt = DateTime.UtcNow
});
await SeedAsync(new Supermarkt
{
Id = Guid.NewGuid(), Naam = "Jumbo",
CreatedAt = DateTime.UtcNow, ModifiedAt = DateTime.UtcNow
});
await SeedAsync(new Supermarkt
{
Id = Guid.NewGuid(), Naam = "Picnic",
CreatedAt = DateTime.UtcNow, ModifiedAt = DateTime.UtcNow
});
var response = await Client.GetAsync("/api/supermarkten?skip=1&take=1");
response.StatusCode.Should().Be(HttpStatusCode.OK);
var body = await response.Content.ReadFromJsonAsync<GetSupermarkten.Response>();
body!.Supermarkten.Should().HaveCount(1);
// Handler orders by Naam ascending, so skip=1 skips "Albert Heijn" and returns "Jumbo".
body.Supermarkten.First().Naam.Should().Be("Jumbo");
}
[Fact]
public async Task Get_Pagination_ReturnsEmptyList_WhenSkipExceedsTotalCount()
{
await SeedAsync(new Supermarkt
{
Id = Guid.NewGuid(), Naam = "Albert Heijn",
CreatedAt = DateTime.UtcNow, ModifiedAt = DateTime.UtcNow
});
var response = await Client.GetAsync("/api/supermarkten?skip=10&take=50");
response.StatusCode.Should().Be(HttpStatusCode.OK);
var body = await response.Content.ReadFromJsonAsync<GetSupermarkten.Response>();
body!.Supermarkten.Should().BeEmpty();
}
// ── GET /api/supermarkten — sorting ───────────────────────────────────────
[Fact]
public async Task Get_ReturnsSupermarkten_OrderedByNaamAscending()
{
await SeedAsync(new Supermarkt
{
Id = Guid.NewGuid(), Naam = "Picnic",
CreatedAt = DateTime.UtcNow, ModifiedAt = DateTime.UtcNow
});
await SeedAsync(new Supermarkt
{
Id = Guid.NewGuid(), Naam = "Albert Heijn",
CreatedAt = DateTime.UtcNow, ModifiedAt = DateTime.UtcNow
});
var response = await Client.GetAsync("/api/supermarkten");
response.StatusCode.Should().Be(HttpStatusCode.OK);
var body = await response.Content.ReadFromJsonAsync<GetSupermarkten.Response>();
var namen = body!.Supermarkten.Select(s => s.Naam).ToList();
namen.Should().BeInAscendingOrder();
}
// ── Soft-delete behaviour ─────────────────────────────────────────────────
[Fact]
public async Task Get_DoesNotReturn_SoftDeletedSupermarkt()
{
await SeedAsync(new Supermarkt
{
Id = Guid.NewGuid(), Naam = "Verwijderd",
CreatedAt = DateTime.UtcNow, ModifiedAt = DateTime.UtcNow,
DeletedAt = DateTime.UtcNow
});
var response = await Client.GetAsync("/api/supermarkten");
response.StatusCode.Should().Be(HttpStatusCode.OK);
var body = await response.Content.ReadFromJsonAsync<GetSupermarkten.Response>();
body!.Supermarkten.Should().BeEmpty();
}
}

View File

@@ -0,0 +1,270 @@
using System.Net;
using System.Net.Http.Json;
using FluentAssertions;
using Lutra.API.IntegrationTests.Infrastructure;
using Lutra.API.Requests;
using Lutra.Application.Verspakketten;
using Lutra.Domain.Entities;
namespace Lutra.API.IntegrationTests.Controllers;
public class VerspakkettenControllerTests(LutraApiFactory factory)
: IntegrationTestBase(factory)
{
// ── GET /api/verspakketten ────────────────────────────────────────────────
[Fact]
public async Task Get_ReturnsOk_WithEmptyList_WhenNoDataExists()
{
var response = await Client.GetAsync("/api/verspakketten");
response.StatusCode.Should().Be(HttpStatusCode.OK);
var body = await response.Content.ReadFromJsonAsync<GetVerspakketten.Response>();
body.Should().NotBeNull();
body!.Verspakketten.Should().BeEmpty();
}
[Fact]
public async Task Get_ReturnsSeededVerspakket()
{
var supermarkt = await SeedAsync(new Supermarkt
{
Id = Guid.NewGuid(), Naam = "AH",
CreatedAt = DateTime.UtcNow, ModifiedAt = DateTime.UtcNow
});
await SeedAsync(new Verspakket
{
Id = Guid.NewGuid(), Naam = "Lente Pakket", AantalPersonen = 2,
SupermarktId = supermarkt.Id,
CreatedAt = DateTime.UtcNow, ModifiedAt = DateTime.UtcNow
});
var response = await Client.GetAsync("/api/verspakketten");
response.StatusCode.Should().Be(HttpStatusCode.OK);
var body = await response.Content.ReadFromJsonAsync<GetVerspakketten.Response>();
body!.Verspakketten.Should().HaveCount(1);
body.Verspakketten.First().Naam.Should().Be("Lente Pakket");
}
// ── GET /api/verspakketten/{id} ───────────────────────────────────────────
[Fact]
public async Task GetById_ReturnsNotFound_WhenVerspakketDoesNotExist()
{
var response = await Client.GetAsync($"/api/verspakketten/{Guid.NewGuid()}");
response.StatusCode.Should().Be(HttpStatusCode.NotFound);
}
[Fact]
public async Task GetById_ReturnsVerspakket_WhenItExists()
{
var supermarkt = await SeedAsync(new Supermarkt
{
Id = Guid.NewGuid(), Naam = "Jumbo",
CreatedAt = DateTime.UtcNow, ModifiedAt = DateTime.UtcNow
});
var verspakket = await SeedAsync(new Verspakket
{
Id = Guid.NewGuid(), Naam = "Zomer Pakket", AantalPersonen = 4,
SupermarktId = supermarkt.Id,
CreatedAt = DateTime.UtcNow, ModifiedAt = DateTime.UtcNow
});
var response = await Client.GetAsync($"/api/verspakketten/{verspakket.Id}");
response.StatusCode.Should().Be(HttpStatusCode.OK);
var body = await response.Content.ReadFromJsonAsync<GetVerspakket.Response>();
body!.Verspakket.Should().NotBeNull();
body.Verspakket!.Naam.Should().Be("Zomer Pakket");
}
// ── POST /api/verspakketten ───────────────────────────────────────────────
[Fact]
public async Task Post_CreatesVerspakket_AndReturns201()
{
var supermarkt = await SeedAsync(new Supermarkt
{
Id = Guid.NewGuid(), Naam = "Picnic",
CreatedAt = DateTime.UtcNow, ModifiedAt = DateTime.UtcNow
});
var command = new CreateVerspakket.Command("Herfst Pakket", 1499, 3, supermarkt.Id);
var response = await Client.PostAsJsonAsync("/api/verspakketten", command);
response.StatusCode.Should().Be(HttpStatusCode.Created);
var body = await response.Content.ReadFromJsonAsync<CreateVerspakket.Response>();
body!.Id.Should().NotBeEmpty();
response.Headers.Location.Should().NotBeNull();
}
[Fact]
public async Task Post_ReturnsBadRequest_WhenSupermarktDoesNotExist()
{
var command = new CreateVerspakket.Command("Winter Pakket", 999, 2, Guid.NewGuid());
var response = await Client.PostAsJsonAsync("/api/verspakketten", command);
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
}
// ── PUT /api/verspakketten/{id} ───────────────────────────────────────────
[Fact]
public async Task Update_ReturnsNoContent_WhenVerspakketExists()
{
var supermarkt = await SeedAsync(new Supermarkt
{
Id = Guid.NewGuid(), Naam = "AH",
CreatedAt = DateTime.UtcNow, ModifiedAt = DateTime.UtcNow
});
var verspakket = await SeedAsync(new Verspakket
{
Id = Guid.NewGuid(), Naam = "Oud Pakket", AantalPersonen = 2,
SupermarktId = supermarkt.Id,
CreatedAt = DateTime.UtcNow, ModifiedAt = DateTime.UtcNow
});
var request = new UpdateVerspakketRequest("Nieuw Pakket", 1999, 3, supermarkt.Id);
var response = await Client.PutAsJsonAsync($"/api/verspakketten/{verspakket.Id}", request);
response.StatusCode.Should().Be(HttpStatusCode.NoContent);
}
[Fact]
public async Task Update_ReturnsNotFound_WhenVerspakketDoesNotExist()
{
var supermarkt = await SeedAsync(new Supermarkt
{
Id = Guid.NewGuid(), Naam = "AH",
CreatedAt = DateTime.UtcNow, ModifiedAt = DateTime.UtcNow
});
var request = new UpdateVerspakketRequest("Pakket", 999, 2, supermarkt.Id);
var response = await Client.PutAsJsonAsync($"/api/verspakketten/{Guid.NewGuid()}", request);
response.StatusCode.Should().Be(HttpStatusCode.NotFound);
}
[Fact]
public async Task Update_ReturnsBadRequest_WhenSupermarktDoesNotExist()
{
var supermarkt = await SeedAsync(new Supermarkt
{
Id = Guid.NewGuid(), Naam = "AH",
CreatedAt = DateTime.UtcNow, ModifiedAt = DateTime.UtcNow
});
var verspakket = await SeedAsync(new Verspakket
{
Id = Guid.NewGuid(), Naam = "Pakket", AantalPersonen = 2,
SupermarktId = supermarkt.Id,
CreatedAt = DateTime.UtcNow, ModifiedAt = DateTime.UtcNow
});
var request = new UpdateVerspakketRequest("Pakket", 999, 2, Guid.NewGuid());
var response = await Client.PutAsJsonAsync($"/api/verspakketten/{verspakket.Id}", request);
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
}
// ── GET /api/verspakketten — pagination & sorting ─────────────────────────
[Fact]
public async Task Get_Pagination_ReturnsCorrectPage()
{
var supermarkt = await SeedAsync(new Supermarkt
{
Id = Guid.NewGuid(), Naam = "AH",
CreatedAt = DateTime.UtcNow, ModifiedAt = DateTime.UtcNow
});
await SeedAsync(new Verspakket
{
Id = Guid.NewGuid(), Naam = "Aardappel Pakket", AantalPersonen = 2,
SupermarktId = supermarkt.Id,
CreatedAt = DateTime.UtcNow, ModifiedAt = DateTime.UtcNow
});
await SeedAsync(new Verspakket
{
Id = Guid.NewGuid(), Naam = "Broccoli Pakket", AantalPersonen = 2,
SupermarktId = supermarkt.Id,
CreatedAt = DateTime.UtcNow, ModifiedAt = DateTime.UtcNow
});
await SeedAsync(new Verspakket
{
Id = Guid.NewGuid(), Naam = "Courgette Pakket", AantalPersonen = 2,
SupermarktId = supermarkt.Id,
CreatedAt = DateTime.UtcNow, ModifiedAt = DateTime.UtcNow
});
var response = await Client.GetAsync("/api/verspakketten?skip=1&take=1");
response.StatusCode.Should().Be(HttpStatusCode.OK);
var body = await response.Content.ReadFromJsonAsync<GetVerspakketten.Response>();
body!.Verspakketten.Should().HaveCount(1);
body.Verspakketten.First().Naam.Should().Be("Broccoli Pakket");
}
[Fact]
public async Task Get_SortDescending_ReturnsItemsInReverseOrder()
{
var supermarkt = await SeedAsync(new Supermarkt
{
Id = Guid.NewGuid(), Naam = "AH",
CreatedAt = DateTime.UtcNow, ModifiedAt = DateTime.UtcNow
});
await SeedAsync(new Verspakket
{
Id = Guid.NewGuid(), Naam = "Aardappel Pakket", AantalPersonen = 2,
SupermarktId = supermarkt.Id,
CreatedAt = DateTime.UtcNow, ModifiedAt = DateTime.UtcNow
});
await SeedAsync(new Verspakket
{
Id = Guid.NewGuid(), Naam = "Zomerpakket", AantalPersonen = 2,
SupermarktId = supermarkt.Id,
CreatedAt = DateTime.UtcNow, ModifiedAt = DateTime.UtcNow
});
var response = await Client.GetAsync("/api/verspakketten?sortDirection=Descending");
response.StatusCode.Should().Be(HttpStatusCode.OK);
var body = await response.Content.ReadFromJsonAsync<GetVerspakketten.Response>();
body!.Verspakketten.First().Naam.Should().Be("Zomerpakket");
body.Verspakketten.Last().Naam.Should().Be("Aardappel Pakket");
}
// ── POST /api/verspakketten/{id}/beoordelingen ────────────────────────────
[Fact]
public async Task AddBeoordeling_ReturnsBadRequest_WhenVerspakketDoesNotExist()
{
var command = new AddBeoordeling.Command(Guid.NewGuid(), 8, 7, true, "Heerlijk!");
var response = await Client.PostAsJsonAsync($"/api/verspakketten/{command.VerspakketId}/beoordelingen", command);
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
}
[Fact]
public async Task AddBeoordeling_Returns201_WhenVerspakketExists()
{
var supermarkt = await SeedAsync(new Supermarkt
{
Id = Guid.NewGuid(), Naam = "Lidl",
CreatedAt = DateTime.UtcNow, ModifiedAt = DateTime.UtcNow
});
var verspakket = await SeedAsync(new Verspakket
{
Id = Guid.NewGuid(), Naam = "Basis Pakket", AantalPersonen = 2,
SupermarktId = supermarkt.Id,
CreatedAt = DateTime.UtcNow, ModifiedAt = DateTime.UtcNow
});
var command = new AddBeoordeling.Command(verspakket.Id, 8, 7, true, "Heerlijk!");
var response = await Client.PostAsJsonAsync($"/api/verspakketten/{verspakket.Id}/beoordelingen", command);
response.StatusCode.Should().Be(HttpStatusCode.Created);
var body = await response.Content.ReadFromJsonAsync<AddBeoordeling.Response>();
body!.Id.Should().NotBeEmpty();
}
}

View File

@@ -0,0 +1,51 @@
using Lutra.Application.Interfaces;
using Lutra.Infrastructure.Sql;
using Microsoft.Extensions.DependencyInjection;
namespace Lutra.API.IntegrationTests.Infrastructure;
/// <summary>
/// Base class for integration tests. Provides a shared factory and helper methods
/// to seed and reset the database between tests.
/// </summary>
public abstract class IntegrationTestBase : IClassFixture<LutraApiFactory>, IAsyncLifetime
{
protected readonly LutraApiFactory Factory;
protected readonly HttpClient Client;
protected IntegrationTestBase(LutraApiFactory factory)
{
Factory = factory;
Client = factory.CreateClient();
}
/// <summary>Seed data or perform setup before each test.</summary>
public virtual Task InitializeAsync()
{
Factory.EnsureSchemaCreated();
return Task.CompletedTask;
}
/// <summary>Reset database state after each test.</summary>
public async Task DisposeAsync()
{
using var scope = Factory.Services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<ILutraDbContext>() as LutraDbContext;
if (db is not null)
{
db.Beoordelingen.RemoveRange(db.Beoordelingen);
db.Verspaketten.RemoveRange(db.Verspaketten);
db.Supermarkten.RemoveRange(db.Supermarkten);
await db.SaveChangesAsync(CancellationToken.None);
}
}
protected async Task<T> SeedAsync<T>(T entity) where T : class
{
using var scope = Factory.Services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<ILutraDbContext>() as LutraDbContext;
db!.Set<T>().Add(entity);
await db.SaveChangesAsync(CancellationToken.None);
return entity;
}
}

View File

@@ -0,0 +1,69 @@
using Lutra.Application.Interfaces;
using Lutra.Infrastructure.Sql;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
namespace Lutra.API.IntegrationTests.Infrastructure;
/// <summary>
/// Custom WebApplicationFactory that replaces the PostgreSQL database with SQLite in-memory
/// so that integration tests can run without a live database server.
/// A single SqliteConnection is kept open for the lifetime of the factory so that
/// all DI scopes share the same in-memory database.
/// </summary>
public class LutraApiFactory : WebApplicationFactory<Program>
{
// Opened immediately so it is ready when ConfigureWebHost runs.
private readonly SqliteConnection _connection = new("Data Source=:memory:");
private bool _schemaCreated;
public LutraApiFactory()
{
_connection.Open();
}
/// <summary>Ensures the SQLite schema is created. Call once before the first test.</summary>
public void EnsureSchemaCreated()
{
if (_schemaCreated) return;
using var scope = Services.CreateScope();
var db = (LutraDbContext)scope.ServiceProvider.GetRequiredService<ILutraDbContext>();
db.Database.EnsureCreated();
_schemaCreated = true;
}
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
// EF Core 10 stores provider configuration in IDbContextOptionsConfiguration<T>
// descriptors (one per AddDbContext call). All four registration types must be
// removed so neither Npgsql options nor its provider services survive into the
// SQLite registration.
services.RemoveAll<ILutraDbContext>();
services.RemoveAll<LutraDbContext>();
services.RemoveAll<DbContextOptions<LutraDbContext>>();
services.RemoveAll<DbContextOptions>();
services.RemoveAll(typeof(IDbContextOptionsConfiguration<LutraDbContext>));
// Register SQLite using the shared open connection.
services.AddDbContext<ILutraDbContext, LutraDbContext>(options =>
options.UseSqlite(_connection));
});
builder.UseEnvironment("Testing");
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
_connection.Dispose();
}
}

View File

@@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="10.0.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="FluentAssertions" Version="8.9.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.6" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Lutra.API\Lutra.API.csproj" />
<ProjectReference Include="..\Lutra.Infrastructure\Lutra.Infrastructure.Sql.csproj" />
</ItemGroup>
</Project>

View File

@@ -85,13 +85,29 @@ namespace Lutra.API.Controllers
/// </returns> /// </returns>
[HttpPut("{id:guid}")] [HttpPut("{id:guid}")]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> Update(Guid id, [FromBody] UpdateVerspakketRequest request) public async Task<IActionResult> Update(Guid id, [FromBody] UpdateVerspakketRequest request)
{
try
{ {
var command = new UpdateVerspakket.Command(id, request.Naam, request.PrijsInCenten, request.AantalPersonen, request.SupermarktId); var command = new UpdateVerspakket.Command(id, request.Naam, request.PrijsInCenten, request.AantalPersonen, request.SupermarktId);
await mediator.SendCommandAsync<UpdateVerspakket.Command, UpdateVerspakket.Response>(command); await mediator.SendCommandAsync<UpdateVerspakket.Command, UpdateVerspakket.Response>(command);
return NoContent(); return NoContent();
} }
catch (ArgumentException ex)
{
return BadRequest(ex.Message);
}
catch (InvalidOperationException ex) when (ex.Message.StartsWith($"Verspakket with id '{id}'"))
{
return NotFound();
}
catch (InvalidOperationException ex)
{
return BadRequest(ex.Message);
}
}
/// <summary> /// <summary>
/// Adds a beoordeling to an existing verspakket. /// Adds a beoordeling to an existing verspakket.

View File

@@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.4" />
<PackageReference Include="FluentAssertions" Version="8.9.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageReference Include="Moq" Version="4.20.72" />
<PackageReference Include="Moq.EntityFrameworkCore" Version="10.0.0.2" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Lutra.Application\Lutra.Application.csproj" />
<ProjectReference Include="..\Lutra.Domain\Lutra.Domain.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,72 @@
using FluentAssertions;
using Lutra.Application.Interfaces;
using Lutra.Application.Verspakketten;
using Microsoft.EntityFrameworkCore;
using Moq;
using Moq.EntityFrameworkCore;
namespace Lutra.Application.UnitTests.Verspakketten;
public class AddBeoordelingHandlerTests
{
private readonly Mock<ILutraDbContext> _contextMock;
private readonly AddBeoordeling.Handler _handler;
public AddBeoordelingHandlerTests()
{
_contextMock = new Mock<ILutraDbContext>();
_handler = new AddBeoordeling.Handler(_contextMock.Object);
}
[Fact]
public async Task Handle_VerspakketExists_AddsBeoordeling()
{
var verspakketId = Guid.NewGuid();
var verspakketten = new List<Domain.Entities.Verspakket>
{
new() { Id = verspakketId, Naam = "Test", AantalPersonen = 2, SupermarktId = Guid.NewGuid(), CreatedAt = DateTime.UtcNow, ModifiedAt = DateTime.UtcNow }
};
_contextMock.Setup(c => c.Verspaketten).ReturnsDbSet(verspakketten);
_contextMock.Setup(c => c.Beoordelingen).ReturnsDbSet(new List<Domain.Entities.Beoordeling>());
_contextMock.Setup(c => c.SaveChangesAsync(It.IsAny<CancellationToken>())).ReturnsAsync(1);
var command = new AddBeoordeling.Command(verspakketId, 8, 7, true, "Lekker!");
var result = await _handler.Handle(command, CancellationToken.None);
result.Id.Should().NotBeEmpty();
_contextMock.Verify(c => c.SaveChangesAsync(It.IsAny<CancellationToken>()), Times.Once);
}
[Fact]
public async Task Handle_VerspakketNotFound_ThrowsInvalidOperationException()
{
_contextMock.Setup(c => c.Verspaketten).ReturnsDbSet(new List<Domain.Entities.Verspakket>());
var command = new AddBeoordeling.Command(Guid.NewGuid(), 8, 7, true, null);
var act = () => _handler.Handle(command, CancellationToken.None);
await act.Should().ThrowAsync<InvalidOperationException>()
.WithMessage("*was not found*");
}
[Fact]
public async Task Handle_VerspakketDeleted_ThrowsInvalidOperationException()
{
var verspakketId = Guid.NewGuid();
var verspakketten = new List<Domain.Entities.Verspakket>
{
new() { Id = verspakketId, Naam = "Deleted", AantalPersonen = 2, SupermarktId = Guid.NewGuid(), CreatedAt = DateTime.UtcNow, ModifiedAt = DateTime.UtcNow, DeletedAt = DateTime.UtcNow }
};
_contextMock.Setup(c => c.Verspaketten).ReturnsDbSet(verspakketten);
var command = new AddBeoordeling.Command(verspakketId, 8, 7, true, null);
var act = () => _handler.Handle(command, CancellationToken.None);
await act.Should().ThrowAsync<InvalidOperationException>();
}
}

View File

@@ -0,0 +1,81 @@
using FluentAssertions;
using Lutra.Application.Interfaces;
using Lutra.Application.Verspakketten;
using Moq;
using Moq.EntityFrameworkCore;
namespace Lutra.Application.UnitTests.Verspakketten;
public class CreateVerspakketHandlerTests
{
private readonly Mock<ILutraDbContext> _contextMock;
private readonly CreateVerspakket.Handler _handler;
public CreateVerspakketHandlerTests()
{
_contextMock = new Mock<ILutraDbContext>();
_handler = new CreateVerspakket.Handler(_contextMock.Object);
}
[Fact]
public async Task Handle_SupermarktExists_CreatesVerspakket()
{
var supermarktId = Guid.NewGuid();
var supermarkten = new List<Domain.Entities.Supermarkt>
{
new() { Id = supermarktId, Naam = "AH", CreatedAt = DateTime.UtcNow, ModifiedAt = DateTime.UtcNow }
};
_contextMock.Setup(c => c.Supermarkten).ReturnsDbSet(supermarkten);
_contextMock.Setup(c => c.Verspaketten).ReturnsDbSet(new List<Domain.Entities.Verspakket>());
_contextMock.Setup(c => c.SaveChangesAsync(It.IsAny<CancellationToken>())).ReturnsAsync(1);
var command = new CreateVerspakket.Command("Lente Pakket", 1299, 2, supermarktId);
var result = await _handler.Handle(command, CancellationToken.None);
result.Id.Should().NotBeEmpty();
_contextMock.Verify(c => c.SaveChangesAsync(It.IsAny<CancellationToken>()), Times.Once);
}
[Fact]
public async Task Handle_SupermarktNotFound_ThrowsInvalidOperationException()
{
_contextMock.Setup(c => c.Supermarkten).ReturnsDbSet(new List<Domain.Entities.Supermarkt>());
var command = new CreateVerspakket.Command("Lente Pakket", 1299, 2, Guid.NewGuid());
var act = () => _handler.Handle(command, CancellationToken.None);
await act.Should().ThrowAsync<InvalidOperationException>()
.WithMessage("*was not found*");
}
[Fact]
public async Task Handle_CreatesVerspakketWithCorrectProperties()
{
var supermarktId = Guid.NewGuid();
var supermarkten = new List<Domain.Entities.Supermarkt>
{
new() { Id = supermarktId, Naam = "Jumbo", CreatedAt = DateTime.UtcNow, ModifiedAt = DateTime.UtcNow }
};
Domain.Entities.Verspakket? savedVerspakket = null;
_contextMock.Setup(c => c.Supermarkten).ReturnsDbSet(supermarkten);
_contextMock.Setup(c => c.Verspaketten).ReturnsDbSet(new List<Domain.Entities.Verspakket>());
_contextMock
.Setup(c => c.Verspaketten.AddAsync(It.IsAny<Domain.Entities.Verspakket>(), It.IsAny<CancellationToken>()))
.Callback<Domain.Entities.Verspakket, CancellationToken>((v, _) => savedVerspakket = v);
_contextMock.Setup(c => c.SaveChangesAsync(It.IsAny<CancellationToken>())).ReturnsAsync(1);
var command = new CreateVerspakket.Command("Zomer Pakket", 999, 4, supermarktId);
await _handler.Handle(command, CancellationToken.None);
savedVerspakket.Should().NotBeNull();
savedVerspakket!.Naam.Should().Be("Zomer Pakket");
savedVerspakket.PrijsInCenten.Should().Be(999);
savedVerspakket.AantalPersonen.Should().Be(4);
savedVerspakket.SupermarktId.Should().Be(supermarktId);
}
}

View File

@@ -15,36 +15,118 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lutra.AppHost", "Lutra.AppH
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lutra.Infrastructure.Migrator", "Lutra.Infrastructure.Migrator\Lutra.Infrastructure.Migrator.csproj", "{6582486C-CEB7-78BF-D043-E0C3C0D6A923}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lutra.Infrastructure.Migrator", "Lutra.Infrastructure.Migrator\Lutra.Infrastructure.Migrator.csproj", "{6582486C-CEB7-78BF-D043-E0C3C0D6A923}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lutra.Application.UnitTests", "Lutra.Application.UnitTests\Lutra.Application.UnitTests.csproj", "{923A138F-A7A1-4254-A95F-447977B7AAC0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lutra.API.IntegrationTests", "Lutra.API.IntegrationTests\Lutra.API.IntegrationTests.csproj", "{E1B105D6-DCC2-42F5-9CB9-3A295804DCF6}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Lutra.Infrastructure", "Lutra.Infrastructure", "{DF712D4B-6D10-CA62-6EF1-190F54D65E10}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution GlobalSection(ProjectConfigurationPlatforms) = postSolution
{22B74CFE-E9D0-42F3-96E3-818D014E1A2E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {22B74CFE-E9D0-42F3-96E3-818D014E1A2E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{22B74CFE-E9D0-42F3-96E3-818D014E1A2E}.Debug|Any CPU.Build.0 = Debug|Any CPU {22B74CFE-E9D0-42F3-96E3-818D014E1A2E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{22B74CFE-E9D0-42F3-96E3-818D014E1A2E}.Debug|x64.ActiveCfg = Debug|Any CPU
{22B74CFE-E9D0-42F3-96E3-818D014E1A2E}.Debug|x64.Build.0 = Debug|Any CPU
{22B74CFE-E9D0-42F3-96E3-818D014E1A2E}.Debug|x86.ActiveCfg = Debug|Any CPU
{22B74CFE-E9D0-42F3-96E3-818D014E1A2E}.Debug|x86.Build.0 = Debug|Any CPU
{22B74CFE-E9D0-42F3-96E3-818D014E1A2E}.Release|Any CPU.ActiveCfg = Release|Any CPU {22B74CFE-E9D0-42F3-96E3-818D014E1A2E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{22B74CFE-E9D0-42F3-96E3-818D014E1A2E}.Release|Any CPU.Build.0 = Release|Any CPU {22B74CFE-E9D0-42F3-96E3-818D014E1A2E}.Release|Any CPU.Build.0 = Release|Any CPU
{22B74CFE-E9D0-42F3-96E3-818D014E1A2E}.Release|x64.ActiveCfg = Release|Any CPU
{22B74CFE-E9D0-42F3-96E3-818D014E1A2E}.Release|x64.Build.0 = Release|Any CPU
{22B74CFE-E9D0-42F3-96E3-818D014E1A2E}.Release|x86.ActiveCfg = Release|Any CPU
{22B74CFE-E9D0-42F3-96E3-818D014E1A2E}.Release|x86.Build.0 = Release|Any CPU
{CD66D294-FB25-4CA1-8DCA-7EF3DDDFE0C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {CD66D294-FB25-4CA1-8DCA-7EF3DDDFE0C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CD66D294-FB25-4CA1-8DCA-7EF3DDDFE0C5}.Debug|Any CPU.Build.0 = Debug|Any CPU {CD66D294-FB25-4CA1-8DCA-7EF3DDDFE0C5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CD66D294-FB25-4CA1-8DCA-7EF3DDDFE0C5}.Debug|x64.ActiveCfg = Debug|Any CPU
{CD66D294-FB25-4CA1-8DCA-7EF3DDDFE0C5}.Debug|x64.Build.0 = Debug|Any CPU
{CD66D294-FB25-4CA1-8DCA-7EF3DDDFE0C5}.Debug|x86.ActiveCfg = Debug|Any CPU
{CD66D294-FB25-4CA1-8DCA-7EF3DDDFE0C5}.Debug|x86.Build.0 = Debug|Any CPU
{CD66D294-FB25-4CA1-8DCA-7EF3DDDFE0C5}.Release|Any CPU.ActiveCfg = Release|Any CPU {CD66D294-FB25-4CA1-8DCA-7EF3DDDFE0C5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CD66D294-FB25-4CA1-8DCA-7EF3DDDFE0C5}.Release|Any CPU.Build.0 = Release|Any CPU {CD66D294-FB25-4CA1-8DCA-7EF3DDDFE0C5}.Release|Any CPU.Build.0 = Release|Any CPU
{CD66D294-FB25-4CA1-8DCA-7EF3DDDFE0C5}.Release|x64.ActiveCfg = Release|Any CPU
{CD66D294-FB25-4CA1-8DCA-7EF3DDDFE0C5}.Release|x64.Build.0 = Release|Any CPU
{CD66D294-FB25-4CA1-8DCA-7EF3DDDFE0C5}.Release|x86.ActiveCfg = Release|Any CPU
{CD66D294-FB25-4CA1-8DCA-7EF3DDDFE0C5}.Release|x86.Build.0 = Release|Any CPU
{DACBC33C-A76A-483A-8535-AA62D33D4977}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {DACBC33C-A76A-483A-8535-AA62D33D4977}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DACBC33C-A76A-483A-8535-AA62D33D4977}.Debug|Any CPU.Build.0 = Debug|Any CPU {DACBC33C-A76A-483A-8535-AA62D33D4977}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DACBC33C-A76A-483A-8535-AA62D33D4977}.Debug|x64.ActiveCfg = Debug|Any CPU
{DACBC33C-A76A-483A-8535-AA62D33D4977}.Debug|x64.Build.0 = Debug|Any CPU
{DACBC33C-A76A-483A-8535-AA62D33D4977}.Debug|x86.ActiveCfg = Debug|Any CPU
{DACBC33C-A76A-483A-8535-AA62D33D4977}.Debug|x86.Build.0 = Debug|Any CPU
{DACBC33C-A76A-483A-8535-AA62D33D4977}.Release|Any CPU.ActiveCfg = Release|Any CPU {DACBC33C-A76A-483A-8535-AA62D33D4977}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DACBC33C-A76A-483A-8535-AA62D33D4977}.Release|Any CPU.Build.0 = Release|Any CPU {DACBC33C-A76A-483A-8535-AA62D33D4977}.Release|Any CPU.Build.0 = Release|Any CPU
{DACBC33C-A76A-483A-8535-AA62D33D4977}.Release|x64.ActiveCfg = Release|Any CPU
{DACBC33C-A76A-483A-8535-AA62D33D4977}.Release|x64.Build.0 = Release|Any CPU
{DACBC33C-A76A-483A-8535-AA62D33D4977}.Release|x86.ActiveCfg = Release|Any CPU
{DACBC33C-A76A-483A-8535-AA62D33D4977}.Release|x86.Build.0 = Release|Any CPU
{0D9E0DD9-E914-483F-8046-8A57B510E984}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0D9E0DD9-E914-483F-8046-8A57B510E984}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0D9E0DD9-E914-483F-8046-8A57B510E984}.Debug|Any CPU.Build.0 = Debug|Any CPU {0D9E0DD9-E914-483F-8046-8A57B510E984}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0D9E0DD9-E914-483F-8046-8A57B510E984}.Debug|x64.ActiveCfg = Debug|Any CPU
{0D9E0DD9-E914-483F-8046-8A57B510E984}.Debug|x64.Build.0 = Debug|Any CPU
{0D9E0DD9-E914-483F-8046-8A57B510E984}.Debug|x86.ActiveCfg = Debug|Any CPU
{0D9E0DD9-E914-483F-8046-8A57B510E984}.Debug|x86.Build.0 = Debug|Any CPU
{0D9E0DD9-E914-483F-8046-8A57B510E984}.Release|Any CPU.ActiveCfg = Release|Any CPU {0D9E0DD9-E914-483F-8046-8A57B510E984}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0D9E0DD9-E914-483F-8046-8A57B510E984}.Release|Any CPU.Build.0 = Release|Any CPU {0D9E0DD9-E914-483F-8046-8A57B510E984}.Release|Any CPU.Build.0 = Release|Any CPU
{0D9E0DD9-E914-483F-8046-8A57B510E984}.Release|x64.ActiveCfg = Release|Any CPU
{0D9E0DD9-E914-483F-8046-8A57B510E984}.Release|x64.Build.0 = Release|Any CPU
{0D9E0DD9-E914-483F-8046-8A57B510E984}.Release|x86.ActiveCfg = Release|Any CPU
{0D9E0DD9-E914-483F-8046-8A57B510E984}.Release|x86.Build.0 = Release|Any CPU
{B9B41350-EB18-4564-81AB-C16F3E5A0CBB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B9B41350-EB18-4564-81AB-C16F3E5A0CBB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B9B41350-EB18-4564-81AB-C16F3E5A0CBB}.Debug|Any CPU.Build.0 = Debug|Any CPU {B9B41350-EB18-4564-81AB-C16F3E5A0CBB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B9B41350-EB18-4564-81AB-C16F3E5A0CBB}.Debug|x64.ActiveCfg = Debug|Any CPU
{B9B41350-EB18-4564-81AB-C16F3E5A0CBB}.Debug|x64.Build.0 = Debug|Any CPU
{B9B41350-EB18-4564-81AB-C16F3E5A0CBB}.Debug|x86.ActiveCfg = Debug|Any CPU
{B9B41350-EB18-4564-81AB-C16F3E5A0CBB}.Debug|x86.Build.0 = Debug|Any CPU
{B9B41350-EB18-4564-81AB-C16F3E5A0CBB}.Release|Any CPU.ActiveCfg = Release|Any CPU {B9B41350-EB18-4564-81AB-C16F3E5A0CBB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B9B41350-EB18-4564-81AB-C16F3E5A0CBB}.Release|Any CPU.Build.0 = Release|Any CPU {B9B41350-EB18-4564-81AB-C16F3E5A0CBB}.Release|Any CPU.Build.0 = Release|Any CPU
{B9B41350-EB18-4564-81AB-C16F3E5A0CBB}.Release|x64.ActiveCfg = Release|Any CPU
{B9B41350-EB18-4564-81AB-C16F3E5A0CBB}.Release|x64.Build.0 = Release|Any CPU
{B9B41350-EB18-4564-81AB-C16F3E5A0CBB}.Release|x86.ActiveCfg = Release|Any CPU
{B9B41350-EB18-4564-81AB-C16F3E5A0CBB}.Release|x86.Build.0 = Release|Any CPU
{6582486C-CEB7-78BF-D043-E0C3C0D6A923}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6582486C-CEB7-78BF-D043-E0C3C0D6A923}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6582486C-CEB7-78BF-D043-E0C3C0D6A923}.Debug|Any CPU.Build.0 = Debug|Any CPU {6582486C-CEB7-78BF-D043-E0C3C0D6A923}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6582486C-CEB7-78BF-D043-E0C3C0D6A923}.Debug|x64.ActiveCfg = Debug|Any CPU
{6582486C-CEB7-78BF-D043-E0C3C0D6A923}.Debug|x64.Build.0 = Debug|Any CPU
{6582486C-CEB7-78BF-D043-E0C3C0D6A923}.Debug|x86.ActiveCfg = Debug|Any CPU
{6582486C-CEB7-78BF-D043-E0C3C0D6A923}.Debug|x86.Build.0 = Debug|Any CPU
{6582486C-CEB7-78BF-D043-E0C3C0D6A923}.Release|Any CPU.ActiveCfg = Release|Any CPU {6582486C-CEB7-78BF-D043-E0C3C0D6A923}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6582486C-CEB7-78BF-D043-E0C3C0D6A923}.Release|Any CPU.Build.0 = Release|Any CPU {6582486C-CEB7-78BF-D043-E0C3C0D6A923}.Release|Any CPU.Build.0 = Release|Any CPU
{6582486C-CEB7-78BF-D043-E0C3C0D6A923}.Release|x64.ActiveCfg = Release|Any CPU
{6582486C-CEB7-78BF-D043-E0C3C0D6A923}.Release|x64.Build.0 = Release|Any CPU
{6582486C-CEB7-78BF-D043-E0C3C0D6A923}.Release|x86.ActiveCfg = Release|Any CPU
{6582486C-CEB7-78BF-D043-E0C3C0D6A923}.Release|x86.Build.0 = Release|Any CPU
{923A138F-A7A1-4254-A95F-447977B7AAC0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{923A138F-A7A1-4254-A95F-447977B7AAC0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{923A138F-A7A1-4254-A95F-447977B7AAC0}.Debug|x64.ActiveCfg = Debug|Any CPU
{923A138F-A7A1-4254-A95F-447977B7AAC0}.Debug|x64.Build.0 = Debug|Any CPU
{923A138F-A7A1-4254-A95F-447977B7AAC0}.Debug|x86.ActiveCfg = Debug|Any CPU
{923A138F-A7A1-4254-A95F-447977B7AAC0}.Debug|x86.Build.0 = Debug|Any CPU
{923A138F-A7A1-4254-A95F-447977B7AAC0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{923A138F-A7A1-4254-A95F-447977B7AAC0}.Release|Any CPU.Build.0 = Release|Any CPU
{923A138F-A7A1-4254-A95F-447977B7AAC0}.Release|x64.ActiveCfg = Release|Any CPU
{923A138F-A7A1-4254-A95F-447977B7AAC0}.Release|x64.Build.0 = Release|Any CPU
{923A138F-A7A1-4254-A95F-447977B7AAC0}.Release|x86.ActiveCfg = Release|Any CPU
{923A138F-A7A1-4254-A95F-447977B7AAC0}.Release|x86.Build.0 = Release|Any CPU
{E1B105D6-DCC2-42F5-9CB9-3A295804DCF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E1B105D6-DCC2-42F5-9CB9-3A295804DCF6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E1B105D6-DCC2-42F5-9CB9-3A295804DCF6}.Debug|x64.ActiveCfg = Debug|Any CPU
{E1B105D6-DCC2-42F5-9CB9-3A295804DCF6}.Debug|x64.Build.0 = Debug|Any CPU
{E1B105D6-DCC2-42F5-9CB9-3A295804DCF6}.Debug|x86.ActiveCfg = Debug|Any CPU
{E1B105D6-DCC2-42F5-9CB9-3A295804DCF6}.Debug|x86.Build.0 = Debug|Any CPU
{E1B105D6-DCC2-42F5-9CB9-3A295804DCF6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E1B105D6-DCC2-42F5-9CB9-3A295804DCF6}.Release|Any CPU.Build.0 = Release|Any CPU
{E1B105D6-DCC2-42F5-9CB9-3A295804DCF6}.Release|x64.ActiveCfg = Release|Any CPU
{E1B105D6-DCC2-42F5-9CB9-3A295804DCF6}.Release|x64.Build.0 = Release|Any CPU
{E1B105D6-DCC2-42F5-9CB9-3A295804DCF6}.Release|x86.ActiveCfg = Release|Any CPU
{E1B105D6-DCC2-42F5-9CB9-3A295804DCF6}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE