import fs from 'node:fs'; import _ from 'lodash'; import Lacuna from '../lacuna'; import { int } from '../core/util'; import { tmpfile } from './tmpfile'; // Nearly every buildings/*.test.ts file calls findBuilding() in its own // beforeAll, which would otherwise mean 1 empire.getStatus() + up to 4 // body.getBuildings() calls *per test file* just to look up a building_id - // on ~40 files that alone eats a big chunk of the server's 200 requests/ // minute budget before any actual method under test even runs. The // building layout doesn't change during a read-only test run, so it's // fetched once and cached on disk for the rest of the run. const CACHE_FILE = tmpfile('tle-client-test-building-inventory.json'); const INVENTORY_TTL_MS = 4 * 60 * 1000; type Inventory = Record>; // body_id -> building_id -> name interface CachedInventory { expiresAt: number; bodies: Inventory; } async function loadInventory(lacuna: Lacuna): Promise { try { const cached = JSON.parse(fs.readFileSync(CACHE_FILE, 'utf8')) as CachedInventory; if (cached.expiresAt > Date.now()) return cached.bodies; } catch { // No cache yet, or it's corrupt/unreadable - fetch fresh below. } const { result: statusResult, error } = await lacuna.empire.getStatus(); if (!statusResult || error) { if (error) throw error; else throw new Error('Could not call get_status on test empire'); } const empire = statusResult.empire; const bodyIds = [empire.home_planet_id, ...empire.bodies.colonies.map((c) => c.id)]; const bodies: Inventory = {}; for (const bodyId of bodyIds) { const { result } = await lacuna.body.getBuildings({ body_id: bodyId }); bodies[bodyId] = _.mapValues(result!.buildings, (b) => b.name); } fs.writeFileSync( CACHE_FILE, JSON.stringify({ expiresAt: Date.now() + INVENTORY_TTL_MS, bodies }) ); return bodies; } /** * Finds the building_id (and body_id it lives on) of the first building * named `buildingName` across every body the test empire owns (home planet + * colonies). Returns `undefined` if none is found - the fixture account * doesn't have that building anywhere yet. Callers should skip (not fail) * their tests in that case; see any buildings/*.test.ts file for the * pattern. */ export async function findBuilding(lacuna: Lacuna, buildingName: string) { const bodies = await loadInventory(lacuna); for (const [bodyIdStr, buildings] of Object.entries(bodies)) { const key = _.chain(buildings) .keys() .filter((k) => buildings[k] === buildingName) .first() .value(); if (key) return { buildingId: int(key), bodyId: int(bodyIdStr) }; } return undefined; }