feat: Implement quest reward system with item selection and storage; update related services and database schema

This commit is contained in:
Alexa
2026-07-08 01:51:55 +03:00
parent 16ae55444d
commit 2e465af49e
13 changed files with 416 additions and 65 deletions
+10
View File
@@ -76,6 +76,16 @@ public sealed class RoomSave2023Tests : IClassFixture<DorkNetServerFactory>
using var json = JsonDocument.Parse(responseText);
Assert.True(json.RootElement.GetProperty("success").GetBoolean(), responseText);
// 2023 commit-response contract (NEOPBOMGIOG): value must carry
// BOTH a Room and a SubRoomDataSave or the client's post-parse
// validator NREs and the watch shows "Failed to save room".
var value = json.RootElement.GetProperty("value");
Assert.Equal(JsonValueKind.Object, value.GetProperty("Room").ValueKind);
var save = value.GetProperty("SubRoomDataSave");
Assert.Equal(blobName, save.GetProperty("DataBlob").GetString());
Assert.True(save.TryGetProperty("SubRoomDataSaveId", out _), responseText);
Assert.True(save.TryGetProperty("CreatedAt", out _), responseText);
await using (var scope = _factory.Services.CreateAsyncScope())
{
var db = scope.ServiceProvider.GetRequiredService<DorkNetDbContext>();
+13 -15
View File
@@ -68,27 +68,25 @@ public sealed class WatchShop2023Tests : IClassFixture<DorkNetServerFactory>
var storefront = await GetJsonAsync(client, "/api/storefronts/v3/giftdropstore/3");
var rows = storefront.GetProperty("StoreItems");
// Every consumable tile carries a bracketed ConsumableItemDesc (the
// client's literal item key) and no avatar item — that's what routes
// it to the Shop's Consumables tab and binds the baked prefab.
var consumables = new List<string>();
// Food / KO / camera consumables surface with the client's literal
// ConsumableItemDesc and no avatar item — that's what routes them to
// the Shop's Consumables tab and binds the baked prefab. (Hair dyes
// are a hybrid: they also carry an AvatarItemDesc colour guid, so
// they're excluded from the no-avatar-item check.)
var byDesc = new Dictionary<string, JsonElement>();
foreach (var row in rows.EnumerateArray())
{
var gd = row.GetProperty("GiftDrop");
var desc = gd.GetProperty("ConsumableItemDesc").GetString() ?? "";
if (desc.StartsWith("[") || desc.StartsWith("("))
{
consumables.Add(desc);
// Consumables are not avatar items: AvatarItemDesc empty and
// AvatarItemType null (a non-null enum here would tab it as
// clothing).
Assert.Equal("", gd.GetProperty("AvatarItemDesc").GetString());
Assert.Equal(JsonValueKind.Null, gd.GetProperty("AvatarItemType").ValueKind);
}
if (desc.Length > 0) byDesc[desc] = gd;
}
Assert.Contains("[FoodConsumable_RootBeer]", consumables);
Assert.Contains("[KOConsumable_Cola]", consumables);
foreach (var key in new[] { "[FoodConsumable_RootBeer]", "[KOConsumable_Cola]" })
{
Assert.True(byDesc.TryGetValue(key, out var gd), $"missing consumable {key}");
Assert.Equal("", gd.GetProperty("AvatarItemDesc").GetString());
Assert.Equal(JsonValueKind.Null, gd.GetProperty("AvatarItemType").ValueKind);
}
}
private HttpClient ApiClient()