Files
SVSimServer/SVSim.UnitTests/RoutingSmokeTests.cs
gamer147 1af56b4ec4 fix(tk2): per-viewer is_join in arena_info + stub /arena/get_challenge_info
Bug 1 ("pay to enter again after restart"):
arena_info[0].is_join shipped from the static ArenaSeasonConfig seed,
so /load/index and /mypage/index always emitted false regardless of
viewer state. The client uses is_join to choose between the "Pay to
enter" and "Resume run" dialogs (Wizard/ChallengeEntry.cs:165 + the
ArenaEntryBase._isJoinFunc pivot). Without a per-viewer override every
cold start after a partial run looked like "no run" and the player got
charged again.

LoadController + MyPageController now compute is_join from
ViewerArenaTwoPickRuns presence. MyPageController grew an
IArenaTwoPickRunRepository dep (LoadController already had _db).

Bug 2: /arena/get_challenge_info 404. Stubbed via a new
ArenaController + DTO pair. Returns the season seed's begin/end_time
+ name where available; placeholder zeros for win history. All 6 keys
required by ChallangeHistoryInfoTask.Parse are present (unconditional
JsonData lookups).

Routing smoke added for /arena/get_challenge_info.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 13:13:11 -04:00

125 lines
5.2 KiB
C#

using System.Net;
using System.Text;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.AspNetCore.TestHost;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using SVSim.Database;
using SVSim.EmulatedEntrypoint;
namespace SVSim.UnitTests;
/// <summary>
/// Verifies the routing-prefix fix (audit step 5) actually exposes endpoints at the URLs the
/// client calls (no `api/` prefix). Posts plain JSON without UnityPlayer UA so the
/// translation middleware bypasses and we test routing in isolation.
/// </summary>
public class RoutingSmokeTests
{
private sealed class TestFactory : WebApplicationFactory<Program>
{
protected override void ConfigureWebHost(Microsoft.AspNetCore.Hosting.IWebHostBuilder builder)
{
builder.ConfigureTestServices(services =>
{
var descriptor = services.SingleOrDefault(d => d.ServiceType == typeof(DbContextOptions<SVSimDbContext>));
if (descriptor != null) services.Remove(descriptor);
services.AddDbContext<SVSimDbContext>(opt => opt.UseInMemoryDatabase("RoutingSmoke"));
});
}
}
private const string ValidBaseRequestJson =
"""{"viewer_id":"0","steam_id":0,"steam_session_ticket":""}""";
[Test]
public async Task CheckSpecialTitle_resolves_to_CheckController()
{
using var factory = new TestFactory();
using var client = factory.CreateClient();
var response = await client.PostAsync("/check/special_title",
new StringContent(ValidBaseRequestJson, Encoding.UTF8, "application/json"));
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK),
$"Expected 200 OK at /check/special_title, got {response.StatusCode}. " +
"If 404, the routing prefix fix (audit step 5) didn't take.");
var body = await response.Content.ReadAsStringAsync();
// Plain-JSON path uses camelCase (System.Text.Json default); MessagePack [Key] only applies
// to the Unity-UA encrypted path through ShadowverseTranslationMiddleware.
Assert.That(body, Does.Contain("\"title_image_id\":\"0\""),
"SpecialTitleCheck should return the built-in title id \"0\".");
}
[Test]
public async Task ImportViewer_route_resolves()
{
// /admin/import_viewer is AllowAnonymous so the route should at least be reachable
// (probably returns 400 for missing steam_id with our empty BaseRequest body; we only
// assert routing not deep behavior).
using var factory = new TestFactory();
using var client = factory.CreateClient();
var response = await client.PostAsync("/admin/import_viewer",
new StringContent(ValidBaseRequestJson, Encoding.UTF8, "application/json"));
Assert.That(response.StatusCode, Is.Not.EqualTo(HttpStatusCode.NotFound),
"/admin/import_viewer didn't resolve to a controller — route registration broken.");
}
[Test]
public async Task ApiPrefixedRoute_returns_404()
{
// The OLD broken path should now 404 — proves we dropped the `api/` prefix cleanly.
using var factory = new TestFactory();
using var client = factory.CreateClient();
var response = await client.PostAsync("/api/check/special_title",
new StringContent(ValidBaseRequestJson, Encoding.UTF8, "application/json"));
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.NotFound));
}
// Authenticated endpoints — we don't set up Steam auth in tests, so we just assert the
// route resolves (anything other than 404). Auth-flow integration tests are a separate
// problem — see PLAN.md status-log open item on body re-read.
[TestCase("/practice/info")]
[TestCase("/practice/deck_list")]
[TestCase("/practice/start")]
[TestCase("/practice/finish")]
[TestCase("/deck/my_list")]
[TestCase("/deck/info")]
[TestCase("/deck/update")]
[TestCase("/deck/update_name")]
[TestCase("/deck/update_sleeve")]
[TestCase("/deck/update_leader_skin")]
[TestCase("/deck/update_random_leader_skin")]
[TestCase("/deck/update_order")]
[TestCase("/deck/delete_deck_list")]
[TestCase("/deck/get_empty_deck_number")]
[TestCase("/deck/set_deck_redis")]
[TestCase("/arena_two_pick/top")]
[TestCase("/arena_two_pick/entry")]
[TestCase("/arena_two_pick/class_choose")]
[TestCase("/arena_two_pick/card_choose")]
[TestCase("/arena_two_pick/retire")]
[TestCase("/arena_two_pick/finish")]
[TestCase("/arena_two_pick_battle/do_matching")]
[TestCase("/arena_two_pick_battle/finish")]
[TestCase("/arena_colosseum/get_fee_info")]
[TestCase("/arena/get_challenge_info")]
public async Task Authenticated_route_resolves(string path)
{
using var factory = new TestFactory();
using var client = factory.CreateClient();
var response = await client.PostAsync(path,
new StringContent(ValidBaseRequestJson, Encoding.UTF8, "application/json"));
Assert.That(response.StatusCode, Is.Not.EqualTo(HttpStatusCode.NotFound),
$"{path} didn't resolve to a controller — route registration broken.");
}
}