Files
ComfyAssets-ComfyUI_PromptM…/tests/js/workspace-system-maintenance.test.js
T
Vito Sansevero 14b575b559 feat(system): run reviewed maintenance natively
System › Maintenance is native: the concept's notice and its Database and
Images & cache surfaces replace the hand-off.

- Integrity & relationship checks runs the health check; the report's
  Review missing links reviews a fresh Missing-file links plan.
- Review plan shows the server's read-only plan (D100): scope, protected,
  backup policy and media lines, the listed records, how long the review
  holds and the backup box; Apply reviewed operation sends only its token
  and opens the job in Activity. busy names the running job, plan-used links
  the job that applied it, an expired or replaced review is reviewed again,
  and a lost answer says Outcome not confirmed and points to Activity.
- Find duplicates starts the search; its view offers Review groups (D101):
  ten groups at a time, Keep radios per copy, each keeper sent as
  {group_id, root_index, path}; the dialog says History keeps the removed
  paths in its run records.
- Thumbnail cache opens the thumbnail dialog; Metadata cache is disabled
  with its reason while nothing is owned (D102, Q5).
- Activity descriptors for maintenance, duplicates and duplicate_cleanup.
- The legacy duplicates dialog's View Full names its source (?root=).
2026-10-24 17:25:23 -07:00

