From db7c443b5047e5de636b6b9fa81374a8baf83490 Mon Sep 17 00:00:00 2001 From: VinPropane Date: Sun, 24 May 2026 18:06:26 -0400 Subject: [PATCH] NEO-70: Fix Bruno craft tests for shared Postgres CI state. Clear refined stock for insufficient_materials deny; spine tolerates node_depleted gathers, reuses persisted scrap, and asserts refine XP delta. --- ...Post craft deny insufficient materials.bru | 32 ++++++---- .../craft/Post craft deny inventory full.bru | 2 +- .../Post craft gather refine make spine.bru | 63 ++++++++++++++----- bruno/neon-sprawl-server/craft/folder.bru | 2 +- 4 files changed, 72 insertions(+), 27 deletions(-) diff --git a/bruno/neon-sprawl-server/craft/Post craft deny insufficient materials.bru b/bruno/neon-sprawl-server/craft/Post craft deny insufficient materials.bru index c6502d5..629b8dc 100644 --- a/bruno/neon-sprawl-server/craft/Post craft deny insufficient materials.bru +++ b/bruno/neon-sprawl-server/craft/Post craft deny insufficient materials.bru @@ -5,20 +5,30 @@ meta { } docs { - NEO-70: craft without seeded materials denies with insufficient_materials. Requires empty bag (run before spine or on fresh server). + NEO-70: make_field_stim_mk0 without refined_plate_stock denies with insufficient_materials. Pre-request clears refined stock (inventory POST setup only) so the deny is stable after dotnet test / prior Bruno requests. } script:pre-request { - const { getInventory, sumItemQuantity } = require("./scripts/inventory-api-helper.js"); - const inventory = await getInventory(bru); - const scrap = sumItemQuantity(inventory, "scrap_metal_bulk"); - const refined = sumItemQuantity(inventory, "refined_plate_stock"); - if (scrap >= 5 || refined > 0) { - throw new Error( - `expected empty craft inputs before insufficient_materials test; scrap=${scrap} refined=${refined} — restart server or run this request before spine`, - ); + const { getInventory, sumItemQuantity, postMutation } = require("./scripts/inventory-api-helper.js"); + + async function clearItem(itemId) { + let qty = sumItemQuantity(await getInventory(bru), itemId); + while (qty > 0) { + const removeQty = Math.min(qty, 99); + const response = await postMutation(bru, { + schemaVersion: 1, + mutationKind: "remove", + itemId, + quantity: removeQty, + }); + if (response.status !== 200 || response.data?.applied !== true) { + throw new Error(`clear ${itemId} failed: ${response.status} ${JSON.stringify(response.data)}`); + } + qty = sumItemQuantity(await getInventory(bru), itemId); + } } - bru.setVar("scrapBeforeInsufficientDeny", scrap); + + await clearItem("refined_plate_stock"); } post { @@ -34,7 +44,7 @@ headers { body:json { { "schemaVersion": 1, - "recipeId": "refine_scrap_standard" + "recipeId": "make_field_stim_mk0" } } diff --git a/bruno/neon-sprawl-server/craft/Post craft deny inventory full.bru b/bruno/neon-sprawl-server/craft/Post craft deny inventory full.bru index 910a47e..cea18be 100644 --- a/bruno/neon-sprawl-server/craft/Post craft deny inventory full.bru +++ b/bruno/neon-sprawl-server/craft/Post craft deny inventory full.bru @@ -1,7 +1,7 @@ meta { name: POST craft deny inventory full type: http - seq: 5 + seq: 3 } docs { diff --git a/bruno/neon-sprawl-server/craft/Post craft gather refine make spine.bru b/bruno/neon-sprawl-server/craft/Post craft gather refine make spine.bru index 708500a..7855cbd 100644 --- a/bruno/neon-sprawl-server/craft/Post craft gather refine make spine.bru +++ b/bruno/neon-sprawl-server/craft/Post craft gather refine make spine.bru @@ -5,15 +5,28 @@ meta { } docs { - NEO-70 prototype spine: gather scrap from four prototype resource nodes (delta 5 + gamma 3 + beta 2 + alpha 1 = 11 scrap), craft refine_scrap_efficient (10→2 refined_plate_stock), then make_field_stim_mk0. No inventory POST shortcuts. + NEO-70 prototype spine: accumulate >=11 scrap_metal_bulk (gather when nodes allow, reuse persisted inventory after dotnet test), refine_scrap_efficient when refined_plate_stock < 2, then make_field_stim_mk0. No inventory POST shortcuts for spine materials. } script:pre-request { const axios = require("axios"); + const { getInventory, sumItemQuantity } = require("./scripts/inventory-api-helper.js"); const baseUrl = bru.getEnvVar("baseUrl") || bru.getVar("baseUrl"); const playerId = bru.getEnvVar("playerId") || bru.getVar("playerId"); const jsonHeaders = { headers: { "Content-Type": "application/json" } }; + const progressionBefore = await axios.get(`${baseUrl}/game/players/${playerId}/skill-progression`); + const refineBefore = progressionBefore.data.skills.find((row) => row.id === "refine"); + bru.setVar("refineXpBeforeSpine", refineBefore ? refineBefore.xp : 0); + + async function currentScrap() { + return sumItemQuantity(await getInventory(bru), "scrap_metal_bulk"); + } + + async function currentRefined() { + return sumItemQuantity(await getInventory(bru), "refined_plate_stock"); + } + async function moveNear(x, z) { await axios.post( `${baseUrl}/game/players/${playerId}/move`, @@ -22,15 +35,22 @@ script:pre-request { ); } - async function gather(interactableId) { + async function tryGather(interactableId) { const response = await axios.post( `${baseUrl}/game/players/${playerId}/interact`, { schemaVersion: 1, interactableId }, jsonHeaders, ); - if (response.status !== 200 || response.data?.allowed !== true) { - throw new Error(`gather ${interactableId} failed: ${response.status} ${JSON.stringify(response.data)}`); + if (response.status !== 200) { + throw new Error(`gather ${interactableId} HTTP ${response.status}`); } + if (response.data?.allowed === true) { + return; + } + if (response.data?.reasonCode === "node_depleted") { + return; + } + throw new Error(`gather ${interactableId} denied: ${JSON.stringify(response.data)}`); } async function craft(recipeId) { @@ -45,16 +65,30 @@ script:pre-request { return response.data; } - await moveNear(16, -2); - await gather("prototype_urban_bulk_delta"); - await moveNear(10, -2); - await gather("prototype_bio_mat_gamma"); - await moveNear(16, -6); - await gather("prototype_subsurface_vein_beta"); - await moveNear(10, -6); - await gather("prototype_resource_node_alpha"); + const gatherPlan = [ + { id: "prototype_urban_bulk_delta", x: 16, z: -2 }, + { id: "prototype_bio_mat_gamma", x: 10, z: -2 }, + { id: "prototype_subsurface_vein_beta", x: 16, z: -6 }, + { id: "prototype_resource_node_alpha", x: 10, z: -6 }, + ]; - await craft("refine_scrap_efficient"); + let scrap = await currentScrap(); + for (const node of gatherPlan) { + if (scrap >= 11) { + break; + } + await moveNear(node.x, node.z); + await tryGather(node.id); + scrap = await currentScrap(); + } + + if (scrap < 11) { + throw new Error(`spine needs >=11 scrap_metal_bulk, have ${scrap} after optional gathers`); + } + + if ((await currentRefined()) < 2) { + await craft("refine_scrap_efficient"); + } } post { @@ -111,7 +145,8 @@ tests { test("refine skill XP increased after spine crafts", function () { const progression = bru.getVar("spineProgression"); const refine = progression.skills.find((row) => row.id === "refine"); + const before = bru.getVar("refineXpBeforeSpine"); expect(refine).to.be.an("object"); - expect(refine.xp).to.be.at.least(20); + expect(refine.xp - before).to.be.at.least(20); }); } diff --git a/bruno/neon-sprawl-server/craft/folder.bru b/bruno/neon-sprawl-server/craft/folder.bru index 077b7ed..37a2533 100644 --- a/bruno/neon-sprawl-server/craft/folder.bru +++ b/bruno/neon-sprawl-server/craft/folder.bru @@ -3,5 +3,5 @@ meta { } docs { - NEO-70 craft HTTP. Run order: deny requests first (seq 1–4), then spine last (seq 5). Spine uses gather interact (no inventory POST shortcuts). Deny setup requests may use inventory POST only where noted. On persistent Postgres dev stores, run insufficient_materials before spine or restart the server so dev-local-1 bag is empty. + NEO-70 craft HTTP. Run order within folder: deny requests (seq 1–4) then spine (seq 5). Full collection runs after dotnet test on shared Postgres — insufficient-materials clears refined stock; spine reuses persisted scrap and tolerates node_depleted gathers. Deny setup requests may use inventory POST only where noted. }