Files
road-compiler/test/workbench-import-compile.js
que01 62795a97b9 fix: install the recompile hook on UI import
`/api/import` set `session.area` but never `session.context.compileFresh`, which
is only wired when the server starts with an area on the command line. Every
session imported through the browser therefore answered "请先导入 OSM 文件" to
`/api/compile`, even though the import had just succeeded.

This predates direct editing — it broke "保存并重新生成" for UI-imported
workspaces — but it surfaced now, because saving a direct edit recompiles and so
reported a failed regeneration after a save that had in fact succeeded.

Covered by a new HTTP-level test: import the fixture, then recompile twice. The
bug lived in the wiring between two handlers rather than in either one, so
nothing below the HTTP boundary could catch it. Verified by reverting the fix and
watching the test fail.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-28 09:37:27 +08:00

95 lines
3.5 KiB
JavaScript

'use strict';
// Importing through the UI and then recompiling — the path a browser actually
// takes. It is exercised over HTTP because the bug it guards against lived in the
// wiring between the two handlers, not in either one: `/api/import` set
// `session.area` but never installed `session.context.compileFresh`, which is only
// wired when the server is started with an area on the command line. So every
// UI-imported session answered "请先导入 OSM 文件" to `/api/compile` — breaking
// "保存并重新生成", and later making a successful direct-edit save report a failed
// regeneration.
const assert = require('assert/strict');
const fs = require('fs');
const http = require('http');
const os = require('os');
const path = require('path');
const { startWorkbench } = require('../workbench/server');
const PORT = 8899;
const dataRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'workbench-import-compile-'));
const osm = fs.readFileSync(path.join(__dirname, 'fixtures', 'fengshu-er-road.osm'));
function request(options, body) {
return new Promise((resolve, reject) => {
const call = http.request({ host: '127.0.0.1', port: PORT, ...options }, (response) => {
const chunks = [];
response.on('data', (chunk) => chunks.push(chunk));
response.on('end', () =>
resolve({ status: response.statusCode, body: JSON.parse(Buffer.concat(chunks).toString('utf8')) }),
);
});
call.on('error', reject);
if (body) call.write(body);
call.end();
});
}
function multipart(field, filename, contents) {
const boundary = '----roadcompilertest';
return {
boundary,
body: Buffer.concat([
Buffer.from(
`--${boundary}\r\nContent-Disposition: form-data; name="${field}"; filename="${filename}"\r\n` +
'Content-Type: application/xml\r\n\r\n',
),
contents,
Buffer.from(`\r\n--${boundary}--\r\n`),
]),
};
}
const server = startWorkbench({ port: PORT, dataRoot, repoRoot: process.cwd() });
async function run() {
const upload = multipart('file', 'fengshu-er-road.osm', osm);
const imported = await request(
{
method: 'POST',
path: '/api/import',
headers: {
'Content-Type': `multipart/form-data; boundary=${upload.boundary}`,
'Content-Length': upload.body.length,
},
},
upload.body,
);
assert.equal(imported.status, 200, `import failed: ${JSON.stringify(imported.body).slice(0, 200)}`);
assert.ok(imported.body.areaId, 'import must report the area it created');
// The regression: this used to answer 400 "请先导入 OSM 文件" even though the
// import above had just succeeded.
const compiled = await request({ method: 'POST', path: '/api/compile' });
assert.equal(compiled.status, 200, `compile after import failed: ${JSON.stringify(compiled.body).slice(0, 200)}`);
assert.ok(compiled.body.compiled, 'compile must return the recompiled state');
assert.ok(compiled.body.compiled.model.roads.length > 0, 'the recompile must still produce roads');
// And it must stay repeatable, since saving a direct edit now recompiles.
const again = await request({ method: 'POST', path: '/api/compile' });
assert.equal(again.status, 200, 'a second recompile must also succeed');
}
run()
.then(() => {
console.log('workbench import/compile tests passed');
})
.catch((error) => {
console.error(error);
process.exitCode = 1;
})
.finally(() => {
server.close();
fs.rmSync(dataRoot, { recursive: true, force: true });
});