Add explicit write transactions and deterministic ordering tests
This commit is contained in:
@@ -13,6 +13,8 @@ internal sealed class AdminWorkflowService(AppDbContext db)
|
|||||||
state.ResultsOpen = request.ResultsOpen;
|
state.ResultsOpen = request.ResultsOpen;
|
||||||
state.UpdatedAt = DateTimeOffset.UtcNow;
|
state.UpdatedAt = DateTimeOffset.UtcNow;
|
||||||
|
|
||||||
|
await using var tx = await db.Database.BeginTransactionAsync();
|
||||||
|
|
||||||
if (request.ResultsOpen)
|
if (request.ResultsOpen)
|
||||||
{
|
{
|
||||||
await db.Players.ExecuteUpdateAsync(p => p.SetProperty(x => x.CurrentPhase, Phase.Results));
|
await db.Players.ExecuteUpdateAsync(p => p.SetProperty(x => x.CurrentPhase, Phase.Results));
|
||||||
@@ -23,6 +25,7 @@ internal sealed class AdminWorkflowService(AppDbContext db)
|
|||||||
}
|
}
|
||||||
|
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
|
await tx.CommitAsync();
|
||||||
var currentState = await db.AppState.AsNoTracking().FirstAsync();
|
var currentState = await db.AppState.AsNoTracking().FirstAsync();
|
||||||
return Results.Ok(new
|
return Results.Ok(new
|
||||||
{
|
{
|
||||||
@@ -207,6 +210,8 @@ internal sealed class AdminWorkflowService(AppDbContext db)
|
|||||||
|
|
||||||
public async Task<IResult> ResetAsync()
|
public async Task<IResult> ResetAsync()
|
||||||
{
|
{
|
||||||
|
await using var tx = await db.Database.BeginTransactionAsync();
|
||||||
|
|
||||||
await db.Votes.ExecuteDeleteAsync();
|
await db.Votes.ExecuteDeleteAsync();
|
||||||
await db.Suggestions.ExecuteDeleteAsync();
|
await db.Suggestions.ExecuteDeleteAsync();
|
||||||
|
|
||||||
@@ -215,6 +220,7 @@ internal sealed class AdminWorkflowService(AppDbContext db)
|
|||||||
state.ResultsOpen = false;
|
state.ResultsOpen = false;
|
||||||
state.UpdatedAt = DateTimeOffset.UtcNow;
|
state.UpdatedAt = DateTimeOffset.UtcNow;
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
|
await tx.CommitAsync();
|
||||||
|
|
||||||
return Results.Ok(new
|
return Results.Ok(new
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -67,6 +67,8 @@ internal sealed class SuggestionWorkflowService(AppDbContext db, IHttpClientFact
|
|||||||
MaxPlayers = request.MaxPlayers
|
MaxPlayers = request.MaxPlayers
|
||||||
};
|
};
|
||||||
|
|
||||||
|
await using var tx = await db.Database.BeginTransactionAsync();
|
||||||
|
|
||||||
db.Suggestions.Add(suggestion);
|
db.Suggestions.Add(suggestion);
|
||||||
|
|
||||||
if (usingJoker)
|
if (usingJoker)
|
||||||
@@ -76,6 +78,7 @@ internal sealed class SuggestionWorkflowService(AppDbContext db, IHttpClientFact
|
|||||||
}
|
}
|
||||||
|
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
|
await tx.CommitAsync();
|
||||||
|
|
||||||
return Results.Created($"/api/suggestions/{suggestion.Id}", new { suggestion.Id });
|
return Results.Created($"/api/suggestions/{suggestion.Id}", new { suggestion.Id });
|
||||||
}
|
}
|
||||||
@@ -95,6 +98,8 @@ internal sealed class SuggestionWorkflowService(AppDbContext db, IHttpClientFact
|
|||||||
if (suggestion == null)
|
if (suggestion == null)
|
||||||
return Results.NotFound(new { error = "Suggestion not found." });
|
return Results.NotFound(new { error = "Suggestion not found." });
|
||||||
|
|
||||||
|
await using var tx = await db.Database.BeginTransactionAsync();
|
||||||
|
|
||||||
await db.Suggestions
|
await db.Suggestions
|
||||||
.Where(s => s.ParentSuggestionId == suggestion.Id)
|
.Where(s => s.ParentSuggestionId == suggestion.Id)
|
||||||
.ExecuteUpdateAsync(s => s.SetProperty(x => x.ParentSuggestionId, (int?)null));
|
.ExecuteUpdateAsync(s => s.SetProperty(x => x.ParentSuggestionId, (int?)null));
|
||||||
@@ -103,6 +108,7 @@ internal sealed class SuggestionWorkflowService(AppDbContext db, IHttpClientFact
|
|||||||
|
|
||||||
db.Suggestions.Remove(suggestion);
|
db.Suggestions.Remove(suggestion);
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
|
await tx.CommitAsync();
|
||||||
return Results.NoContent();
|
return Results.NoContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -244,11 +244,11 @@ public class AdminTests
|
|||||||
{
|
{
|
||||||
var p = await db.Players.FirstAsync(x => !x.IsAdmin);
|
var p = await db.Players.FirstAsync(x => !x.IsAdmin);
|
||||||
p.VotesFinal = true;
|
p.VotesFinal = true;
|
||||||
|
var state = await db.AppState.FirstAsync();
|
||||||
|
state.UpdatedAt = DateTimeOffset.UnixEpoch;
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
});
|
});
|
||||||
|
|
||||||
var beforeState = await factory.WithDbContextAsync(async db => await db.AppState.AsNoTracking().FirstAsync());
|
|
||||||
await Task.Delay(5);
|
|
||||||
var close = await admin.PostAsJsonAsync("/api/admin/results", new { resultsOpen = false });
|
var close = await admin.PostAsJsonAsync("/api/admin/results", new { resultsOpen = false });
|
||||||
close.EnsureSuccessStatusCode();
|
close.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
@@ -259,7 +259,7 @@ public class AdminTests
|
|||||||
Assert.False(p.VotesFinal);
|
Assert.False(p.VotesFinal);
|
||||||
var state = await db.AppState.AsNoTracking().FirstAsync();
|
var state = await db.AppState.AsNoTracking().FirstAsync();
|
||||||
Assert.False(state.ResultsOpen);
|
Assert.False(state.ResultsOpen);
|
||||||
Assert.True(state.UpdatedAt > beforeState.UpdatedAt);
|
Assert.True(state.UpdatedAt > DateTimeOffset.UnixEpoch);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -365,28 +365,15 @@ public class SuggestionTests
|
|||||||
var client = factory.CreateClientWithCookies();
|
var client = factory.CreateClientWithCookies();
|
||||||
await client.RegisterAsync("mine");
|
await client.RegisterAsync("mine");
|
||||||
|
|
||||||
await client.PostAsJsonAsync("/api/suggestions", new
|
var secondId = await client.CreateSuggestionAsync("Second");
|
||||||
|
var thirdId = await client.CreateSuggestionAsync("Third");
|
||||||
|
await factory.WithDbContextAsync(async db =>
|
||||||
{
|
{
|
||||||
Name = "Second",
|
var second = await db.Suggestions.FindAsync(secondId);
|
||||||
Genre = (string?)null,
|
var third = await db.Suggestions.FindAsync(thirdId);
|
||||||
Description = (string?)null,
|
second!.CreatedAt = DateTimeOffset.UtcNow.AddMinutes(-1);
|
||||||
ScreenshotUrl = (string?)null,
|
third!.CreatedAt = DateTimeOffset.UtcNow;
|
||||||
YoutubeUrl = (string?)null,
|
await db.SaveChangesAsync();
|
||||||
GameUrl = (string?)null,
|
|
||||||
MinPlayers = (int?)null,
|
|
||||||
MaxPlayers = (int?)null
|
|
||||||
});
|
|
||||||
await Task.Delay(10);
|
|
||||||
await client.PostAsJsonAsync("/api/suggestions", new
|
|
||||||
{
|
|
||||||
Name = "Third",
|
|
||||||
Genre = (string?)null,
|
|
||||||
Description = (string?)null,
|
|
||||||
ScreenshotUrl = (string?)null,
|
|
||||||
YoutubeUrl = (string?)null,
|
|
||||||
GameUrl = (string?)null,
|
|
||||||
MinPlayers = (int?)null,
|
|
||||||
MaxPlayers = (int?)null
|
|
||||||
});
|
});
|
||||||
|
|
||||||
var mine = await client.GetFromJsonAsync<List<JsonElement>>("/api/suggestions/mine");
|
var mine = await client.GetFromJsonAsync<List<JsonElement>>("/api/suggestions/mine");
|
||||||
@@ -572,11 +559,13 @@ public class SuggestionTests
|
|||||||
await client.RegisterAsync("owner");
|
await client.RegisterAsync("owner");
|
||||||
|
|
||||||
var id1 = await client.CreateSuggestionAsync("Alpha");
|
var id1 = await client.CreateSuggestionAsync("Alpha");
|
||||||
await Task.Delay(10);
|
|
||||||
var id2 = await client.CreateSuggestionAsync("Beta");
|
var id2 = await client.CreateSuggestionAsync("Beta");
|
||||||
await factory.WithDbContextAsync(async db =>
|
await factory.WithDbContextAsync(async db =>
|
||||||
{
|
{
|
||||||
|
var alpha = await db.Suggestions.FindAsync(id1);
|
||||||
var beta = await db.Suggestions.FindAsync(id2);
|
var beta = await db.Suggestions.FindAsync(id2);
|
||||||
|
alpha!.CreatedAt = DateTimeOffset.UtcNow.AddMinutes(-1);
|
||||||
|
beta!.CreatedAt = DateTimeOffset.UtcNow;
|
||||||
beta!.ParentSuggestionId = id1;
|
beta!.ParentSuggestionId = id1;
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user