In progress
This commit is contained in:
@@ -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>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace ColaFlow.Modules.Identity.Infrastructure.Tests;
|
||||
|
||||
public class UnitTest1
|
||||
{
|
||||
[Fact]
|
||||
public void Test1()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user