fix(backend): resolve shift signup by looking up Member via ExternalUserId #3
@@ -118,17 +118,17 @@ public static class ShiftEndpoints
|
|||||||
ShiftService shiftService,
|
ShiftService shiftService,
|
||||||
HttpContext httpContext)
|
HttpContext httpContext)
|
||||||
{
|
{
|
||||||
var userIdClaim = httpContext.User.FindFirst("sub")?.Value;
|
var externalUserId = httpContext.User.FindFirst("sub")?.Value;
|
||||||
if (string.IsNullOrEmpty(userIdClaim) || !Guid.TryParse(userIdClaim, out var memberId))
|
if (string.IsNullOrEmpty(externalUserId))
|
||||||
{
|
{
|
||||||
return TypedResults.UnprocessableEntity("Invalid user ID");
|
return TypedResults.UnprocessableEntity("Invalid user ID");
|
||||||
}
|
}
|
||||||
|
|
||||||
var (success, error, isConflict) = await shiftService.SignUpForShiftAsync(id, memberId);
|
var (success, error, isConflict) = await shiftService.SignUpForShiftAsync(id, externalUserId);
|
||||||
|
|
||||||
if (!success)
|
if (!success)
|
||||||
{
|
{
|
||||||
if (error == "Shift not found")
|
if (error == "Shift not found" || error == "Member not found")
|
||||||
return TypedResults.NotFound();
|
return TypedResults.NotFound();
|
||||||
|
|
||||||
if (error == "Cannot sign up for past shifts")
|
if (error == "Cannot sign up for past shifts")
|
||||||
@@ -146,17 +146,17 @@ public static class ShiftEndpoints
|
|||||||
ShiftService shiftService,
|
ShiftService shiftService,
|
||||||
HttpContext httpContext)
|
HttpContext httpContext)
|
||||||
{
|
{
|
||||||
var userIdClaim = httpContext.User.FindFirst("sub")?.Value;
|
var externalUserId = httpContext.User.FindFirst("sub")?.Value;
|
||||||
if (string.IsNullOrEmpty(userIdClaim) || !Guid.TryParse(userIdClaim, out var memberId))
|
if (string.IsNullOrEmpty(externalUserId))
|
||||||
{
|
{
|
||||||
return TypedResults.UnprocessableEntity("Invalid user ID");
|
return TypedResults.UnprocessableEntity("Invalid user ID");
|
||||||
}
|
}
|
||||||
|
|
||||||
var (success, error) = await shiftService.CancelSignupAsync(id, memberId);
|
var (success, error) = await shiftService.CancelSignupAsync(id, externalUserId);
|
||||||
|
|
||||||
if (!success)
|
if (!success)
|
||||||
{
|
{
|
||||||
if (error == "Sign-up not found")
|
if (error == "Sign-up not found" || error == "Member not found")
|
||||||
return TypedResults.NotFound();
|
return TypedResults.NotFound();
|
||||||
|
|
||||||
return TypedResults.UnprocessableEntity(error!);
|
return TypedResults.UnprocessableEntity(error!);
|
||||||
|
|||||||
@@ -207,10 +207,18 @@ public class ShiftService
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<(bool success, string? error, bool isConflict)> SignUpForShiftAsync(Guid shiftId, Guid memberId)
|
public async Task<(bool success, string? error, bool isConflict)> SignUpForShiftAsync(Guid shiftId, string externalUserId)
|
||||||
{
|
{
|
||||||
var tenantId = _tenantProvider.GetTenantId();
|
var tenantId = _tenantProvider.GetTenantId();
|
||||||
|
|
||||||
|
var member = await _context.Members
|
||||||
|
.FirstOrDefaultAsync(m => m.ExternalUserId == externalUserId && m.TenantId == tenantId);
|
||||||
|
|
||||||
|
if (member == null)
|
||||||
|
return (false, "Member not found", false);
|
||||||
|
|
||||||
|
var memberId = member.Id;
|
||||||
|
|
||||||
var shift = await _context.Shifts.FindAsync(shiftId);
|
var shift = await _context.Shifts.FindAsync(shiftId);
|
||||||
|
|
||||||
if (shift == null)
|
if (shift == null)
|
||||||
@@ -265,10 +273,18 @@ public class ShiftService
|
|||||||
return (false, "Shift capacity changed during sign-up", true);
|
return (false, "Shift capacity changed during sign-up", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<(bool success, string? error)> CancelSignupAsync(Guid shiftId, Guid memberId)
|
public async Task<(bool success, string? error)> CancelSignupAsync(Guid shiftId, string externalUserId)
|
||||||
{
|
{
|
||||||
|
var tenantId = _tenantProvider.GetTenantId();
|
||||||
|
|
||||||
|
var member = await _context.Members
|
||||||
|
.FirstOrDefaultAsync(m => m.ExternalUserId == externalUserId && m.TenantId == tenantId);
|
||||||
|
|
||||||
|
if (member == null)
|
||||||
|
return (false, "Member not found");
|
||||||
|
|
||||||
var signup = await _context.ShiftSignups
|
var signup = await _context.ShiftSignups
|
||||||
.FirstOrDefaultAsync(ss => ss.ShiftId == shiftId && ss.MemberId == memberId);
|
.FirstOrDefaultAsync(ss => ss.ShiftId == shiftId && ss.MemberId == member.Id);
|
||||||
|
|
||||||
if (signup == null)
|
if (signup == null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using System.Net.Http.Json;
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using WorkClub.Domain.Entities;
|
using WorkClub.Domain.Entities;
|
||||||
|
using WorkClub.Domain.Enums;
|
||||||
using WorkClub.Infrastructure.Data;
|
using WorkClub.Infrastructure.Data;
|
||||||
using WorkClub.Tests.Integration.Infrastructure;
|
using WorkClub.Tests.Integration.Infrastructure;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
@@ -23,9 +24,60 @@ public class ShiftCrudTests : IntegrationTestBase
|
|||||||
// Clean up existing test data
|
// Clean up existing test data
|
||||||
context.ShiftSignups.RemoveRange(context.ShiftSignups);
|
context.ShiftSignups.RemoveRange(context.ShiftSignups);
|
||||||
context.Shifts.RemoveRange(context.Shifts);
|
context.Shifts.RemoveRange(context.Shifts);
|
||||||
|
context.Members.RemoveRange(context.Members);
|
||||||
|
context.Clubs.RemoveRange(context.Clubs);
|
||||||
await context.SaveChangesAsync();
|
await context.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<(Guid clubId, Guid memberId, string externalUserId)> SeedMemberAsync(
|
||||||
|
string tenantId,
|
||||||
|
string email,
|
||||||
|
string? externalUserId = null,
|
||||||
|
ClubRole role = ClubRole.Member)
|
||||||
|
{
|
||||||
|
externalUserId ??= Guid.NewGuid().ToString();
|
||||||
|
var clubId = Guid.NewGuid();
|
||||||
|
var memberId = Guid.NewGuid();
|
||||||
|
var now = DateTimeOffset.UtcNow;
|
||||||
|
|
||||||
|
using var scope = Factory.Services.CreateScope();
|
||||||
|
var context = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||||
|
|
||||||
|
var existingClub = await context.Clubs.FirstOrDefaultAsync(c => c.TenantId == tenantId);
|
||||||
|
if (existingClub != null)
|
||||||
|
{
|
||||||
|
clubId = existingClub.Id;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
context.Clubs.Add(new Club
|
||||||
|
{
|
||||||
|
Id = clubId,
|
||||||
|
TenantId = tenantId,
|
||||||
|
Name = "Test Club",
|
||||||
|
SportType = SportType.Tennis,
|
||||||
|
CreatedAt = now,
|
||||||
|
UpdatedAt = now
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Members.Add(new Member
|
||||||
|
{
|
||||||
|
Id = memberId,
|
||||||
|
TenantId = tenantId,
|
||||||
|
ExternalUserId = externalUserId,
|
||||||
|
DisplayName = email.Split('@')[0],
|
||||||
|
Email = email,
|
||||||
|
Role = role,
|
||||||
|
ClubId = clubId,
|
||||||
|
CreatedAt = now,
|
||||||
|
UpdatedAt = now
|
||||||
|
});
|
||||||
|
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
return (clubId, memberId, externalUserId);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task CreateShift_AsManager_ReturnsCreated()
|
public async Task CreateShift_AsManager_ReturnsCreated()
|
||||||
{
|
{
|
||||||
@@ -343,8 +395,8 @@ public class ShiftCrudTests : IntegrationTestBase
|
|||||||
public async Task SignUpForShift_WithCapacity_ReturnsOk()
|
public async Task SignUpForShift_WithCapacity_ReturnsOk()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
var (clubId, memberId, externalUserId) = await SeedMemberAsync("tenant1", "member@test.com");
|
||||||
var shiftId = Guid.NewGuid();
|
var shiftId = Guid.NewGuid();
|
||||||
var clubId = Guid.NewGuid();
|
|
||||||
var createdBy = Guid.NewGuid();
|
var createdBy = Guid.NewGuid();
|
||||||
var now = DateTimeOffset.UtcNow;
|
var now = DateTimeOffset.UtcNow;
|
||||||
|
|
||||||
@@ -370,7 +422,7 @@ public class ShiftCrudTests : IntegrationTestBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
SetTenant("tenant1");
|
SetTenant("tenant1");
|
||||||
AuthenticateAs("member@test.com", new Dictionary<string, string> { ["tenant1"] = "Member" });
|
AuthenticateAs("member@test.com", new Dictionary<string, string> { ["tenant1"] = "Member" }, externalUserId);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var response = await Client.PostAsync($"/api/shifts/{shiftId}/signup", null);
|
var response = await Client.PostAsync($"/api/shifts/{shiftId}/signup", null);
|
||||||
@@ -384,6 +436,7 @@ public class ShiftCrudTests : IntegrationTestBase
|
|||||||
var context = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
var context = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||||
var signups = await context.ShiftSignups.Where(ss => ss.ShiftId == shiftId).ToListAsync();
|
var signups = await context.ShiftSignups.Where(ss => ss.ShiftId == shiftId).ToListAsync();
|
||||||
Assert.Single(signups);
|
Assert.Single(signups);
|
||||||
|
Assert.Equal(memberId, signups[0].MemberId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -391,11 +444,14 @@ public class ShiftCrudTests : IntegrationTestBase
|
|||||||
public async Task SignUpForShift_WhenFull_ReturnsConflict()
|
public async Task SignUpForShift_WhenFull_ReturnsConflict()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
var (clubId, _, externalUserId) = await SeedMemberAsync("tenant1", "member@test.com");
|
||||||
var shiftId = Guid.NewGuid();
|
var shiftId = Guid.NewGuid();
|
||||||
var clubId = Guid.NewGuid();
|
|
||||||
var createdBy = Guid.NewGuid();
|
var createdBy = Guid.NewGuid();
|
||||||
var now = DateTimeOffset.UtcNow;
|
var now = DateTimeOffset.UtcNow;
|
||||||
|
|
||||||
|
// Seed a different member to fill the single slot
|
||||||
|
var (_, fillerMemberId, _) = await SeedMemberAsync("tenant1", "filler@test.com");
|
||||||
|
|
||||||
using (var scope = Factory.Services.CreateScope())
|
using (var scope = Factory.Services.CreateScope())
|
||||||
{
|
{
|
||||||
var context = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
var context = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||||
@@ -420,7 +476,7 @@ public class ShiftCrudTests : IntegrationTestBase
|
|||||||
Id = Guid.NewGuid(),
|
Id = Guid.NewGuid(),
|
||||||
TenantId = "tenant1",
|
TenantId = "tenant1",
|
||||||
ShiftId = shiftId,
|
ShiftId = shiftId,
|
||||||
MemberId = Guid.NewGuid(),
|
MemberId = fillerMemberId,
|
||||||
SignedUpAt = now
|
SignedUpAt = now
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -428,7 +484,7 @@ public class ShiftCrudTests : IntegrationTestBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
SetTenant("tenant1");
|
SetTenant("tenant1");
|
||||||
AuthenticateAs("member@test.com", new Dictionary<string, string> { ["tenant1"] = "Member" });
|
AuthenticateAs("member@test.com", new Dictionary<string, string> { ["tenant1"] = "Member" }, externalUserId);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var response = await Client.PostAsync($"/api/shifts/{shiftId}/signup", null);
|
var response = await Client.PostAsync($"/api/shifts/{shiftId}/signup", null);
|
||||||
@@ -441,8 +497,8 @@ public class ShiftCrudTests : IntegrationTestBase
|
|||||||
public async Task SignUpForShift_ForPastShift_ReturnsUnprocessableEntity()
|
public async Task SignUpForShift_ForPastShift_ReturnsUnprocessableEntity()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
var (clubId, _, externalUserId) = await SeedMemberAsync("tenant1", "member@test.com");
|
||||||
var shiftId = Guid.NewGuid();
|
var shiftId = Guid.NewGuid();
|
||||||
var clubId = Guid.NewGuid();
|
|
||||||
var createdBy = Guid.NewGuid();
|
var createdBy = Guid.NewGuid();
|
||||||
var now = DateTimeOffset.UtcNow;
|
var now = DateTimeOffset.UtcNow;
|
||||||
|
|
||||||
@@ -455,7 +511,7 @@ public class ShiftCrudTests : IntegrationTestBase
|
|||||||
Id = shiftId,
|
Id = shiftId,
|
||||||
TenantId = "tenant1",
|
TenantId = "tenant1",
|
||||||
Title = "Past Shift",
|
Title = "Past Shift",
|
||||||
StartTime = now.AddHours(-2), // Past shift
|
StartTime = now.AddHours(-2),
|
||||||
EndTime = now.AddHours(-1),
|
EndTime = now.AddHours(-1),
|
||||||
Capacity = 5,
|
Capacity = 5,
|
||||||
ClubId = clubId,
|
ClubId = clubId,
|
||||||
@@ -468,7 +524,7 @@ public class ShiftCrudTests : IntegrationTestBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
SetTenant("tenant1");
|
SetTenant("tenant1");
|
||||||
AuthenticateAs("member@test.com", new Dictionary<string, string> { ["tenant1"] = "Member" });
|
AuthenticateAs("member@test.com", new Dictionary<string, string> { ["tenant1"] = "Member" }, externalUserId);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var response = await Client.PostAsync($"/api/shifts/{shiftId}/signup", null);
|
var response = await Client.PostAsync($"/api/shifts/{shiftId}/signup", null);
|
||||||
@@ -481,10 +537,9 @@ public class ShiftCrudTests : IntegrationTestBase
|
|||||||
public async Task SignUpForShift_Duplicate_ReturnsConflict()
|
public async Task SignUpForShift_Duplicate_ReturnsConflict()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
var (clubId, memberId, externalUserId) = await SeedMemberAsync("tenant1", "member@test.com");
|
||||||
var shiftId = Guid.NewGuid();
|
var shiftId = Guid.NewGuid();
|
||||||
var clubId = Guid.NewGuid();
|
|
||||||
var createdBy = Guid.NewGuid();
|
var createdBy = Guid.NewGuid();
|
||||||
var memberId = Guid.Parse("00000000-0000-0000-0000-000000000001"); // Fixed member ID
|
|
||||||
var now = DateTimeOffset.UtcNow;
|
var now = DateTimeOffset.UtcNow;
|
||||||
|
|
||||||
using (var scope = Factory.Services.CreateScope())
|
using (var scope = Factory.Services.CreateScope())
|
||||||
@@ -505,7 +560,6 @@ public class ShiftCrudTests : IntegrationTestBase
|
|||||||
UpdatedAt = now
|
UpdatedAt = now
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add existing signup
|
|
||||||
context.ShiftSignups.Add(new ShiftSignup
|
context.ShiftSignups.Add(new ShiftSignup
|
||||||
{
|
{
|
||||||
Id = Guid.NewGuid(),
|
Id = Guid.NewGuid(),
|
||||||
@@ -519,7 +573,7 @@ public class ShiftCrudTests : IntegrationTestBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
SetTenant("tenant1");
|
SetTenant("tenant1");
|
||||||
AuthenticateAs("member@test.com", new Dictionary<string, string> { ["tenant1"] = "Member" }, memberId.ToString());
|
AuthenticateAs("member@test.com", new Dictionary<string, string> { ["tenant1"] = "Member" }, externalUserId);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var response = await Client.PostAsync($"/api/shifts/{shiftId}/signup", null);
|
var response = await Client.PostAsync($"/api/shifts/{shiftId}/signup", null);
|
||||||
@@ -532,10 +586,9 @@ public class ShiftCrudTests : IntegrationTestBase
|
|||||||
public async Task CancelSignup_BeforeShift_ReturnsOk()
|
public async Task CancelSignup_BeforeShift_ReturnsOk()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
var (clubId, memberId, externalUserId) = await SeedMemberAsync("tenant1", "member@test.com");
|
||||||
var shiftId = Guid.NewGuid();
|
var shiftId = Guid.NewGuid();
|
||||||
var clubId = Guid.NewGuid();
|
|
||||||
var createdBy = Guid.NewGuid();
|
var createdBy = Guid.NewGuid();
|
||||||
var memberId = Guid.Parse("00000000-0000-0000-0000-000000000001");
|
|
||||||
var now = DateTimeOffset.UtcNow;
|
var now = DateTimeOffset.UtcNow;
|
||||||
|
|
||||||
using (var scope = Factory.Services.CreateScope())
|
using (var scope = Factory.Services.CreateScope())
|
||||||
@@ -569,7 +622,7 @@ public class ShiftCrudTests : IntegrationTestBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
SetTenant("tenant1");
|
SetTenant("tenant1");
|
||||||
AuthenticateAs("member@test.com", new Dictionary<string, string> { ["tenant1"] = "Member" }, memberId.ToString());
|
AuthenticateAs("member@test.com", new Dictionary<string, string> { ["tenant1"] = "Member" }, externalUserId);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var response = await Client.DeleteAsync($"/api/shifts/{shiftId}/signup");
|
var response = await Client.DeleteAsync($"/api/shifts/{shiftId}/signup");
|
||||||
@@ -577,7 +630,6 @@ public class ShiftCrudTests : IntegrationTestBase
|
|||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
|
||||||
// Verify signup was deleted
|
|
||||||
using (var scope = Factory.Services.CreateScope())
|
using (var scope = Factory.Services.CreateScope())
|
||||||
{
|
{
|
||||||
var context = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
var context = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||||
@@ -590,8 +642,11 @@ public class ShiftCrudTests : IntegrationTestBase
|
|||||||
public async Task SignUpForShift_ConcurrentLastSlot_HandlesRaceCondition()
|
public async Task SignUpForShift_ConcurrentLastSlot_HandlesRaceCondition()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
var (clubId, fillerMemberId, _) = await SeedMemberAsync("tenant1", "filler@test.com");
|
||||||
|
var (_, _, externalUserId1) = await SeedMemberAsync("tenant1", "member1@test.com");
|
||||||
|
var (_, _, externalUserId2) = await SeedMemberAsync("tenant1", "member2@test.com");
|
||||||
|
|
||||||
var shiftId = Guid.NewGuid();
|
var shiftId = Guid.NewGuid();
|
||||||
var clubId = Guid.NewGuid();
|
|
||||||
var createdBy = Guid.NewGuid();
|
var createdBy = Guid.NewGuid();
|
||||||
var now = DateTimeOffset.UtcNow;
|
var now = DateTimeOffset.UtcNow;
|
||||||
|
|
||||||
@@ -613,13 +668,12 @@ public class ShiftCrudTests : IntegrationTestBase
|
|||||||
UpdatedAt = now
|
UpdatedAt = now
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add one signup (leaving one slot)
|
|
||||||
context.ShiftSignups.Add(new ShiftSignup
|
context.ShiftSignups.Add(new ShiftSignup
|
||||||
{
|
{
|
||||||
Id = Guid.NewGuid(),
|
Id = Guid.NewGuid(),
|
||||||
TenantId = "tenant1",
|
TenantId = "tenant1",
|
||||||
ShiftId = shiftId,
|
ShiftId = shiftId,
|
||||||
MemberId = Guid.NewGuid(),
|
MemberId = fillerMemberId,
|
||||||
SignedUpAt = now
|
SignedUpAt = now
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -628,24 +682,20 @@ public class ShiftCrudTests : IntegrationTestBase
|
|||||||
|
|
||||||
SetTenant("tenant1");
|
SetTenant("tenant1");
|
||||||
|
|
||||||
// Act - Simulate two concurrent requests
|
// Act
|
||||||
var member1 = Guid.NewGuid();
|
AuthenticateAs("member1@test.com", new Dictionary<string, string> { ["tenant1"] = "Member" }, externalUserId1);
|
||||||
var member2 = Guid.NewGuid();
|
|
||||||
|
|
||||||
AuthenticateAs("member1@test.com", new Dictionary<string, string> { ["tenant1"] = "Member" }, member1.ToString());
|
|
||||||
var response1Task = Client.PostAsync($"/api/shifts/{shiftId}/signup", null);
|
var response1Task = Client.PostAsync($"/api/shifts/{shiftId}/signup", null);
|
||||||
|
|
||||||
AuthenticateAs("member2@test.com", new Dictionary<string, string> { ["tenant1"] = "Member" }, member2.ToString());
|
AuthenticateAs("member2@test.com", new Dictionary<string, string> { ["tenant1"] = "Member" }, externalUserId2);
|
||||||
var response2Task = Client.PostAsync($"/api/shifts/{shiftId}/signup", null);
|
var response2Task = Client.PostAsync($"/api/shifts/{shiftId}/signup", null);
|
||||||
|
|
||||||
var responses = await Task.WhenAll(response1Task, response2Task);
|
var responses = await Task.WhenAll(response1Task, response2Task);
|
||||||
|
|
||||||
// Assert - One should succeed (200), one should fail (409)
|
// Assert
|
||||||
var statuses = responses.Select(r => r.StatusCode).OrderBy(s => s).ToList();
|
var statuses = responses.Select(r => r.StatusCode).OrderBy(s => s).ToList();
|
||||||
Assert.Contains(HttpStatusCode.OK, statuses);
|
Assert.Contains(HttpStatusCode.OK, statuses);
|
||||||
Assert.Contains(HttpStatusCode.Conflict, statuses);
|
Assert.Contains(HttpStatusCode.Conflict, statuses);
|
||||||
|
|
||||||
// Verify only 2 total signups exist (capacity limit enforced)
|
|
||||||
using (var scope = Factory.Services.CreateScope())
|
using (var scope = Factory.Services.CreateScope())
|
||||||
{
|
{
|
||||||
var context = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
var context = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||||
|
|||||||
Reference in New Issue
Block a user