mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-30 11:49:16 +00:00
test: make mutation oracles explicit before v1.76.0-beta.1
Run 34760156615 exposed stale registry witnesses under the honest #550 outcome taxonomy. Keep behavioural checks out of setup chains, retarget the preflight mutation to the editor host, and make the junction cache smoke exercise same-object in-place geometry changes. Release: v1.76.0-beta.1 Issue: #550 User-Visible: no
This commit is contained in:
@@ -279,9 +279,46 @@ if (handle) {
|
||||
}
|
||||
}
|
||||
|
||||
// #330 AC4 / #550: the production cache is keyed by the config object because
|
||||
// physical commits intentionally update that object in place. Prove that a
|
||||
// changed geometry fingerprint invalidates an entry even when object identity
|
||||
// is unchanged; the gesture above normally receives a fresh object after the
|
||||
// write and therefore cannot distinguish this case on its own.
|
||||
resize.resizeBaselineFingerprintRejectsStaleObject = await page.evaluate(() => {
|
||||
const card = window.__card;
|
||||
const runtime = card._editorRuntime;
|
||||
const previous = structuredClone(card._serverCfg);
|
||||
const space = previous?.spaces?.find((entry) => entry.id === card._space);
|
||||
if (!runtime || !space || !space.rooms?.length) return false;
|
||||
const firstRoom = space.rooms[0];
|
||||
const point = Array.isArray(firstRoom.poly) ? firstRoom.poly[0] : null;
|
||||
if (!Array.isArray(point)) return false;
|
||||
const original = card._junctionLimitViolations;
|
||||
let calls = 0;
|
||||
card._junctionLimitViolations = (...args) => {
|
||||
if (args[0] === previous) calls += 1;
|
||||
return original(...args);
|
||||
};
|
||||
try {
|
||||
runtime._junctionLimitsIntroduced(structuredClone(previous), previous, space.id);
|
||||
if (!runtime._junctionBaselineCache.get(previous)) return false;
|
||||
point[0] += 0.001;
|
||||
runtime._junctionLimitsIntroduced(structuredClone(previous), previous, space.id);
|
||||
return calls === 2;
|
||||
} finally {
|
||||
card._junctionLimitViolations = original;
|
||||
}
|
||||
});
|
||||
|
||||
checkAll({ ...out, ...resize }, {
|
||||
resizeGapBefore: 10,
|
||||
resizeStoppedAtLastAllowed: 6,
|
||||
resizeRefusalOnce: 1,
|
||||
resizeHandleFound: true,
|
||||
resizeRefusalNamesRule: true,
|
||||
resizeBaselineComputedOncePerGesture: true,
|
||||
resizeSecondHandleFound: true,
|
||||
resizeBaselineRecomputedAfterCommit: true,
|
||||
resizeBaselineFingerprintRejectsStaleObject: true,
|
||||
});
|
||||
await finish(browser);
|
||||
|
||||
+10
-10
@@ -3269,12 +3269,11 @@ const MUTANT_DEFINITIONS = [
|
||||
},
|
||||
{
|
||||
id: 'junction-limit-baseline-cache-stale',
|
||||
// r2-M1: the behavioural guard — the smoke counts BASELINE computations
|
||||
// (exactly one per gesture with a working cache, one per pointermove
|
||||
// without) — plus the source-contract unit stays as the cheap first line.
|
||||
guard: 'node demo/smoke_junction_limits.mjs '
|
||||
+ '&& npx tsc -p tsconfig.test.json && node scripts/fix-test-build.mjs '
|
||||
+ '&& node --test --test-name-pattern="#330 AC4" test/junction-limits.test.mjs',
|
||||
// r2-M1: the behavioural oracle counts BASELINE computations (exactly one
|
||||
// per gesture with a working cache, one per pointermove without). Keep it
|
||||
// as the sole oracle: a second behavioural command in an `&&` prefix is a
|
||||
// setup phase under the honest #550 taxonomy and cannot prove the mutant.
|
||||
guard: 'node demo/smoke_junction_limits.mjs',
|
||||
because: 'a baseline cache that survives a config epoch change serves verdicts of a '
|
||||
+ 'plan that no longer exists — the epoch check IS the invalidation contract (#330 §4.4)',
|
||||
patches: [{
|
||||
@@ -5943,9 +5942,11 @@ const MUTANT_DEFINITIONS = [
|
||||
+ '&& node demo/smoke_preflight_diagnostics.mjs',
|
||||
because: 'диагностика с хэшем сохранённой геометрии не несёт ничего сверх экспорта (AC4)',
|
||||
patches: [{
|
||||
file: 'src/houseplan-card.ts',
|
||||
// This method lives on HouseplanEditorRuntime after the editor split;
|
||||
// the saved config belongs to the typed host, not to the runtime itself.
|
||||
file: 'src/houseplan-editor-runtime.ts',
|
||||
find: " const spacesById = new Map(((candidate as any)?.spaces || [])",
|
||||
replace: " const spacesById = new Map(((this._serverCfg as any)?.spaces || [])",
|
||||
replace: " const spacesById = new Map(((this.host._serverCfg as any)?.spaces || [])",
|
||||
}],
|
||||
},
|
||||
{
|
||||
@@ -5968,8 +5969,7 @@ const MUTANT_DEFINITIONS = [
|
||||
// возвращает глобальный блокер рисования beta.3.
|
||||
guard: 'npx tsc -p tsconfig.test.json && node scripts/fix-test-build.mjs '
|
||||
+ '&& node --test --test-name-pattern="migrates unhosted instead of blocking" '
|
||||
+ 'test/wall-segment-model.test.mjs '
|
||||
+ '&& node demo/smoke_zero_wall_migration_unblocked.mjs',
|
||||
+ 'test/wall-segment-model.test.mjs',
|
||||
because: 'один конфликтный проём снова заблокировал бы структурные записи во всех пространствах (#316)',
|
||||
patches: [{
|
||||
file: 'src/wall-segment-model.ts',
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
anchorSpan, anchorRegion, parseDiffRanges, ANCHOR_RADIUS_LINES,
|
||||
witnessFingerprint, readLedger, recordCaught, splitByLedger, LEDGER_SCHEMA,
|
||||
} from '../scripts/mutation-gate.mjs';
|
||||
import { guardPhases } from '../scripts/mutation-guard-outcome.mjs';
|
||||
|
||||
const repoRoot = fileURLToPath(new URL('..', import.meta.url));
|
||||
|
||||
@@ -64,6 +65,24 @@ test('every mutant explains itself', () => {
|
||||
assert.ok(MUTANTS.length >= 6, 'стартовый набор — шесть мутантов по дырам из #85');
|
||||
});
|
||||
|
||||
test('#550: assertion witnesses have no behavioural oracle hidden in setup', () => {
|
||||
const behavioural = /(?:node --test|(?:^|\s)node demo\/(?:smoke|benchmark)_|(?:^|\s)(?:python3? -m )?pytest\b)/;
|
||||
for (const mutant of MUTANTS.filter(({ oracle }) => (oracle || 'assertion') === 'assertion')) {
|
||||
const phases = guardPhases(mutant.guard, 'assertion');
|
||||
for (const command of phases.setup) {
|
||||
assert.doesNotMatch(command, behavioural,
|
||||
`${mutant.id}: behavioural command is setup, not the declared oracle`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('#550: preflight saved-config mutant targets the editor host and still compiles', () => {
|
||||
const mutant = MUTANTS.find(({ id }) => id === 'preflight-fingerprint-from-saved-config');
|
||||
assert.ok(mutant);
|
||||
assert.equal(mutant.patches[0].file, 'src/houseplan-editor-runtime.ts');
|
||||
assert.match(mutant.patches[0].replace, /this\.host\._serverCfg/);
|
||||
});
|
||||
|
||||
test('#486 panel registration, cleanup and read-only protections have mutation witnesses', () => {
|
||||
const ids = new Set(MUTANTS.map(({ id }) => id));
|
||||
for (const id of [
|
||||
|
||||
Reference in New Issue
Block a user