Forgot unversioned xd
This commit is contained in:
118
SVSim.UnitTests/Controllers/AdminControllerTests.cs
Normal file
118
SVSim.UnitTests/Controllers/AdminControllerTests.cs
Normal file
@@ -0,0 +1,118 @@
|
||||
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.EmulatedEntrypoint.Models.Dtos.Requests.Admin;
|
||||
using SVSim.EmulatedEntrypoint.Models.Dtos.Responses.Admin;
|
||||
using SVSim.UnitTests.Infrastructure;
|
||||
|
||||
namespace SVSim.UnitTests.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// End-to-end coverage for <c>/admin/import_viewer</c>. The endpoint is [AllowAnonymous] so
|
||||
/// these tests don't need to seed a viewer first; the fresh-user path exercises the just-fixed
|
||||
/// nav-graph NRE inside <c>ViewerRepository.RegisterViewer</c>, and the existing-user path
|
||||
/// exercises the owned-type lookup used to dedupe by Steam id.
|
||||
/// </summary>
|
||||
public class AdminControllerTests
|
||||
{
|
||||
private static readonly JsonSerializerOptions JsonOptions = new() { PropertyNameCaseInsensitive = true };
|
||||
|
||||
[Test]
|
||||
public async Task ImportViewer_fresh_user_creates_viewer_and_returns_ids()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
using var client = factory.CreateClient();
|
||||
|
||||
var response = await client.PostAsJsonAsync("/admin/import_viewer", new ImportViewerRequest
|
||||
{
|
||||
SteamId = 76_561_198_222_333_444UL,
|
||||
DisplayName = "Fresh User",
|
||||
CountryCode = "USA",
|
||||
TutorialState = 100,
|
||||
Currency = new ImportCurrency { Crystals = 12345 }
|
||||
});
|
||||
|
||||
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK),
|
||||
await response.Content.ReadAsStringAsync());
|
||||
|
||||
var body = await response.Content.ReadFromJsonAsync<ImportViewerResponse>(JsonOptions);
|
||||
|
||||
Assert.That(body, Is.Not.Null);
|
||||
Assert.That(body!.ViewerId, Is.GreaterThan(0), "RegisterViewer must persist and return a non-zero id.");
|
||||
Assert.That(body.WasCreated, Is.True);
|
||||
|
||||
using var scope = factory.Services.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
||||
var stored = await db.Viewers
|
||||
.Include(v => v.SocialAccountConnections)
|
||||
.Include(v => v.Currency)
|
||||
.Include(v => v.Info)
|
||||
.FirstAsync(v => v.Id == body.ViewerId);
|
||||
|
||||
Assert.That(stored.DisplayName, Is.EqualTo("Fresh User"));
|
||||
Assert.That(stored.Currency.Crystals, Is.EqualTo(12345UL),
|
||||
"ImportViewer should overwrite the seed-config crystal default with the requested value.");
|
||||
Assert.That(stored.Info.CountryCode, Is.EqualTo("USA"));
|
||||
Assert.That(stored.SocialAccountConnections.Count, Is.EqualTo(1));
|
||||
Assert.That(stored.SocialAccountConnections[0].AccountId, Is.EqualTo(76_561_198_222_333_444UL));
|
||||
Assert.That(stored.SocialAccountConnections[0].AccountType, Is.EqualTo(SocialAccountType.Steam));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ImportViewer_existing_user_updates_in_place()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
const ulong steamId = 76_561_198_555_666_777UL;
|
||||
long seededId = await factory.SeedViewerAsync(steamId: steamId, displayName: "Original Name");
|
||||
|
||||
using var client = factory.CreateClient();
|
||||
var response = await client.PostAsJsonAsync("/admin/import_viewer", new ImportViewerRequest
|
||||
{
|
||||
SteamId = steamId,
|
||||
DisplayName = "Updated Name",
|
||||
CountryCode = "JPN"
|
||||
});
|
||||
|
||||
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK),
|
||||
await response.Content.ReadAsStringAsync());
|
||||
|
||||
var body = await response.Content.ReadFromJsonAsync<ImportViewerResponse>(JsonOptions);
|
||||
|
||||
Assert.That(body, Is.Not.Null);
|
||||
Assert.That(body!.ViewerId, Is.EqualTo(seededId),
|
||||
"Re-importing the same SteamId must reuse the existing viewer row, not create a new one.");
|
||||
Assert.That(body.WasCreated, Is.False);
|
||||
|
||||
using var scope = factory.Services.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
||||
var stored = await db.Viewers
|
||||
.Include(v => v.Info)
|
||||
.FirstAsync(v => v.Id == seededId);
|
||||
|
||||
Assert.That(stored.DisplayName, Is.EqualTo("Updated Name"));
|
||||
Assert.That(stored.Info.CountryCode, Is.EqualTo("JPN"));
|
||||
|
||||
var viewerCount = await db.Viewers.CountAsync(v =>
|
||||
v.SocialAccountConnections.Any(s => s.AccountType == SocialAccountType.Steam && s.AccountId == steamId));
|
||||
Assert.That(viewerCount, Is.EqualTo(1), "Owned-type dedup must not produce a second row.");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ImportViewer_missing_steam_id_returns_400()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
using var client = factory.CreateClient();
|
||||
|
||||
var response = await client.PostAsJsonAsync("/admin/import_viewer", new ImportViewerRequest
|
||||
{
|
||||
SteamId = 0,
|
||||
DisplayName = "No Steam"
|
||||
});
|
||||
|
||||
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest));
|
||||
}
|
||||
}
|
||||
117
SVSim.UnitTests/Controllers/CheckControllerTests.cs
Normal file
117
SVSim.UnitTests/Controllers/CheckControllerTests.cs
Normal file
@@ -0,0 +1,117 @@
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using SVSim.UnitTests.Infrastructure;
|
||||
|
||||
namespace SVSim.UnitTests.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// Coverage for <c>/check/*</c> — the first two endpoints the client hits on boot. The
|
||||
/// SpecialTitle smoke is duplicated in RoutingSmokeTests for routing-prefix coverage; this
|
||||
/// test layers shape assertions over the deeper boot-path concern.
|
||||
/// </summary>
|
||||
public class CheckControllerTests
|
||||
{
|
||||
private const string BaseRequestJson =
|
||||
"""{"viewerId":"0","steamId":0,"steamSessionTicket":""}""";
|
||||
|
||||
private const string GameStartRequestJson =
|
||||
"""{"viewerId":"0","steamId":0,"steamSessionTicket":"","appType":0,"campaignData":"","campaignSign":"","campaignUser":0}""";
|
||||
|
||||
[Test]
|
||||
public async Task SpecialTitle_returns_default_title_id()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
using var client = factory.CreateClient();
|
||||
|
||||
var response = await client.PostAsync("/check/special_title",
|
||||
new StringContent(BaseRequestJson, Encoding.UTF8, "application/json"));
|
||||
|
||||
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK));
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
using var doc = JsonDocument.Parse(body);
|
||||
Assert.That(doc.RootElement.GetProperty("titleImageId").GetString(), Is.EqualTo("0"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GameStart_with_authed_viewer_returns_spec_shape()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
long viewerId = await factory.SeedViewerAsync();
|
||||
using var client = factory.CreateAuthenticatedClient(viewerId);
|
||||
|
||||
var response = await client.PostAsync("/check/game_start",
|
||||
new StringContent(GameStartRequestJson, Encoding.UTF8, "application/json"));
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK), body);
|
||||
|
||||
using var doc = JsonDocument.Parse(body);
|
||||
var root = doc.RootElement;
|
||||
|
||||
// now_tutorial_step is a STRING on the wire (prod sends "100"); client calls .ToInt().
|
||||
Assert.That(root.GetProperty("nowTutorialStep").GetString(), Is.EqualTo("100"),
|
||||
"RegisterViewer's seed-config default sets tutorial_state=100 (tutorial complete).");
|
||||
Assert.That(root.GetProperty("tosState").GetInt32(), Is.EqualTo(1));
|
||||
Assert.That(root.GetProperty("policyState").GetInt32(), Is.EqualTo(1));
|
||||
Assert.That(root.GetProperty("korAuthorityState").GetInt32(), Is.EqualTo(0));
|
||||
Assert.That(root.GetProperty("tosId").GetInt32(), Is.EqualTo(1));
|
||||
Assert.That(root.GetProperty("policyId").GetInt32(), Is.EqualTo(1));
|
||||
Assert.That(root.GetProperty("korAuthorityId").GetInt32(), Is.EqualTo(0));
|
||||
|
||||
// Prod-shape fields (not strictly read by GameStartCheckTask.Parse but sent by prod).
|
||||
Assert.That(root.GetProperty("nowViewerId").GetInt64(), Is.GreaterThan(0));
|
||||
Assert.That(root.GetProperty("nowName").GetString(), Is.Not.Empty);
|
||||
Assert.That(root.GetProperty("nowRank").ValueKind, Is.EqualTo(JsonValueKind.Object));
|
||||
|
||||
// Steam connection should round-trip into transition_account_data — all three fields
|
||||
// serialized as strings (matches prod wire shape).
|
||||
var transitions = root.GetProperty("transitionAccountData");
|
||||
Assert.That(transitions.ValueKind, Is.EqualTo(JsonValueKind.Array));
|
||||
Assert.That(transitions.GetArrayLength(), Is.EqualTo(1),
|
||||
"Seeded viewer has exactly one Steam social account connection.");
|
||||
Assert.That(transitions[0].GetProperty("socialAccountType").GetString(),
|
||||
Is.EqualTo(((int)SVSim.Database.Enums.SocialAccountType.Steam).ToString()));
|
||||
Assert.That(transitions[0].GetProperty("socialAccountId").GetString(), Is.Not.Empty);
|
||||
Assert.That(transitions[0].GetProperty("connectedViewerId").GetString(), Is.Not.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GameStart_does_not_expose_unsettable_optional_fields()
|
||||
{
|
||||
// GameStartCheckTask.Parse uses `Keys.Contains("rewrite_viewer_id")` + `.ToInt()` with
|
||||
// no null guard, and same for `account_delete_reservation_status` (presence-only check).
|
||||
// We can't omit nullable properties on the encrypted MessagePack path — the [Key]
|
||||
// formatter writes them as Nil unconditionally. So these keys must not exist on
|
||||
// GameStartResponse at all. If a future change re-adds them, this test breaks the build.
|
||||
using var factory = new SVSimTestFactory();
|
||||
long viewerId = await factory.SeedViewerAsync();
|
||||
using var client = factory.CreateAuthenticatedClient(viewerId);
|
||||
|
||||
var response = await client.PostAsync("/check/game_start",
|
||||
new StringContent(GameStartRequestJson, Encoding.UTF8, "application/json"));
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK), body);
|
||||
|
||||
using var doc = JsonDocument.Parse(body);
|
||||
var root = doc.RootElement;
|
||||
Assert.That(root.TryGetProperty("rewriteViewerId", out _), Is.False,
|
||||
"rewrite_viewer_id must NOT be present in the response — client NREs on null .ToInt().");
|
||||
Assert.That(root.TryGetProperty("accountDeleteReservationStatus", out _), Is.False,
|
||||
"account_delete_reservation_status must NOT be present — presence triggers client behavior.");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GameStart_with_no_viewer_returns_401()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
using var client = factory.CreateClient();
|
||||
|
||||
var response = await client.PostAsync("/check/game_start",
|
||||
new StringContent(GameStartRequestJson, Encoding.UTF8, "application/json"));
|
||||
|
||||
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized));
|
||||
}
|
||||
}
|
||||
409
SVSim.UnitTests/Controllers/DeckControllerTests.cs
Normal file
409
SVSim.UnitTests/Controllers/DeckControllerTests.cs
Normal file
@@ -0,0 +1,409 @@
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using SVSim.Database;
|
||||
using SVSim.Database.Enums;
|
||||
using SVSim.UnitTests.Infrastructure;
|
||||
|
||||
namespace SVSim.UnitTests.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// Coverage for <c>/deck/*</c> — the deck-editor CRUD surface. Plain-JSON path; the
|
||||
/// camelCase'd C# property names are what tests see (see the note on Phase 6 / encrypted
|
||||
/// pipeline for the msgpack contract).
|
||||
/// </summary>
|
||||
public class DeckControllerTests
|
||||
{
|
||||
private static string DeckFormatRequestJson(Format f) =>
|
||||
$$"""{"viewerId":"0","steamId":0,"steamSessionTicket":"","deckFormat":{{(int)f}}}""";
|
||||
|
||||
private static StringContent JsonBody(string json) => new(json, Encoding.UTF8, "application/json");
|
||||
|
||||
private static async Task<(int classId, int sleeveId, int leaderSkinId)> FetchSeededIds(SVSimTestFactory factory)
|
||||
{
|
||||
using var scope = factory.Services.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
||||
var cls = await db.Classes.Select(c => c.Id).FirstAsync();
|
||||
var sleeve = await db.Sleeves.Select(s => s.Id).FirstAsync();
|
||||
var skin = await db.LeaderSkins.Select(s => s.Id).FirstAsync();
|
||||
return (cls, sleeve, skin);
|
||||
}
|
||||
|
||||
// ---- read endpoints ----
|
||||
|
||||
[Test]
|
||||
public async Task MyList_returns_decks_for_format()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
long viewerId = await factory.SeedViewerAsync();
|
||||
await factory.SeedDeckAsync(viewerId, Format.Rotation, 1, "Slot 1");
|
||||
await factory.SeedDeckAsync(viewerId, Format.Rotation, 2, "Slot 2");
|
||||
await factory.SeedDeckAsync(viewerId, Format.Unlimited, 1, "Wrong-format deck");
|
||||
|
||||
using var client = factory.CreateAuthenticatedClient(viewerId);
|
||||
var response = await client.PostAsync("/deck/my_list", JsonBody(DeckFormatRequestJson(Format.Rotation)));
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK), body);
|
||||
|
||||
using var doc = JsonDocument.Parse(body);
|
||||
var decks = doc.RootElement.GetProperty("userDeckList");
|
||||
Assert.That(decks.GetArrayLength(), Is.EqualTo(2),
|
||||
"Only Rotation-format decks should be returned for a Rotation request.");
|
||||
var names = Enumerable.Range(0, decks.GetArrayLength())
|
||||
.Select(i => decks[i].GetProperty("name").GetString())
|
||||
.ToList();
|
||||
Assert.That(names, Is.EquivalentTo(new[] { "Slot 1", "Slot 2" }));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Info_returns_decks_for_format()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
long viewerId = await factory.SeedViewerAsync();
|
||||
await factory.SeedDeckAsync(viewerId, Format.Unlimited, 1, "Unlimited Deck");
|
||||
|
||||
using var client = factory.CreateAuthenticatedClient(viewerId);
|
||||
var response = await client.PostAsync("/deck/info", JsonBody(DeckFormatRequestJson(Format.Unlimited)));
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK), body);
|
||||
|
||||
using var doc = JsonDocument.Parse(body);
|
||||
var decks = doc.RootElement.GetProperty("userDeckList");
|
||||
Assert.That(decks.GetArrayLength(), Is.EqualTo(1));
|
||||
Assert.That(decks[0].GetProperty("name").GetString(), Is.EqualTo("Unlimited Deck"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task MyList_empty_when_viewer_has_no_decks()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
long viewerId = await factory.SeedViewerAsync();
|
||||
using var client = factory.CreateAuthenticatedClient(viewerId);
|
||||
|
||||
var response = await client.PostAsync("/deck/my_list", JsonBody(DeckFormatRequestJson(Format.Rotation)));
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK), body);
|
||||
|
||||
using var doc = JsonDocument.Parse(body);
|
||||
var decks = doc.RootElement.GetProperty("userDeckList");
|
||||
Assert.That(decks.GetArrayLength(), Is.EqualTo(0));
|
||||
}
|
||||
|
||||
// ---- get_empty_deck_number ----
|
||||
|
||||
[Test]
|
||||
public async Task GetEmptyDeckNumber_returns_1_when_viewer_has_no_decks()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
long viewerId = await factory.SeedViewerAsync();
|
||||
using var client = factory.CreateAuthenticatedClient(viewerId);
|
||||
|
||||
var response = await client.PostAsync("/deck/get_empty_deck_number",
|
||||
JsonBody(DeckFormatRequestJson(Format.Rotation)));
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
using var doc = JsonDocument.Parse(body);
|
||||
Assert.That(doc.RootElement.GetProperty("emptyDeckNum").GetInt32(), Is.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetEmptyDeckNumber_returns_next_free_slot_when_slots_filled()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
long viewerId = await factory.SeedViewerAsync();
|
||||
await factory.SeedDeckAsync(viewerId, Format.Rotation, 1);
|
||||
await factory.SeedDeckAsync(viewerId, Format.Rotation, 2);
|
||||
// Skip slot 3 so the algorithm should hand it back.
|
||||
await factory.SeedDeckAsync(viewerId, Format.Rotation, 4);
|
||||
using var client = factory.CreateAuthenticatedClient(viewerId);
|
||||
|
||||
var response = await client.PostAsync("/deck/get_empty_deck_number",
|
||||
JsonBody(DeckFormatRequestJson(Format.Rotation)));
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
using var doc = JsonDocument.Parse(body);
|
||||
Assert.That(doc.RootElement.GetProperty("emptyDeckNum").GetInt32(), Is.EqualTo(3),
|
||||
"Algorithm must return the smallest free slot, not just one past the highest used.");
|
||||
}
|
||||
|
||||
// ---- update (create / update / delete) ----
|
||||
|
||||
[Test]
|
||||
public async Task Update_creates_new_deck_when_slot_empty()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
long viewerId = await factory.SeedViewerAsync();
|
||||
var (classId, sleeveId, leaderSkinId) = await FetchSeededIds(factory);
|
||||
using var client = factory.CreateAuthenticatedClient(viewerId);
|
||||
|
||||
var updateJson = $$"""
|
||||
{"viewerId":"0","steamId":0,"steamSessionTicket":"",
|
||||
"deckNo":1,"classId":{{classId}},"leaderSkinId":{{leaderSkinId}},
|
||||
"isRandomLeaderSkin":false,"sleeveId":{{sleeveId}},"deckName":"Fresh Deck",
|
||||
"isDelete":0,"deckFormat":0}
|
||||
""";
|
||||
var response = await client.PostAsync("/deck/update", JsonBody(updateJson));
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK), body);
|
||||
|
||||
using var scope = factory.Services.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
||||
var count = await db.Decks.CountAsync(d => d.Number == 1 && d.Format == Format.Rotation);
|
||||
Assert.That(count, Is.EqualTo(1), "A new deck row should have been inserted.");
|
||||
var persisted = await db.Decks.FirstAsync(d => d.Number == 1 && d.Format == Format.Rotation);
|
||||
Assert.That(persisted.Name, Is.EqualTo("Fresh Deck"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Update_updates_existing_deck_in_place()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
long viewerId = await factory.SeedViewerAsync();
|
||||
await factory.SeedDeckAsync(viewerId, Format.Rotation, 1, name: "Original");
|
||||
var (classId, sleeveId, leaderSkinId) = await FetchSeededIds(factory);
|
||||
using var client = factory.CreateAuthenticatedClient(viewerId);
|
||||
|
||||
var updateJson = $$"""
|
||||
{"viewerId":"0","steamId":0,"steamSessionTicket":"",
|
||||
"deckNo":1,"classId":{{classId}},"leaderSkinId":{{leaderSkinId}},
|
||||
"isRandomLeaderSkin":false,"sleeveId":{{sleeveId}},"deckName":"Renamed",
|
||||
"isDelete":0,"deckFormat":0}
|
||||
""";
|
||||
await client.PostAsync("/deck/update", JsonBody(updateJson));
|
||||
|
||||
using var scope = factory.Services.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
||||
var rows = await db.Decks.Where(d => d.Number == 1 && d.Format == Format.Rotation).ToListAsync();
|
||||
Assert.That(rows.Count, Is.EqualTo(1), "Update must not insert a duplicate row.");
|
||||
Assert.That(rows[0].Name, Is.EqualTo("Renamed"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Update_with_is_delete_1_removes_the_slot()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
long viewerId = await factory.SeedViewerAsync();
|
||||
await factory.SeedDeckAsync(viewerId, Format.Rotation, 1, name: "Doomed");
|
||||
var (classId, sleeveId, leaderSkinId) = await FetchSeededIds(factory);
|
||||
using var client = factory.CreateAuthenticatedClient(viewerId);
|
||||
|
||||
var deleteJson = $$"""
|
||||
{"viewerId":"0","steamId":0,"steamSessionTicket":"",
|
||||
"deckNo":1,"classId":{{classId}},"leaderSkinId":{{leaderSkinId}},
|
||||
"isRandomLeaderSkin":false,"sleeveId":{{sleeveId}},"deckName":null,
|
||||
"isDelete":1,"deckFormat":0}
|
||||
""";
|
||||
var response = await client.PostAsync("/deck/update", JsonBody(deleteJson));
|
||||
|
||||
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK));
|
||||
|
||||
using var scope = factory.Services.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
||||
var still = await db.Decks.AnyAsync(d => d.Number == 1 && d.Format == Format.Rotation);
|
||||
Assert.That(still, Is.False, "is_delete=1 should remove the row.");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Update_returns_refreshed_deck_list()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
long viewerId = await factory.SeedViewerAsync();
|
||||
await factory.SeedDeckAsync(viewerId, Format.Rotation, 1, name: "Existing");
|
||||
var (classId, sleeveId, leaderSkinId) = await FetchSeededIds(factory);
|
||||
using var client = factory.CreateAuthenticatedClient(viewerId);
|
||||
|
||||
var updateJson = $$"""
|
||||
{"viewerId":"0","steamId":0,"steamSessionTicket":"",
|
||||
"deckNo":2,"classId":{{classId}},"leaderSkinId":{{leaderSkinId}},
|
||||
"isRandomLeaderSkin":false,"sleeveId":{{sleeveId}},"deckName":"Second",
|
||||
"isDelete":0,"deckFormat":0}
|
||||
""";
|
||||
var response = await client.PostAsync("/deck/update", JsonBody(updateJson));
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
using var doc = JsonDocument.Parse(body);
|
||||
var decks = doc.RootElement.GetProperty("userDeckList");
|
||||
Assert.That(decks.GetArrayLength(), Is.EqualTo(2),
|
||||
"/deck/update should hand back the full refreshed list, saving the client a follow-up.");
|
||||
var names = Enumerable.Range(0, decks.GetArrayLength())
|
||||
.Select(i => decks[i].GetProperty("name").GetString())
|
||||
.ToList();
|
||||
Assert.That(names, Is.EquivalentTo(new[] { "Existing", "Second" }));
|
||||
}
|
||||
|
||||
// ---- single-field mutations ----
|
||||
|
||||
[Test]
|
||||
public async Task UpdateName_persists_and_returns_updated_user_deck()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
long viewerId = await factory.SeedViewerAsync();
|
||||
await factory.SeedDeckAsync(viewerId, Format.Rotation, 1, name: "Old Name");
|
||||
using var client = factory.CreateAuthenticatedClient(viewerId);
|
||||
|
||||
var json = """{"viewerId":"0","steamId":0,"steamSessionTicket":"","deckNo":1,"deckName":"New Name","deckFormat":0}""";
|
||||
var response = await client.PostAsync("/deck/update_name", JsonBody(json));
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
using var doc = JsonDocument.Parse(body);
|
||||
Assert.That(doc.RootElement.GetProperty("userDeck").GetProperty("name").GetString(),
|
||||
Is.EqualTo("New Name"));
|
||||
|
||||
using var scope = factory.Services.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
||||
var deck = await db.Decks.FirstAsync(d => d.Number == 1 && d.Format == Format.Rotation);
|
||||
Assert.That(deck.Name, Is.EqualTo("New Name"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task UpdateSleeve_persists_and_returns_updated_user_deck()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
long viewerId = await factory.SeedViewerAsync();
|
||||
await factory.SeedDeckAsync(viewerId, Format.Rotation, 1);
|
||||
// Pick a different sleeve than the seed default to prove the change took.
|
||||
int sleeveId;
|
||||
using (var scope = factory.Services.CreateScope())
|
||||
{
|
||||
var db = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
||||
sleeveId = await db.Sleeves.OrderByDescending(s => s.Id).Select(s => s.Id).FirstAsync();
|
||||
}
|
||||
using var client = factory.CreateAuthenticatedClient(viewerId);
|
||||
|
||||
var json = $$"""{"viewerId":"0","steamId":0,"steamSessionTicket":"","deckNo":1,"sleeveId":{{sleeveId}},"deckFormat":0}""";
|
||||
var response = await client.PostAsync("/deck/update_sleeve", JsonBody(json));
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
using var doc = JsonDocument.Parse(body);
|
||||
Assert.That(doc.RootElement.GetProperty("userDeck").GetProperty("sleeveId").GetInt32(),
|
||||
Is.EqualTo(sleeveId));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task UpdateLeaderSkin_persists_and_clears_random_flag()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
long viewerId = await factory.SeedViewerAsync();
|
||||
await factory.SeedDeckAsync(viewerId, Format.Rotation, 1);
|
||||
int skinId;
|
||||
using (var scope = factory.Services.CreateScope())
|
||||
{
|
||||
var db = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
||||
skinId = await db.LeaderSkins.OrderByDescending(s => s.Id).Select(s => s.Id).FirstAsync();
|
||||
}
|
||||
using var client = factory.CreateAuthenticatedClient(viewerId);
|
||||
|
||||
var json = $$"""{"viewerId":"0","steamId":0,"steamSessionTicket":"","deckNo":1,"leaderSkinId":{{skinId}},"deckFormat":0}""";
|
||||
var response = await client.PostAsync("/deck/update_leader_skin", JsonBody(json));
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
using var doc = JsonDocument.Parse(body);
|
||||
var userDeck = doc.RootElement.GetProperty("userDeck");
|
||||
Assert.That(userDeck.GetProperty("leaderSkinId").GetInt32(), Is.EqualTo(skinId));
|
||||
Assert.That(userDeck.GetProperty("isRandomLeaderSkin").GetInt32(), Is.EqualTo(0),
|
||||
"Selecting a specific leader skin clears the random-skin flag.");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task UpdateRandomLeaderSkin_picks_from_pool_and_persists()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
long viewerId = await factory.SeedViewerAsync();
|
||||
await factory.SeedDeckAsync(viewerId, Format.Rotation, 1);
|
||||
List<int> pool;
|
||||
using (var scope = factory.Services.CreateScope())
|
||||
{
|
||||
var db = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
||||
pool = await db.LeaderSkins.OrderBy(s => s.Id).Take(3).Select(s => s.Id).ToListAsync();
|
||||
}
|
||||
using var client = factory.CreateAuthenticatedClient(viewerId);
|
||||
|
||||
var json =
|
||||
$$"""{"viewerId":"0","steamId":0,"steamSessionTicket":"","deckNo":1,"deckFormat":0,"leaderSkinIdList":[{{string.Join(',', pool)}}]}""";
|
||||
var response = await client.PostAsync("/deck/update_random_leader_skin", JsonBody(json));
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK), body);
|
||||
|
||||
using var doc = JsonDocument.Parse(body);
|
||||
var userDeck = doc.RootElement.GetProperty("userDeck");
|
||||
Assert.That(pool, Contains.Item(userDeck.GetProperty("leaderSkinId").GetInt32()),
|
||||
"Chosen skin must come from the supplied pool.");
|
||||
Assert.That(userDeck.GetProperty("isRandomLeaderSkin").GetInt32(), Is.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task UpdateRandomLeaderSkin_rejects_empty_pool_with_400()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
long viewerId = await factory.SeedViewerAsync();
|
||||
await factory.SeedDeckAsync(viewerId, Format.Rotation, 1);
|
||||
using var client = factory.CreateAuthenticatedClient(viewerId);
|
||||
|
||||
var json =
|
||||
"""{"viewerId":"0","steamId":0,"steamSessionTicket":"","deckNo":1,"deckFormat":0,"leaderSkinIdList":[]}""";
|
||||
var response = await client.PostAsync("/deck/update_random_leader_skin", JsonBody(json));
|
||||
|
||||
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task UpdateOrder_returns_200()
|
||||
{
|
||||
// No persistence today (slot Number doubles as display order); just confirm the
|
||||
// endpoint round-trips so a future ordering schema doesn't silently regress 200→500.
|
||||
using var factory = new SVSimTestFactory();
|
||||
long viewerId = await factory.SeedViewerAsync();
|
||||
await factory.SeedDeckAsync(viewerId, Format.Rotation, 1);
|
||||
await factory.SeedDeckAsync(viewerId, Format.Rotation, 2);
|
||||
using var client = factory.CreateAuthenticatedClient(viewerId);
|
||||
|
||||
var json =
|
||||
"""{"viewerId":"0","steamId":0,"steamSessionTicket":"","deckOrder":[2,1],"deckFormat":0}""";
|
||||
var response = await client.PostAsync("/deck/update_order", JsonBody(json));
|
||||
|
||||
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task DeleteDeckList_removes_listed_slots()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
long viewerId = await factory.SeedViewerAsync();
|
||||
await factory.SeedDeckAsync(viewerId, Format.Rotation, 1);
|
||||
await factory.SeedDeckAsync(viewerId, Format.Rotation, 2);
|
||||
await factory.SeedDeckAsync(viewerId, Format.Rotation, 3);
|
||||
using var client = factory.CreateAuthenticatedClient(viewerId);
|
||||
|
||||
var json =
|
||||
"""{"viewerId":"0","steamId":0,"steamSessionTicket":"","deckNoList":[1,3],"deckFormat":0}""";
|
||||
var response = await client.PostAsync("/deck/delete_deck_list", JsonBody(json));
|
||||
|
||||
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK));
|
||||
|
||||
using var scope = factory.Services.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
||||
var remaining = await db.Decks.Where(d => d.Format == Format.Rotation)
|
||||
.Select(d => d.Number).OrderBy(n => n).ToListAsync();
|
||||
Assert.That(remaining, Is.EqualTo(new[] { 2 }));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task SetDeckRedis_returns_200_for_authed_viewer()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
long viewerId = await factory.SeedViewerAsync();
|
||||
using var client = factory.CreateAuthenticatedClient(viewerId);
|
||||
|
||||
var json = """{"viewerId":"0","steamId":0,"steamSessionTicket":"","deckNo":1,"classId":1}""";
|
||||
var response = await client.PostAsync("/deck/set_deck_redis", JsonBody(json));
|
||||
|
||||
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK));
|
||||
}
|
||||
}
|
||||
155
SVSim.UnitTests/Controllers/LoadControllerTests.cs
Normal file
155
SVSim.UnitTests/Controllers/LoadControllerTests.cs
Normal file
@@ -0,0 +1,155 @@
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using SVSim.UnitTests.Infrastructure;
|
||||
|
||||
namespace SVSim.UnitTests.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// Coverage for <c>/load/index</c>. The endpoint hits the heaviest <c>.Include</c> chain in the
|
||||
/// app (<c>ViewerRepository.GetViewerByShortUdid</c>) and serializes the wide
|
||||
/// <c>IndexResponse</c> shape — first end-to-end exercise of either against a real EF provider.
|
||||
/// Shape assertions are split per test so a single regression pinpoints one named expectation.
|
||||
/// </summary>
|
||||
public class LoadControllerTests
|
||||
{
|
||||
private const string IndexRequestJson =
|
||||
"""{"viewerId":"0","steamId":0,"steamSessionTicket":"","carrier":"steam","cardMasterHash":""}""";
|
||||
|
||||
/// <summary>
|
||||
/// JSON keys (camelCased C# property names) for fields the client reads unconditionally.
|
||||
/// These come from the plain-JSON path; the wire-format snake_case keys
|
||||
/// (<c>user_rank</c>, <c>rotation_card_set_id_list</c>, ...) only apply when the
|
||||
/// encrypted msgpack pipeline is in play — see <c>EncryptedPipelineTests</c> (Phase 6).
|
||||
/// Missing any of these is a wire-shape regression in either path.
|
||||
/// </summary>
|
||||
private static readonly string[] RequiredIndexKeys =
|
||||
{
|
||||
"userTutorial", "userInfo", "userCurrency", "userItems",
|
||||
"userRotationDecks", "userUnlimitedDecks", "userMyRotationDecks",
|
||||
"userCards", "userClasses", "sleeves", "userEmblems",
|
||||
"userDegrees", "leaderSkins", "myPageBackgrounds",
|
||||
"userRankInfo", "userRankedMatches", "dailyLoginBonus", "arenaConfig",
|
||||
"redEtherOverrides", "maintenanceCards", "arenaInfos", "rankInfo",
|
||||
"classExp", "loadingTipCardExclusions", "defaultSettings",
|
||||
"unlimitedBanList", "rotationSets",
|
||||
"reprintedCards", "spotCards", "featureMaintenances",
|
||||
"specialCrystalInfos", "openBattlefieldIds", "lootBoxRegulations",
|
||||
"gatheringInfo", "userConfig", "deckFormat", "cardSetIdForResourceDlView"
|
||||
};
|
||||
|
||||
private static async Task<JsonElement> PostIndexAndReadBody(SVSimTestFactory factory, long viewerId)
|
||||
{
|
||||
using var client = factory.CreateAuthenticatedClient(viewerId);
|
||||
var response = await client.PostAsync("/load/index",
|
||||
new StringContent(IndexRequestJson, Encoding.UTF8, "application/json"));
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK), body);
|
||||
|
||||
var doc = JsonDocument.Parse(body);
|
||||
return doc.RootElement.Clone();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Index_with_minimal_viewer_returns_200()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
long viewerId = await factory.SeedViewerAsync();
|
||||
using var client = factory.CreateAuthenticatedClient(viewerId);
|
||||
|
||||
var response = await client.PostAsync("/load/index",
|
||||
new StringContent(IndexRequestJson, Encoding.UTF8, "application/json"));
|
||||
|
||||
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK),
|
||||
await response.Content.ReadAsStringAsync());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Index_with_no_auth_header_returns_401()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
using var client = factory.CreateClient();
|
||||
|
||||
var response = await client.PostAsync("/load/index",
|
||||
new StringContent(IndexRequestJson, Encoding.UTF8, "application/json"));
|
||||
|
||||
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Index_returns_all_required_keys()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
long viewerId = await factory.SeedViewerAsync();
|
||||
|
||||
var root = await PostIndexAndReadBody(factory, viewerId);
|
||||
|
||||
var missing = RequiredIndexKeys.Where(k => !root.TryGetProperty(k, out _)).ToList();
|
||||
Assert.That(missing, Is.Empty,
|
||||
$"Required IndexResponse keys missing: {string.Join(", ", missing)}");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Index_rank_info_is_array_not_dict()
|
||||
{
|
||||
// Guards the dict-vs-array regression that ate a previous release. Client iterates
|
||||
// user_rank by index; a dict would silently deserialize as zero entries.
|
||||
using var factory = new SVSimTestFactory();
|
||||
long viewerId = await factory.SeedViewerAsync();
|
||||
|
||||
var root = await PostIndexAndReadBody(factory, viewerId);
|
||||
|
||||
Assert.That(root.GetProperty("userRankInfo").ValueKind, Is.EqualTo(JsonValueKind.Array));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Index_user_rank_has_five_entries()
|
||||
{
|
||||
// Hard-coded format list in LoadController.RankFormats — five entries, one per
|
||||
// deck_format discriminator. Client indexes by format value; mismatched count
|
||||
// would point the wrong format at the wrong rank slot.
|
||||
using var factory = new SVSimTestFactory();
|
||||
long viewerId = await factory.SeedViewerAsync();
|
||||
|
||||
var root = await PostIndexAndReadBody(factory, viewerId);
|
||||
|
||||
Assert.That(root.GetProperty("userRankInfo").GetArrayLength(), Is.EqualTo(5));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Index_rotation_card_set_id_list_has_at_least_two_entries()
|
||||
{
|
||||
// LoadDetail.cs:184 unconditionally indexes [1] and [Count-1] — fewer than two
|
||||
// entries crashes the client at the home screen.
|
||||
using var factory = new SVSimTestFactory();
|
||||
long viewerId = await factory.SeedViewerAsync();
|
||||
|
||||
var root = await PostIndexAndReadBody(factory, viewerId);
|
||||
|
||||
Assert.That(root.GetProperty("rotationSets").GetArrayLength(),
|
||||
Is.GreaterThanOrEqualTo(2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Index_when_viewer_has_no_decks_returns_empty_format_lists()
|
||||
{
|
||||
// A freshly-registered viewer has no decks of any format. The three per-format deck
|
||||
// containers must still be present and empty so the client's iteration is well-formed.
|
||||
using var factory = new SVSimTestFactory();
|
||||
long viewerId = await factory.SeedViewerAsync();
|
||||
|
||||
var root = await PostIndexAndReadBody(factory, viewerId);
|
||||
|
||||
foreach (var key in new[] { "userRotationDecks", "userUnlimitedDecks", "userMyRotationDecks" })
|
||||
{
|
||||
var container = root.GetProperty(key);
|
||||
Assert.That(container.ValueKind, Is.EqualTo(JsonValueKind.Object),
|
||||
$"{key} should be the UserFormatDeckInfo object wrapper, not a raw array.");
|
||||
var inner = container.GetProperty("userDecks");
|
||||
Assert.That(inner.ValueKind, Is.EqualTo(JsonValueKind.Array));
|
||||
Assert.That(inner.GetArrayLength(), Is.EqualTo(0),
|
||||
$"{key}.userDecks must be an empty array for a deckless viewer, not null.");
|
||||
}
|
||||
}
|
||||
}
|
||||
120
SVSim.UnitTests/Controllers/PracticeControllerTests.cs
Normal file
120
SVSim.UnitTests/Controllers/PracticeControllerTests.cs
Normal file
@@ -0,0 +1,120 @@
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using SVSim.Database.Enums;
|
||||
using SVSim.UnitTests.Infrastructure;
|
||||
|
||||
namespace SVSim.UnitTests.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// Coverage for <c>/practice/*</c>. The solo-battle subsystem is mostly stubbed (no XP,
|
||||
/// no missions, no rewards) but the endpoints must still round-trip successfully or the
|
||||
/// solo-play UI breaks before reaching the battle screen.
|
||||
/// </summary>
|
||||
public class PracticeControllerTests
|
||||
{
|
||||
private const string BaseRequestJson =
|
||||
"""{"viewerId":"0","steamId":0,"steamSessionTicket":""}""";
|
||||
|
||||
private static string DeckFormatRequestJson(Format f) =>
|
||||
$$"""{"viewerId":"0","steamId":0,"steamSessionTicket":"","deckFormat":{{(int)f}}}""";
|
||||
|
||||
[Test]
|
||||
public async Task Info_returns_non_empty_opponent_array()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
long viewerId = await factory.SeedViewerAsync();
|
||||
using var client = factory.CreateAuthenticatedClient(viewerId);
|
||||
|
||||
var response = await client.PostAsync("/practice/info",
|
||||
new StringContent(BaseRequestJson, Encoding.UTF8, "application/json"));
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK), body);
|
||||
|
||||
using var doc = JsonDocument.Parse(body);
|
||||
Assert.That(doc.RootElement.ValueKind, Is.EqualTo(JsonValueKind.Array),
|
||||
"/practice/info returns a bare array (no wrapper object) per spec.");
|
||||
Assert.That(doc.RootElement.GetArrayLength(), Is.GreaterThan(0));
|
||||
Assert.That(doc.RootElement[0].GetProperty("practiceId").GetInt32(), Is.GreaterThan(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task DeckList_returns_viewer_decks()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
long viewerId = await factory.SeedViewerAsync();
|
||||
await factory.SeedDeckAsync(viewerId, Format.Rotation, number: 1, name: "Rotation Deck");
|
||||
await factory.SeedDeckAsync(viewerId, Format.Unlimited, number: 1, name: "Unlimited Deck");
|
||||
|
||||
using var client = factory.CreateAuthenticatedClient(viewerId);
|
||||
var response = await client.PostAsync("/practice/deck_list",
|
||||
new StringContent(DeckFormatRequestJson(Format.All), Encoding.UTF8, "application/json"));
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK), body);
|
||||
|
||||
using var doc = JsonDocument.Parse(body);
|
||||
var rotation = doc.RootElement.GetProperty("userDeckRotation");
|
||||
var unlimited = doc.RootElement.GetProperty("userDeckUnlimited");
|
||||
Assert.That(rotation.GetArrayLength(), Is.EqualTo(1));
|
||||
Assert.That(rotation[0].GetProperty("name").GetString(), Is.EqualTo("Rotation Deck"));
|
||||
Assert.That(unlimited.GetArrayLength(), Is.EqualTo(1));
|
||||
Assert.That(unlimited[0].GetProperty("name").GetString(), Is.EqualTo("Unlimited Deck"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task DeckList_empty_when_viewer_has_none()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
long viewerId = await factory.SeedViewerAsync();
|
||||
using var client = factory.CreateAuthenticatedClient(viewerId);
|
||||
|
||||
var response = await client.PostAsync("/practice/deck_list",
|
||||
new StringContent(DeckFormatRequestJson(Format.All), Encoding.UTF8, "application/json"));
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK), body);
|
||||
|
||||
using var doc = JsonDocument.Parse(body);
|
||||
Assert.That(doc.RootElement.GetProperty("userDeckRotation").GetArrayLength(), Is.EqualTo(0));
|
||||
Assert.That(doc.RootElement.GetProperty("userDeckUnlimited").GetArrayLength(), Is.EqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Start_returns_200()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
long viewerId = await factory.SeedViewerAsync();
|
||||
using var client = factory.CreateAuthenticatedClient(viewerId);
|
||||
|
||||
var response = await client.PostAsync("/practice/start",
|
||||
new StringContent(BaseRequestJson, Encoding.UTF8, "application/json"));
|
||||
|
||||
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Finish_accepts_any_recovery_data_returns_zero_xp()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
long viewerId = await factory.SeedViewerAsync();
|
||||
using var client = factory.CreateAuthenticatedClient(viewerId);
|
||||
|
||||
// recoveryData is an opaque JSON blob serialized to string by the client; the server
|
||||
// is supposed to accept it without validation. Anything goes.
|
||||
var finishJson =
|
||||
"""{"viewerId":"0","steamId":0,"steamSessionTicket":"","deckNo":1,"isWin":1,"evolveCount":2,"totalTurn":5,"enemyClassId":3,"difficulty":1,"deckFormat":0,"classId":1,"recoveryData":"{\"opaque\":\"blob\"}"}""";
|
||||
|
||||
var response = await client.PostAsync("/practice/finish",
|
||||
new StringContent(finishJson, Encoding.UTF8, "application/json"));
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK), body);
|
||||
|
||||
using var doc = JsonDocument.Parse(body);
|
||||
Assert.That(doc.RootElement.GetProperty("getClassExperience").GetInt32(), Is.EqualTo(0));
|
||||
Assert.That(doc.RootElement.GetProperty("classExperience").GetInt32(), Is.EqualTo(0));
|
||||
Assert.That(doc.RootElement.GetProperty("rewardList").GetArrayLength(), Is.EqualTo(0));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user