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.
pull/104/head
VinPropane 2026-05-24 18:06:26 -04:00
parent 8ebe83d701
commit db7c443b50
4 changed files with 72 additions and 27 deletions

View File

@ -5,20 +5,30 @@ meta {
} }
docs { 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 { script:pre-request {
const { getInventory, sumItemQuantity } = require("./scripts/inventory-api-helper.js"); const { getInventory, sumItemQuantity, postMutation } = require("./scripts/inventory-api-helper.js");
const inventory = await getInventory(bru);
const scrap = sumItemQuantity(inventory, "scrap_metal_bulk"); async function clearItem(itemId) {
const refined = sumItemQuantity(inventory, "refined_plate_stock"); let qty = sumItemQuantity(await getInventory(bru), itemId);
if (scrap >= 5 || refined > 0) { while (qty > 0) {
throw new Error( const removeQty = Math.min(qty, 99);
`expected empty craft inputs before insufficient_materials test; scrap=${scrap} refined=${refined} — restart server or run this request before spine`, 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 { post {
@ -34,7 +44,7 @@ headers {
body:json { body:json {
{ {
"schemaVersion": 1, "schemaVersion": 1,
"recipeId": "refine_scrap_standard" "recipeId": "make_field_stim_mk0"
} }
} }

View File

@ -1,7 +1,7 @@
meta { meta {
name: POST craft deny inventory full name: POST craft deny inventory full
type: http type: http
seq: 5 seq: 3
} }
docs { docs {

View File

@ -5,15 +5,28 @@ meta {
} }
docs { 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 { script:pre-request {
const axios = require("axios"); const axios = require("axios");
const { getInventory, sumItemQuantity } = require("./scripts/inventory-api-helper.js");
const baseUrl = bru.getEnvVar("baseUrl") || bru.getVar("baseUrl"); const baseUrl = bru.getEnvVar("baseUrl") || bru.getVar("baseUrl");
const playerId = bru.getEnvVar("playerId") || bru.getVar("playerId"); const playerId = bru.getEnvVar("playerId") || bru.getVar("playerId");
const jsonHeaders = { headers: { "Content-Type": "application/json" } }; 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) { async function moveNear(x, z) {
await axios.post( await axios.post(
`${baseUrl}/game/players/${playerId}/move`, `${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( const response = await axios.post(
`${baseUrl}/game/players/${playerId}/interact`, `${baseUrl}/game/players/${playerId}/interact`,
{ schemaVersion: 1, interactableId }, { schemaVersion: 1, interactableId },
jsonHeaders, jsonHeaders,
); );
if (response.status !== 200 || response.data?.allowed !== true) { if (response.status !== 200) {
throw new Error(`gather ${interactableId} failed: ${response.status} ${JSON.stringify(response.data)}`); 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) { async function craft(recipeId) {
@ -45,16 +65,30 @@ script:pre-request {
return response.data; return response.data;
} }
await moveNear(16, -2); const gatherPlan = [
await gather("prototype_urban_bulk_delta"); { id: "prototype_urban_bulk_delta", x: 16, z: -2 },
await moveNear(10, -2); { id: "prototype_bio_mat_gamma", x: 10, z: -2 },
await gather("prototype_bio_mat_gamma"); { id: "prototype_subsurface_vein_beta", x: 16, z: -6 },
await moveNear(16, -6); { id: "prototype_resource_node_alpha", x: 10, z: -6 },
await gather("prototype_subsurface_vein_beta"); ];
await moveNear(10, -6);
await gather("prototype_resource_node_alpha");
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 { post {
@ -111,7 +145,8 @@ tests {
test("refine skill XP increased after spine crafts", function () { test("refine skill XP increased after spine crafts", function () {
const progression = bru.getVar("spineProgression"); const progression = bru.getVar("spineProgression");
const refine = progression.skills.find((row) => row.id === "refine"); const refine = progression.skills.find((row) => row.id === "refine");
const before = bru.getVar("refineXpBeforeSpine");
expect(refine).to.be.an("object"); expect(refine).to.be.an("object");
expect(refine.xp).to.be.at.least(20); expect(refine.xp - before).to.be.at.least(20);
}); });
} }

View File

@ -3,5 +3,5 @@ meta {
} }
docs { docs {
NEO-70 craft HTTP. Run order: deny requests first (seq 14), 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 14) 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.
} }