test(card): /card/create controller integration

Adds CreateBody helper and 15 test runs (7 methods + 8 parametrized
cases) covering happy path, 401 unauthenticated, malformed inner JSON,
empty object, unknown card, not_craftable, would_exceed_max_copies,
and insufficient_vials error paths for POST /card/create.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
gamer147
2026-05-28 01:38:42 -04:00
parent 9b5fe6dd83
commit bac10b91ff

View File

@@ -19,6 +19,12 @@ public class CardControllerTests
Encoding.UTF8,
"application/json");
private static StringContent CreateBody(string innerJson) =>
new(
$$"""{"card_id_number_array":{{JsonSerializer.Serialize(innerJson)}},"viewer_id":"0","steam_id":0,"steam_session_ticket":""}""",
Encoding.UTF8,
"application/json");
[Test]
public async Task Destruct_happy_path_returns_redether_and_card_post_totals()
{
@@ -192,4 +198,149 @@ public class CardControllerTests
Assert.That(entries, Has.Member((Type: 1, Id: 0L, Num: 50)));
Assert.That(entries, Has.Member((Type: 5, Id: 10001001L, Num: 2)));
}
[Test]
public async Task Create_happy_path_returns_redether_and_card_post_totals()
{
using var factory = new SVSimTestFactory();
long viewerId = await factory.SeedViewerAsync();
await factory.SeedOwnedCardAsync(viewerId, cardId: 10001001L, count: 0, craftCost: 200);
await factory.SetRedEtherAsync(viewerId, 1_000UL);
using var client = factory.CreateAuthenticatedClient(viewerId);
var response = await client.PostAsync("/card/create",
CreateBody("{\"10001001\":\"2,0\"}"));
var body = await response.Content.ReadAsStringAsync();
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK), body);
var entries = JsonDocument.Parse(body).RootElement
.GetProperty("reward_list")
.EnumerateArray()
.Select(e => (Type: e.GetProperty("reward_type").GetInt32(),
Id: e.GetProperty("reward_id").GetInt64(),
Num: e.GetProperty("reward_num").GetInt32()))
.ToList();
// 1000 - (2 * 200) = 600
Assert.That(entries, Has.Member((Type: 1, Id: 0L, Num: 600)),
"RedEther post-state total = 1000 - 400 = 600");
Assert.That(entries, Has.Member((Type: 5, Id: 10001001L, Num: 2)),
"Card post-state owned count = 0 + 2 = 2");
}
[Test]
public async Task Create_without_auth_header_returns_401()
{
using var factory = new SVSimTestFactory();
using var client = factory.CreateClient();
var response = await client.PostAsync("/card/create",
CreateBody("{\"10001001\":\"1,0\"}"));
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized));
}
[TestCase("", Description = "empty string")]
[TestCase("not json", Description = "non-JSON garbage")]
[TestCase("{\"10001001\":\"1\"}", Description = "value missing snapshot")]
[TestCase("{\"10001001\":\"0,0\"}", Description = "num=0 not allowed")]
[TestCase("{\"10001001\":\"-1,0\"}", Description = "negative num")]
[TestCase("{\"abc\":\"1,0\"}", Description = "non-numeric cardId")]
[TestCase("{\"10001001\":5}", Description = "value not a string")]
[TestCase("[]", Description = "root must be object, not array")]
public async Task Create_with_malformed_inner_json_returns_400(string innerJson)
{
using var factory = new SVSimTestFactory();
long viewerId = await factory.SeedViewerAsync();
using var client = factory.CreateAuthenticatedClient(viewerId);
var response = await client.PostAsync("/card/create", CreateBody(innerJson));
var body = await response.Content.ReadAsStringAsync();
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest), body);
Assert.That(body, Does.Contain("malformed_request"));
}
[Test]
public async Task Create_with_empty_inner_object_returns_400()
{
using var factory = new SVSimTestFactory();
long viewerId = await factory.SeedViewerAsync();
using var client = factory.CreateAuthenticatedClient(viewerId);
var response = await client.PostAsync("/card/create", CreateBody("{}"));
var body = await response.Content.ReadAsStringAsync();
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest), body);
Assert.That(body, Does.Contain("malformed_request"));
}
[Test]
public async Task Create_unknown_card_returns_400_unknown_card()
{
using var factory = new SVSimTestFactory();
long viewerId = await factory.SeedViewerAsync();
await factory.SetRedEtherAsync(viewerId, 1_000UL);
using var client = factory.CreateAuthenticatedClient(viewerId);
var response = await client.PostAsync("/card/create",
CreateBody("{\"99999999\":\"1,0\"}"));
var body = await response.Content.ReadAsStringAsync();
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest), body);
Assert.That(body, Does.Contain("unknown_card"));
}
[Test]
public async Task Create_not_craftable_returns_400_not_craftable()
{
using var factory = new SVSimTestFactory();
long viewerId = await factory.SeedViewerAsync();
await factory.SeedOwnedCardAsync(viewerId, cardId: 10001001L, count: 0, craftCost: 0, dustReward: 0);
await factory.SetRedEtherAsync(viewerId, 1_000UL);
using var client = factory.CreateAuthenticatedClient(viewerId);
var response = await client.PostAsync("/card/create",
CreateBody("{\"10001001\":\"1,0\"}"));
var body = await response.Content.ReadAsStringAsync();
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest), body);
Assert.That(body, Does.Contain("not_craftable"));
}
[Test]
public async Task Create_would_exceed_max_copies_returns_400()
{
using var factory = new SVSimTestFactory();
long viewerId = await factory.SeedViewerAsync();
await factory.SeedOwnedCardAsync(viewerId, cardId: 10001001L, count: 3, craftCost: 200);
await factory.SetRedEtherAsync(viewerId, 1_000UL);
using var client = factory.CreateAuthenticatedClient(viewerId);
var response = await client.PostAsync("/card/create",
CreateBody("{\"10001001\":\"1,3\"}"));
var body = await response.Content.ReadAsStringAsync();
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest), body);
Assert.That(body, Does.Contain("would_exceed_max_copies"));
}
[Test]
public async Task Create_insufficient_vials_returns_400()
{
using var factory = new SVSimTestFactory();
long viewerId = await factory.SeedViewerAsync();
await factory.SeedOwnedCardAsync(viewerId, cardId: 10001001L, count: 0, craftCost: 200);
await factory.SetRedEtherAsync(viewerId, 100UL); // half of needed
using var client = factory.CreateAuthenticatedClient(viewerId);
var response = await client.PostAsync("/card/create",
CreateBody("{\"10001001\":\"1,0\"}"));
var body = await response.Content.ReadAsStringAsync();
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest), body);
Assert.That(body, Does.Contain("insufficient_vials"));
}
}