Some checks failed
Forgejo Android APK / Root app tests (push) Successful in 52s
Forgejo Docker Build / Root app tests (push) Successful in 47s
Forgejo Android APK / Build signed APK (push) Successful in 1m56s
Forgejo Docker Build / Build Docker image (push) Successful in 11s
Forgejo Docker Build / Deploy to the host (push) Failing after 0s
A .pptx landing in someone's own storage is worth having; a markdown blob is not, and it is not what they would have downloaded. So this renders through exactly the path the download uses — stored deck, its figures, the chosen theme — and PUTs the bytes. The file never travels through the browser. Offered only when a Nextcloud is connected: an action that always fails is worse than one that is not offered. An article offers Word, a deck PowerPoint, and asking for slides from an article is refused with the reason. Putting a file in Nextcloud now lives in src/utils/nextcloudFiles.js. Two callers want it and neither should grow its own copy of the WebDAV dance — make the dated folder a segment at a time, PUT, migrate a legacy plaintext token — because it reaches into storage that is not ours and a second slightly different copy is how the two drift. It also replaces a route importing another route. Also: a model that leaves the roster now leaves every list that names it. clinical_assistant.allowed_models and the image roster are advisory copies of the roster, and a stale id there was invisible until someone asked a clinical question and the request failed at the gateway. Removing or disabling a model prunes it; clearing the roster clears them. Re-enabling deliberately does not re-allow it — that is a separate decision. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU
46 lines
2.4 KiB
JavaScript
46 lines
2.4 KiB
JavaScript
// A model that leaves the roster has to leave every list that names it. The
|
|
// allowed lists are advisory copies of the roster; the roster is the fact. A
|
|
// stale id there is invisible until someone asks a clinical question and the
|
|
// request fails at the gateway.
|
|
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
|
|
const route = fs.readFileSync(path.join(__dirname, '..', 'src/routes/adminConfig.js'), 'utf8');
|
|
|
|
test('one helper owns forgetting a model, and covers every list that names one', () => {
|
|
assert.match(route, /async function forgetModelEverywhere\(ids\)/);
|
|
for (const key of ['clinical_assistant.allowed_models',
|
|
'clinical_assistant.allowed_image_models',
|
|
'clinical_assistant.image_model_roster']) {
|
|
assert.ok(route.includes("'" + key + "'"), key + ' is not pruned');
|
|
}
|
|
// And a default pointing at a model that has gone.
|
|
assert.match(route, /gone\.indexOf\(current\) !== -1\) await db\.setSetting\('models\.default', ''\)/);
|
|
});
|
|
|
|
test('removing a custom model prunes it', () => {
|
|
const handler = route.slice(route.indexOf("router.delete('/config/models/custom"));
|
|
assert.match(handler.slice(0, 900), /await forgetModelEverywhere\(modelId\)/);
|
|
});
|
|
|
|
test('disabling a model prunes it too — it stops being selectable either way', () => {
|
|
const handler = route.slice(route.indexOf("router.put('/config/models/toggle"));
|
|
assert.match(handler.slice(0, 1600), /if \(!enabled\) await forgetModelEverywhere\(modelId\)/);
|
|
// Re-enabling must NOT silently re-allow it: that is a separate decision.
|
|
assert.doesNotMatch(handler.slice(0, 1600), /if \(enabled\) await forgetModelEverywhere/);
|
|
});
|
|
|
|
test('clearing every model clears every allowed list', () => {
|
|
const handler = route.slice(route.indexOf("router.post('/config/models/clear-all"));
|
|
assert.match(handler.slice(0, 900), /await db\.setSetting\(key, ''\)/);
|
|
assert.match(handler.slice(0, 900), /Nothing is on the roster any more/);
|
|
});
|
|
|
|
test('a list is only written when it actually changed', () => {
|
|
// Avoids a settings write, an updated_at bump and a cache refresh per removal
|
|
// when nothing referenced the model.
|
|
const fn = route.slice(route.indexOf('async function forgetModelEverywhere'));
|
|
assert.match(fn.slice(0, 1200), /if \(kept\.join\(','\) !== current\)/);
|
|
});
|