feat(replay): add ViewerBattleHistory entity + migration
New table backs /replay/info; composite PK (ViewerId, BattleId), index on (ViewerId, CreateTime) for the newest-first list query. 50-row per-viewer retention enforced by BattleHistoryWriter (next commit). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using SVSim.Database;
|
||||
using SVSim.Database.Models;
|
||||
using SVSim.UnitTests.Infrastructure;
|
||||
|
||||
namespace SVSim.UnitTests.Persistence;
|
||||
|
||||
public class ViewerBattleHistoryPersistenceTests
|
||||
{
|
||||
[Test]
|
||||
public async Task ViewerBattleHistory_round_trips_composite_PK_row()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
long viewerId = await factory.SeedViewerAsync(steamId: 76_561_198_000_000_001UL);
|
||||
|
||||
using (var seedScope = factory.Services.CreateScope())
|
||||
{
|
||||
var db = seedScope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
||||
db.ViewerBattleHistories.Add(new ViewerBattleHistory
|
||||
{
|
||||
ViewerId = viewerId,
|
||||
BattleId = 234_471_983_876L,
|
||||
BattleType = 4,
|
||||
DeckFormat = 2,
|
||||
TwoPickType = 0,
|
||||
IsLimitTurn = 0,
|
||||
SelfClassId = 8,
|
||||
SelfSubClassId = 0,
|
||||
SelfCharaId = 8,
|
||||
SelfRotationId = "0",
|
||||
OpponentClassId = 5,
|
||||
OpponentSubClassId = 0,
|
||||
OpponentCharaId = 805,
|
||||
OpponentName = "Foo",
|
||||
OpponentCountryCode = "",
|
||||
OpponentEmblemId = 721_341_010L,
|
||||
OpponentDegreeId = 120_023L,
|
||||
OpponentRotationId = "0",
|
||||
IsWin = false,
|
||||
BattleStartTime = new DateTime(2026, 6, 4, 17, 13, 13, DateTimeKind.Utc),
|
||||
CreateTime = new DateTime(2026, 6, 4, 17, 16, 6, DateTimeKind.Utc),
|
||||
});
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
using (var readScope = factory.Services.CreateScope())
|
||||
{
|
||||
var db = readScope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
||||
var row = await db.ViewerBattleHistories.SingleAsync(h =>
|
||||
h.ViewerId == viewerId && h.BattleId == 234_471_983_876L);
|
||||
Assert.That(row.OpponentName, Is.EqualTo("Foo"));
|
||||
Assert.That(row.OpponentEmblemId, Is.EqualTo(721_341_010L));
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ViewerBattleHistory_composite_PK_rejects_duplicate_battle_id_for_same_viewer()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
long viewerId = await factory.SeedViewerAsync(steamId: 76_561_198_000_000_001UL);
|
||||
|
||||
using (var seedScope = factory.Services.CreateScope())
|
||||
{
|
||||
var db = seedScope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
||||
db.ViewerBattleHistories.Add(NewRow(viewerId, battleId: 1L));
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
using (var dupScope = factory.Services.CreateScope())
|
||||
{
|
||||
var db = dupScope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
||||
db.ViewerBattleHistories.Add(NewRow(viewerId, battleId: 1L));
|
||||
Assert.ThrowsAsync<DbUpdateException>(async () => await db.SaveChangesAsync());
|
||||
}
|
||||
}
|
||||
|
||||
private static ViewerBattleHistory NewRow(long viewerId, long battleId) => new()
|
||||
{
|
||||
ViewerId = viewerId,
|
||||
BattleId = battleId,
|
||||
SelfRotationId = "0",
|
||||
OpponentName = "",
|
||||
OpponentCountryCode = "",
|
||||
OpponentRotationId = "0",
|
||||
BattleStartTime = DateTime.UtcNow,
|
||||
CreateTime = DateTime.UtcNow,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user