Skip to content

Commit d56487f

Browse files
Copilotffflorian
andauthored
test: expand package test coverage and enforce minimum test count (#1175)
* test: add broad package-level test coverage Agent-Logs-Url: https://github.com/ffflorian/node-packages/sessions/e3f75eee-6963-4601-9442-d7358ba984f2 Co-authored-by: ffflorian <5497598+ffflorian@users.noreply.github.com> * test: fix lint and stabilize new coverage tests Agent-Logs-Url: https://github.com/ffflorian/node-packages/sessions/e3f75eee-6963-4601-9442-d7358ba984f2 Co-authored-by: ffflorian <5497598+ffflorian@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: ffflorian <5497598+ffflorian@users.noreply.github.com>
1 parent 933f10b commit d56487f

25 files changed

Lines changed: 865 additions & 82 deletions

File tree

packages/api-client/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
"build": "tsc -p tsconfig.build.json",
2727
"clean": "rm -rf dist",
2828
"dist": "yarn clean && yarn build",
29-
"test": "exit 0"
29+
"test": "vitest run"
3030
},
3131
"type": "module",
3232
"version": "2.5.1"
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/* eslint-disable no-magic-numbers */
2+
3+
import {afterEach, describe, expect, test, vi} from 'vitest';
4+
5+
import {APIClient} from './APIClient.js';
6+
7+
describe('APIClient', () => {
8+
afterEach(() => {
9+
vi.restoreAllMocks();
10+
});
11+
12+
test('adds query params and skips nullish params', async () => {
13+
const fetchMock = vi.fn().mockResolvedValue(new Response(JSON.stringify({ok: true}), {status: 200}));
14+
vi.stubGlobal('fetch', fetchMock);
15+
const client = new APIClient('https://example.com');
16+
17+
await client.get('/users', {params: {alpha: 1, beta: null, delta: 'x', gamma: undefined}});
18+
19+
expect(fetchMock).toHaveBeenCalledTimes(1);
20+
expect(fetchMock.mock.calls[0][0].toString()).toContain('/users?alpha=1&delta=x');
21+
});
22+
23+
test('sets authorization header from auth config', async () => {
24+
const fetchMock = vi.fn().mockResolvedValue(new Response(JSON.stringify({ok: true}), {status: 200}));
25+
vi.stubGlobal('fetch', fetchMock);
26+
const client = new APIClient('https://example.com', {auth: {password: 'pass', username: 'user'}});
27+
28+
await client.get('/auth');
29+
30+
const [, options] = fetchMock.mock.calls[0];
31+
expect((options as RequestInit).headers).toMatchObject({
32+
Authorization: `Basic ${Buffer.from('user:pass').toString('base64')}`,
33+
});
34+
});
35+
36+
test('serializes object body and sets content-type', async () => {
37+
const fetchMock = vi.fn().mockResolvedValue(new Response(JSON.stringify({ok: true}), {status: 200}));
38+
vi.stubGlobal('fetch', fetchMock);
39+
const client = new APIClient('https://example.com');
40+
41+
await client.post('/items', {id: 1, name: 'abc'});
42+
43+
const [, options] = fetchMock.mock.calls[0];
44+
expect(options).toMatchObject({
45+
body: JSON.stringify({id: 1, name: 'abc'}),
46+
method: 'POST',
47+
});
48+
expect((options as RequestInit).headers).toMatchObject({'Content-Type': 'application/json'});
49+
});
50+
51+
test('throws status and response text for failed requests', async () => {
52+
const fetchMock = vi.fn().mockResolvedValue(new Response('Bad input', {status: 400, statusText: 'Bad Request'}));
53+
vi.stubGlobal('fetch', fetchMock);
54+
const client = new APIClient('https://example.com');
55+
56+
await expect(client.get('/fail')).rejects.toThrow('Request failed with status code 400: Bad input');
57+
});
58+
59+
test('supports responseType text', async () => {
60+
const fetchMock = vi.fn().mockResolvedValue(new Response('hello', {status: 200}));
61+
vi.stubGlobal('fetch', fetchMock);
62+
const client = new APIClient('https://example.com');
63+
64+
const response = await client.get('/text', {responseType: 'text'});
65+
66+
expect(response.data).toBe('hello');
67+
expect(response.status).toBe(200);
68+
});
69+
});

