feat(friend): bulk apply ops + IPlayedTogetherWriter with retention cap
Implements RejectAllAppliesAsync, CancelAllAppliesAsync (ExecuteDelete bulk deletes on incoming/outgoing applies respectively) and RecordAsync (upsert played-together row with 50-row per-viewer retention eviction). 4 new tests added; all 1186 tests pass. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -535,4 +535,108 @@ public class FriendServiceTests
|
||||
using var verifyScope = factory.Services.CreateScope();
|
||||
Assert.That(await Ctx(verifyScope).ViewerFriends.CountAsync(), Is.EqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task RejectAllAppliesAsync_deletes_only_incoming_for_caller()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
long me = await SeedViewer(factory, 76_561_198_000_002_001UL);
|
||||
long other = await SeedViewer(factory, 76_561_198_000_002_002UL);
|
||||
|
||||
using (var scope = factory.Services.CreateScope())
|
||||
{
|
||||
var ctx = Ctx(scope);
|
||||
ctx.ViewerFriendApplies.Add(new ViewerFriendApply { FromViewerId = other, ToViewerId = me, CreatedAt = DateTime.UtcNow });
|
||||
ctx.ViewerFriendApplies.Add(new ViewerFriendApply { FromViewerId = me, ToViewerId = other, CreatedAt = DateTime.UtcNow });
|
||||
await ctx.SaveChangesAsync();
|
||||
}
|
||||
|
||||
var svc = Service(factory, out var scope2);
|
||||
using (scope2) await svc.RejectAllAppliesAsync(me, default);
|
||||
|
||||
using var verifyScope = factory.Services.CreateScope();
|
||||
var remaining = await Ctx(verifyScope).ViewerFriendApplies.AsNoTracking().ToListAsync();
|
||||
Assert.That(remaining, Has.Count.EqualTo(1));
|
||||
Assert.That(remaining[0].FromViewerId, Is.EqualTo(me), "Outgoing must survive");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task CancelAllAppliesAsync_deletes_only_outgoing_for_caller()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
long me = await SeedViewer(factory, 76_561_198_000_002_003UL);
|
||||
long other = await SeedViewer(factory, 76_561_198_000_002_004UL);
|
||||
|
||||
using (var scope = factory.Services.CreateScope())
|
||||
{
|
||||
var ctx = Ctx(scope);
|
||||
ctx.ViewerFriendApplies.Add(new ViewerFriendApply { FromViewerId = me, ToViewerId = other, CreatedAt = DateTime.UtcNow });
|
||||
ctx.ViewerFriendApplies.Add(new ViewerFriendApply { FromViewerId = other, ToViewerId = me, CreatedAt = DateTime.UtcNow });
|
||||
await ctx.SaveChangesAsync();
|
||||
}
|
||||
|
||||
var svc = Service(factory, out var scope2);
|
||||
using (scope2) await svc.CancelAllAppliesAsync(me, default);
|
||||
|
||||
using var verifyScope = factory.Services.CreateScope();
|
||||
var remaining = await Ctx(verifyScope).ViewerFriendApplies.AsNoTracking().ToListAsync();
|
||||
Assert.That(remaining, Has.Count.EqualTo(1));
|
||||
Assert.That(remaining[0].ToViewerId, Is.EqualTo(me), "Incoming must survive");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task RecordAsync_upserts_PlayedAt_for_existing_pair()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
long me = await SeedViewer(factory, 76_561_198_000_002_005UL);
|
||||
long opp = await SeedViewer(factory, 76_561_198_000_002_006UL);
|
||||
|
||||
var ctxFactory = factory.Services;
|
||||
IPlayedTogetherWriter writer;
|
||||
using (var scope = ctxFactory.CreateScope())
|
||||
{
|
||||
writer = scope.ServiceProvider.GetRequiredService<IPlayedTogetherWriter>();
|
||||
await writer.RecordAsync(me, opp, new BattleParticipationContext(1, 2, 3, 4), default);
|
||||
}
|
||||
DateTime firstTimestamp;
|
||||
using (var scope = ctxFactory.CreateScope())
|
||||
{
|
||||
firstTimestamp = (await Ctx(scope).ViewerPlayedTogethers.AsNoTracking()
|
||||
.FirstAsync(p => p.OwnerViewerId == me && p.OpponentViewerId == opp)).PlayedAt;
|
||||
}
|
||||
|
||||
// Wait a tick, record again.
|
||||
await Task.Delay(20);
|
||||
using (var scope = ctxFactory.CreateScope())
|
||||
{
|
||||
writer = scope.ServiceProvider.GetRequiredService<IPlayedTogetherWriter>();
|
||||
await writer.RecordAsync(me, opp, new BattleParticipationContext(5, 6, 7, 8), default);
|
||||
}
|
||||
|
||||
using var verifyScope = ctxFactory.CreateScope();
|
||||
var rows = await Ctx(verifyScope).ViewerPlayedTogethers.AsNoTracking()
|
||||
.Where(p => p.OwnerViewerId == me).ToListAsync();
|
||||
Assert.That(rows, Has.Count.EqualTo(1), "Upsert — no duplicate row");
|
||||
Assert.That(rows[0].PlayedAt, Is.GreaterThan(firstTimestamp));
|
||||
Assert.That(rows[0].PlayedMode, Is.EqualTo(5), "Latest context wins");
|
||||
Assert.That(rows[0].BattleType, Is.EqualTo(6));
|
||||
Assert.That(rows[0].DeckFormat, Is.EqualTo(7));
|
||||
Assert.That(rows[0].TwoPickType, Is.EqualTo(8));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task RecordAsync_no_op_when_owner_equals_opponent()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
long me = await SeedViewer(factory, 76_561_198_000_002_007UL);
|
||||
|
||||
using (var scope = factory.Services.CreateScope())
|
||||
{
|
||||
var writer = scope.ServiceProvider.GetRequiredService<IPlayedTogetherWriter>();
|
||||
await writer.RecordAsync(me, me, new BattleParticipationContext(0, 0, 0, 0), default);
|
||||
}
|
||||
|
||||
using var verifyScope = factory.Services.CreateScope();
|
||||
Assert.That(await Ctx(verifyScope).ViewerPlayedTogethers.CountAsync(), Is.EqualTo(0));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user