[grade=A urn:ump:qszwspay7vhx7wcpqekkkidni3ehhgidvze5qk2y22fvg2wqz3ja] fix(DC-170): reliability batch — hangs, atomicity, fail-closed, SW (audit batch 6)
8 reliability fixes across 8 files (AUDIT-2026-10-27.md findings
9/11/12/13/14/16/20 + calendar credential item):
- dns.js check-update/update HANGS: bare returns + empty else/catch
branches left requests hanging until client timeout; every path now
answers (502 with message); upstream JSON.parse wrapped (non-JSON ->
502)
- calendar.js ICS fetch timeout actually applies: fetchT ignores
opts.timeout (undici) — was running at 5s default, not the intended
15s; now timeoutMs third arg
- calendar.js PUT /sources: updating pass no longer nulls the stored
token (and vice versa) — existing credential record loaded, only the
provided field overwritten
- state-manager.js ATOMIC WRITES: _atomicWrite = tmp file + fs.rename
(POSIX-atomic); write()/update() no longer truncate-then-write the
live file (crash mid-write corrupted services.json/config.json with
no recovery path). Test mock extended to the tmp+rename contract
- dashboard-ws.js auth fallback FAIL-CLOSED: missing authVerifier used
to accept any non-empty session cookie (DC-061 footgun on unwired
boot paths); now rejects + loud CRITICAL log
- middleware.js tailscale gate: new tailscale.failClosed config (default
off = unchanged); when set, outages/errors deny (503) instead of
silently disabling the gate; fail-open path now logs the exact key
- sw.js: offline fallback actually works ((await match) || (await
match) — old Promise||Promise was always truthy); only 2xx cached
(5xx pages were stored and served as the app shell); install is
per-URL tolerant (one bad asset kept the stale SW alive forever)
- config/settings.js GET /config (public): corrupt config.json degrades
to {setupComplete:false, corrupt:true} instead of 500 on every load
SCOPE CORRECTION: audit finding #10 (7 reversed-arg errorResponse
sites) was a FALSE POSITIVE — those files alias message-first error();
see lesson #14. Full suite 173/173 suites, 3740/3740 tests.
This commit is contained in:
8 files changed
+115
-31
No files matched your search
@@ -6,6 +6,9 @@ jest.mock('fs', () => ({
|
||||
promises: {
|
||||
readFile: jest.fn().mockResolvedValue('[]'),
|
||||
writeFile: jest.fn().mockResolvedValue(),
|
||||
// DC-170 atomic writes: _atomicWrite serializes to a tmp file then renames
|
||||
rename: jest.fn().mockResolvedValue(),
|
||||
unlink: jest.fn().mockResolvedValue(),
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -74,18 +77,23 @@ describe('StateManager', () => {
|
||||
});
|
||||
|
||||
describe('write', () => {
|
||||
it('acquires lock, writes JSON, releases lock', async () => {
|
||||
it('acquires lock, writes JSON atomically (tmp+rename), releases lock', async () => {
|
||||
const releaseFn = jest.fn().mockResolvedValue();
|
||||
lockfile.lock.mockResolvedValue(releaseFn);
|
||||
|
||||
await sm.write([{ id: 'new' }]);
|
||||
|
||||
expect(lockfile.lock).toHaveBeenCalledWith(TEST_PATH, expect.any(Object));
|
||||
// DC-170 atomic contract: tmp file write + rename over the live file
|
||||
expect(fs.promises.writeFile).toHaveBeenCalledWith(
|
||||
TEST_PATH,
|
||||
expect.stringContaining(`${TEST_PATH}.tmp-`),
|
||||
JSON.stringify([{ id: 'new' }], null, 2),
|
||||
'utf8'
|
||||
);
|
||||
expect(fs.promises.rename).toHaveBeenCalledWith(
|
||||
expect.stringContaining(`${TEST_PATH}.tmp-`),
|
||||
TEST_PATH
|
||||
);
|
||||
expect(releaseFn).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user