Oregami
Repositories/oxedyne/daimond

oxedyne/daimond/dev/verify_settings.mjs

4.3 KiB, 1 run

created by r2519314175:673, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1// A key the provider rejects must not be reported "Saved."/connected: the BYOK
2// form fetches /models with the key it was given, and a 401 there has to reach
3// the user as a refusal rather than as a saved setting.
4//
5// The file used to say it also proved "a no-account stranger must have a way to
6// create one from the credits pitch". It never did -- nothing below opens the
7// pitch -- and the sentence is gone rather than left standing as a claim the
8// run does not make.
9//
10// PROVED AGAINST A KEY THE MOCK ACCEPTS FIRST. `--break accept` sends a key the
11// mock does NOT 401, so the note reads as a save and the check below must go
12// red. A check that has only ever seen the failing input cannot tell the two
13// apart.
14//
15// node dev/verify_settings.mjs --break accept # expected to FAIL
16// node dev/verify_settings.mjs # and then, clean
17import { open, errors, MOCK } from './harness.mjs';
18
19const ok = [], bad = [];
20const check = (name, pass, detail) => {
21 (pass ? ok : bad).push(name + (detail ? ' — ' + detail : ''));
22 console.log((pass ? ' ok ' : ' FAIL ') + name + (detail ? ' — ' + detail : ''));
23};
24
25const BREAK = (() => {
26 const i = process.argv.indexOf('--break');
27 return i > 0 ? String(process.argv[i + 1] || '') : '';
28})();
29// "reject" is the mock's sentinel: /v1/models 401s any key containing it (see
30// dev/mockllm.mjs). Anything else is accepted.
31const KEY = BREAK === 'accept' ? 'accept-me' : 'reject';
32
33const s = await open({ name: 'settings', connect: false });
34
35// Open settings and save a key the model-fetch will reject.
36const result = await s.page.evaluate(async (arg) => {
37 const cog = document.getElementById('settings-btn');
38 if (cog) cog.click();
39 await new Promise(r => setTimeout(r, 300));
40 const prov = document.getElementById('cfg-provider');
41 if (!prov) return { missing: '#cfg-provider' };
42 prov.value = 'custom'; prov.dispatchEvent(new Event('change', { bubbles: true }));
43 await new Promise(r => setTimeout(r, 200));
44 const url = document.getElementById('cfg-base-url');
45 const key = document.getElementById('cfg-api-key');
46 const save = document.getElementById('byok-save');
47 const note = document.getElementById('byok-note');
48 if (!url || !key || !save || !note) {
49 return { missing: [['#cfg-base-url', url], ['#cfg-api-key', key], ['#byok-save', save],
50 ['#byok-note', note]].filter(p => !p[1]).map(p => p[0]).join(', ') };
51 }
52 url.value = arg.mock;
53 url.dispatchEvent(new Event('input', { bubbles: true }));
54 url.dispatchEvent(new Event('change', { bubbles: true }));
55 key.value = arg.key;
56 key.dispatchEvent(new Event('input', { bubbles: true }));
57 key.dispatchEvent(new Event('change', { bubbles: true }));
58 await new Promise(r => setTimeout(r, 1200)); // let the model fetch run and fail
59 // Type a model by hand so validation passes and the key-rejection check is reached.
60 const cus = document.getElementById('cfg-model-custom');
61 if (cus) { cus.style.display = ''; cus.value = 'mock/fast'; cus.dispatchEvent(new Event('input', { bubbles: true })); }
62 save.click();
63 await new Promise(r => setTimeout(r, 400));
64 return { note: note.textContent };
65}, { mock: MOCK, key: KEY });
66
67// A form that has moved is not a passing test: the checks below all read the
68// note, and a note that is not there would read as "no rejection" forever.
69check('the BYOK form is on the screen the settings cog opens',
70 !result.missing, result.missing ? `missing ${result.missing}` : 'provider, base URL, key, save and note all present');
71
72const note = String(result.note || '');
73check('A KEY THE PROVIDER REJECTS IS REPORTED AS REJECTED',
74 /rejected/i.test(note), `the note reads ${JSON.stringify(note)}`);
75// The other half of the same claim: it must not ALSO say the thing was saved or
76// connected, which is what the user acts on.
77check('and is not reported saved or connected in the same breath',
78 !/\bsaved\b|\bconnected\b/i.test(note), `the note reads ${JSON.stringify(note)}`);
79
80// 502 is this world with no gateway behind it; 401 is the rejection this file
81// exists to drive.
82const errs = errors(s).filter(e => !/502|Bad Gateway|401|Unauthorized/.test(e));
83check('nothing threw', errs.length === 0, errs.slice(0, 2).join(' | '));
84
85await s.close();
86console.log(`\n${ok.length} passed, ${bad.length} failed`);
87if (bad.length) { bad.forEach(b => console.log(' FAILED: ' + b)); process.exit(1); }