mirror of
https://github.com/Matysh/houseplan-card
synced 2026-07-31 08:28:31 +00:00
v1.45.0: external review of v1.44.8 — R2-1, R2-2, R2-3
R2-1 (high): plan replacement committed filesystem state before the config CAS.
The upload wrote the final name and unlinked the other extension, so a rejected
config write left the live plan already replaced — or the stored config
pointing at a deleted file. Uploads now go to <space>.<token>.<ext> and delete
nothing; houseplan/plan/cleanup runs only after the config write is accepted.
The '.' separator is load-bearing: a space id cannot contain one, so cleaning
'f1' can never reach the files of 'f1-attic'.
R2-2: the backend signs at most MAX_SIGN_PATHS (200) per request and ignores
the rest silently, while the card sent its whole cache in one call and trusted
any cached entry forever — past 200 attachments the later ones stopped being
refreshed and expired for good. Requests are chunked to the shared constant,
entries carry their issue time (aging urls keep rendering while a replacement
is fetched, expired ones are dropped), and the cache is pruned to urls the live
config still references.
R2-3: areaClimate() rescanned the whole registry per room and per measurement.
areaClimateMap() classifies once and returns Map<area,{temp,hum}>, memoized on
hass identity so fresh states are always observed. Smoke measurement: 133
registry scans per update with 44 rooms before, 2 after, flat in room count.
Also: smoke_ux_fixes wrote its screenshot to a hard-coded /tmp path and could
not run on Windows.
Tests: smoke_plan_upload_reject, smoke_sign_cap, smoke_climate_once (all fail
on v1.44.8), three backend tests for versioned plan names and cleanup scoping,
unit tests for chunk/referencedContentUrls and areaClimateMap.
Docs: CHANGELOG.md + CHANGELOG.ru.md + ARCHITECTURE.md + TESTING.md + STATUS.md.
This commit is contained in:
+28
-1
@@ -12,7 +12,7 @@ import {
|
||||
swipeTarget, clampScale,
|
||||
migratePdfUrls,
|
||||
roomFillModeOf,
|
||||
contentUrl,
|
||||
contentUrl, chunk, referencedContentUrls, MAX_SIGN_PATHS,
|
||||
interiorPoint,
|
||||
segmentCm, formatLength, roomEdges, roomPoly, pointOnBoundary, pointStrictlyInside, roomsOverlap,
|
||||
mergeRooms, splitRoom, polygonArea, closestPointOnBoundary, isActiveState, snapToWall, openingAmount, fillColorsOf, lerpColor, roomFillStyle, stateIcon, lightColorOf, isAlarmState, parseRoomRef, diffNewDevices,
|
||||
@@ -881,3 +881,30 @@ test('segKey: one wall, one key at any precision (audit G3)', () => {
|
||||
// разные стены — разные ключи
|
||||
assert.notEqual(segKey([0, 0], [1, 1]), segKey([0, 0], [2, 2]));
|
||||
});
|
||||
|
||||
test('chunk / referencedContentUrls: signing batches and cache pruning (review R2-2)', () => {
|
||||
assert.deepEqual(chunk([1, 2, 3, 4, 5], 2), [[1, 2], [3, 4], [5]]);
|
||||
assert.deepEqual(chunk([], 200), []);
|
||||
assert.equal(chunk(new Array(201).fill(0), MAX_SIGN_PATHS).length, 2);
|
||||
assert.deepEqual(chunk([1, 2, 3], 0), [[1], [2], [3]]); // never an infinite loop
|
||||
|
||||
const cfg = {
|
||||
spaces: [
|
||||
{ id: 'f1', plan_url: '/houseplan_files/plans/f1.svg' }, // legacy, rewritten on read
|
||||
{ id: 'f2', plan_url: '/api/houseplan/content/plans/_/f2.abc.png' },
|
||||
{ id: 'f3', plan_url: null },
|
||||
{ id: 'f4', plan_url: '/local/not-ours.png' }, // not our endpoint
|
||||
],
|
||||
markers: [
|
||||
{ id: 'm1', pdfs: [{ url: '/houseplan_files/files/m1/manual.pdf' }, { url: '' }] },
|
||||
{ id: 'm2' },
|
||||
],
|
||||
};
|
||||
assert.deepEqual([...referencedContentUrls(cfg)].sort(), [
|
||||
'/api/houseplan/content/files/m1/manual.pdf',
|
||||
'/api/houseplan/content/plans/_/f1.svg',
|
||||
'/api/houseplan/content/plans/_/f2.abc.png',
|
||||
]);
|
||||
assert.equal(referencedContentUrls(null).size, 0);
|
||||
assert.equal(referencedContentUrls({}).size, 0);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user