Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 130 additions & 0 deletions WardrobeManager.Api.Tests/Database/DatabaseContextTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
using FluentAssertions;
using FluentAssertions.Execution;
using Microsoft.EntityFrameworkCore;
using WardrobeManager.Api.Database;
using WardrobeManager.Api.Database.Entities;
using WardrobeManager.Shared.Enums;

namespace WardrobeManager.Api.Tests.Database;

public class DatabaseContextTests
{
private DatabaseContext _context;

[SetUp]
public void Setup()
{
var options = new DbContextOptionsBuilder<DatabaseContext>()
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
.Options;

_context = new DatabaseContext(options);
}

[TearDown]
public async Task TearDown()
{
await _context.DisposeAsync();
}

[Test]
public void DatabaseContext_HasUsersDbSet()
{
// Assert
_context.Users.Should().NotBeNull();
}

[Test]
public void DatabaseContext_HasClothingItemsDbSet()
{
// Assert
_context.ClothingItems.Should().NotBeNull();
}

[Test]
public void DatabaseContext_HasLogsDbSet()
{
// Assert
_context.Logs.Should().NotBeNull();
}

[Test]
public async Task DatabaseContext_WhenUserAddedWithClothingItems_SavesRelationship()
{
// Arrange
var user = new User
{
Id = "test-user",
UserName = "testuser",
ServerClothingItems = new List<ClothingItem>
{
new ClothingItem("T-Shirt", ClothingCategory.TShirt, Season.Fall,
WearLocation.HomeAndOutside, false, 3, null)
}
};

// Act
await _context.Users.AddAsync(user);
await _context.SaveChangesAsync();

// Assert
var savedUser = await _context.Users
.Include(u => u.ServerClothingItems)
.FirstOrDefaultAsync(u => u.Id == "test-user");

using (new AssertionScope())
{
savedUser.Should().NotBeNull();
savedUser!.ServerClothingItems.Should().HaveCount(1);
savedUser.ServerClothingItems.First().Name.Should().Be("T-Shirt");
}
}

[Test]
public async Task DatabaseContext_WhenUserDeleted_CascadeDeletesClothingItems()
{
// Arrange
var user = new User
{
Id = "cascade-test-user",
UserName = "cascadeuser",
ServerClothingItems = new List<ClothingItem>
{
new ClothingItem("Item 1", ClothingCategory.Jeans, Season.Fall,
WearLocation.Outside, false, 0, null)
}
};
await _context.Users.AddAsync(user);
await _context.SaveChangesAsync();

// Act
_context.Users.Remove(user);
await _context.SaveChangesAsync();

// Assert - cascade delete should have removed the clothing items too
var clothingItemCount = await _context.ClothingItems
.Where(c => c.UserId == "cascade-test-user")
.CountAsync();
clothingItemCount.Should().Be(0);
}

[Test]
public async Task DatabaseContext_CanAddAndRetrieveLogs()
{
// Arrange
var log = new Log("Test log", "Description", LogType.Info, LogOrigin.Backend);

// Act
await _context.Logs.AddAsync(log);
await _context.SaveChangesAsync();

// Assert
var savedLog = await _context.Logs.FirstOrDefaultAsync(l => l.Title == "Test log");
using (new AssertionScope())
{
savedLog.Should().NotBeNull();
savedLog!.Description.Should().Be("Description");
savedLog.Type.Should().Be(LogType.Info);
}
}
}
111 changes: 111 additions & 0 deletions WardrobeManager.Api.Tests/Database/DatabaseInitializerTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
using FluentAssertions;
using Microsoft.AspNetCore.Identity;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Moq;
using WardrobeManager.Api.Database;
using WardrobeManager.Api.Database.Entities;

namespace WardrobeManager.Api.Tests.Database;

