Files
swarm-assistent/test/patch.test.js
T
Leonid PershinandCursor b0f2736f65 Ship Assistent 0.15.11: collaborative params, richer UI, and strict image critique.
Adds session_exact pinning, sampler/scheduler chips, expanded param tags with non-default highlighting, QLoRA HF presets, LoRA strength editing, SQLite bootstrap for training memory, last-job UI, and harsher critique_image QC so result review leads with defects instead of praise.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-23 19:59:45 +03:00

244 lines
8.7 KiB
JavaScript

import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import {
extractPatch,
isPatchObject,
normalizePatch,
setPatchKeys,
visibleProse,
} from '../src/patch.js';
import {
mergeDelta,
emptySession,
patchWantsGenerate,
sessionFromLegacyParams,
toPersistParams,
resolveTurnIntent,
mergeExactParamsForGenerate,
resolveExactProfileDefaults,
} from '../src/session.js';
describe('patch.js', () => {
it('extractPatch finds generation patch in fence', () => {
const text = 'Here you go\n```json\n{"prompt":"A red fox in snow","generate":true}\n```';
const { prose, patch } = extractPatch(text);
assert.ok(patch);
assert.equal(patch.prompt, 'A red fox in snow');
assert.equal(patch.generate, true);
assert.ok(!prose.includes('```'));
});
it('normalizePatch maps legacy actions generate', () => {
const p = normalizePatch({ prompt: 'x', actions: ['generate'] });
assert.equal(p.generate, true);
});
it('extractPatch prefers last terminal fence over a later weak pack fence', () => {
const text = [
'ok',
'```json',
'{"prompt":"A slender redhead in a leather jacket, 85mm, street light","generate":true}',
'```',
'```json',
'{"pack":"ordinary"}',
'```',
].join('\n');
const { patch } = extractPatch(text);
assert.equal(patch.generate, true);
assert.match(patch.prompt, /redhead/);
});
it('extractPatch accepts generate true string and unfenced object', () => {
const fenced = extractPatch('ok\n```json\n{"prompt":"A slender redhead in a leather jacket, 85mm","generate":"true"}\n```');
assert.equal(fenced.patch.generate, true);
assert.match(fenced.patch.prompt, /redhead/);
const raw = extractPatch('done\n{"prompt":"A fiery redhead in leather, street light","generate":true}');
assert.equal(raw.patch.generate, true);
assert.equal(raw.prose, 'done');
});
it('visibleProse hides the JSON fence when chat text is empty', () => {
assert.equal(visibleProse('```json\n{"prompt":"fox","generate":true}\n```'), '');
assert.equal(visibleProse('Ок, держи\n```json\n{"prompt":"fox"}\n```'), 'Ок, держи');
assert.equal(visibleProse('просто чат без патча'), 'просто чат без патча');
});
it('scheduler-only patch is detected with full key list', () => {
setPatchKeys(['prompt', 'scheduler', 'generate', 'ask']);
assert.equal(isPatchObject({ scheduler: 'euler' }), true);
});
});
describe('session.js', () => {
it('mergeDelta is sparse and keeps other fields', () => {
let s = emptySession();
s.gen.prompt = 'old';
s.gen.steps = 8;
s = mergeDelta(s, { aspect: '16:9', generate: true });
assert.equal(s.gen.prompt, 'old');
assert.equal(s.gen.steps, 8);
assert.equal(s.gen.aspect, '16:9');
assert.equal(patchWantsGenerate({ generate: true }), true);
assert.equal(patchWantsGenerate({ actions: ['generate'] }), true);
});
it('legacy flat params upgrade to session shape', () => {
const s = sessionFromLegacyParams({
prompt: 'fox',
steps: 28,
loras: [{ name: 'a', weight: 0.8 }],
genResults: [{ id: 'var1', src: '/View/x.png' }],
persona: 'neutral',
});
assert.equal(s.gen.prompt, 'fox');
assert.equal(s.gen.steps, 28);
assert.equal(s.board.genResults.length, 1);
const blob = toPersistParams(s);
assert.ok(blob.gen);
assert.ok(blob.board);
});
it('resolveTurnIntent vetoes generate', () => {
const intent = resolveTurnIntent(
{ generate: true, prompt: 'x' },
'не генерируй',
{ vetoFn: (t) => /не\s+генерир/i.test(t) },
);
assert.equal(intent.generate, false);
assert.equal(intent.vetoed, true);
});
it('resolveTurnIntent honors сгенерируй when prompt is present', () => {
const intent = resolveTurnIntent(
{ prompt: 'A slender redhead in a leather jacket' },
'сгенерируй',
{ askGenerateFn: (t) => /сгенер/i.test(t) },
);
assert.equal(intent.generate, true);
assert.equal(patchWantsGenerate({ generate: 'true' }), true);
assert.equal(patchWantsGenerate({ generate: true }), true);
});
it('resolveTurnIntent ignores model generate:true on a knowledge question', () => {
const intent = resolveTurnIntent(
{ prompt: 'civitai example prompt', generate: true },
'а ты знаешь какие то промпты с civitai',
{ askGenerateFn: (t) => /сгенер|нарисуй/i.test(t) },
);
assert.equal(intent.generate, false);
assert.equal(intent.modelAsked, true);
});
it('mergeExactParamsForGenerate fills turbo profile when LLM omits steps/cfg', () => {
const exact = {
generation: { profile: 'turbo', steps: 8, cfg: 1, sigma_shift: 1.15 },
profiles: {
turbo: { steps: 8, cfg: 1, sigma_shift: 1.15 },
raw: { steps: 28, cfg: 4.5, sigma_shift: 1.15 },
},
};
const { patch, clearSessionKeys, profile } = mergeExactParamsForGenerate(
{ prompt: 'a fox', generate: true },
{
exact,
profiles: exact.profiles,
profileName: 'turbo',
sessionExact: { steps: 20, cfg: 7 },
userParamIntent: false,
},
);
assert.equal(profile, 'turbo');
assert.equal(patch.steps, 8);
assert.equal(patch.cfg, 1);
assert.equal(patch.sigma_shift, 1.15);
assert.deepEqual(clearSessionKeys.sort(), ['cfg', 'steps']);
});
it('mergeExactParamsForGenerate keeps user-intent sessionExact and explicit patch', () => {
const exact = {
generation: { steps: 8, cfg: 1 },
profiles: { raw: { steps: 28, cfg: 4.5, sigma_shift: 1.15 } },
};
const kept = mergeExactParamsForGenerate(
{ generate: true, actions: ['generate'] },
{
exact,
profiles: exact.profiles,
profileName: 'raw',
sessionExact: { steps: 20 },
userParamIntent: true,
},
);
assert.equal(kept.patch.steps, 20);
assert.equal(kept.patch.cfg, 4.5);
assert.deepEqual(kept.clearSessionKeys, []);
const explicit = mergeExactParamsForGenerate(
{ generate: true, steps: 12, cfg: 2 },
{ exact, profiles: exact.profiles, profileName: 'turbo', userParamIntent: true },
);
assert.equal(explicit.patch.steps, 12);
assert.equal(explicit.patch.cfg, 2);
const forced = mergeExactParamsForGenerate(
{ generate: true, steps: 20, cfg: 7 },
{
exact: { generation: { steps: 8, cfg: 1 }, profiles: { turbo: { steps: 8, cfg: 1, sigma_shift: 1.15 } } },
profiles: { turbo: { steps: 8, cfg: 1, sigma_shift: 1.15 } },
profileName: 'turbo',
userParamIntent: false,
},
);
assert.equal(forced.patch.steps, 8);
assert.equal(forced.patch.cfg, 1);
});
it('mergeExactParamsForGenerate preserves pinned session_exact sampler/scheduler when LLM omits them', () => {
const exact = {
generation: { profile: 'turbo', steps: 8, cfg: 1, sampler: 'euler', scheduler: 'normal' },
profiles: { turbo: { steps: 8, cfg: 1, sigma_shift: 1.15 } },
};
const { patch, clearSessionKeys } = mergeExactParamsForGenerate(
{ prompt: 'a fox', generate: true },
{
exact,
profiles: exact.profiles,
profileName: 'turbo',
sessionExact: { sampler: 'heun', scheduler: 'simple', aspect: '16:9' },
userParamIntent: false,
},
);
assert.equal(patch.sampler, 'heun');
assert.equal(patch.scheduler, 'simple');
assert.equal(patch.aspect, '16:9');
assert.equal(patch.steps, 8);
assert.deepEqual(clearSessionKeys.sort(), []);
});
it('mergeExactParamsForGenerate does not overwrite explicit LLM sampler/scheduler', () => {
const { patch } = mergeExactParamsForGenerate(
{ generate: true, sampler: 'dpmpp_2m', scheduler: 'karras' },
{
exact: { generation: { steps: 8, cfg: 1 } },
profiles: { turbo: { steps: 8, cfg: 1 } },
profileName: 'turbo',
sessionExact: { sampler: 'heun', scheduler: 'simple' },
userParamIntent: false,
},
);
assert.equal(patch.sampler, 'dpmpp_2m');
assert.equal(patch.scheduler, 'karras');
});
it('resolveExactProfileDefaults picks raw over generation defaults', () => {
const d = resolveExactProfileDefaults({
exact: { generation: { steps: 8, cfg: 1, sigma_shift: 1.15 } },
profiles: { raw: { steps: 28, cfg: 4.5 } },
profileName: 'raw',
});
assert.equal(d.steps, 28);
assert.equal(d.cfg, 4.5);
assert.equal(d.sigma_shift, 1.15);
});
});