using System.Net.Http.Json; using System.Text.Json; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using SVSim.Database; using SVSim.Database.Entities.Guild; using SVSim.UnitTests.Infrastructure; using GuildEntity = SVSim.Database.Entities.Guild.Guild; namespace SVSim.UnitTests.Integration.Guild; public class GuildSearchFlowTests { private const string Vid = "0"; private const int Sid = 0; private const string Stk = ""; /// /// Seeds three guilds with different activity / join_condition / member counts directly /// via DbContext (bypassing the service to keep the test focused on /search_guild). /// private static async Task<(int SmallFree, int MedApproval, int LargeInvite)> SeedThreeGuildsAsync( SVSimTestFactory factory, long leaderId) { using var scope = factory.Services.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); var now = DateTime.UtcNow; // Small guild (bucket=1: 1..10 members), activity=1, join_condition=1 (Free) var gSmall = new GuildEntity { GuildId = 100_000_901, Name = "SmallFreeGuild", Description = "", LeaderViewerId = leaderId, EmblemId = 100_000_000L, Activity = GuildActivity.All, JoinCondition = GuildJoinCondition.Free, CreatedAt = now, }; db.Guilds.Add(gSmall); // 5 members (leader + 4 extras) db.GuildMembers.Add(new GuildMember { GuildId = 100_000_901, ViewerId = leaderId, Role = GuildRole.Leader, JoinedAt = now }); for (int i = 1; i <= 4; i++) db.GuildMembers.Add(new GuildMember { GuildId = 100_000_901, ViewerId = 900_000_000 + i, Role = GuildRole.Regular, JoinedAt = now }); // Medium guild (bucket=2: 11..25 members), activity=2, join_condition=2 (Approval) var gMed = new GuildEntity { GuildId = 100_000_902, Name = "MedApprovalGuild", Description = "", LeaderViewerId = leaderId, EmblemId = 100_000_000L, Activity = (GuildActivity)2, JoinCondition = GuildJoinCondition.Approval, CreatedAt = now, }; db.Guilds.Add(gMed); // 12 members for (int i = 1; i <= 12; i++) db.GuildMembers.Add(new GuildMember { GuildId = 100_000_902, ViewerId = 900_001_000 + i, Role = GuildRole.Regular, JoinedAt = now }); // Large guild (bucket=3: 26..MaxMemberNum members), activity=3, join_condition=3 (Invite) var gLarge = new GuildEntity { GuildId = 100_000_903, Name = "LargeInviteGuild", Description = "", LeaderViewerId = leaderId, EmblemId = 100_000_000L, Activity = (GuildActivity)3, JoinCondition = GuildJoinCondition.OnlyInvite, CreatedAt = now, }; db.Guilds.Add(gLarge); // 27 members for (int i = 1; i <= 27; i++) db.GuildMembers.Add(new GuildMember { GuildId = 100_000_903, ViewerId = 900_002_000 + i, Role = GuildRole.Regular, JoinedAt = now }); await db.SaveChangesAsync(); return (100_000_901, 100_000_902, 100_000_903); } private static async Task PostSearchAsync(HttpClient client, string guildName, int activity, int joinCondition, int memberConditionRange) { var resp = await client.PostAsync("/guild/search_guild", JsonContent.Create(new { guild_name = guildName, activity, join_condition = joinCondition, member_condition_range = memberConditionRange, viewer_id = Vid, steam_id = Sid, steam_session_ticket = Stk, })); Assert.That(resp.IsSuccessStatusCode, Is.True, $"search_guild HTTP {resp.StatusCode}: {await resp.Content.ReadAsStringAsync()}"); var json = await resp.Content.ReadAsStringAsync(); return JsonDocument.Parse(json).RootElement.Clone(); } private static List GetListNames(JsonElement root) { var names = new List(); var arr = root.GetProperty("list"); foreach (var entry in arr.EnumerateArray()) names.Add(entry.GetProperty("guild_name").GetString()!); return names; } [Test] public async Task SearchGuild_no_filter_returns_all_guilds() { using var factory = new SVSimTestFactory(); var leaderId = await factory.SeedViewerAsync(76_561_198_300_000_001UL, "SearchLeader1"); await SeedThreeGuildsAsync(factory, leaderId); using var client = factory.CreateAuthenticatedClient(leaderId); var root = await PostSearchAsync(client, "", 0, 0, 0); var names = GetListNames(root); Assert.That(names, Does.Contain("SmallFreeGuild"), "All-filter should include SmallFreeGuild"); Assert.That(names, Does.Contain("MedApprovalGuild"), "All-filter should include MedApprovalGuild"); Assert.That(names, Does.Contain("LargeInviteGuild"), "All-filter should include LargeInviteGuild"); } [Test] public async Task SearchGuild_activity_filter_returns_matching_guilds_only() { using var factory = new SVSimTestFactory(); var leaderId = await factory.SeedViewerAsync(76_561_198_300_000_002UL, "SearchLeader2"); await SeedThreeGuildsAsync(factory, leaderId); using var client = factory.CreateAuthenticatedClient(leaderId); // activity=2 should return only MedApprovalGuild var root = await PostSearchAsync(client, "", 2, 0, 0); var names = GetListNames(root); Assert.That(names, Does.Contain("MedApprovalGuild"), "activity=2 should match MedApprovalGuild"); Assert.That(names, Does.Not.Contain("SmallFreeGuild"), "activity=2 should not match SmallFreeGuild (activity=1)"); Assert.That(names, Does.Not.Contain("LargeInviteGuild"), "activity=2 should not match LargeInviteGuild (activity=3)"); } [Test] public async Task SearchGuild_join_condition_filter_returns_matching_guilds_only() { using var factory = new SVSimTestFactory(); var leaderId = await factory.SeedViewerAsync(76_561_198_300_000_003UL, "SearchLeader3"); await SeedThreeGuildsAsync(factory, leaderId); using var client = factory.CreateAuthenticatedClient(leaderId); // join_condition=1 (Free) should return only SmallFreeGuild var root = await PostSearchAsync(client, "", 0, 1, 0); var names = GetListNames(root); Assert.That(names, Does.Contain("SmallFreeGuild"), "join_condition=1 should match SmallFreeGuild"); Assert.That(names, Does.Not.Contain("MedApprovalGuild"), "join_condition=1 should not match MedApprovalGuild (approval)"); Assert.That(names, Does.Not.Contain("LargeInviteGuild"), "join_condition=1 should not match LargeInviteGuild (invite)"); } [Test] public async Task SearchGuild_bucket1_returns_small_guilds() { using var factory = new SVSimTestFactory(); var leaderId = await factory.SeedViewerAsync(76_561_198_300_000_004UL, "SearchLeader4"); await SeedThreeGuildsAsync(factory, leaderId); using var client = factory.CreateAuthenticatedClient(leaderId); // bucket=1: 1..10 members var root = await PostSearchAsync(client, "", 0, 0, 1); var names = GetListNames(root); Assert.That(names, Does.Contain("SmallFreeGuild"), "bucket=1 should include SmallFreeGuild (5 members)"); Assert.That(names, Does.Not.Contain("MedApprovalGuild"), "bucket=1 should not include MedApprovalGuild (12 members)"); Assert.That(names, Does.Not.Contain("LargeInviteGuild"), "bucket=1 should not include LargeInviteGuild (27 members)"); } [Test] public async Task SearchGuild_bucket3_returns_large_guilds() { using var factory = new SVSimTestFactory(); var leaderId = await factory.SeedViewerAsync(76_561_198_300_000_005UL, "SearchLeader5"); await SeedThreeGuildsAsync(factory, leaderId); using var client = factory.CreateAuthenticatedClient(leaderId); // bucket=3: 26..MaxMemberNum var root = await PostSearchAsync(client, "", 0, 0, 3); var names = GetListNames(root); Assert.That(names, Does.Contain("LargeInviteGuild"), "bucket=3 should include LargeInviteGuild (27 members)"); Assert.That(names, Does.Not.Contain("SmallFreeGuild"), "bucket=3 should not include SmallFreeGuild (5 members)"); Assert.That(names, Does.Not.Contain("MedApprovalGuild"), "bucket=3 should not include MedApprovalGuild (12 members)"); } [Test] public async Task SearchGuild_name_prefix_returns_matching_guilds_only() { using var factory = new SVSimTestFactory(); var leaderId = await factory.SeedViewerAsync(76_561_198_300_000_006UL, "SearchLeader6"); await SeedThreeGuildsAsync(factory, leaderId); using var client = factory.CreateAuthenticatedClient(leaderId); // prefix "Small" should match only SmallFreeGuild var root = await PostSearchAsync(client, "Small", 0, 0, 0); var names = GetListNames(root); Assert.That(names, Does.Contain("SmallFreeGuild"), "name 'Small' should match SmallFreeGuild"); Assert.That(names, Does.Not.Contain("MedApprovalGuild"), "name 'Small' should not match MedApprovalGuild"); Assert.That(names, Does.Not.Contain("LargeInviteGuild"), "name 'Small' should not match LargeInviteGuild"); } [Test] public async Task SearchGuild_response_list_entries_are_flat_not_wrapped() { using var factory = new SVSimTestFactory(); var leaderId = await factory.SeedViewerAsync(76_561_198_300_000_007UL, "SearchLeader7"); await SeedThreeGuildsAsync(factory, leaderId); using var client = factory.CreateAuthenticatedClient(leaderId); var root = await PostSearchAsync(client, "SmallFreeGuild", 0, 0, 0); var arr = root.GetProperty("list"); Assert.That(arr.ValueKind, Is.EqualTo(JsonValueKind.Array)); Assert.That(arr.GetArrayLength(), Is.GreaterThanOrEqualTo(1)); var entry = arr[0]; // guild_id is directly on the entry, not under a "detail" wrapper Assert.That(entry.TryGetProperty("guild_id", out _), Is.True, "guild_id should be flat on each list entry"); Assert.That(entry.TryGetProperty("detail", out _), Is.False, "no 'detail' wrapper on search list entries"); Assert.That(entry.TryGetProperty("guild_name", out var gn), Is.True); Assert.That(gn.GetString(), Is.EqualTo("SmallFreeGuild")); // leader_name must be populated from the seeded viewer's DisplayName, not empty string Assert.That(entry.TryGetProperty("leader_name", out var ln), Is.True, "leader_name field must be present"); Assert.That(ln.GetString(), Is.EqualTo("SearchLeader7"), "leader_name must match the seeded leader's DisplayName"); } }