In progress
Some checks failed
Code Coverage / Generate Coverage Report (push) Has been cancelled
Tests / Run Tests (9.0.x) (push) Has been cancelled
Tests / Docker Build Test (push) Has been cancelled
Tests / Test Summary (push) Has been cancelled

This commit is contained in:
Yaojia Wang
2025-11-03 14:00:24 +01:00
parent fe8ad1c1f9
commit 1f66b25f30
74 changed files with 9609 additions and 28 deletions

View File

@@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.2" />
<PackageReference Include="FluentAssertions" Version="8.8.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="9.0.10" />
<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" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\src\Modules\Identity\ColaFlow.Modules.Identity.Infrastructure\ColaFlow.Modules.Identity.Infrastructure.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,171 @@
using ColaFlow.Modules.Identity.Domain.Aggregates.Tenants;
using ColaFlow.Modules.Identity.Domain.Aggregates.Users;
using ColaFlow.Modules.Identity.Infrastructure.Persistence;
using ColaFlow.Modules.Identity.Infrastructure.Services;
using FluentAssertions;
using Microsoft.EntityFrameworkCore;
using Moq;
namespace ColaFlow.Modules.Identity.Infrastructure.Tests.Persistence;
public class GlobalQueryFilterTests : IDisposable
{
private readonly Mock<ITenantContext> _mockTenantContext;
private readonly IdentityDbContext _context;
public GlobalQueryFilterTests()
{
var options = new DbContextOptionsBuilder<IdentityDbContext>()
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
.Options;
_mockTenantContext = new Mock<ITenantContext>();
_context = new IdentityDbContext(options, _mockTenantContext.Object);
}
[Fact]
public async Task GlobalQueryFilter_ShouldFilterByTenant()
{
// Arrange - Create 2 users from different tenants
var tenant1Id = TenantId.CreateUnique();
var tenant2Id = TenantId.CreateUnique();
// Setup mock to filter for tenant1
var mockTenantContext = new Mock<ITenantContext>();
mockTenantContext.Setup(x => x.IsSet).Returns(true);
mockTenantContext.Setup(x => x.TenantId).Returns(tenant1Id);
var options = new DbContextOptionsBuilder<IdentityDbContext>()
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
.Options;
using var context = new IdentityDbContext(options, mockTenantContext.Object);
var user1 = User.CreateLocal(
tenant1Id,
Email.Create("user1@tenant1.com"),
"password123",
FullName.Create("User One"));
var user2 = User.CreateLocal(
tenant2Id,
Email.Create("user2@tenant2.com"),
"password123",
FullName.Create("User Two"));
await context.Users.AddAsync(user1);
await context.Users.AddAsync(user2);
await context.SaveChangesAsync();
// Act - Query users (should be filtered by tenant1)
var filteredUsers = await context.Users.ToListAsync();
// Assert - Should only return tenant1's user
filteredUsers.Should().HaveCount(1);
filteredUsers[0].Email.Value.Should().Be("user1@tenant1.com");
}
[Fact]
public async Task WithoutTenantFilter_ShouldReturnAllUsers()
{
// Arrange - Create 2 users from different tenants
var tenant1Id = TenantId.CreateUnique();
var tenant2Id = TenantId.CreateUnique();
// Setup mock to filter for tenant1
var mockTenantContext = new Mock<ITenantContext>();
mockTenantContext.Setup(x => x.IsSet).Returns(true);
mockTenantContext.Setup(x => x.TenantId).Returns(tenant1Id);
var options = new DbContextOptionsBuilder<IdentityDbContext>()
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
.Options;
using var context = new IdentityDbContext(options, mockTenantContext.Object);
var user1 = User.CreateLocal(tenant1Id, Email.Create("admin@tenant1.com"), "pass", FullName.Create("Admin One"));
var user2 = User.CreateLocal(tenant2Id, Email.Create("admin@tenant2.com"), "pass", FullName.Create("Admin Two"));
await context.Users.AddAsync(user1);
await context.Users.AddAsync(user2);
await context.SaveChangesAsync();
// Act - Use WithoutTenantFilter to bypass filter
var allUsers = await context.WithoutTenantFilter<User>().ToListAsync();
// Assert - Should return all users
allUsers.Should().HaveCount(2);
}
[Fact]
public async Task GlobalQueryFilter_ShouldNotFilter_WhenTenantContextNotSet()
{
// Arrange - Tenant context not set
var tenant1Id = TenantId.CreateUnique();
var tenant2Id = TenantId.CreateUnique();
var mockTenantContext = new Mock<ITenantContext>();
mockTenantContext.Setup(x => x.IsSet).Returns(false); // NOT set
var options = new DbContextOptionsBuilder<IdentityDbContext>()
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
.Options;
using var context = new IdentityDbContext(options, mockTenantContext.Object);
var user1 = User.CreateLocal(tenant1Id, Email.Create("user1@test.com"), "pass", FullName.Create("User One"));
var user2 = User.CreateLocal(tenant2Id, Email.Create("user2@test.com"), "pass", FullName.Create("User Two"));
await context.Users.AddAsync(user1);
await context.Users.AddAsync(user2);
await context.SaveChangesAsync();
// Act - Query without tenant filter (because IsSet = false)
var allUsers = await context.Users.ToListAsync();
// Assert - Should return all users (no filtering)
allUsers.Should().HaveCount(2);
}
[Fact]
public async Task UserRepository_ShouldRespectTenantFilter()
{
// Arrange
var tenant1Id = TenantId.CreateUnique();
var tenant2Id = TenantId.CreateUnique();
var mockTenantContext = new Mock<ITenantContext>();
mockTenantContext.Setup(x => x.IsSet).Returns(true);
mockTenantContext.Setup(x => x.TenantId).Returns(tenant1Id);
var options = new DbContextOptionsBuilder<IdentityDbContext>()
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
.Options;
using var context = new IdentityDbContext(options, mockTenantContext.Object);
var user1 = User.CreateLocal(tenant1Id, Email.Create("john@tenant1.com"), "pass", FullName.Create("John Doe"));
var user2 = User.CreateLocal(tenant2Id, Email.Create("jane@tenant2.com"), "pass", FullName.Create("Jane Doe"));
await context.Users.AddAsync(user1);
await context.Users.AddAsync(user2);
await context.SaveChangesAsync();
// Act
var repository = new ColaFlow.Modules.Identity.Infrastructure.Persistence.Repositories.UserRepository(context);
var retrievedUser = await repository.GetByIdAsync(UserId.Create(user1.Id));
// Assert - Should find user1 (same tenant)
retrievedUser.Should().NotBeNull();
retrievedUser!.Email.Value.Should().Be("john@tenant1.com");
// Trying to get user2 (from different tenant) should return null due to filter
var user2Attempt = await repository.GetByIdAsync(UserId.Create(user2.Id));
user2Attempt.Should().BeNull();
}
public void Dispose()
{
_context.Dispose();
}
}

