Files
adachi0130-Adachi-Inpaint-W…/tests/test_draft_guard.mjs
T

119 lines
5.1 KiB
JavaScript

import assert from "node:assert/strict";
import { canStoreDraft, prepareInlineDraft, syncInlineWidgets, inlineWidgetNames } from "../web/draft_guard.js";
class Storage {
constructor(limit = 10000) {
this.limit = limit;
this.data = new Map();
this.removed = [];
}
getItem(k) {
return this.data.get(k) ?? null;
}
setItem(k, v) {
const used = [...this.data].reduce(
(n, [key, value]) => n + (key === k ? 0 : value.length),
0,
);
if (used + v.length > this.limit)
throw Object.assign(new Error("full"), { name: "QuotaExceededError" });
this.data.set(k, v);
}
removeItem(k) {
this.removed.push(k);
this.data.delete(k);
}
}
const storage = new Storage();
storage.setItem("unrelated", "keep");
assert.equal(canStoreDraft({ nodes: [] }, { storage }), true);
assert.equal(canStoreDraft({ data: "x".repeat(20000) }, { storage }), false);
assert.equal(storage.getItem("unrelated"), "keep");
assert.ok(storage.removed.every((key) => key.startsWith("Adachi.DraftProbe:")));
assert.equal(storage.data.size, 1);
// Replacing a draft must reuse its existing space, rather than testing two full copies.
const path = "workflows/Test.json";
let hash = 2166136261;
for (const c of path) hash = Math.imul(hash ^ c.charCodeAt(0), 16777619);
const payloadKey = `Comfy.Workflow.Draft.v2:personal:${(hash >>> 0).toString(16).padStart(8, "0")}`;
const oldGraph = { image: "x".repeat(6000) },
oldRaw = JSON.stringify({
data: JSON.stringify(oldGraph),
updatedAt: Date.now(),
});
storage.setItem(
"Comfy.Workflow.DraftIndex.v2:personal",
JSON.stringify({ entries: { one: { path } } }),
);
storage.setItem(payloadKey, oldRaw);
assert.equal(
canStoreDraft({ ...oldGraph, mode: "crop" }, { path, storage }),
true,
);
assert.equal(storage.getItem(payloadKey), oldRaw);
assert.equal(
canStoreDraft({ image: "x".repeat(12000) }, { path, storage }),
false,
);
assert.equal(storage.getItem(payloadKey), oldRaw);
assert.equal(
canStoreDraft(
{},
{
storage: {
getItem() {
throw Error("blocked");
},
setItem() {
throw Object.assign(Error("blocked"), { name: "SecurityError" });
},
removeItem() {},
},
},
),
null,
);
console.log(
"Draft guard: quota detection, replacement credit, probe cleanup and preservation of existing storage passed",
);
const large = "data:image/png;base64," + "a".repeat(300000);
const node = () => ({ type: "AdachiInlineMask", widgets_values: ["old.png", "mask", large, "crop", "region", "blend", "layer.png"], widgets_values_named: { image: "stale.png", mask_data: "stale-mask", source_data: large, edit_mode: "mask" } });
const first = node(), nested = node();
const graph = { nodes: [first, {type:"Other", widgets_values:[large]}], definitions: {subgraphs:[{nodes:[nested]}]} };
const original = structuredClone(graph);
let uploads = 0;
assert.equal(await prepareInlineDraft(graph, { fits: g => g.nodes[0].widgets_values[2].length < 100 && g.definitions.subgraphs[0].nodes[0].widgets_values[2].length < 100, upload: async bytes => { assert.equal(bytes,large); return `source-${++uploads}.png`; } }),2);
assert.equal(uploads,2);
for (const n of [first,nested]) {
assert.equal(n.widgets_values[2],"");
assert.equal(n.widgets_values_named.source_data,"");
assert.equal(n.widgets_values_named.image,n.widgets_values[0]);
assert.equal(n.widgets_values_named.mask_data,"mask");
assert.deepEqual(n.widgets_values.slice(3),["crop","region","blend","layer.png"]);
}
assert.deepEqual(graph.nodes[1],original.nodes[1]);
for (const fits of [() => true, () => null]) {
const g = {nodes:[node()]};
assert.equal(await prepareInlineDraft(g,{fits,upload:()=>{throw Error("unexpected upload")}}),0);
assert.equal(g.nodes[0].widgets_values[2],large);
}
const failure = {nodes:[node()]};
await assert.rejects(prepareInlineDraft(failure,{fits:()=>false,upload:async()=>{throw Error("upload failed")}}),/upload failed/);
assert.equal(failure.nodes[0].widgets_values[2],large);
assert.equal(failure.nodes[0].widgets_values[0],"old.png");
const namedOnly = {nodes:[{type:"AdachiInlineMask", widgets_values_named:{source_data:large,mask_data:"keep"}}]};
await prepareInlineDraft(namedOnly,{fits:()=>false,upload:async()=>"named.png"});
assert.equal(namedOnly.nodes[0].widgets_values_named.source_data,"");
assert.equal(namedOnly.nodes[0].widgets_values_named.mask_data,"keep");
const widgets=inlineWidgetNames.map((name,index)=>({name,value:`live-${index}`}));
widgets.splice(1,0,{name:"unrelated",value:"untouched"});
const snapshot={widgets_values:Array(8).fill("old"),widgets_values_named:{source_data:large,unrelated:"keep"}};
syncInlineWidgets(snapshot,widgets);
assert.equal(snapshot.widgets_values[3],"live-2");
assert.equal(snapshot.widgets_values[1],"old");
assert.equal(snapshot.widgets_values_named.source_data,"live-2");
assert.equal(snapshot.widgets_values_named.unrelated,"keep");
const arrayOnly={widgets_values:[]}; syncInlineWidgets(arrayOnly,widgets);
assert.equal(arrayOnly.widgets_values_named,undefined);
console.log("Draft preflight: root/subgraph sources, dual snapshots, unchanged masks/layers, fitting sources and failed uploads passed");