test: strengthen safety-critical coverage

This commit is contained in:
NuklearRabbit
2026-07-29 19:40:27 +02:00
parent 0ed202ec95
commit c826561c77
11 changed files with 613 additions and 5 deletions
+134
View File
@@ -194,3 +194,137 @@ test('creates repository-scoped read-only deploy keys and reuses only safe match
(error) => error.code === 'DEPLOY_KEY_NOT_READ_ONLY',
);
});
test('request sends scoped credentials, parses response types and redacts rejected secrets', async (context) => {
const originalFetch = globalThis.fetch;
context.after(() => { globalThis.fetch = originalFetch; });
const calls = [];
const diagnostics = { debug: async (...args) => calls.push(['debug', ...args]), warning: async (...args) => calls.push(['warning', ...args]) };
const service = new GiteaService(makeStore(), diagnostics);
globalThis.fetch = async (url, options) => {
calls.push([url, options]);
return new Response(JSON.stringify({ ok: true }), { status: 200, headers: { 'x-test': 'yes' } });
};
const json = await service.request('/user', { method: 'POST', body: { hello: 'world' }, headers: { 'X-Extra': 'value' } });
assert.deepEqual(json.data, { ok: true });
assert.equal(calls[0][1].headers.Authorization, 'token demo-token');
assert.equal(calls[0][1].headers['Content-Type'], 'application/json');
assert.equal(calls[0][1].headers['X-Extra'], 'value');
globalThis.fetch = async () => new Response('plain', { status: 200 });
assert.equal((await service.request('/plain', { responseType: 'text', auth: false })).data, 'plain');
globalThis.fetch = async () => new Response(Uint8Array.from([1, 2, 3]), { status: 200 });
assert.deepEqual((await service.request('/binary', { responseType: 'buffer' })).data, Buffer.from([1, 2, 3]));
globalThis.fetch = async () => new Response(null, { status: 204 });
assert.equal((await service.request('/empty')).data, null);
globalThis.fetch = async () => new Response(JSON.stringify({ message: 'bad demo-token' }), { status: 403, statusText: 'Forbidden' });
await assert.rejects(service.request('/denied'), (error) => error.status === 403 && !error.message.includes('demo-token'));
globalThis.fetch = async () => new Response('not found', { status: 404, statusText: 'Not Found' });
await assert.rejects(service.request('/missing'), (error) => error.status === 404 && error.payload === 'not found');
});
test('request rejects absent credentials and wraps network failures', async (context) => {
const originalFetch = globalThis.fetch;
context.after(() => { globalThis.fetch = originalFetch; });
const warnings = [];
const service = new GiteaService({ data: { gitea: { baseUrl: 'https://gitea.example.test' } }, getToken: () => '' }, { warning: async (...args) => warnings.push(args) });
await assert.rejects(service.request('/user'), /no Gitea access token/i);
globalThis.fetch = async () => { const error = new Error('connect ECONNREFUSED'); error.code = 'ECONNREFUSED'; throw error; };
await assert.rejects(service.request('/version', { auth: false }), (error) => error.code === 'ECONNREFUSED' && /could not reach/i.test(error.message));
assert.equal(warnings[0][0], 'gitea.request.failed');
});
test('repository pagination, connection validation and simple endpoints preserve API data', async () => {
const service = new GiteaService(makeStore());
let pages = 0;
service.request = async (pathname) => {
if (pathname === '/user') return { data: { login: 'jens' } };
if (pathname === '/version') throw Object.assign(new Error('unsupported'), { status: 404 });
if (pathname.includes('/user/repos')) { pages += 1; return { data: pages === 1 ? Array.from({ length: 50 }, (_, id) => ({ id })) : [{ id: 51 }] }; }
if (pathname.includes('/branches/')) return { data: { name: 'main' } };
return { data: { id: 1 } };
};
const validated = await service.validateConnection('https://gitea.example.test/', 'token');
assert.equal(validated.repositoryCount, 50);
assert.equal(validated.version, null);
pages = 0;
assert.equal((await service.listRepositories()).length, 51);
assert.equal((await service.getRepository('owner space', 'repo/name')).id, 1);
assert.equal((await service.getBranch('owner', 'repo', 'feature/test')).name, 'main');
});
test('branch protection tolerates unsupported APIs but propagates server failures', async () => {
const service = new GiteaService(makeStore());
service.getBranch = async () => ({ protected: false });
service.request = async () => { throw Object.assign(new Error('unsupported'), { status: 404 }); };
const absent = await service.getBranchProtection('owner', 'repo', 'main');
assert.equal(absent.protected, false);
assert.equal(absent.enablePush, null);
service.request = async () => { throw Object.assign(new Error('down'), { status: 500 }); };
await assert.rejects(service.getBranchProtection('owner', 'repo', 'main'), /down/);
});
test('file, release, pull request and deploy-key helpers validate malformed API inputs', async () => {
const service = new GiteaService(makeStore());
service.request = async () => ({ data: [] });
assert.deepEqual(await service.listDeployKeys('owner', 'repo'), []);
assert.deepEqual(await service.listPullRequests({ owner: 'owner', repo: 'repo', limit: 500 }), []);
await assert.rejects(service.ensureReadOnlyDeployKey({ owner: 'owner', repo: 'repo', publicKey: 'invalid' }), /valid SSH public key/i);
await assert.rejects(service.createReadOnlyDeployKey({ owner: 'owner', repo: 'repo', publicKey: 'invalid' }), /valid SSH public key/i);
await assert.rejects(service.deleteDeployKey('owner', 'repo', 0), /valid deploy-key ID/i);
await assert.rejects(service.createPullRequest({ owner: 'owner', repo: 'repo', head: 'a', base: 'b', title: '' }), /1-255/);
await assert.rejects(service.createPullRequest({ owner: 'owner', repo: 'repo', head: 'a', base: 'b', title: 'x'.repeat(256) }), /1-255/);
await assert.rejects(service.getRepositoryFile({ owner: 'owner', repo: 'repo', filePath: 'folder' }), /not a file/i);
service.request = async () => ({ data: { encoding: 'base64', content: Buffer.from('hello').toString('base64') } });
assert.equal((await service.getRepositoryFile({ owner: 'owner', repo: 'repo', filePath: 'README' })).decoded, 'hello');
service.request = async () => ({ data: { content: 'plain' } });
assert.equal((await service.getRepositoryFile({ owner: 'owner', repo: 'repo', filePath: 'README' })).decoded, 'plain');
service.request = async () => ({ data: { encoding: 'none' } });
await assert.rejects(service.getRepositoryFile({ owner: 'owner', repo: 'repo', filePath: 'README' }), /readable content/i);
for (const method of ['getLatestRelease', 'getReleaseByTag']) {
service.request = async () => { throw Object.assign(new Error('missing'), { status: 404 }); };
assert.equal(await service[method]('owner', 'repo', 'v1'), null);
service.request = async () => { throw Object.assign(new Error('server'), { status: 500 }); };
await assert.rejects(service[method]('owner', 'repo', 'v1'), /server/);
}
});
test('authenticated downloads keep tokens same-origin and enforce secure redirects', async (context) => {
const originalFetch = globalThis.fetch;
context.after(() => { globalThis.fetch = originalFetch; });
const service = new GiteaService(makeStore());
const calls = [];
globalThis.fetch = async (url, options) => {
calls.push({ url: String(url), options });
if (calls.length === 1) return new Response(null, { status: 302, headers: { location: 'https://cdn.example.test/release.exe' } });
return new Response('asset', { status: 200 });
};
assert.equal((await service.downloadAuthenticated('/attachments/release.exe')).toString(), 'asset');
assert.equal(calls[0].options.headers.Authorization, 'token demo-token');
assert.equal(calls[1].options.headers.Authorization, undefined);
globalThis.fetch = async () => new Response(null, { status: 302, headers: { location: 'http://cdn.example.test/file' } });
await assert.rejects(service.downloadAuthenticated('/file'), /insecure cross-origin/i);
globalThis.fetch = async () => new Response(null, { status: 302 });
await assert.rejects(service.downloadAuthenticated('/file'), /did not contain a destination/i);
globalThis.fetch = async () => new Response('missing', { status: 404 });
await assert.rejects(service.downloadAuthenticated('/file'), /HTTP 404/i);
let redirects = 0;
globalThis.fetch = async () => new Response(null, { status: 302, headers: { location: `/redirect-${redirects += 1}` } });
await assert.rejects(service.downloadAuthenticated('/file'), /redirect limit/i);
});
test('release downloads and workflow dispatch reject inconsistent evidence', async () => {
const service = new GiteaService(makeStore());
await assert.rejects(service.downloadReleaseAsset('owner', 'repo', 1, 0), /invalid release asset ID/i);
service.request = async () => ({ data: { id: 2, browser_download_url: 'https://gitea.example/file' } });
await assert.rejects(service.downloadReleaseAsset('owner', 'repo', 1, 3), /different release asset/i);
service.request = async () => ({ data: { id: 3, browser_download_url: '' } });
await assert.rejects(service.downloadReleaseAsset('owner', 'repo', 1, 3), /did not provide/i);
service.request = async () => ({ status: 202, data: null });
assert.deepEqual(await service.dispatchWorkflow({ owner: 'owner', repo: 'repo', workflowFile: 'deploy.yml', ref: 'main' }), { accepted: false, status: 202 });
});