Files
SVSimServer/SVSim.UnitTests/Services/FriendServiceTests.cs
gamer147 d078f275f8 feat(friend): implement 5 read methods on FriendService + register DI + read test suite
GetFriendsAsync, GetReceiveAppliesAsync, GetSendAppliesAsync, GetPlayedTogetherAsync,
SearchAsync all implemented. LoadViewerProjectionAsync materialises the full Viewer
entity (with Include/ThenInclude for SelectedEmblem/Degree) then projects in-memory —
avoids the EF Core limitation where Include is silently ignored under Select.
FriendService + IPlayedTogetherWriter registered as Scoped in Program.cs.
12 read tests, all green; full suite 1171/1171 still passing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 21:53:10 -04:00

297 lines
12 KiB
C#

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using SVSim.Database;
using SVSim.Database.Models;
using SVSim.Database.Services.Friend;
using SVSim.UnitTests.Infrastructure;
namespace SVSim.UnitTests.Services;
public class FriendServiceTests
{
private static async Task<long> SeedViewer(SVSimTestFactory factory, ulong steamId, string name = "Test Viewer")
=> await factory.SeedViewerAsync(steamId: steamId, displayName: name);
private static IFriendService Service(SVSimTestFactory factory, out IServiceScope scope)
{
scope = factory.Services.CreateScope();
return scope.ServiceProvider.GetRequiredService<IFriendService>();
}
private static SVSimDbContext Ctx(IServiceScope scope) =>
scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
[Test]
public async Task GetFriendsAsync_returns_empty_for_fresh_viewer()
{
using var factory = new SVSimTestFactory();
long viewerId = await SeedViewer(factory, 76_561_198_000_000_001UL);
var svc = Service(factory, out var scope);
using (scope)
{
var result = await svc.GetFriendsAsync(viewerId, default);
Assert.That(result.Friends, Is.Empty);
Assert.That(result.Count, Is.EqualTo(0));
Assert.That(result.MaxCount, Is.EqualTo(110));
}
}
[Test]
public async Task GetFriendsAsync_returns_15_field_entries_for_seeded_friend()
{
using var factory = new SVSimTestFactory();
long owner = await SeedViewer(factory, 76_561_198_000_000_002UL, "Owner");
long friend = await SeedViewer(factory, 76_561_198_000_000_003UL, "Friend");
using (var scope = factory.Services.CreateScope())
{
var ctx = Ctx(scope);
ctx.ViewerFriends.Add(new ViewerFriend { OwnerViewerId = owner, FriendViewerId = friend, CreatedAt = DateTime.UtcNow });
await ctx.SaveChangesAsync();
}
var svc = Service(factory, out var scope2);
using (scope2)
{
var result = await svc.GetFriendsAsync(owner, default);
Assert.That(result.Friends, Has.Count.EqualTo(1));
Assert.That(result.Count, Is.EqualTo(1));
var entry = result.Friends[0];
Assert.That(entry.ViewerId, Is.EqualTo((int)friend));
Assert.That(entry.Name, Is.EqualTo("Friend"));
Assert.That(entry.DeviceType, Is.EqualTo("2"));
Assert.That(entry.MaxFriend, Is.EqualTo("110"));
Assert.That(entry.IsOfficial, Is.EqualTo("0"));
}
}
[Test]
public async Task GetReceiveAppliesAsync_returns_incoming_with_correct_viewer_id_and_id()
{
using var factory = new SVSimTestFactory();
long target = await SeedViewer(factory, 76_561_198_000_000_004UL, "Target");
long sender = await SeedViewer(factory, 76_561_198_000_000_005UL, "Sender");
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)
{
var result = await svc.GetReceiveAppliesAsync(target, default);
Assert.That(result.ReceiveApplies, Has.Count.EqualTo(1));
Assert.That(result.ReceiveApplies[0].Id, Is.EqualTo(applyId));
Assert.That(result.ReceiveApplies[0].ViewerId, Is.EqualTo((int)sender), "viewer_id is the SENDER's id");
Assert.That(result.ReceiveApplies[0].Name, Is.EqualTo("Sender"));
}
}
[Test]
public async Task GetSendAppliesAsync_returns_outgoing_with_remaining_count()
{
using var factory = new SVSimTestFactory();
long sender = await SeedViewer(factory, 76_561_198_000_000_006UL, "Sender");
long target = await SeedViewer(factory, 76_561_198_000_000_007UL, "Target");
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)
{
var result = await svc.GetSendAppliesAsync(sender, default);
Assert.That(result.SendApplies, Has.Count.EqualTo(1));
Assert.That(result.SendApplyMaxCount, Is.EqualTo(110));
Assert.That(result.RemainingApplyCount, Is.EqualTo(109));
Assert.That(result.SendApplies[0].ViewerId, Is.EqualTo((int)target), "viewer_id is the TARGET's id");
}
}
[Test]
public async Task GetPlayedTogetherAsync_returns_empty_for_fresh_viewer()
{
using var factory = new SVSimTestFactory();
long viewerId = await SeedViewer(factory, 76_561_198_000_000_008UL);
var svc = Service(factory, out var scope);
using (scope)
{
var result = await svc.GetPlayedTogetherAsync(viewerId, default);
Assert.That(result.Histories, Is.Empty);
}
}
[Test]
public async Task GetPlayedTogetherAsync_computes_friend_status_NO_ACTION_for_stranger()
{
using var factory = new SVSimTestFactory();
long me = await SeedViewer(factory, 76_561_198_000_000_009UL, "Me");
long opponent = await SeedViewer(factory, 76_561_198_000_000_010UL, "Opponent");
using (var scope = factory.Services.CreateScope())
{
var ctx = Ctx(scope);
ctx.ViewerPlayedTogethers.Add(new ViewerPlayedTogether
{
OwnerViewerId = me, OpponentViewerId = opponent,
PlayedAt = DateTime.UtcNow, PlayedMode = 1, BattleType = 2, DeckFormat = 3, TwoPickType = 4,
});
await ctx.SaveChangesAsync();
}
var svc = Service(factory, out var scope2);
using (scope2)
{
var result = await svc.GetPlayedTogetherAsync(me, default);
Assert.That(result.Histories, Has.Count.EqualTo(1));
Assert.That(result.Histories[0].FriendStatus, Is.EqualTo(0), "no apply, no friendship → NO_ACTION");
Assert.That(result.Histories[0].FriendApplyId, Is.EqualTo(0));
}
}
[Test]
public async Task GetPlayedTogetherAsync_computes_friend_status_IS_FRIEND_for_friend()
{
using var factory = new SVSimTestFactory();
long me = await SeedViewer(factory, 76_561_198_000_000_011UL, "Me");
long friend = await SeedViewer(factory, 76_561_198_000_000_012UL, "Friend");
using (var scope = factory.Services.CreateScope())
{
var ctx = Ctx(scope);
ctx.ViewerFriends.Add(new ViewerFriend { OwnerViewerId = me, FriendViewerId = friend, CreatedAt = DateTime.UtcNow });
ctx.ViewerPlayedTogethers.Add(new ViewerPlayedTogether
{
OwnerViewerId = me, OpponentViewerId = friend,
PlayedAt = DateTime.UtcNow, PlayedMode = 1, BattleType = 2, DeckFormat = 3, TwoPickType = 4,
});
await ctx.SaveChangesAsync();
}
var svc = Service(factory, out var scope2);
using (scope2)
{
var result = await svc.GetPlayedTogetherAsync(me, default);
Assert.That(result.Histories[0].FriendStatus, Is.EqualTo(1), "IS_FRIEND");
}
}
[Test]
public async Task GetPlayedTogetherAsync_computes_friend_status_IS_SEND_with_apply_id()
{
using var factory = new SVSimTestFactory();
long me = await SeedViewer(factory, 76_561_198_000_000_013UL, "Me");
long target = await SeedViewer(factory, 76_561_198_000_000_014UL, "Target");
int applyId;
using (var scope = factory.Services.CreateScope())
{
var ctx = Ctx(scope);
var apply = new ViewerFriendApply { FromViewerId = me, ToViewerId = target, CreatedAt = DateTime.UtcNow };
ctx.ViewerFriendApplies.Add(apply);
ctx.ViewerPlayedTogethers.Add(new ViewerPlayedTogether
{
OwnerViewerId = me, OpponentViewerId = target,
PlayedAt = DateTime.UtcNow, PlayedMode = 1, BattleType = 2, DeckFormat = 3, TwoPickType = 4,
});
await ctx.SaveChangesAsync();
applyId = apply.Id;
}
var svc = Service(factory, out var scope2);
using (scope2)
{
var result = await svc.GetPlayedTogetherAsync(me, default);
Assert.That(result.Histories[0].FriendStatus, Is.EqualTo(2), "IS_SEND");
Assert.That(result.Histories[0].FriendApplyId, Is.EqualTo(applyId));
}
}
[Test]
public async Task GetPlayedTogetherAsync_computes_friend_status_IS_RECEIVED_with_apply_id()
{
using var factory = new SVSimTestFactory();
long me = await SeedViewer(factory, 76_561_198_000_000_015UL, "Me");
long sender = await SeedViewer(factory, 76_561_198_000_000_016UL, "Sender");
int applyId;
using (var scope = factory.Services.CreateScope())
{
var ctx = Ctx(scope);
var apply = new ViewerFriendApply { FromViewerId = sender, ToViewerId = me, CreatedAt = DateTime.UtcNow };
ctx.ViewerFriendApplies.Add(apply);
ctx.ViewerPlayedTogethers.Add(new ViewerPlayedTogether
{
OwnerViewerId = me, OpponentViewerId = sender,
PlayedAt = DateTime.UtcNow, PlayedMode = 1, BattleType = 2, DeckFormat = 3, TwoPickType = 4,
});
await ctx.SaveChangesAsync();
applyId = apply.Id;
}
var svc = Service(factory, out var scope2);
using (scope2)
{
var result = await svc.GetPlayedTogetherAsync(me, default);
Assert.That(result.Histories[0].FriendStatus, Is.EqualTo(3), "IS_RECEIVED");
Assert.That(result.Histories[0].FriendApplyId, Is.EqualTo(applyId));
}
}
[Test]
public async Task SearchAsync_returns_entry_for_existing_viewer()
{
using var factory = new SVSimTestFactory();
long me = await SeedViewer(factory, 76_561_198_000_000_017UL, "Me");
long target = await SeedViewer(factory, 76_561_198_000_000_018UL, "Target");
var svc = Service(factory, out var scope);
using (scope)
{
var result = await svc.SearchAsync(me, (int)target, default);
Assert.That(result, Is.Not.Null);
Assert.That(result!.Name, Is.EqualTo("Target"));
}
}
[Test]
public async Task SearchAsync_returns_null_for_self_search()
{
using var factory = new SVSimTestFactory();
long me = await SeedViewer(factory, 76_561_198_000_000_019UL);
var svc = Service(factory, out var scope);
using (scope)
{
var result = await svc.SearchAsync(me, (int)me, default);
Assert.That(result, Is.Null);
}
}
[Test]
public async Task SearchAsync_returns_null_for_unknown_viewer_id()
{
using var factory = new SVSimTestFactory();
long me = await SeedViewer(factory, 76_561_198_000_000_020UL);
var svc = Service(factory, out var scope);
using (scope)
{
var result = await svc.SearchAsync(me, 999_999_999, default);
Assert.That(result, Is.Null);
}
}
}