mirror of
https://github.com/Matysh/houseplan-card
synced 2026-07-31 16:38:31 +00:00
fix v1.43.0: external audit P0 — data loss, split geometry, auth, dialog zombies
L2 (silent data loss): debounce gains flush()/pending(); _reloadConfigOnly flushes a pending write and defers while one is in flight; conflict path forces; failed reload now toasts instead of an empty catch; teardown flushes. G1 (split corruption): same-edge cuts carve the niche properly instead of walking the outline twice; partition invariant (parts sum to the original) rejects anything else; +1 unit test covering 5 niche shapes and both legacy cut shapes. B1 (unauthenticated content): plans and marker files move to HouseplanContentView (/api/houseplan/content/..., requires_auth); only the card bundle stays static; contentUrl() rewrites legacy URLs on read (no storage migration); repairs.py accepts both prefixes; +1 unit test. L3 (dialog zombies): all four save catch-blocks guard against a closed dialog; the card no longer blanks when a save fails after Esc. smokes: smoke_save_race, smoke_dialog_zombie; docs (TESTING/CHANGELOG/ ARCHITECTURE incl. the optimistic-UI note) same-commit
This commit is contained in:
@@ -12,6 +12,7 @@ import {
|
||||
swipeTarget, clampScale,
|
||||
migratePdfUrls,
|
||||
roomFillModeOf,
|
||||
contentUrl,
|
||||
segmentCm, formatLength, roomEdges, roomPoly, pointOnBoundary, pointStrictlyInside, roomsOverlap,
|
||||
mergeRooms, splitRoom, polygonArea, closestPointOnBoundary, isActiveState, snapToWall, openingAmount, fillColorsOf, lerpColor, roomFillStyle, stateIcon, lightColorOf, isAlarmState, parseRoomRef, diffNewDevices,
|
||||
} from '../test-build/logic.js';
|
||||
@@ -809,3 +810,37 @@ test('roomFillModeOf: tier-3 override beats the space, junk inherits', () => {
|
||||
assert.equal(roomFillModeOf('temp', null), 'temp');
|
||||
assert.equal(roomFillModeOf('temp', { settings: { fill_mode: 'glow' } }), 'temp'); // glow нельзя выбрать per-room
|
||||
});
|
||||
|
||||
test('splitRoomPath: both ends on the SAME edge carve a niche (audit G1)', () => {
|
||||
const sq = [[0, 0], [10, 0], [10, 10], [0, 10]];
|
||||
const A = (p) => Math.round(polygonArea(p) * 1000) / 1000;
|
||||
// ИНВАРИАНТ разреза: части в сумме дают исходную площадь
|
||||
const invariant = (pts, label) => {
|
||||
const r = splitRoomPath(sq, pts);
|
||||
assert.ok(r, label + ': разрез должен приниматься');
|
||||
assert.ok(Math.abs(A(r[0]) + A(r[1]) - A(sq)) < 1e-6,
|
||||
label + ': сумма частей ' + (A(r[0]) + A(r[1])) + ' != ' + A(sq));
|
||||
return r;
|
||||
};
|
||||
const niche = invariant([[2, 0], [2, 3], [8, 3], [8, 0]], 'ниша снизу');
|
||||
assert.deepEqual([A(niche[0]), A(niche[1])].sort((x, y) => x - y), [18, 82]);
|
||||
invariant([[2, 10], [2, 7], [8, 7], [8, 10]], 'ниша сверху');
|
||||
invariant([[0, 2], [3, 2], [3, 8], [0, 8]], 'ниша слева');
|
||||
invariant([[8, 0], [8, 3], [2, 3], [2, 0]], 'обратный порядок точек');
|
||||
invariant([[1, 0], [3, 4], [5, 1], [7, 4], [9, 0]], 'зигзаг');
|
||||
// вырожденная ниша нулевой площади — отказ
|
||||
assert.equal(splitRoomPath(sq, [[2, 0], [2, 1e-9], [8, 0]]), null);
|
||||
// старые формы разрезов сохраняют инвариант
|
||||
invariant([[5, 0], [5, 10]], 'прямая хорда');
|
||||
invariant([[4, 0], [4, 6], [10, 6]], 'Г-образный');
|
||||
});
|
||||
|
||||
test('contentUrl: legacy static paths become authenticated ones (audit B1)', () => {
|
||||
assert.equal(contentUrl('/houseplan_files/plans/f1.svg?v=1'), '/api/houseplan/content/plans/_/f1.svg?v=1');
|
||||
assert.equal(contentUrl('/houseplan_files/files/dev1/a.pdf?v=2'), '/api/houseplan/content/files/dev1/a.pdf?v=2');
|
||||
// новые и внешние адреса не трогаем
|
||||
assert.equal(contentUrl('/api/houseplan/content/files/x/y.pdf'), '/api/houseplan/content/files/x/y.pdf');
|
||||
assert.equal(contentUrl('https://example.com/a.pdf'), 'https://example.com/a.pdf');
|
||||
assert.equal(contentUrl(''), '');
|
||||
assert.equal(contentUrl(null), '');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user