feat(friend): implement single-target write methods on FriendService

SendApplyAsync, ApproveApplyAsync, RejectApplyAsync, CancelApplyAsync,
RejectFriendAsync all implemented. 11 new tests appended; all 23 friend
tests pass, full suite 1182/1182 green.
This commit is contained in:
gamer147
2026-06-09 21:59:32 -04:00
parent d078f275f8
commit 17591a6ebd
2 changed files with 350 additions and 10 deletions

View File

@@ -293,4 +293,246 @@ public class FriendServiceTests
Assert.That(result, Is.Null);
}
}
[Test]
public async Task SendApplyAsync_creates_apply_row()
{
using var factory = new SVSimTestFactory();
long sender = await SeedViewer(factory, 76_561_198_000_001_001UL);
long target = await SeedViewer(factory, 76_561_198_000_001_002UL);
var svc = Service(factory, out var scope);
using (scope) await svc.SendApplyAsync(sender, (int)target, default);
using var verifyScope = factory.Services.CreateScope();
var ctx = Ctx(verifyScope);
var applies = await ctx.ViewerFriendApplies.AsNoTracking().Where(a => a.FromViewerId == sender).ToListAsync();
Assert.That(applies, Has.Count.EqualTo(1));
Assert.That(applies[0].ToViewerId, Is.EqualTo(target));
}
[Test]
public async Task SendApplyAsync_no_op_for_self()
{
using var factory = new SVSimTestFactory();
long sender = await SeedViewer(factory, 76_561_198_000_001_003UL);
var svc = Service(factory, out var scope);
using (scope) await svc.SendApplyAsync(sender, (int)sender, default);
using var verifyScope = factory.Services.CreateScope();
var count = await Ctx(verifyScope).ViewerFriendApplies.CountAsync();
Assert.That(count, Is.EqualTo(0));
}
[Test]
public async Task SendApplyAsync_no_op_for_unknown_target()
{
using var factory = new SVSimTestFactory();
long sender = await SeedViewer(factory, 76_561_198_000_001_004UL);
var svc = Service(factory, out var scope);
using (scope) await svc.SendApplyAsync(sender, 999_999_999, default);
using var verifyScope = factory.Services.CreateScope();
var count = await Ctx(verifyScope).ViewerFriendApplies.CountAsync();
Assert.That(count, Is.EqualTo(0));
}
[Test]
public async Task SendApplyAsync_no_op_when_already_friends()
{
using var factory = new SVSimTestFactory();
long me = await SeedViewer(factory, 76_561_198_000_001_005UL);
long friend = await SeedViewer(factory, 76_561_198_000_001_006UL);
using (var scope = factory.Services.CreateScope())
{
var ctx = Ctx(scope);
ctx.ViewerFriends.Add(new ViewerFriend { OwnerViewerId = me, FriendViewerId = friend, CreatedAt = DateTime.UtcNow });
await ctx.SaveChangesAsync();
}
var svc = Service(factory, out var scope2);
using (scope2) await svc.SendApplyAsync(me, (int)friend, default);
using var verifyScope = factory.Services.CreateScope();
var count = await Ctx(verifyScope).ViewerFriendApplies.CountAsync();
Assert.That(count, Is.EqualTo(0));
}
[Test]
public async Task SendApplyAsync_no_op_when_already_pending()
{
using var factory = new SVSimTestFactory();
long sender = await SeedViewer(factory, 76_561_198_000_001_007UL);
long target = await SeedViewer(factory, 76_561_198_000_001_008UL);
using (var scope = factory.Services.CreateScope())
{
var ctx = Ctx(scope);
ctx.ViewerFriendApplies.Add(new ViewerFriendApply { FromViewerId = sender, ToViewerId = target, CreatedAt = DateTime.UtcNow });
await ctx.SaveChangesAsync();
}
var svc = Service(factory, out var scope2);
using (scope2) await svc.SendApplyAsync(sender, (int)target, default);
using var verifyScope = factory.Services.CreateScope();
var count = await Ctx(verifyScope).ViewerFriendApplies.CountAsync();
Assert.That(count, Is.EqualTo(1), "Pre-existing apply must not be duplicated");
}
[Test]
public async Task ApproveApplyAsync_creates_two_friend_rows_and_deletes_apply()
{
using var factory = new SVSimTestFactory();
long target = await SeedViewer(factory, 76_561_198_000_001_009UL);
long sender = await SeedViewer(factory, 76_561_198_000_001_010UL);
int applyId;
using (var scope = factory.Services.CreateScope())
{
var ctx = Ctx(scope);
var apply = new ViewerFriendApply { FromViewerId = sender, ToViewerId = target, CreatedAt = DateTime.UtcNow };
ctx.ViewerFriendApplies.Add(apply);
await ctx.SaveChangesAsync();
applyId = apply.Id;
}
var svc = Service(factory, out var scope2);
using (scope2) await svc.ApproveApplyAsync(target, applyId, default);
using var verifyScope = factory.Services.CreateScope();
var ctx2 = Ctx(verifyScope);
var friends = await ctx2.ViewerFriends.AsNoTracking().ToListAsync();
Assert.That(friends, Has.Count.EqualTo(2));
Assert.That(friends.Any(f => f.OwnerViewerId == target && f.FriendViewerId == sender));
Assert.That(friends.Any(f => f.OwnerViewerId == sender && f.FriendViewerId == target));
Assert.That(await ctx2.ViewerFriendApplies.CountAsync(), Is.EqualTo(0));
}
[Test]
public async Task ApproveApplyAsync_cleans_reverse_direction_apply_if_present()
{
using var factory = new SVSimTestFactory();
long target = await SeedViewer(factory, 76_561_198_000_001_011UL);
long sender = await SeedViewer(factory, 76_561_198_000_001_012UL);
int applyId;
using (var scope = factory.Services.CreateScope())
{
var ctx = Ctx(scope);
var a = new ViewerFriendApply { FromViewerId = sender, ToViewerId = target, CreatedAt = DateTime.UtcNow };
// The reverse-direction apply that should get cleaned.
var b = new ViewerFriendApply { FromViewerId = target, ToViewerId = sender, CreatedAt = DateTime.UtcNow };
ctx.ViewerFriendApplies.AddRange(a, b);
await ctx.SaveChangesAsync();
applyId = a.Id;
}
var svc = Service(factory, out var scope2);
using (scope2) await svc.ApproveApplyAsync(target, applyId, default);
using var verifyScope = factory.Services.CreateScope();
Assert.That(await Ctx(verifyScope).ViewerFriendApplies.CountAsync(), Is.EqualTo(0),
"Both directions' applies must be cleaned");
}
[Test]
public async Task ApproveApplyAsync_no_op_when_apply_not_addressed_to_caller()
{
using var factory = new SVSimTestFactory();
long imposter = await SeedViewer(factory, 76_561_198_000_001_013UL);
long target = await SeedViewer(factory, 76_561_198_000_001_014UL);
long sender = await SeedViewer(factory, 76_561_198_000_001_015UL);
int applyId;
using (var scope = factory.Services.CreateScope())
{
var ctx = Ctx(scope);
var apply = new ViewerFriendApply { FromViewerId = sender, ToViewerId = target, CreatedAt = DateTime.UtcNow };
ctx.ViewerFriendApplies.Add(apply);
await ctx.SaveChangesAsync();
applyId = apply.Id;
}
var svc = Service(factory, out var scope2);
using (scope2) await svc.ApproveApplyAsync(imposter, applyId, default);
using var verifyScope = factory.Services.CreateScope();
var ctx2 = Ctx(verifyScope);
Assert.That(await ctx2.ViewerFriendApplies.CountAsync(), Is.EqualTo(1), "Apply must survive");
Assert.That(await ctx2.ViewerFriends.CountAsync(), Is.EqualTo(0), "No friendship created");
}
[Test]
public async Task RejectApplyAsync_deletes_apply_only_for_correct_recipient()
{
using var factory = new SVSimTestFactory();
long target = await SeedViewer(factory, 76_561_198_000_001_016UL);
long sender = await SeedViewer(factory, 76_561_198_000_001_017UL);
int applyId;
using (var scope = factory.Services.CreateScope())
{
var ctx = Ctx(scope);
var apply = new ViewerFriendApply { FromViewerId = sender, ToViewerId = target, CreatedAt = DateTime.UtcNow };
ctx.ViewerFriendApplies.Add(apply);
await ctx.SaveChangesAsync();
applyId = apply.Id;
}
var svc = Service(factory, out var scope2);
using (scope2) await svc.RejectApplyAsync(target, applyId, default);
using var verifyScope = factory.Services.CreateScope();
Assert.That(await Ctx(verifyScope).ViewerFriendApplies.CountAsync(), Is.EqualTo(0));
}
[Test]
public async Task CancelApplyAsync_deletes_apply_only_for_correct_sender()
{
using var factory = new SVSimTestFactory();
long sender = await SeedViewer(factory, 76_561_198_000_001_018UL);
long target = await SeedViewer(factory, 76_561_198_000_001_019UL);
int applyId;
using (var scope = factory.Services.CreateScope())
{
var ctx = Ctx(scope);
var apply = new ViewerFriendApply { FromViewerId = sender, ToViewerId = target, CreatedAt = DateTime.UtcNow };
ctx.ViewerFriendApplies.Add(apply);
await ctx.SaveChangesAsync();
applyId = apply.Id;
}
var svc = Service(factory, out var scope2);
using (scope2) await svc.CancelApplyAsync(sender, applyId, default);
using var verifyScope = factory.Services.CreateScope();
Assert.That(await Ctx(verifyScope).ViewerFriendApplies.CountAsync(), Is.EqualTo(0));
}
[Test]
public async Task RejectFriendAsync_deletes_both_directions()
{
using var factory = new SVSimTestFactory();
long me = await SeedViewer(factory, 76_561_198_000_001_020UL);
long other = await SeedViewer(factory, 76_561_198_000_001_021UL);
using (var scope = factory.Services.CreateScope())
{
var ctx = Ctx(scope);
ctx.ViewerFriends.Add(new ViewerFriend { OwnerViewerId = me, FriendViewerId = other, CreatedAt = DateTime.UtcNow });
ctx.ViewerFriends.Add(new ViewerFriend { OwnerViewerId = other, FriendViewerId = me, CreatedAt = DateTime.UtcNow });
await ctx.SaveChangesAsync();
}
var svc = Service(factory, out var scope2);
using (scope2) await svc.RejectFriendAsync(me, (int)other, default);
using var verifyScope = factory.Services.CreateScope();
Assert.That(await Ctx(verifyScope).ViewerFriends.CountAsync(), Is.EqualTo(0));
}
}