Fix city generation retries and remove city-wide economy mechanics

This commit is contained in:
mrleo1nid committed 2026-10-30 22:54:06 +03:00
1 parent 85df37c26e
commit 63f0c72ff3
21 files changed
+252 -585

No files matched your search

-4
View File
@@ -24,10 +24,6 @@ try {
await page.waitForFunction(()=>document.querySelector('.viewer-top [role=status]')?.textContent.includes('в загруженной области'),null,{timeout:60000});
const visibleMs=Math.round(performance.now()-started);
await page.waitForFunction(()=>document.querySelector('.room-clock')?.textContent.includes('День'),null,{timeout:10000});
await page.waitForFunction(()=>document.querySelector('.room-gameplay')?.textContent.includes('Казна'),null,{timeout:10000});
await page.locator('[data-action=meal]').click();
await page.waitForFunction(()=>document.querySelector('[data-action=meal]')?.disabled,null,{timeout:5000});
await page.waitForFunction(()=>document.querySelector('.room-gameplay')?.textContent.includes('Общий обед'),null,{timeout:15000});
await page.getByRole('button',{name:'Пауза',exact:true}).click();
await page.waitForFunction(()=>document.querySelector('.room-clock')?.textContent.includes('×0'),null,{timeout:10000});
await page.getByRole('button',{name:'1',exact:true}).click();
+37
View File
@@ -0,0 +1,37 @@
// Run with `node --import tsx scripts/generation-browser-smoke.mjs`.
// Uses the same optional Playwright environment variables as browser-smoke.mjs.
import { createRequire } from 'node:module';
import { resolve } from 'node:path';
import assert from 'node:assert/strict';
import { create, toBinary } from '@bufbuild/protobuf';
import { CityJobSchema } from '../client/generated/city_pb.ts';
const require = createRequire(process.env.CITYGEN_BROWSER_MODULES ? resolve(process.env.CITYGEN_BROWSER_MODULES, 'package.json') : import.meta.url);
const { chromium } = require('playwright');
const browser = await chromium.launch({ headless: true, executablePath: process.env.CITYGEN_BROWSER_EXECUTABLE });
try {
const page = await browser.newPage();
const errors = []; page.on('pageerror', error => errors.push(error.message));
let posts = 0, polls = 0, failed = false;
await page.route('**/api/cities/jobs**', async route => {
if (route.request().method() === 'POST') posts++;
else polls++;
const job = create(CityJobSchema, { id: 'resume-test', cityId: 'test', status: failed ? 'failed' : 'running', stage: 'Повтор Overpass', error: failed ? 'Overpass временно недоступен' : '' });
await route.fulfill({ contentType: 'application/x-protobuf', body: Buffer.from(toBinary(CityJobSchema, job)) });
});
const url = `${process.env.CITYGEN_TEST_URL || 'http://127.0.0.1:3000'}/?create-city&job=resume-test`;
await page.goto(url);
await page.getByText('Повтор Overpass…', { exact: true }).waitFor();
await page.reload();
await page.getByText('Повтор Overpass…', { exact: true }).waitFor();
await page.getByRole('button', { name: 'Закрыть ожидание', exact: true }).click();
await page.locator('.overlay').waitFor({ state: 'hidden' });
await page.getByRole('button', { name: 'Сгенерировать', exact: true }).click();
await page.getByText('Повтор Overpass…', { exact: true }).waitFor();
failed = true;
await page.locator('.err').filter({ hasText: 'Overpass временно недоступен' }).waitFor();
assert.equal(new URL(page.url()).searchParams.has('job'), false);
assert.equal(posts, 0, 'reload and resume must not create another job');
assert.ok(polls >= 3);
assert.deepEqual(errors, []);
console.log(JSON.stringify({ generationResume: 'passed', posts, polls }));
} finally { await browser.close(); }
+11 -4
View File
@@ -27,7 +27,7 @@ test('times out a hanging request and tries the next server', async (t) => {
return ++calls === 1 ? pending(init.signal!) : Promise.resolve(ok());
});
const result = fetchOverpass(bbox);
t.mock.timers.tick(45_000);
t.mock.timers.tick(90_000);
assert.deepEqual(await result, elements);
assert.equal(calls, 2);
});
@@ -41,7 +41,7 @@ test('timeout also covers downloading the response body', async (t) => {
});
const result = fetchOverpass(bbox);
await Promise.resolve();
t.mock.timers.tick(45_000);
t.mock.timers.tick(90_000);
assert.deepEqual(await result, elements);
assert.equal(calls, 2);
});
@@ -76,11 +76,18 @@ test('reports failure when every server times out', async (t) => {
t.mock.timers.enable({ apis: ['setTimeout'] });
const fetch = t.mock.method(globalThis, 'fetch', (_url: string, init: RequestInit) => pending(init.signal!));
const result = fetchOverpass(bbox);
const check = assert.rejects(result, /Таймаут.*45 секунд/);
const check = assert.rejects(result, /Таймаут.*90 секунд/);
for (let i = 0; i < OVERPASS_ENDPOINTS.length; i++) {
t.mock.timers.tick(45_000);
t.mock.timers.tick(90_000);
await Promise.resolve();
}
await check;
assert.equal(fetch.mock.callCount(), OVERPASS_ENDPOINTS.length);
});
test('server identifies itself to Overpass rather than using the generic Node user agent', async (t) => {
t.mock.method(globalThis, 'fetch', async (_url: string, init: RequestInit) => {
assert.match(new Headers(init.headers).get('User-Agent')!, /citygen/);
return ok();
});
assert.deepEqual(await fetchOverpass(bbox), elements);
});