using System.Net;
using System.Net.Http.Json;
using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using SVSim.Database;
using SVSim.Database.Enums;
using SVSim.Database.Models;
using SVSim.UnitTests.Infrastructure;
namespace SVSim.UnitTests.Controllers;
///
/// Phase 3 2-Pick draft coverage on /arena_colosseum/{get_candidate_classes, class_choose,
/// get_candidate_cards, card_choose}. Mirrors the existing ArenaTwoPick tests, with the
/// pool override sourced from ColosseumSeasonConfig.PoolCardSetIds instead of
/// ChallengeConfig.PoolCardSetIds.
///
public class ArenaColosseumControllerDraftTests
{
private static readonly object Envelope =
new { viewer_id = "0", steam_id = 0, steam_session_ticket = "" };
private static async Task ActivateChaosCapableSeasonAsync(SVSimTestFactory factory)
{
using var scope = factory.Services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService();
// Pool override: the test card-set's id (10001) — see SVSimTestFactory.SeedMinimalCardSet.
var seasonJson = JsonSerializer.Serialize(new
{
IsColosseumPeriod = true,
SeasonId = 7,
DeckFormat = (int)Format.TwoPick,
IsNormalTwoPick = true,
PoolCardSetIds = new[] { 10001 },
});
await UpsertConfigAsync(db, "ColosseumSeason", seasonJson);
}
private static async Task UpsertConfigAsync(SVSimDbContext db, string section, string json)
{
var existing = await db.GameConfigs.FirstOrDefaultAsync(s => s.SectionName == section);
if (existing is null)
db.GameConfigs.Add(new GameConfigSection { SectionName = section, ValueJson = json });
else
existing.ValueJson = json;
await db.SaveChangesAsync();
}
private static async Task SeedRunAsync(SVSimTestFactory factory, long viewerId)
{
using var scope = factory.Services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService();
db.ViewerArenaColosseumRuns.Add(new ViewerArenaColosseumRun
{
ViewerId = viewerId,
EntryId = 9999,
SeasonId = 7,
RoundId = 1,
DeckFormat = Format.TwoPick,
});
// The pool service filters cards by `CollectionInfo != null` — the minimal SVSimTestFactory
// seed lacks that. Stamp it on every card in the test set so the pool service can
// actually emit a candidate pair for any class.
var cards = await db.Cards.ToListAsync();
foreach (var c in cards)
{
c.CollectionInfo = new CardCollectionInfo { CraftCost = 40, DustReward = 10 };
}
await db.SaveChangesAsync();
}
[Test]
public async Task GetCandidateClasses_seeds_three_classes_on_run()
{
using var factory = new SVSimTestFactory();
await ActivateChaosCapableSeasonAsync(factory);
var vid = await factory.SeedViewerAsync();
await SeedRunAsync(factory, vid);
using var client = factory.CreateAuthenticatedClient(vid);
var resp = await client.PostAsync("/arena_colosseum/get_candidate_classes",
JsonContent.Create(Envelope));
Assert.That(resp.StatusCode, Is.EqualTo(HttpStatusCode.OK));
var body = await resp.Content.ReadAsStringAsync();
using var doc = JsonDocument.Parse(body);
var root = doc.RootElement;
Assert.That(root.GetProperty("class_id_1").GetInt32(), Is.GreaterThan(0));
Assert.That(root.GetProperty("class_id_2").GetInt32(), Is.GreaterThan(0));
Assert.That(root.GetProperty("class_id_3").GetInt32(), Is.GreaterThan(0));
using var verifyScope = factory.Services.CreateScope();
var db = verifyScope.ServiceProvider.GetRequiredService();
var run = await db.ViewerArenaColosseumRuns.FirstAsync(r => r.ViewerId == vid);
var stored = JsonSerializer.Deserialize>(run.CandidateClassIdsJson)!;
Assert.That(stored.Count, Is.EqualTo(3),
"the slate must be persisted onto the run so /class_choose can validate against it");
}
[Test]
public async Task ClassChoose_rejects_class_not_in_slate()
{
using var factory = new SVSimTestFactory();
await ActivateChaosCapableSeasonAsync(factory);
var vid = await factory.SeedViewerAsync();
await SeedRunAsync(factory, vid);
// Force a known slate.
using (var scope = factory.Services.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService();
var run = await db.ViewerArenaColosseumRuns.FirstAsync(r => r.ViewerId == vid);
run.CandidateClassIdsJson = "[1,2,3]";
await db.SaveChangesAsync();
}
using var client = factory.CreateAuthenticatedClient(vid);
var resp = await client.PostAsync("/arena_colosseum/class_choose",
JsonContent.Create(new { class_id = 8, viewer_id = "0", steam_id = 0, steam_session_ticket = "" }));
Assert.That(resp.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest));
var body = await resp.Content.ReadAsStringAsync();
StringAssert.Contains("class_not_offered", body);
}
[Test]
public async Task ClassChoose_normal_advances_run_to_turn_1_with_a_pair_offered()
{
using var factory = new SVSimTestFactory();
await ActivateChaosCapableSeasonAsync(factory);
var vid = await factory.SeedViewerAsync();
await SeedRunAsync(factory, vid);
using (var scope = factory.Services.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService();
var run = await db.ViewerArenaColosseumRuns.FirstAsync(r => r.ViewerId == vid);
run.CandidateClassIdsJson = "[1,2,3]";
await db.SaveChangesAsync();
}
using var client = factory.CreateAuthenticatedClient(vid);
var resp = await client.PostAsync("/arena_colosseum/class_choose",
JsonContent.Create(new { class_id = 1, viewer_id = "0", steam_id = 0, steam_session_ticket = "" }));
Assert.That(resp.StatusCode, Is.EqualTo(HttpStatusCode.OK));
var body = await resp.Content.ReadAsStringAsync();
using var doc = JsonDocument.Parse(body);
var root = doc.RootElement;
Assert.That(root.GetProperty("class_info").GetProperty("selected_class_id").GetString(), Is.EqualTo("1"),
"selected_class_id is wire-stringified per existing TwoPick convention");
Assert.That(root.GetProperty("candidate_card_list").GetArrayLength(), Is.EqualTo(2),
"the pool service emits exactly 2 candidate pairs per turn");
using var verifyScope = factory.Services.CreateScope();
var verifyDb = verifyScope.ServiceProvider.GetRequiredService();
var verifyRun = await verifyDb.ViewerArenaColosseumRuns.FirstAsync(r => r.ViewerId == vid);
Assert.That(verifyRun.ClassId, Is.EqualTo(1));
Assert.That(verifyRun.SelectTurn, Is.EqualTo(1));
}
[Test]
public async Task CardChoose_appends_to_selected_cards_and_advances_turn()
{
using var factory = new SVSimTestFactory();
await ActivateChaosCapableSeasonAsync(factory);
var vid = await factory.SeedViewerAsync();
await SeedRunAsync(factory, vid);
using (var scope = factory.Services.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService();
var run = await db.ViewerArenaColosseumRuns.FirstAsync(r => r.ViewerId == vid);
run.CandidateClassIdsJson = "[1,2,3]";
await db.SaveChangesAsync();
}
using var client = factory.CreateAuthenticatedClient(vid);
// Drive /class_choose to populate the pending pair.
var classResp = await client.PostAsync("/arena_colosseum/class_choose",
JsonContent.Create(new { class_id = 1, viewer_id = "0", steam_id = 0, steam_session_ticket = "" }));
var classBody = await classResp.Content.ReadAsStringAsync();
using var classDoc = JsonDocument.Parse(classBody);
long firstPairId = long.Parse(classDoc.RootElement.GetProperty("candidate_card_list")[0].GetProperty("id").GetString()!);
var cardResp = await client.PostAsync("/arena_colosseum/card_choose",
JsonContent.Create(new { selected_id = firstPairId, viewer_id = "0", steam_id = 0, steam_session_ticket = "" }));
Assert.That(cardResp.StatusCode, Is.EqualTo(HttpStatusCode.OK));
using var verifyScope = factory.Services.CreateScope();
var verifyDb = verifyScope.ServiceProvider.GetRequiredService();
var verifyRun = await verifyDb.ViewerArenaColosseumRuns.FirstAsync(r => r.ViewerId == vid);
var picks = JsonSerializer.Deserialize>(verifyRun.SelectedCardIdsJson)!;
Assert.That(picks.Count, Is.EqualTo(2),
"first card_choose appends both cards from the picked pair");
Assert.That(verifyRun.SelectTurn, Is.EqualTo(2));
}
[Test]
public async Task ClassChoose_chaos_branch_stores_chaos_id_on_run()
{
using var factory = new SVSimTestFactory();
await ActivateChaosCapableSeasonAsync(factory);
var vid = await factory.SeedViewerAsync();
await SeedRunAsync(factory, vid);
using var client = factory.CreateAuthenticatedClient(vid);
var resp = await client.PostAsync("/arena_colosseum/class_choose",
JsonContent.Create(new { chaos_id = 101, viewer_id = "0", steam_id = 0, steam_session_ticket = "" }));
Assert.That(resp.StatusCode, Is.EqualTo(HttpStatusCode.OK));
using var scope = factory.Services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService();
var run = await db.ViewerArenaColosseumRuns.FirstAsync(r => r.ViewerId == vid);
Assert.That(run.ChaosId, Is.EqualTo(101));
Assert.That(run.ClassId, Is.GreaterThan(0),
"chaos id must resolve to a non-zero class for the pool service");
}
}