131 lines
9.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// System › Maintenance (#249 System S7 Task 19): the concept's rows (app.js 688–690), the D100 plan review built from
// the server's Plan, the D101 duplicates review's keep entries and copy, and the D102 metadata cache row.
const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const maintenance = import('../../web/js/workspace/system-maintenance.mjs');
const review = import('../../web/js/workspace/maintenance-review.mjs');
const jobView = import('../../web/js/workspace/activity-job-view.mjs');
const CONCEPT = fs.readFileSync(path.join(__dirname, '../../docs/concepts/workspace/app.js'), 'utf8');
test('the section lists the concept rows in its order, labels and danger marks', async () => {
const { MAINTENANCE_SURFACES } = await maintenance;
const start = CONCEPT.indexOf('case "maintenance":');
const body = CONCEPT.slice(start, CONCEPT.indexOf('break;', start));
const concept = [...body.matchAll(/operation\("([^"]+)", "([^"]+)", "[^"]+", "([^"]+)"(?:, '[^']*'|, "")?(, true)?\)/g)]
.map(([, title, description, label, danger]) => [title, description, label, Boolean(danger)]);
const surfaces = [...body.matchAll(/<section class="surface"><h2>([^<]+)<\/h2>/g)].map(match => match[1]);
assert.deepEqual(MAINTENANCE_SURFACES.map(([heading]) => heading), surfaces.map(text => text.replace('&amp;', '&')));
const production = MAINTENANCE_SURFACES.flatMap(([, rows]) => rows.map(([title, description, label, , danger = false]) =>
[title, description, label, danger]));
assert.deepEqual(production, concept.map(([title, ...rest]) => [title.replace('&amp;', '&'), ...rest]));
assert.equal(production.length, 8);
});
const PLAN = Object.freeze({
success: true, token: 't'.repeat(43), operation: 'duplicate_prompts', expires_at: '2026-10-24T12:15:00+00:00',
scope: { count: 3, label: '3 duplicate prompts in 2 groups' },
protected: { count: 1, reasons: ['1 group belongs to a saved pair'] },
backup: { policy: 'on', destination_label: 'prompts.db.backup_<date and time>', expected_bytes: 4096 },
media: { deletes_files: false, label: 'No files are deleted' },
disk: { free_bytes: 2 ** 30, required_bytes: 2 ** 24, sufficient: true },
details: [{ label: 'a cat', detail: '2 copies merged into the oldest' }, { label: 'a dog', detail: '' }],
});
test("plan dialog shows the server's scope, protected, backup policy and media lines (D100)", async () => {
const { planGrid, planNote, detailLines, backupText, protectedText, OPERATION_TITLES } = await review;
assert.deepEqual(planGrid(PLAN), [
['Scope', '3 duplicate prompts in 2 groups'],
['Protected', '1 group belongs to a saved pair'],
['Backup', 'Create and verify before changes (prompts.db.backup_<date and time>)'],
['Media on disk', 'No files are deleted'],
]);
assert.equal(backupText({ policy: 'off', destination_label: 'x' }), 'No safety backup (the policy is off)');
assert.equal(backupText({ policy: 'required', destination_label: 'prompts.db.pre-workflow-storage_<date and time>' }),
'Always created and verified first (prompts.db.pre-workflow-storage_<date and time>)');
assert.equal(backupText(undefined), 'Create and verify before changes');
assert.equal(protectedText({ count: 0, reasons: [] }), 'Nothing is protected');
assert.equal(protectedText({ count: 2, reasons: [] }), '2 records kept');
assert.equal(protectedText({ count: 3, reasons: ['1 saved pair', '2 titled parents'] }), '1 saved pair · 2 titled parents');
assert.deepEqual(planGrid({ scope: { count: 1 } })[0], ['Scope', '1 record']);
assert.deepEqual(planGrid({})[3], ['Media on disk', 'No files are removed']);
const now = Date.parse('2026-10-24T12:00:00+00:00');
assert.equal(planNote(PLAN, { now }), 'Nothing changes until you apply. This review holds for 15 minutes.');
assert.equal(planNote({ ...PLAN, expires_at: 'soon' }, { now }), 'Nothing changes until you apply.');
assert.equal(planNote({ ...PLAN, expires_at: '2026-10-24T12:00:20+00:00', disk: { free_bytes: 2 ** 20, required_bytes: 3 * 2 ** 20,
sufficient: false } }, { now }), 'Nothing changes until you apply. This review holds for 1 minute. ' +
'Not enough free disk space: about 3.0 MB is needed and 1.0 MB is free.');
assert.deepEqual(detailLines(PLAN), ['a cat · 2 copies merged into the oldest', 'a dog']);
assert.deepEqual(detailLines({ details: [null, { label: 7 }] }), []);
assert.equal(OPERATION_TITLES.missing_links, 'Missing-file links');
});
test('a refused apply names its reason; a lost answer is not confirmed', async () => {
const { applyRefusal, reviewRefusal, NOT_CONFIRMED, APPLY_FAILED, REVIEW_FAILED } = await review;
const error = (status, code, message, extra = {}) => Object.assign(new Error(message), { status, code, ...extra });
assert.deepEqual(applyRefusal(new TypeError('Failed to fetch')), { toast: NOT_CONFIRMED, lost: true });
assert.deepEqual(NOT_CONFIRMED, ['Outcome not confirmed. ', { text: 'Check Activity', href: '#/activity' },
' before applying it again.']);
const busy = applyRefusal(error(409, 'busy', 'busy', { running: { id: 'a'.repeat(32), title: 'Scan for metadata' } }));
assert.deepEqual(busy.toast, ['Another job is running: ', { text: 'Scan for metadata', href: `#/activity/${'a'.repeat(32)}` }, '.']);
assert.deepEqual(applyRefusal(error(409, 'plan-used', 'used', { job: { id: 'b'.repeat(32), title: 'Optimize database' } })).toast,
['This plan was already applied: ', { text: 'Optimize database', href: `#/activity/${'b'.repeat(32)}` }, '.']);
assert.deepEqual(applyRefusal(error(409, 'plan-used', 'used', { job: { id: 'b'.repeat(32), title: null } })).toast[1].text, 'its job');
assert.deepEqual(applyRefusal(error(409, 'plan-used', 'used', { job: null })), { toast: 'This plan was already applied.' });
assert.deepEqual(applyRefusal(error(410, 'plan-expired', 'This review expired. Review the plan again.')),
{ toast: 'This review expired. Review the plan again.', review: true });
assert.deepEqual(applyRefusal(error(409, 'storage-changed', 'Replaced.')), { toast: 'Replaced.', review: true });
assert.deepEqual(applyRefusal(error(507, 'insufficient-space', 'Not enough free disk space.')), { toast: 'Not enough free disk space.' });
assert.deepEqual(applyRefusal(error(500, null, 'Traceback …')), { toast: APPLY_FAILED });
assert.equal(reviewRefusal(error(413, 'too-large', 'This plan is too large to review at once.')),
'This plan is too large to review at once.');
assert.equal(reviewRefusal(error(500, null, 'boom')), REVIEW_FAILED);
assert.equal(reviewRefusal(new TypeError('x')), REVIEW_FAILED);
});
test('keep entries name each copy by group, source index and path, never a bare path', async () => {
const { keepEntries } = await review;
// Two sources hold a file at the same relative path: only the source index tells them apart.
const output = { path: 'same.png', root: 'output', root_index: 0 };
const input = { path: 'same.png', root: 'input', root_index: 1 };
const chosen = new Map([['hash-a', input], ['hash-b', output]]);
assert.deepEqual(keepEntries(chosen), [
{ group_id: 'hash-a', root_index: 1, path: 'same.png' },
{ group_id: 'hash-b', root_index: 0, path: 'same.png' },
]);
});
test('the duplicates dialog says what removing a copy does, History included', async () => {
const { DUPLICATES_NOTE, pageText, groupText, copyLabel } = await review;
assert.match(DUPLICATES_NOTE, /History keeps the removed paths in its run records\./);
assert.match(DUPLICATES_NOTE, /deletes its file from disk/);
assert.equal(pageText(0, 10, 24), 'Groups 1–10 of 24');
assert.equal(pageText(20, 4, 24), 'Groups 21–24 of 24');
assert.equal(pageText(0, 0, 0), 'No duplicate files were found.');
assert.equal(groupText({ count: 3, size: 120 * 1024, files: [] }), '3 identical files · 120 KB each');
assert.equal(groupText({ size: 3 * 2 ** 20, files: [{}, {}] }), '2 identical files · 3.0 MB each');
assert.equal(groupText({ count: 2, size: 10, files: [] }), '2 identical files · 1 KB each');
assert.equal(copyLabel({ root: 'output', path: 'a/b.png' }), 'Keep output/a/b.png');
assert.equal(copyLabel({ root: '', path: 'b.png', missing: true }), 'Keep b.png (no longer on disk)');
});
test('Metadata cache follows Q5: disabled with its reason while nothing is owned (D102)', async () => {
const { metadataCacheDescription, METADATA_CACHE_DESCRIPTION, NOTHING_CACHED_REASON } = await maintenance;
assert.equal(METADATA_CACHE_DESCRIPTION, 'Clear only Prompt Manager metadata cached by this browser.');
assert.equal(metadataCacheDescription({ keys: [], caches: [] }), `${METADATA_CACHE_DESCRIPTION} ${NOTHING_CACHED_REASON}`);
assert.equal(metadataCacheDescription({ keys: ['k'], caches: [] }), METADATA_CACHE_DESCRIPTION);
});
test('only a finished duplicate search with groups offers Review groups', async () => {
const { hasDuplicateGroups } = await jobView;
const job = (state, groups, kind = 'duplicates') => ({ kind, state, counters: { groups_found: groups } });
assert.equal(hasDuplicateGroups(job('finished', 2)), true);
assert.equal(hasDuplicateGroups(job('finished', 0)), false);
assert.equal(hasDuplicateGroups(job('running', 2)), false);
assert.equal(hasDuplicateGroups(job('finished', 2, 'scan')), false);
assert.equal(hasDuplicateGroups({ kind: 'duplicates', state: 'finished', counters: {} }), false);
});