test(tutorial): add end-to-end capture-replay smoke
Adds TutorialFlowEndToEndTests covering the full fresh-signup flow: account/update_name → tutorial/update (11→21→31) → update_action → gift_top → gift_receive (verifies 400 crystal + 100 rupy delta and 31→41 state advance) → pack_info → pack_open (verifies tutorial_step=100 and viewer state END). Includes the set-90001 card-pool seed required by the tutorial pack resolver (mirrors PackControllerTests pattern). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
110
SVSim.UnitTests/Controllers/TutorialFlowEndToEndTests.cs
Normal file
110
SVSim.UnitTests/Controllers/TutorialFlowEndToEndTests.cs
Normal file
@@ -0,0 +1,110 @@
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using NUnit.Framework;
|
||||
using SVSim.Database;
|
||||
using SVSim.Database.Enums;
|
||||
using SVSim.Database.Models;
|
||||
using SVSim.UnitTests.Infrastructure;
|
||||
|
||||
namespace SVSim.UnitTests.Controllers;
|
||||
|
||||
public class TutorialFlowEndToEndTests
|
||||
{
|
||||
[Test]
|
||||
public async Task FreshSignup_through_pack_open_reaches_tutorial_step_100()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
await factory.SeedGlobalsAsync();
|
||||
|
||||
// Fresh viewer at PRE_TUTORIAL_STEP (the real prod default after Task 1).
|
||||
long viewerId = await factory.SeedViewerAsync(tutorialState: 0);
|
||||
|
||||
// Pack 99047 (starter legendary) has base_pack_id=90001. Seed the card set used by
|
||||
// the tutorial pack pool resolver — mirrors the pattern in PackControllerTests.
|
||||
using (var scope = factory.Services.CreateScope())
|
||||
{
|
||||
var db = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
||||
db.CardSets.Add(new ShadowverseCardSetEntry
|
||||
{
|
||||
Id = 90001,
|
||||
Name = "TutorialStarterSet",
|
||||
IsInRotation = true,
|
||||
IsBasic = false,
|
||||
Cards =
|
||||
[
|
||||
new ShadowverseCardEntry { Id = 90001001L, Name = "StarterCard1", Rarity = Rarity.Bronze },
|
||||
new ShadowverseCardEntry { Id = 90001002L, Name = "StarterCard2", Rarity = Rarity.Gold },
|
||||
new ShadowverseCardEntry { Id = 90001003L, Name = "StarterCard3", Rarity = Rarity.Legendary },
|
||||
],
|
||||
});
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
using var client = factory.CreateAuthenticatedClient(viewerId);
|
||||
|
||||
var preCurrency = await factory.GetViewerCurrencyAsync(viewerId);
|
||||
|
||||
// 1. /account/update_name (after name-entry screen).
|
||||
var nameResp = await Post(client, "/account/update_name",
|
||||
"""{"name":"e2e_test_user","viewer_id":"0","steam_id":0,"steam_session_ticket":""}""");
|
||||
Assert.That(nameResp.StatusCode, Is.EqualTo(HttpStatusCode.OK));
|
||||
|
||||
// 2. Step transitions observed in capture: 11 → 21 → 31.
|
||||
foreach (var step in new[] { 11, 21, 31 })
|
||||
{
|
||||
var json = $$"""{"tutorial_step":{{step}},"is_skip":0,"viewer_id":"0","steam_id":0,"steam_session_ticket":""}""";
|
||||
var resp = await Post(client, "/tutorial/update", json);
|
||||
using var doc = JsonDocument.Parse(await resp.Content.ReadAsStringAsync());
|
||||
Assert.That(doc.RootElement.GetProperty("tutorial_step").GetInt32(), Is.EqualTo(step));
|
||||
}
|
||||
|
||||
// 3. /tutorial/update_action — a couple of representative sub-step calls.
|
||||
await Post(client, "/tutorial/update_action",
|
||||
"""{"tutorial_step":1,"tutorial_action_number":2,"viewer_id":"0","steam_id":0,"steam_session_ticket":""}""");
|
||||
|
||||
// 4. /tutorial/gift_top — surface the bundle.
|
||||
var topResp = await Post(client, "/tutorial/gift_top",
|
||||
"""{"page":1,"viewer_id":"0","steam_id":0,"steam_session_ticket":""}""");
|
||||
using (var doc = JsonDocument.Parse(await topResp.Content.ReadAsStringAsync()))
|
||||
{
|
||||
Assert.That(doc.RootElement.GetProperty("present_list").GetArrayLength(), Is.EqualTo(5));
|
||||
}
|
||||
|
||||
// 5. /tutorial/gift_receive — claim them.
|
||||
var receiveResp = await Post(client, "/tutorial/gift_receive",
|
||||
"""{"present_id_array":["71478630","71478629","71478628","71478627","71478626"],"state":1,"viewer_id":"0","steam_id":0,"steam_session_ticket":""}""");
|
||||
Assert.That(receiveResp.StatusCode, Is.EqualTo(HttpStatusCode.OK));
|
||||
|
||||
var midCurrency = await factory.GetViewerCurrencyAsync(viewerId);
|
||||
Assert.That(midCurrency.Crystals - preCurrency.Crystals, Is.EqualTo(400UL));
|
||||
Assert.That(midCurrency.Rupees - preCurrency.Rupees, Is.EqualTo(100UL));
|
||||
|
||||
// gift_receive should also have advanced the tutorial step to 41 server-side.
|
||||
Assert.That(await factory.GetViewerTutorialStateAsync(viewerId), Is.EqualTo(41));
|
||||
|
||||
// 6. /tutorial/pack_info — show the 3 active packs.
|
||||
var packInfoResp = await Post(client, "/tutorial/pack_info",
|
||||
"""{"viewer_id":"0","steam_id":0,"steam_session_ticket":""}""");
|
||||
Assert.That(packInfoResp.StatusCode, Is.EqualTo(HttpStatusCode.OK));
|
||||
|
||||
// 7. /tutorial/pack_open of the starter legendary pack — END transition.
|
||||
var openBody = """{"parent_gacha_id":99047,"gacha_id":990047,"gacha_type":1,"pack_number":1,"exclude_card_ids":[],"viewer_id":"0","steam_id":0,"steam_session_ticket":""}""";
|
||||
var openResp = await Post(client, "/tutorial/pack_open", openBody);
|
||||
var openRespBody = await openResp.Content.ReadAsStringAsync();
|
||||
Assert.That(openResp.StatusCode, Is.EqualTo(HttpStatusCode.OK), openRespBody);
|
||||
|
||||
using (var doc = JsonDocument.Parse(openRespBody))
|
||||
{
|
||||
Assert.That(doc.RootElement.GetProperty("tutorial_step").GetInt32(), Is.EqualTo(100));
|
||||
}
|
||||
|
||||
Assert.That(await factory.GetViewerTutorialStateAsync(viewerId), Is.EqualTo(100),
|
||||
"Viewer reaches TUTORIAL_END after the full flow.");
|
||||
}
|
||||
|
||||
private static Task<HttpResponseMessage> Post(HttpClient client, string url, string body)
|
||||
=> client.PostAsync(url, new StringContent(body, Encoding.UTF8, "application/json"));
|
||||
}
|
||||
Reference in New Issue
Block a user