Files
wehub-resource-sync 9b395f5cc3
E2E Headed Chrome / e2e-headed (macos-15) (push) Has been cancelled
E2E Headed Chrome / e2e-headed (ubuntu-latest) (push) Has been cancelled
E2E Headed Chrome / e2e-headed (windows-latest) (push) Has been cancelled
CI / build (macos-latest) (push) Has been cancelled
CI / build (ubuntu-latest) (push) Has been cancelled
CI / build (windows-latest) (push) Has been cancelled
CI / unit-test (push) Has been cancelled
CI / bun-test (push) Has been cancelled
CI / adapter-test (push) Has been cancelled
CI / smoke-test (macos-latest) (push) Has been cancelled
CI / smoke-test (ubuntu-latest) (push) Has been cancelled
Security Audit / audit (push) Has been cancelled
Build Chrome Extension / build (push) Has been cancelled
Trigger Website Rebuild (Docs Updated) / dispatch (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:39:48 +08:00

98 lines
4.6 KiB
JavaScript

import { afterEach, describe, expect, it, vi } from 'vitest';
import { getRegistry } from '@jackwener/opencli/registry';
import { ArgumentError, CommandExecutionError, EmptyResultError } from '@jackwener/opencli/errors';
import './vulnerability.js';
import './query.js';
afterEach(() => {
vi.unstubAllGlobals();
vi.restoreAllMocks();
});
describe('osv vulnerability adapter', () => {
const cmd = getRegistry().get('osv/vulnerability');
it('rejects empty / malformed id before fetching', async () => {
const fetchMock = vi.fn();
vi.stubGlobal('fetch', fetchMock);
await expect(cmd.func({ id: '' })).rejects.toThrow(ArgumentError);
await expect(cmd.func({ id: 'has spaces' })).rejects.toThrow(ArgumentError);
expect(fetchMock).not.toHaveBeenCalled();
});
it('maps HTTP 404 to EmptyResultError', async () => {
vi.stubGlobal('fetch', vi.fn().mockResolvedValue(new Response('not found', { status: 404 })));
await expect(cmd.func({ id: 'GHSA-aaaa-bbbb-cccc' })).rejects.toThrow(EmptyResultError);
});
it('returns flattened affected packages with severity from database_specific', async () => {
const vuln = {
id: 'GHSA-29mw-wpgm-hmr9',
summary: 'ReDoS in lodash',
aliases: ['CVE-2020-28500'],
published: '2022-01-06T20:30:46Z',
modified: '2025-09-29T21:12:31.102523Z',
database_specific: { severity: 'MODERATE', cwe_ids: ['CWE-1333', 'CWE-400'] },
severity: [{ type: 'CVSS_V3', score: 'CVSS:3.1/...' }],
affected: [
{ package: { name: 'lodash', ecosystem: 'npm' } },
{ package: { name: 'lodash-rails', ecosystem: 'RubyGems' } },
],
references: [{ url: 'https://example' }],
};
vi.stubGlobal('fetch', vi.fn().mockResolvedValue(new Response(JSON.stringify(vuln), { status: 200 })));
const rows = await cmd.func({ id: 'GHSA-29mw-wpgm-hmr9' });
expect(rows).toEqual([expect.objectContaining({
id: 'GHSA-29mw-wpgm-hmr9',
severity: 'MODERATE',
aliases: 'CVE-2020-28500',
affectedPackages: 'npm:lodash, RubyGems:lodash-rails',
cwes: 'CWE-1333, CWE-400',
referenceCount: 1,
modified: '2025-09-29T21:12:31Z',
})]);
});
});
describe('osv query adapter', () => {
const cmd = getRegistry().get('osv/query');
it('rejects bad ecosystem and bad limit before fetching', async () => {
const fetchMock = vi.fn();
vi.stubGlobal('fetch', fetchMock);
await expect(cmd.func({ package: 'lodash', ecosystem: 'foo', limit: 5 })).rejects.toThrow(ArgumentError);
await expect(cmd.func({ package: 'django', ecosystem: 'pypi', limit: 5 })).rejects.toThrow(ArgumentError);
await expect(cmd.func({ package: 'lodash', ecosystem: 'npm', limit: 0 })).rejects.toThrow(ArgumentError);
await expect(cmd.func({ package: '', ecosystem: 'npm', limit: 5 })).rejects.toThrow(ArgumentError);
expect(fetchMock).not.toHaveBeenCalled();
});
it('maps HTTP 429 to CommandExecutionError', async () => {
vi.stubGlobal('fetch', vi.fn().mockResolvedValue(new Response('rate limited', { status: 429 })));
await expect(cmd.func({ package: 'lodash', ecosystem: 'npm', limit: 5 })).rejects.toThrow(CommandExecutionError);
});
it('throws EmptyResultError when no vulns reported', async () => {
vi.stubGlobal('fetch', vi.fn().mockResolvedValue(new Response(JSON.stringify({}), { status: 200 })));
await expect(cmd.func({ package: 'lodash', ecosystem: 'npm', limit: 5 })).rejects.toThrow(EmptyResultError);
});
it('sorts by published date descending and rows have id round-tripable into osv vulnerability', async () => {
const body = {
vulns: [
{ id: 'GHSA-old', summary: 'old', published: '2020-01-01T00:00:00Z', affected: [{ package: { name: 'django', ecosystem: 'PyPI' } }] },
{ id: 'GHSA-new', summary: 'new', published: '2026-01-01T00:00:00Z', affected: [{ package: { name: 'django', ecosystem: 'PyPI' } }] },
],
};
vi.stubGlobal('fetch', vi.fn().mockResolvedValue(new Response(JSON.stringify(body), { status: 200 })));
const rows = await cmd.func({ package: 'django', ecosystem: 'PyPI', limit: 5 });
expect(rows[0]).toMatchObject({ rank: 1, id: 'GHSA-new', published: '2026-01-01T00:00:00Z' });
expect(rows[1]).toMatchObject({ rank: 2, id: 'GHSA-old' });
expect(rows[0].url).toBe('https://osv.dev/vulnerability/GHSA-new');
});
});