View File

@@ -0,0 +1,160 @@
using ColaFlow.Modules.Identity.Domain.Aggregates.Tenants;
using ColaFlow.Modules.Identity.Infrastructure.Persistence;
using ColaFlow.Modules.Identity.Infrastructure.Persistence.Repositories;
using ColaFlow.Modules.Identity.Infrastructure.Services;
using FluentAssertions;
using Microsoft.EntityFrameworkCore;
using Moq;
namespace ColaFlow.Modules.Identity.Infrastructure.Tests.Repositories;
public class TenantRepositoryTests : IDisposable
{
private readonly IdentityDbContext _context;
private readonly TenantRepository _repository;
public TenantRepositoryTests()
{
var options = new DbContextOptionsBuilder<IdentityDbContext>()
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
.Options;
var mockTenantContext = new Mock<ITenantContext>();
mockTenantContext.Setup(x => x.IsSet).Returns(false);
_context = new IdentityDbContext(options, mockTenantContext.Object);
_repository = new TenantRepository(_context);
}
[Fact]
public async Task AddAsync_ShouldPersistTenant()
{
// Arrange
var tenant = Tenant.Create(
TenantName.Create("Test Company"),
TenantSlug.Create("test-company"),
SubscriptionPlan.Professional);
// Act
await _repository.AddAsync(tenant);
// Assert
var retrieved = await _repository.GetByIdAsync(TenantId.Create(tenant.Id));
retrieved.Should().NotBeNull();
retrieved!.Name.Value.Should().Be("Test Company");
retrieved.Slug.Value.Should().Be("test-company");
retrieved.Plan.Should().Be(SubscriptionPlan.Professional);
}
[Fact]
public async Task GetBySlugAsync_ShouldReturnTenant()
{
// Arrange
var slug = TenantSlug.Create("acme-corp");
var tenant = Tenant.Create(
TenantName.Create("Acme Corp"),
slug,
SubscriptionPlan.Enterprise);
await _repository.AddAsync(tenant);
// Act
var retrieved = await _repository.GetBySlugAsync(slug);
// Assert
retrieved.Should().NotBeNull();
retrieved!.Slug.Value.Should().Be("acme-corp");
retrieved.Name.Value.Should().Be("Acme Corp");
}
[Fact]
public async Task ExistsBySlugAsync_ShouldReturnTrue_WhenSlugExists()
{
// Arrange
var slug = TenantSlug.Create("unique-slug");
var tenant = Tenant.Create(
TenantName.Create("Unique Company"),
slug,
SubscriptionPlan.Free);
await _repository.AddAsync(tenant);
// Act
var exists = await _repository.ExistsBySlugAsync(slug);
// Assert
exists.Should().BeTrue();
}
[Fact]
public async Task ExistsBySlugAsync_ShouldReturnFalse_WhenSlugDoesNotExist()
{
// Arrange
var slug = TenantSlug.Create("non-existent");
// Act
var exists = await _repository.ExistsBySlugAsync(slug);
// Assert
exists.Should().BeFalse();
}
[Fact]
public async Task UpdateAsync_ShouldModifyTenant()
{
// Arrange
var tenant = Tenant.Create(
TenantName.Create("Original Name"),
TenantSlug.Create("original-slug"),
SubscriptionPlan.Free);
await _repository.AddAsync(tenant);
// Act
tenant.UpdateName(TenantName.Create("Updated Name"));
await _repository.UpdateAsync(tenant);
// Assert
var retrieved = await _repository.GetByIdAsync(TenantId.Create(tenant.Id));
retrieved.Should().NotBeNull();
retrieved!.Name.Value.Should().Be("Updated Name");
}
[Fact]
public async Task GetAllAsync_ShouldReturnAllTenants()
{
// Arrange
var tenant1 = Tenant.Create(TenantName.Create("Tenant 1"), TenantSlug.Create("tenant-1"), SubscriptionPlan.Free);
var tenant2 = Tenant.Create(TenantName.Create("Tenant 2"), TenantSlug.Create("tenant-2"), SubscriptionPlan.Starter);
await _repository.AddAsync(tenant1);
await _repository.AddAsync(tenant2);
// Act
var allTenants = await _repository.GetAllAsync();
// Assert
allTenants.Should().HaveCount(2);
allTenants.Should().Contain(t => t.Name.Value == "Tenant 1");
allTenants.Should().Contain(t => t.Name.Value == "Tenant 2");
}
[Fact]
public async Task DeleteAsync_ShouldRemoveTenant()
{
// Arrange
var tenant = Tenant.Create(
TenantName.Create("To Delete"),
TenantSlug.Create("to-delete"),
SubscriptionPlan.Free);
await _repository.AddAsync(tenant);
// Act
await _repository.DeleteAsync(tenant);
// Assert
var retrieved = await _repository.GetByIdAsync(TenantId.Create(tenant.Id));
retrieved.Should().BeNull();
}
public void Dispose()
{
_context.Dispose();
}
}

View File

@@ -0,0 +1,10 @@
namespace ColaFlow.Modules.Identity.Infrastructure.Tests;
public class UnitTest1
{
[Fact]
public void Test1()
{
}
}