packages/auto-merge/src/AutoMerge.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,16 @@ import type {GitHubPullRequest, Repository} from './types/index.js';
77
import {AutoMerge} from './AutoMerge.js';
88

99
describe('AutoMerge', () => {
10+
test('throws when auth token is missing', () => {
11+
expect(
12+
() =>
13+
new AutoMerge({
14+
authToken: '',
15+
projects: {gitHub: ['ffflorian/example-project']},
16+
})
17+
).toThrow('No authentication token specified');
18+
});
19+
1020
describe('checkRepository', () => {
1121
let autoMerge: AutoMerge;
1222

packages/crates-updater/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@
3535
"build": "tsc -p tsconfig.build.json",
3636
"clean": "rm -rf dist",
3737
"dist": "yarn clean && yarn build",
38-
"start": "tsx src/cli.ts"
38+
"start": "tsx src/cli.ts",
39+
"test": "vitest run"
3940
},
4041
"type": "module",
4142
"version": "1.17.1"
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import {beforeEach, describe, expect, test, vi} from 'vitest';
2+
3+
const getVersionsMock = vi.hoisted(() => vi.fn());
4+
5+
vi.mock('crates.io', () => ({
6+
CratesIO: class {
7+
api = {
8+
crates: {
9+
getVersions: getVersionsMock,
10+
},
11+
};
12+
},
13+
Version: class {},
14+
}));
15+
16+
const modulePromise = import('./CratesUpdater.js');
17+
18+
describe('CratesUpdater', () => {
19+
beforeEach(() => {
20+
vi.clearAllMocks();
21+
});
22+
23+
test('getVersions returns API versions', async () => {
24+
getVersionsMock.mockResolvedValue({versions: [{num: '1.0.0'}]});
25+
const {getVersions} = await modulePromise;
26+
27+
const versions = await getVersions('example');
28+
29+
expect(getVersionsMock).toHaveBeenCalledWith('example');
30+
expect(versions).toEqual([{num: '1.0.0'}]);
31+
});
32+
33+
test('getLatestVersion returns highest version from unsorted input', async () => {
34+
getVersionsMock.mockResolvedValue({versions: [{num: '1.2.0'}, {num: '1.10.0'}, {num: '1.3.0'}]});
35+
const {getLatestVersion} = await modulePromise;
36+
37+
const latest = await getLatestVersion('example');
38+
39+
expect(latest.num).toBe('1.10.0');
40+
});
41+
42+
test('checkForUpdate returns latest when newer version exists', async () => {
43+
getVersionsMock.mockResolvedValue({versions: [{num: '2.0.0'}, {num: '1.0.0'}]});
44+
const {checkForUpdate} = await modulePromise;
45+
46+
await expect(checkForUpdate('example', '1.5.0')).resolves.toBe('2.0.0');
47+
});
48+
49+
test('checkForUpdate returns null when versions are equal', async () => {
50+
getVersionsMock.mockResolvedValue({versions: [{num: '1.5.0'}]});
51+
const {checkForUpdate} = await modulePromise;
52+
53+
await expect(checkForUpdate('example', '1.5.0')).resolves.toBeNull();
54+
});
55+
56+
test('checkForUpdate returns null when local version is newer', async () => {
57+
getVersionsMock.mockResolvedValue({versions: [{num: '1.2.0'}]});
58+
const {checkForUpdate} = await modulePromise;
59+
60+
await expect(checkForUpdate('example', '1.9.0')).resolves.toBeNull();
61+
});
62+
});

packages/electron-icon-generator/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
"clean": "rm -rf dist",
3737
"dist": "yarn clean && yarn build",
3838
"start": "tsx src/cli.ts",
39-
"test": "exit 0"
39+
"test": "vitest run"
4040
},
4141
"type": "module",
4242
"version": "1.15.1"
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/* eslint-disable id-length, no-magic-numbers */
2+
3+
import path from 'node:path';
4+
import {beforeEach, describe, expect, test, vi} from 'vitest';
5+
6+
const mkdirMock = vi.hoisted(() => vi.fn());
7+
const renameMock = vi.hoisted(() => vi.fn());
8+
const iconGenMock = vi.hoisted(() => vi.fn());
9+
const jimpReadMock = vi.hoisted(() => vi.fn());
10+
11+
vi.mock('node:fs/promises', () => ({
12+
default: {
13+
mkdir: mkdirMock,
14+
rename: renameMock,
15+
},
16+
}));
17+
18+
vi.mock('icon-gen', () => ({
19+
default: {
20+
default: iconGenMock,
21+
},
22+
}));
23+
24+
vi.mock('jimp', () => ({
25+
Jimp: {
26+
read: jimpReadMock,
27+
},
28+
}));
29+
30+
const modulePromise = import('./index.js');
31+
32+
describe('IconGenerator', () => {
33+
beforeEach(() => {
34+
vi.clearAllMocks();
35+
});
36+
37+
test('resolves constructor input and output paths', async () => {
38+
const {IconGenerator} = await modulePromise;
39+
const generator = new IconGenerator({input: './in.png', output: './out', silent: true});
40+
41+
expect((generator as any).options.input).toBe(path.resolve('./in.png'));
42+
expect((generator as any).options.output).toBe(path.resolve('./out'));
43+
});
44+
45+
test('logConsole is silent when silent option is true', async () => {
46+
const {IconGenerator} = await modulePromise;
47+
const generator = new IconGenerator({input: './in.png', output: './out', silent: true});
48+
const infoSpy = vi.spyOn(console, 'info').mockImplementation(() => {});
49+
50+
(generator as any).logConsole('test message');
51+
52+
expect(infoSpy).not.toHaveBeenCalled();
53+
});
54+
55+
test('createPNG creates output folders and writes resized file', async () => {
56+
mkdirMock.mockResolvedValue(undefined);
57+
renameMock.mockResolvedValue(undefined);
58+
const writeMock = vi.fn().mockResolvedValue(undefined);
59+
const resizeMock = vi.fn();
60+
jimpReadMock.mockResolvedValue({resize: resizeMock, write: writeMock});
61+
const {IconGenerator} = await modulePromise;
62+
const generator = new IconGenerator({input: './in.png', output: './out', silent: true});
63+
64+
const message = await (generator as any).createPNG(128);
65+
66+
expect(mkdirMock).toHaveBeenCalled();
67+
expect(resizeMock).toHaveBeenCalledWith({h: 128, w: 128});
68+
expect(writeMock).toHaveBeenCalledWith(expect.stringMatching(/128\.png$/));
69+
expect(message).toContain('Created');
70+
});
71+
72+
test('renamePNGs renames file and logs completion on last item', async () => {
73+
renameMock.mockResolvedValue(undefined);
74+
const {IconGenerator} = await modulePromise;
75+
const generator = new IconGenerator({input: './in.png', output: './out', silent: true});
76+
77+
await (generator as any).renamePNGs(8);
78+
79+
expect(renameMock).toHaveBeenCalledTimes(1);
80+
expect(renameMock.mock.calls[0][0]).toMatch(/1024\.png$/);
81+
expect(renameMock.mock.calls[0][1]).toMatch(/1024x1024\.png$/);
82+
});
83+
84+
test('start runs generation and icon conversion', async () => {
85+
mkdirMock.mockResolvedValue(undefined);
86+
renameMock.mockResolvedValue(undefined);
87+
iconGenMock.mockResolvedValue(undefined);
88+
const writeMock = vi.fn().mockResolvedValue(undefined);
89+
const resizeMock = vi.fn();
90+
jimpReadMock.mockResolvedValue({resize: resizeMock, write: writeMock});
91+
const {IconGenerator} = await modulePromise;
92+
const generator = new IconGenerator({input: './in.png', output: './out', silent: true});
93+
94+
await generator.start();
95+
96+
expect(iconGenMock).toHaveBeenCalledTimes(2);
97+
expect(renameMock).toHaveBeenCalledTimes(9);
98+
});
99+
});

packages/exposure-keys/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@
4141
"build:ts": "tsc -p tsconfig.build.json",
4242
"clean": "rm -rf dist",
4343
"dist": "yarn clean && yarn build",
44-
"start": "tsx src/cli.ts -d"
44+
"start": "tsx src/cli.ts -d",
45+
"test": "vitest run"
4546
},
4647
"type": "module",
4748
"version": "1.18.1"
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/* eslint-disable no-magic-numbers */
2+
3+
import JSZip from 'jszip';
4+
import {describe, expect, test} from 'vitest';
5+
6+
import {TEKSignatureList, TemporaryExposureKeyExport} from '../proto/export.js';
7+
import {loadKeys, loadSignature, loadZip, riskCount} from './ExposureKeys.js';
8+
9+
const header = Buffer.from('EK Export v1 ', 'utf-8');
10+
11+
describe('ExposureKeys', () => {
12+
test('loadKeys rejects zip files', () => {
13+
const zipHeader = Buffer.from([0x50, 0x4b, 0x03, 0x04]);
14+
expect(() => loadKeys(zipHeader)).toThrow('Please use `loadZip()`');
15+
});
16+
17+
test('loadKeys rejects missing header', () => {
18+
const content = Buffer.concat([Buffer.from('wrong header '), Buffer.from([1, 2, 3])]);
19+
expect(() => loadKeys(content)).toThrow('missing a correct header');
20+
});
21+
22+
test('loadKeys decodes key export payload', () => {
23+
const encoded = TemporaryExposureKeyExport.encode({
24+
keys: [{transmissionRiskLevel: 3}, {transmissionRiskLevel: 2}],
25+
}).finish();
26+
const content = Buffer.concat([header, Buffer.from(encoded)]);
27+
28+
const result = loadKeys(content);
29+
30+
expect(result.keys).toHaveLength(2);
31+
expect(result.keys[0].transmissionRiskLevel).toBe(3);
32+
});
33+
34+
test('loadSignature decodes signature payload', () => {
35+
const encoded = TEKSignatureList.encode({
36+
signatures: [{batchNum: 1, batchSize: 2, signature: Buffer.from([1, 2])}],
37+
}).finish();
38+
39+
const result = loadSignature(Buffer.from(encoded));
40+
41+
expect(result.signatures).toHaveLength(1);
42+
expect(result.signatures[0].batchNum).toBe(1);
43+
});
44+
45+
test('loadZip extracts export and signature files', async () => {
46+
const zip = new JSZip();
47+
zip.file('export.bin', Buffer.from('key-data'));
48+
zip.file('export.sig', Buffer.from('sig-data'));
49+
const content = Buffer.from(await zip.generateAsync({type: 'nodebuffer'}));
50+
51+
const result = await loadZip(content);
52+
53+
expect(result.keys.toString()).toBe('key-data');
54+
expect(result.signature.toString()).toBe('sig-data');
55+
});
56+
57+
test('riskCount returns number of matching transmission levels', () => {
58+
const exportData = TemporaryExposureKeyExport.fromObject({
59+
keys: [{transmissionRiskLevel: 1}, {transmissionRiskLevel: 2}, {transmissionRiskLevel: 2}],
60+
});
61+
62+
expect(riskCount(exportData, 2)).toBe(2);
63+
});
64+
});

packages/https-proxy/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@
3535
"build": "tsc -p tsconfig.build.json",
3636
"clean": "rm -rf dist",
3737
"dist": "yarn clean && yarn build",
38-
"start": "tsx src/cli.ts"
38+
"start": "tsx src/cli.ts",
39+
"test": "vitest run"
3940
},
4041
"type": "module",
4142
"version": "1.20.1"

0 commit comments

Comments
 (0)