public class DatabaseInitializerTests
{
private Mock<RoleManager<IdentityRole>> _mockRoleManager;
private DatabaseContext _context;
private SqliteConnection _connection;

[SetUp]
public void Setup()
{
// Use a persistent in-memory SQLite connection so migrations can run
_connection = new SqliteConnection("DataSource=:memory:");
_connection.Open();

var dbOptions = new DbContextOptionsBuilder<DatabaseContext>()
.UseSqlite(_connection)
.Options;
_context = new DatabaseContext(dbOptions);

// Setup mock RoleManager
var roleStore = new Mock<IRoleStore<IdentityRole>>();
_mockRoleManager = new Mock<RoleManager<IdentityRole>>(
roleStore.Object, null!, null!, null!, null!);
}

[TearDown]
public async Task TearDown()
{
await _context.DisposeAsync();
await _connection.DisposeAsync();
}

private IServiceScope BuildScope()
{
var mockServiceProvider = new Mock<IServiceProvider>();
mockServiceProvider
.Setup(sp => sp.GetService(typeof(DatabaseContext)))
.Returns(_context);
mockServiceProvider
.Setup(sp => sp.GetService(typeof(RoleManager<IdentityRole>)))
.Returns(_mockRoleManager.Object);

var mockScope = new Mock<IServiceScope>();
mockScope.Setup(s => s.ServiceProvider).Returns(mockServiceProvider.Object);
return mockScope.Object;
}

[Test]
public async Task InitializeAsync_WhenNoUsersExist_CreatesAdminAndUserRoles()
{
// Arrange
_mockRoleManager.Setup(r => r.RoleExistsAsync("Admin")).ReturnsAsync(false);
_mockRoleManager.Setup(r => r.RoleExistsAsync("User")).ReturnsAsync(false);
_mockRoleManager
.Setup(r => r.CreateAsync(It.IsAny<IdentityRole>()))
.ReturnsAsync(IdentityResult.Success);

var scope = BuildScope();

// Act
await DatabaseInitializer.InitializeAsync(scope);

// Assert
_mockRoleManager.Verify(r => r.CreateAsync(It.Is<IdentityRole>(role => role.Name == "Admin")), Times.Once);
_mockRoleManager.Verify(r => r.CreateAsync(It.Is<IdentityRole>(role => role.Name == "User")), Times.Once);
}

[Test]
public async Task InitializeAsync_WhenRolesAlreadyExist_DoesNotRecreateRoles()
{
// Arrange
_mockRoleManager.Setup(r => r.RoleExistsAsync("Admin")).ReturnsAsync(true);
_mockRoleManager.Setup(r => r.RoleExistsAsync("User")).ReturnsAsync(true);

var scope = BuildScope();

// Act
await DatabaseInitializer.InitializeAsync(scope);

// Assert
_mockRoleManager.Verify(r => r.CreateAsync(It.IsAny<IdentityRole>()), Times.Never);
}

[Test]
public async Task InitializeAsync_WhenUsersExist_SkipsRoleCreation()
{
// Arrange - Apply migrations first so we can add a user
await _context.Database.MigrateAsync();
var user = new User { Id = "existing-user", UserName = "existinguser" };
await _context.Users.AddAsync(user);
await _context.SaveChangesAsync();

var scope = BuildScope();

// Act - InitializeAsync will call MigrateAsync (no-op, already migrated) then exit early
await DatabaseInitializer.InitializeAsync(scope);

// Assert - RoleManager should NOT be called since users already exist (early return)
_mockRoleManager.Verify(r => r.RoleExistsAsync(It.IsAny<string>()), Times.Never);
}
}
105 changes: 105 additions & 0 deletions WardrobeManager.Api.Tests/Database/Entities/ClothingItemTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
using FluentAssertions;
using FluentAssertions.Execution;
using WardrobeManager.Api.Database.Entities;
using WardrobeManager.Shared.Enums;

namespace WardrobeManager.Api.Tests.Database.Entities;

public class ClothingItemTests
{
private ClothingItem _clothingItem;

[SetUp]
public void Setup()
{
_clothingItem = new ClothingItem("T-Shirt", ClothingCategory.TShirt, Season.Fall,
WearLocation.HomeAndOutside, false, 3, null);
}

[Test]
public void Wear_WhenCalledOnce_IncrementsCounters()
{
// Arrange - item has 0 wears

// Act
_clothingItem.Wear();

// Assert
using (new AssertionScope())
{
_clothingItem.TimesWornTotal.Should().Be(1);
_clothingItem.TimesWornSinceWash.Should().Be(1);
}
}

[Test]
public void Wear_WhenCalledMultipleTimes_IncrementsCountersCorrectly()
{
// Arrange
const int wearCount = 5;

// Act
for (int i = 0; i < wearCount; i++)
{
_clothingItem.Wear();
}

// Assert
using (new AssertionScope())
{
_clothingItem.TimesWornTotal.Should().Be(wearCount);
_clothingItem.TimesWornSinceWash.Should().Be(wearCount);
}
}

[Test]
public void Wash_AfterWearing_ResetsTimesWornSinceWash()
{
// Arrange
_clothingItem.Wear();
_clothingItem.Wear();

// Act
_clothingItem.Wash();

// Assert
using (new AssertionScope())
{
_clothingItem.TimesWornSinceWash.Should().Be(0);
_clothingItem.TimesWornTotal.Should().Be(2); // total does not reset on wash
}
}

[Test]
public void Wear_WhenCalled_UpdatesLastWornDate()
{
// Arrange
var beforeWear = DateTime.UtcNow;

// Act
_clothingItem.Wear();

// Assert
_clothingItem.LastWorn.Should().BeOnOrAfter(beforeWear);
}

[Test]
public void ClothingItem_WhenCreated_HasCorrectDefaults()
{
// Arrange - item created in Setup

// Assert
using (new AssertionScope())
{
_clothingItem.Name.Should().Be("T-Shirt");
_clothingItem.Category.Should().Be(ClothingCategory.TShirt);
_clothingItem.Season.Should().Be(Season.Fall);
_clothingItem.WearLocation.Should().Be(WearLocation.HomeAndOutside);
_clothingItem.Favourited.Should().BeFalse();
_clothingItem.DesiredTimesWornBeforeWash.Should().Be(3);
_clothingItem.TimesWornTotal.Should().Be(0);
_clothingItem.TimesWornSinceWash.Should().Be(0);
_clothingItem.Size.Should().Be(ClothingSize.NotSpecified); // added in US-003
}
}
}
Loading