Forgot unversioned xd
This commit is contained in:
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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user