367 lines
16 KiB
C#
367 lines
16 KiB
C#
using Microsoft.AspNetCore.TestHost;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using SVSim.Database;
|
|
using SVSim.Database.Entities.Guild;
|
|
using SVSim.Database.Services.Guild;
|
|
using SVSim.UnitTests.Infrastructure;
|
|
|
|
namespace SVSim.UnitTests.Services.Guild;
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Spy IGuildChatService — counts EmitSystemEventAsync calls per GuildChatMessageType.
|
|
// ---------------------------------------------------------------------------
|
|
internal sealed class SpyGuildChatService : IGuildChatService
|
|
{
|
|
public readonly List<(int GuildId, long ActorId, GuildChatMessageType Type)> Emissions = new();
|
|
|
|
public Task EmitSystemEventAsync(int guildId, long actorViewerId, GuildChatMessageType type, string? body = null, CancellationToken ct = default)
|
|
{
|
|
Emissions.Add((guildId, actorViewerId, type));
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
// ---- stub out the rest -----------------------------------------------
|
|
public Task<ChatWindow> GetWindowAsync(long v, int s, int d, int w, CancellationToken ct = default) => throw new NotImplementedException();
|
|
public Task<ChatPostResult> PostTextOrStampAsync(long v, int t, string m, CancellationToken ct = default) => throw new NotImplementedException();
|
|
public Task<ChatPostResult> PostDeckAsync(long v, string j, CancellationToken ct = default) => throw new NotImplementedException();
|
|
public Task<bool> DeleteDeckAsync(long v, int id, CancellationToken ct = default) => throw new NotImplementedException();
|
|
public Task<ChatPostResult> PostReplayAsync(long v, string j, CancellationToken ct = default) => throw new NotImplementedException();
|
|
public Task<string?> GetReplayDetailAsync(long v, int id, CancellationToken ct = default) => throw new NotImplementedException();
|
|
public Task<string?> GetDeckLogAsync(long v, int id, CancellationToken ct = default) => throw new NotImplementedException();
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Factory variant that replaces IGuildChatService with our spy singleton.
|
|
// ---------------------------------------------------------------------------
|
|
internal sealed class SpyGuildChatFactory : SVSimTestFactory
|
|
{
|
|
public readonly SpyGuildChatService ChatSpy = new();
|
|
|
|
protected override void ConfigureWebHost(Microsoft.AspNetCore.Hosting.IWebHostBuilder builder)
|
|
{
|
|
base.ConfigureWebHost(builder);
|
|
builder.ConfigureTestServices(services =>
|
|
{
|
|
// Remove all existing registrations for IGuildChatService.
|
|
var descriptors = services
|
|
.Where(d => d.ServiceType == typeof(IGuildChatService))
|
|
.ToList();
|
|
foreach (var d in descriptors) services.Remove(d);
|
|
|
|
// Register our singleton spy so every scope gets the same instance.
|
|
services.AddSingleton<IGuildChatService>(ChatSpy);
|
|
});
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Tests
|
|
// ---------------------------------------------------------------------------
|
|
|
|
public class GuildServiceUpdateTests
|
|
{
|
|
// -----------------------------------------------------------------------
|
|
// UpdateAsync — activity + join_condition
|
|
// -----------------------------------------------------------------------
|
|
|
|
[Test]
|
|
public async Task UpdateAsync_leader_can_change_activity_and_join_condition()
|
|
{
|
|
using var factory = new SVSimTestFactory();
|
|
var leaderId = await factory.SeedViewerAsync(76_561_198_200_000_001UL, "UpdateLeader1");
|
|
|
|
int guildId;
|
|
using (var scope = factory.Services.CreateScope())
|
|
{
|
|
var svc = scope.ServiceProvider.GetRequiredService<IGuildService>();
|
|
var res = await svc.CreateAsync(leaderId, new("UpdateGuild1", (int)GuildActivity.All, (int)GuildJoinCondition.Free));
|
|
Assert.That(res.IsOk, Is.True);
|
|
guildId = res.GuildId!.Value;
|
|
}
|
|
|
|
using (var scope = factory.Services.CreateScope())
|
|
{
|
|
var svc = scope.ServiceProvider.GetRequiredService<IGuildService>();
|
|
var r = await svc.UpdateAsync(leaderId, new((int)GuildActivity.Rotation, (int)GuildJoinCondition.Approval));
|
|
Assert.That(r.IsOk, Is.True);
|
|
}
|
|
|
|
using (var scope = factory.Services.CreateScope())
|
|
{
|
|
var db = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
|
var g = await db.Guilds.FirstAsync(x => x.GuildId == guildId);
|
|
Assert.That((int)g.Activity, Is.EqualTo((int)GuildActivity.Rotation));
|
|
Assert.That((int)g.JoinCondition, Is.EqualTo((int)GuildJoinCondition.Approval));
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public async Task UpdateAsync_non_leader_returns_PermissionDenied()
|
|
{
|
|
using var factory = new SVSimTestFactory();
|
|
var leaderId = await factory.SeedViewerAsync(76_561_198_200_000_002UL, "UpdateLeader2");
|
|
var memberId = await factory.SeedViewerAsync(76_561_198_200_000_003UL, "UpdateMember2");
|
|
|
|
int guildId;
|
|
using (var scope = factory.Services.CreateScope())
|
|
{
|
|
var svc = scope.ServiceProvider.GetRequiredService<IGuildService>();
|
|
var res = await svc.CreateAsync(leaderId, new("UpdateGuild2", (int)GuildActivity.All, (int)GuildJoinCondition.Free));
|
|
Assert.That(res.IsOk, Is.True);
|
|
guildId = res.GuildId!.Value;
|
|
}
|
|
|
|
// Add a regular member.
|
|
using (var scope = factory.Services.CreateScope())
|
|
{
|
|
var db = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
|
db.GuildMembers.Add(new GuildMember { GuildId = guildId, ViewerId = memberId, Role = GuildRole.Regular, JoinedAt = DateTime.UtcNow });
|
|
var v = await db.Viewers.FirstAsync(x => x.Id == memberId);
|
|
v.GuildId = guildId;
|
|
await db.SaveChangesAsync();
|
|
}
|
|
|
|
using (var scope = factory.Services.CreateScope())
|
|
{
|
|
var svc = scope.ServiceProvider.GetRequiredService<IGuildService>();
|
|
var r = await svc.UpdateAsync(memberId, new((int)GuildActivity.Stoic, null));
|
|
Assert.That(r.Code, Is.EqualTo(GuildOpResultCode.PermissionDenied));
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public async Task UpdateAsync_invalid_activity_returns_NameInvalid()
|
|
{
|
|
using var factory = new SVSimTestFactory();
|
|
var leaderId = await factory.SeedViewerAsync(76_561_198_200_000_004UL, "UpdateLeader3");
|
|
|
|
using (var scope = factory.Services.CreateScope())
|
|
{
|
|
var svc = scope.ServiceProvider.GetRequiredService<IGuildService>();
|
|
await svc.CreateAsync(leaderId, new("UpdateGuild3", (int)GuildActivity.All, (int)GuildJoinCondition.Free));
|
|
}
|
|
|
|
using (var scope = factory.Services.CreateScope())
|
|
{
|
|
var svc = scope.ServiceProvider.GetRequiredService<IGuildService>();
|
|
// Activity 0 is out of range (valid: 1..16).
|
|
var r = await svc.UpdateAsync(leaderId, new(0, null));
|
|
Assert.That(r.Code, Is.EqualTo(GuildOpResultCode.NameInvalid));
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public async Task UpdateAsync_invalid_join_condition_returns_NameInvalid()
|
|
{
|
|
using var factory = new SVSimTestFactory();
|
|
var leaderId = await factory.SeedViewerAsync(76_561_198_200_000_005UL, "UpdateLeader4");
|
|
|
|
using (var scope = factory.Services.CreateScope())
|
|
{
|
|
var svc = scope.ServiceProvider.GetRequiredService<IGuildService>();
|
|
await svc.CreateAsync(leaderId, new("UpdateGuild4", (int)GuildActivity.All, (int)GuildJoinCondition.Free));
|
|
}
|
|
|
|
using (var scope = factory.Services.CreateScope())
|
|
{
|
|
var svc = scope.ServiceProvider.GetRequiredService<IGuildService>();
|
|
// JoinCondition 99 is out of range (valid: 1..3).
|
|
var r = await svc.UpdateAsync(leaderId, new(null, 99));
|
|
Assert.That(r.Code, Is.EqualTo(GuildOpResultCode.NameInvalid));
|
|
}
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// UpdateDescriptionAsync
|
|
// -----------------------------------------------------------------------
|
|
|
|
[Test]
|
|
public async Task UpdateDescriptionAsync_leader_updates_description_and_emits_chat_event()
|
|
{
|
|
using var factory = new SpyGuildChatFactory();
|
|
var leaderId = await factory.SeedViewerAsync(76_561_198_200_000_010UL, "DescLeader1");
|
|
|
|
int guildId;
|
|
using (var scope = factory.Services.CreateScope())
|
|
{
|
|
var svc = scope.ServiceProvider.GetRequiredService<IGuildService>();
|
|
var res = await svc.CreateAsync(leaderId, new("DescGuild1", (int)GuildActivity.All, (int)GuildJoinCondition.Free));
|
|
Assert.That(res.IsOk, Is.True);
|
|
guildId = res.GuildId!.Value;
|
|
}
|
|
|
|
// Drain any emissions from CreateAsync (CreateGuild event).
|
|
factory.ChatSpy.Emissions.Clear();
|
|
|
|
using (var scope = factory.Services.CreateScope())
|
|
{
|
|
var svc = scope.ServiceProvider.GetRequiredService<IGuildService>();
|
|
var r = await svc.UpdateDescriptionAsync(leaderId, "New description text");
|
|
Assert.That(r.IsOk, Is.True);
|
|
}
|
|
|
|
// Verify DB updated.
|
|
using (var scope = factory.Services.CreateScope())
|
|
{
|
|
var db = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
|
var g = await db.Guilds.FirstAsync(x => x.GuildId == guildId);
|
|
Assert.That(g.Description, Is.EqualTo("New description text"));
|
|
}
|
|
|
|
// Verify exactly one Description chat event was emitted.
|
|
Assert.That(factory.ChatSpy.Emissions.Count, Is.EqualTo(1));
|
|
Assert.That(factory.ChatSpy.Emissions[0].Type, Is.EqualTo(GuildChatMessageType.Description));
|
|
Assert.That(factory.ChatSpy.Emissions[0].GuildId, Is.EqualTo(guildId));
|
|
Assert.That(factory.ChatSpy.Emissions[0].ActorId, Is.EqualTo(leaderId));
|
|
}
|
|
|
|
[Test]
|
|
public async Task UpdateDescriptionAsync_non_leader_returns_PermissionDenied()
|
|
{
|
|
using var factory = new SVSimTestFactory();
|
|
var leaderId = await factory.SeedViewerAsync(76_561_198_200_000_011UL, "DescLeader2");
|
|
var memberId = await factory.SeedViewerAsync(76_561_198_200_000_012UL, "DescMember2");
|
|
|
|
int guildId;
|
|
using (var scope = factory.Services.CreateScope())
|
|
{
|
|
var svc = scope.ServiceProvider.GetRequiredService<IGuildService>();
|
|
var res = await svc.CreateAsync(leaderId, new("DescGuild2", (int)GuildActivity.All, (int)GuildJoinCondition.Free));
|
|
Assert.That(res.IsOk, Is.True);
|
|
guildId = res.GuildId!.Value;
|
|
}
|
|
|
|
using (var scope = factory.Services.CreateScope())
|
|
{
|
|
var db = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
|
db.GuildMembers.Add(new GuildMember { GuildId = guildId, ViewerId = memberId, Role = GuildRole.Regular, JoinedAt = DateTime.UtcNow });
|
|
var v = await db.Viewers.FirstAsync(x => x.Id == memberId);
|
|
v.GuildId = guildId;
|
|
await db.SaveChangesAsync();
|
|
}
|
|
|
|
using (var scope = factory.Services.CreateScope())
|
|
{
|
|
var svc = scope.ServiceProvider.GetRequiredService<IGuildService>();
|
|
var r = await svc.UpdateDescriptionAsync(memberId, "Hacked");
|
|
Assert.That(r.Code, Is.EqualTo(GuildOpResultCode.PermissionDenied));
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public async Task UpdateDescriptionAsync_over_512_chars_returns_NameInvalid()
|
|
{
|
|
using var factory = new SVSimTestFactory();
|
|
var leaderId = await factory.SeedViewerAsync(76_561_198_200_000_013UL, "DescLeader3");
|
|
|
|
using (var scope = factory.Services.CreateScope())
|
|
{
|
|
var svc = scope.ServiceProvider.GetRequiredService<IGuildService>();
|
|
await svc.CreateAsync(leaderId, new("DescGuild3", (int)GuildActivity.All, (int)GuildJoinCondition.Free));
|
|
}
|
|
|
|
using (var scope = factory.Services.CreateScope())
|
|
{
|
|
var svc = scope.ServiceProvider.GetRequiredService<IGuildService>();
|
|
var tooLong = new string('x', 513);
|
|
var r = await svc.UpdateDescriptionAsync(leaderId, tooLong);
|
|
Assert.That(r.Code, Is.EqualTo(GuildOpResultCode.NameInvalid));
|
|
}
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// UpdateEmblemAsync
|
|
// -----------------------------------------------------------------------
|
|
|
|
[Test]
|
|
public async Task UpdateEmblemAsync_leader_updates_emblem_id()
|
|
{
|
|
using var factory = new SVSimTestFactory();
|
|
var leaderId = await factory.SeedViewerAsync(76_561_198_200_000_020UL, "EmblemLeader1");
|
|
|
|
int guildId;
|
|
using (var scope = factory.Services.CreateScope())
|
|
{
|
|
var svc = scope.ServiceProvider.GetRequiredService<IGuildService>();
|
|
var res = await svc.CreateAsync(leaderId, new("EmblemGuild1", (int)GuildActivity.All, (int)GuildJoinCondition.Free));
|
|
Assert.That(res.IsOk, Is.True);
|
|
guildId = res.GuildId!.Value;
|
|
}
|
|
|
|
const long newEmblemId = 100_000_042L;
|
|
using (var scope = factory.Services.CreateScope())
|
|
{
|
|
var svc = scope.ServiceProvider.GetRequiredService<IGuildService>();
|
|
var r = await svc.UpdateEmblemAsync(leaderId, newEmblemId);
|
|
Assert.That(r.IsOk, Is.True);
|
|
}
|
|
|
|
using (var scope = factory.Services.CreateScope())
|
|
{
|
|
var db = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
|
var g = await db.Guilds.FirstAsync(x => x.GuildId == guildId);
|
|
Assert.That(g.EmblemId, Is.EqualTo(newEmblemId));
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public async Task UpdateEmblemAsync_non_leader_returns_PermissionDenied()
|
|
{
|
|
using var factory = new SVSimTestFactory();
|
|
var leaderId = await factory.SeedViewerAsync(76_561_198_200_000_021UL, "EmblemLeader2");
|
|
var memberId = await factory.SeedViewerAsync(76_561_198_200_000_022UL, "EmblemMember2");
|
|
|
|
int guildId;
|
|
using (var scope = factory.Services.CreateScope())
|
|
{
|
|
var svc = scope.ServiceProvider.GetRequiredService<IGuildService>();
|
|
var res = await svc.CreateAsync(leaderId, new("EmblemGuild2", (int)GuildActivity.All, (int)GuildJoinCondition.Free));
|
|
Assert.That(res.IsOk, Is.True);
|
|
guildId = res.GuildId!.Value;
|
|
}
|
|
|
|
using (var scope = factory.Services.CreateScope())
|
|
{
|
|
var db = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
|
db.GuildMembers.Add(new GuildMember { GuildId = guildId, ViewerId = memberId, Role = GuildRole.Regular, JoinedAt = DateTime.UtcNow });
|
|
var v = await db.Viewers.FirstAsync(x => x.Id == memberId);
|
|
v.GuildId = guildId;
|
|
await db.SaveChangesAsync();
|
|
}
|
|
|
|
using (var scope = factory.Services.CreateScope())
|
|
{
|
|
var svc = scope.ServiceProvider.GetRequiredService<IGuildService>();
|
|
var r = await svc.UpdateEmblemAsync(memberId, 999L);
|
|
Assert.That(r.Code, Is.EqualTo(GuildOpResultCode.PermissionDenied));
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public async Task UpdateEmblemAsync_does_NOT_emit_chat_event()
|
|
{
|
|
using var factory = new SpyGuildChatFactory();
|
|
var leaderId = await factory.SeedViewerAsync(76_561_198_200_000_023UL, "EmblemLeader3");
|
|
|
|
using (var scope = factory.Services.CreateScope())
|
|
{
|
|
var svc = scope.ServiceProvider.GetRequiredService<IGuildService>();
|
|
await svc.CreateAsync(leaderId, new("EmblemGuild3", (int)GuildActivity.All, (int)GuildJoinCondition.Free));
|
|
}
|
|
|
|
// Drain CreateGuild system event.
|
|
factory.ChatSpy.Emissions.Clear();
|
|
|
|
using (var scope = factory.Services.CreateScope())
|
|
{
|
|
var svc = scope.ServiceProvider.GetRequiredService<IGuildService>();
|
|
var r = await svc.UpdateEmblemAsync(leaderId, 100_000_099L);
|
|
Assert.That(r.IsOk, Is.True);
|
|
}
|
|
|
|
Assert.That(factory.ChatSpy.Emissions.Count, Is.EqualTo(0),
|
|
"UpdateEmblemAsync must NOT emit a chat system event");
|
|
}
|
|
}
|