Files
work-club-manager/backend/WorkClub.Tests.Integration/Members/MemberEndpointsTests.cs

272 lines
9.4 KiB
C#
Raw Normal View History

using System.Net;
using System.Net.Http.Json;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using WorkClub.Domain.Entities;
using WorkClub.Domain.Enums;
using WorkClub.Infrastructure.Data;
using WorkClub.Tests.Integration.Infrastructure;
using Xunit;
namespace WorkClub.Tests.Integration.Members;
public class MemberEndpointsTests : IntegrationTestBase
{
public MemberEndpointsTests(CustomWebApplicationFactory<Program> factory) : base(factory)
{
}
public override async Task InitializeAsync()
{
using var scope = Factory.Services.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<AppDbContext>();
context.Members.RemoveRange(context.Members);
context.Clubs.RemoveRange(context.Clubs);
await context.SaveChangesAsync();
var club1Id = Guid.NewGuid();
var club2Id = Guid.NewGuid();
context.Clubs.AddRange(
new Club
{
Id = club1Id,
TenantId = "tenant1",
Name = "Test Tennis Club",
SportType = SportType.Tennis,
CreatedAt = DateTimeOffset.UtcNow,
UpdatedAt = DateTimeOffset.UtcNow
},
new Club
{
Id = club2Id,
TenantId = "tenant2",
Name = "Test Cycling Club",
SportType = SportType.Cycling,
CreatedAt = DateTimeOffset.UtcNow,
UpdatedAt = DateTimeOffset.UtcNow
});
var adminId = Guid.NewGuid();
var managerId = Guid.NewGuid();
var member1Id = Guid.NewGuid();
context.Members.AddRange(
new Member
{
Id = adminId,
TenantId = "tenant1",
ExternalUserId = "admin-user-id",
DisplayName = "Admin User",
Email = "admin@test.com",
Role = ClubRole.Admin,
ClubId = club1Id,
CreatedAt = DateTimeOffset.UtcNow,
UpdatedAt = DateTimeOffset.UtcNow
},
new Member
{
Id = managerId,
TenantId = "tenant1",
ExternalUserId = "manager-user-id",
DisplayName = "Manager User",
Email = "manager@test.com",
Role = ClubRole.Manager,
ClubId = club1Id,
CreatedAt = DateTimeOffset.UtcNow,
UpdatedAt = DateTimeOffset.UtcNow
},
new Member
{
Id = member1Id,
TenantId = "tenant1",
ExternalUserId = "member1-user-id",
DisplayName = "Member One",
Email = "member1@test.com",
Role = ClubRole.Member,
ClubId = club1Id,
CreatedAt = DateTimeOffset.UtcNow,
UpdatedAt = DateTimeOffset.UtcNow
},
new Member
{
Id = Guid.NewGuid(),
TenantId = "tenant2",
ExternalUserId = "other-user-id",
DisplayName = "Other User",
Email = "other@test.com",
Role = ClubRole.Member,
ClubId = club2Id,
CreatedAt = DateTimeOffset.UtcNow,
UpdatedAt = DateTimeOffset.UtcNow
});
await context.SaveChangesAsync();
}
[Fact]
public async Task GetMembers_ReturnsOnlyCurrentTenantMembers()
{
SetTenant("tenant1");
AuthenticateAs("admin@test.com", new Dictionary<string, string>
{
["tenant1"] = "Admin"
}, userId: "admin-user-id");
var response = await Client.GetAsync("/api/members");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var members = await response.Content.ReadFromJsonAsync<List<MemberListResponse>>();
Assert.NotNull(members);
Assert.Equal(3, members.Count);
Assert.DoesNotContain(members, m => m.Email == "other@test.com");
}
[Fact]
public async Task GetMembers_DifferentTenant_ReturnsDifferentMembers()
{
SetTenant("tenant2");
AuthenticateAs("other@test.com", new Dictionary<string, string>
{
["tenant2"] = "Member"
}, userId: "other-user-id");
var response = await Client.GetAsync("/api/members");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var members = await response.Content.ReadFromJsonAsync<List<MemberListResponse>>();
Assert.NotNull(members);
Assert.Single(members);
Assert.Equal("other@test.com", members[0].Email);
}
[Fact]
public async Task GetMembers_AsViewer_ReturnsForbidden()
{
SetTenant("tenant1");
AuthenticateAs("viewer@test.com", new Dictionary<string, string>
{
["tenant1"] = "Viewer"
}, userId: "viewer-user-id");
var response = await Client.GetAsync("/api/members");
Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode);
}
[Fact]
public async Task GetMemberById_ExistingMember_ReturnsMemberDetail()
{
using var scope = Factory.Services.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<AppDbContext>();
var member = await context.Members.FirstAsync(m => m.Email == "manager@test.com");
SetTenant("tenant1");
AuthenticateAs("admin@test.com", new Dictionary<string, string>
{
["tenant1"] = "Admin"
}, userId: "admin-user-id");
var response = await Client.GetAsync($"/api/members/{member.Id}");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var result = await response.Content.ReadFromJsonAsync<MemberDetailResponse>();
Assert.NotNull(result);
Assert.Equal(member.Id, result.Id);
Assert.Equal("Manager User", result.DisplayName);
Assert.Equal("manager@test.com", result.Email);
Assert.Equal("Manager", result.Role);
}
[Fact]
public async Task GetMemberById_WrongTenant_ReturnsNotFound()
{
using var scope = Factory.Services.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<AppDbContext>();
var tenant1Member = await context.Members.FirstAsync(m => m.TenantId == "tenant1");
SetTenant("tenant2");
AuthenticateAs("other@test.com", new Dictionary<string, string>
{
["tenant2"] = "Member"
}, userId: "other-user-id");
var response = await Client.GetAsync($"/api/members/{tenant1Member.Id}");
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
public async Task GetMembersMe_ReturnsCurrentUserMembership()
{
SetTenant("tenant1");
AuthenticateAs("manager@test.com", new Dictionary<string, string>
{
["tenant1"] = "Manager"
}, userId: "manager-user-id");
var response = await Client.GetAsync("/api/members/me");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var result = await response.Content.ReadFromJsonAsync<MemberDetailResponse>();
Assert.NotNull(result);
Assert.Equal("Manager User", result.DisplayName);
Assert.Equal("manager@test.com", result.Email);
Assert.Equal("Manager", result.Role);
}
[Fact]
public async Task MemberAutoSync_NewUser_CreatesMembeRecordFromJwt()
{
SetTenant("tenant1");
AuthenticateAs("newuser@test.com", new Dictionary<string, string>
{
["tenant1"] = "Member"
}, userId: "new-user-id");
var response = await Client.GetAsync("/api/members/me");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
using var scope = Factory.Services.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<AppDbContext>();
var syncedMember = await context.Members.FirstOrDefaultAsync(m => m.ExternalUserId == "new-user-id");
Assert.NotNull(syncedMember);
Assert.Equal("newuser@test.com", syncedMember.Email);
Assert.Equal("tenant1", syncedMember.TenantId);
Assert.Equal(ClubRole.Member, syncedMember.Role);
}
[Fact]
public async Task MemberAutoSync_ExistingUser_DoesNotDuplicate()
{
using var scope1 = Factory.Services.CreateScope();
var context1 = scope1.ServiceProvider.GetRequiredService<AppDbContext>();
var initialCount = await context1.Members.CountAsync(m => m.ExternalUserId == "admin-user-id");
SetTenant("tenant1");
AuthenticateAs("admin@test.com", new Dictionary<string, string>
{
["tenant1"] = "Admin"
}, userId: "admin-user-id");
var response = await Client.GetAsync("/api/members/me");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
using var scope2 = Factory.Services.CreateScope();
var context2 = scope2.ServiceProvider.GetRequiredService<AppDbContext>();
var finalCount = await context2.Members.CountAsync(m => m.ExternalUserId == "admin-user-id");
Assert.Equal(initialCount, finalCount);
}
}
public record MemberListResponse(Guid Id, string DisplayName, string Email, string Role);
public record MemberDetailResponse(Guid Id, string DisplayName, string Email, string Role, Guid ClubId, DateTimeOffset CreatedAt);