fix: 修正凭据名称避免 DSH 重启失败
This commit is contained in:
1 parent
108e957c64
commit
7e2ec28b80
4 files changed
+57
-6
No files matched your search
+42
-4
@@ -5,6 +5,12 @@ import { mkdtemp, readFile, rm } from 'node:fs/promises';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join, dirname, basename } from 'node:path';
|
||||
|
||||
// Match DSH credentials credentialKey/parseCredentialKey admission rules.
|
||||
// Validate at every service boundary AND on reload, unlike the original permissive mock.
|
||||
function validateCredentialName(key) {
|
||||
assert.match(key, /^[a-z][a-z0-9-]*\/[a-z][a-z0-9-]*$/);
|
||||
}
|
||||
|
||||
const PNG = Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+jVv8AAAAASUVORK5CYII=', 'base64');
|
||||
|
||||
test('native host integration using only a local image API', async t => {
|
||||
@@ -47,9 +53,9 @@ test('native host integration using only a local image API', async t => {
|
||||
} } },
|
||||
tools: { register(tool) { tools.set(tool.name, tool); return () => tools.delete(tool.name); } },
|
||||
credentials: {
|
||||
async readRecord(key) { return records.get(key); },
|
||||
async modifyRecord(key, change) { const value = await change(records.get(key)); records.set(key, value); return value; },
|
||||
async deleteRecord(key) { records.delete(key); },
|
||||
async readRecord(key) { validateCredentialName(key); return records.get(key); },
|
||||
async modifyRecord(key, change) { validateCredentialName(key); const value = await change(records.get(key)); records.set(key, value); return value; },
|
||||
async deleteRecord(key) { validateCredentialName(key); records.delete(key); },
|
||||
},
|
||||
attachments: { async saveImage(image) {
|
||||
assert.deepEqual(Buffer.from(image.data), PNG);
|
||||
@@ -90,7 +96,7 @@ test('native host integration using only a local image API', async t => {
|
||||
assert.ok(!JSON.stringify(value).includes(key));
|
||||
assert.equal(records.size, 1);
|
||||
const [credentialName, credential] = [...records][0];
|
||||
assert.match(credentialName, /^dsh-imagegen:[0-9a-f]{64}$/);
|
||||
assert.match(credentialName, /^imagegen-local\/endpoint-[0-9a-f]{64}$/);
|
||||
assert.deepEqual(credential, { kind: 'api-key', key });
|
||||
const disk = await readFile(join(home, 'imagegen', 'settings.json'), 'utf8');
|
||||
assert.deepEqual(JSON.parse(disk), settings);
|
||||
@@ -100,6 +106,18 @@ test('native host integration using only a local image API', async t => {
|
||||
assert.ok(!JSON.stringify(status).includes(key));
|
||||
assert.ok(!(await (await call('/api/imagegen/settings')).text()).includes(key));
|
||||
});
|
||||
await t.test('saved credentials survive strict reload and host reinitialization', async () => {
|
||||
const persisted = JSON.stringify({ version: 1, records: Object.fromEntries(records) });
|
||||
records.clear();
|
||||
for (const [address, value] of Object.entries(JSON.parse(persisted).records)) {
|
||||
validateCredentialName(address);
|
||||
records.set(address, value);
|
||||
}
|
||||
listeners.get('dispose')();
|
||||
routes.clear(); tools.clear();
|
||||
await host.apply(ctx);
|
||||
assert.equal((await tools.get('imagegen_status').execute()).configured, true);
|
||||
});
|
||||
await t.test('lists models using the local authenticated API', async () => {
|
||||
const response = await call('/api/imagegen/models', 'POST');
|
||||
assert.equal(response.status, 200);
|
||||
@@ -157,6 +175,26 @@ test('native host integration using only a local image API', async t => {
|
||||
assert.deepEqual(await readFile(value.images[0].path), PNG);
|
||||
assert.ok(!JSON.stringify(value).includes(key));
|
||||
});
|
||||
await t.test('all digest prefixes are valid and clearing only removes the selected endpoint', async () => {
|
||||
const originalRecords = new Map(records);
|
||||
const prefixes = new Set();
|
||||
for (let i = 0; prefixes.size < 16 && i < 512; i++) {
|
||||
const endpoint = { ...settings, baseUrl: `https://example.com/v1/provider-${i}` };
|
||||
const response = await call('/api/imagegen/settings', 'POST', { ...endpoint, apiKey: 'test-only-temporary-key' });
|
||||
assert.equal(response.status, 200);
|
||||
assert.equal((await response.json()).configured, true);
|
||||
const address = [...records.keys()].find(item => !originalRecords.has(item));
|
||||
validateCredentialName(address);
|
||||
prefixes.add(address.split('endpoint-')[1][0]);
|
||||
assert.equal((await (await call('/api/imagegen/settings', 'POST', endpoint)).json()).configured, true);
|
||||
const cleared = await call('/api/imagegen/settings', 'POST', { ...endpoint, clearKey: true });
|
||||
assert.equal(cleared.status, 200);
|
||||
assert.equal((await cleared.json()).configured, false);
|
||||
assert.deepEqual(records, originalRecords);
|
||||
}
|
||||
assert.equal(prefixes.size, 16, 'exercise every hexadecimal leading character, including 0–9');
|
||||
await call('/api/imagegen/settings', 'POST', settings);
|
||||
});
|
||||
await t.test('file traversal, absolute paths and missing files return 404', async () => {
|
||||
for (const filename of ['../settings.json', '..\\settings.json', join(home, 'imagegen', 'settings.json'), '/etc/passwd', '', '123-00000000-0000-0000-0000-000000000000.png']) {
|
||||
assert.equal((await call(`/api/imagegen/file?name=${encodeURIComponent(filename)}`)).status, 404);
|
||||
|
||||
Reference in new issue
Block a user