@jackwener/opencli 0.6.1 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/actions/setup-chrome/action.yml +26 -0
- package/.github/workflows/ci.yml +59 -3
- package/.github/workflows/e2e-headed.yml +37 -0
- package/README.md +19 -0
- package/TESTING.md +233 -0
- package/dist/bilibili.js +2 -2
- package/dist/browser.d.ts +1 -1
- package/dist/browser.js +12 -3
- package/dist/browser.test.js +56 -16
- package/dist/interceptor.test.d.ts +4 -0
- package/dist/interceptor.test.js +81 -0
- package/dist/output.test.d.ts +3 -0
- package/dist/output.test.js +60 -0
- package/dist/pipeline/executor.js +0 -6
- package/dist/pipeline/executor.test.d.ts +4 -0
- package/dist/pipeline/executor.test.js +145 -0
- package/dist/pipeline/steps/fetch.js +4 -3
- package/dist/registry.d.ts +2 -2
- package/package.json +4 -4
- package/src/bilibili.ts +2 -2
- package/src/browser.test.ts +54 -16
- package/src/browser.ts +11 -3
- package/src/clis/twitter/notifications.ts +1 -1
- package/src/engine.ts +2 -2
- package/src/interceptor.test.ts +94 -0
- package/src/output.test.ts +69 -4
- package/src/pipeline/executor.test.ts +161 -0
- package/src/pipeline/executor.ts +0 -5
- package/src/pipeline/steps/fetch.ts +4 -3
- package/src/registry.ts +2 -2
- package/tests/e2e/browser-auth.test.ts +90 -0
- package/tests/e2e/browser-public.test.ts +169 -0
- package/tests/e2e/helpers.ts +63 -0
- package/tests/e2e/management.test.ts +106 -0
- package/tests/e2e/output-formats.test.ts +48 -0
- package/tests/e2e/public-commands.test.ts +56 -0
- package/tests/smoke/api-health.test.ts +72 -0
- package/tsconfig.json +1 -0
- package/vitest.config.ts +1 -1
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* E2E tests for output format rendering.
|
|
3
|
+
* Uses hackernews (public, fast) as a stable data source.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, it, expect } from 'vitest';
|
|
7
|
+
import { runCli, parseJsonOutput } from './helpers.js';
|
|
8
|
+
|
|
9
|
+
const FORMATS = ['json', 'yaml', 'csv', 'md'] as const;
|
|
10
|
+
|
|
11
|
+
describe('output formats E2E', () => {
|
|
12
|
+
for (const fmt of FORMATS) {
|
|
13
|
+
it(`hackernews top -f ${fmt} produces valid output`, async () => {
|
|
14
|
+
const { stdout, code } = await runCli(['hackernews', 'top', '--limit', '2', '-f', fmt]);
|
|
15
|
+
expect(code).toBe(0);
|
|
16
|
+
expect(stdout.trim().length).toBeGreaterThan(0);
|
|
17
|
+
|
|
18
|
+
if (fmt === 'json') {
|
|
19
|
+
const data = parseJsonOutput(stdout);
|
|
20
|
+
expect(Array.isArray(data)).toBe(true);
|
|
21
|
+
expect(data.length).toBe(2);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (fmt === 'yaml') {
|
|
25
|
+
expect(stdout).toContain('title:');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (fmt === 'csv') {
|
|
29
|
+
// CSV should have a header row + data rows
|
|
30
|
+
const lines = stdout.trim().split('\n');
|
|
31
|
+
expect(lines.length).toBeGreaterThanOrEqual(2);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (fmt === 'md') {
|
|
35
|
+
// Markdown table should have pipe characters
|
|
36
|
+
expect(stdout).toContain('|');
|
|
37
|
+
}
|
|
38
|
+
}, 30_000);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
it('list -f csv produces valid csv', async () => {
|
|
42
|
+
const { stdout, code } = await runCli(['list', '-f', 'csv']);
|
|
43
|
+
expect(code).toBe(0);
|
|
44
|
+
const lines = stdout.trim().split('\n');
|
|
45
|
+
// Header + many data lines
|
|
46
|
+
expect(lines.length).toBeGreaterThan(50);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* E2E tests for public API commands (browser: false).
|
|
3
|
+
* These commands use Node.js fetch directly — no browser needed.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, it, expect } from 'vitest';
|
|
7
|
+
import { runCli, parseJsonOutput } from './helpers.js';
|
|
8
|
+
|
|
9
|
+
describe('public commands E2E', () => {
|
|
10
|
+
// ── hackernews ──
|
|
11
|
+
it('hackernews top returns structured data', async () => {
|
|
12
|
+
const { stdout, code } = await runCli(['hackernews', 'top', '--limit', '3', '-f', 'json']);
|
|
13
|
+
expect(code).toBe(0);
|
|
14
|
+
const data = parseJsonOutput(stdout);
|
|
15
|
+
expect(Array.isArray(data)).toBe(true);
|
|
16
|
+
expect(data.length).toBe(3);
|
|
17
|
+
expect(data[0]).toHaveProperty('title');
|
|
18
|
+
expect(data[0]).toHaveProperty('score');
|
|
19
|
+
expect(data[0]).toHaveProperty('rank');
|
|
20
|
+
}, 30_000);
|
|
21
|
+
|
|
22
|
+
it('hackernews top respects --limit', async () => {
|
|
23
|
+
const { stdout, code } = await runCli(['hackernews', 'top', '--limit', '1', '-f', 'json']);
|
|
24
|
+
expect(code).toBe(0);
|
|
25
|
+
const data = parseJsonOutput(stdout);
|
|
26
|
+
expect(data.length).toBe(1);
|
|
27
|
+
}, 30_000);
|
|
28
|
+
|
|
29
|
+
// ── v2ex (public API, browser: false) ──
|
|
30
|
+
it('v2ex hot returns topics', async () => {
|
|
31
|
+
const { stdout, code } = await runCli(['v2ex', 'hot', '--limit', '3', '-f', 'json']);
|
|
32
|
+
expect(code).toBe(0);
|
|
33
|
+
const data = parseJsonOutput(stdout);
|
|
34
|
+
expect(Array.isArray(data)).toBe(true);
|
|
35
|
+
expect(data.length).toBeGreaterThanOrEqual(1);
|
|
36
|
+
expect(data[0]).toHaveProperty('title');
|
|
37
|
+
}, 30_000);
|
|
38
|
+
|
|
39
|
+
it('v2ex latest returns topics', async () => {
|
|
40
|
+
const { stdout, code } = await runCli(['v2ex', 'latest', '--limit', '3', '-f', 'json']);
|
|
41
|
+
expect(code).toBe(0);
|
|
42
|
+
const data = parseJsonOutput(stdout);
|
|
43
|
+
expect(Array.isArray(data)).toBe(true);
|
|
44
|
+
expect(data.length).toBeGreaterThanOrEqual(1);
|
|
45
|
+
}, 30_000);
|
|
46
|
+
|
|
47
|
+
it('v2ex topic returns topic detail', async () => {
|
|
48
|
+
// Topic 1000001 is a well-known V2EX topic
|
|
49
|
+
const { stdout, code } = await runCli(['v2ex', 'topic', '--id', '1000001', '-f', 'json']);
|
|
50
|
+
// May fail if V2EX rate-limits, but should return structured data
|
|
51
|
+
if (code === 0) {
|
|
52
|
+
const data = parseJsonOutput(stdout);
|
|
53
|
+
expect(data).toBeDefined();
|
|
54
|
+
}
|
|
55
|
+
}, 30_000);
|
|
56
|
+
});
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Smoke tests for external API health.
|
|
3
|
+
* Only run on schedule or manual dispatch — NOT on every push/PR.
|
|
4
|
+
* These verify that external APIs haven't changed their structure.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, it, expect } from 'vitest';
|
|
8
|
+
import { runCli, parseJsonOutput } from '../e2e/helpers.js';
|
|
9
|
+
|
|
10
|
+
describe('API health smoke tests', () => {
|
|
11
|
+
|
|
12
|
+
// ── Public API commands (should always work) ──
|
|
13
|
+
it('hackernews API is responsive and returns expected structure', async () => {
|
|
14
|
+
const { stdout, code } = await runCli(['hackernews', 'top', '--limit', '5', '-f', 'json']);
|
|
15
|
+
expect(code).toBe(0);
|
|
16
|
+
const data = parseJsonOutput(stdout);
|
|
17
|
+
expect(data.length).toBe(5);
|
|
18
|
+
for (const item of data) {
|
|
19
|
+
expect(item).toHaveProperty('title');
|
|
20
|
+
expect(item).toHaveProperty('score');
|
|
21
|
+
expect(item).toHaveProperty('author');
|
|
22
|
+
expect(item).toHaveProperty('rank');
|
|
23
|
+
}
|
|
24
|
+
}, 30_000);
|
|
25
|
+
|
|
26
|
+
it('v2ex hot API is responsive', async () => {
|
|
27
|
+
const { stdout, code } = await runCli(['v2ex', 'hot', '--limit', '3', '-f', 'json']);
|
|
28
|
+
expect(code).toBe(0);
|
|
29
|
+
const data = parseJsonOutput(stdout);
|
|
30
|
+
expect(data.length).toBeGreaterThanOrEqual(1);
|
|
31
|
+
expect(data[0]).toHaveProperty('title');
|
|
32
|
+
}, 30_000);
|
|
33
|
+
|
|
34
|
+
it('v2ex latest API is responsive', async () => {
|
|
35
|
+
const { stdout, code } = await runCli(['v2ex', 'latest', '--limit', '3', '-f', 'json']);
|
|
36
|
+
expect(code).toBe(0);
|
|
37
|
+
const data = parseJsonOutput(stdout);
|
|
38
|
+
expect(data.length).toBeGreaterThanOrEqual(1);
|
|
39
|
+
}, 30_000);
|
|
40
|
+
|
|
41
|
+
it('v2ex topic API is responsive', async () => {
|
|
42
|
+
const { stdout, code } = await runCli(['v2ex', 'topic', '--id', '1000001', '-f', 'json']);
|
|
43
|
+
if (code === 0) {
|
|
44
|
+
const data = parseJsonOutput(stdout);
|
|
45
|
+
expect(data).toBeDefined();
|
|
46
|
+
}
|
|
47
|
+
}, 30_000);
|
|
48
|
+
|
|
49
|
+
// ── Validate all adapters ──
|
|
50
|
+
it('all adapter definitions are valid', async () => {
|
|
51
|
+
const { stdout, code } = await runCli(['validate']);
|
|
52
|
+
expect(code).toBe(0);
|
|
53
|
+
expect(stdout).toContain('PASS');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// ── Command registry integrity ──
|
|
57
|
+
it('all expected sites are registered', async () => {
|
|
58
|
+
const { stdout, code } = await runCli(['list', '-f', 'json']);
|
|
59
|
+
expect(code).toBe(0);
|
|
60
|
+
const data = parseJsonOutput(stdout);
|
|
61
|
+
const sites = new Set(data.map((d: any) => d.site));
|
|
62
|
+
// Verify all 17 sites are present
|
|
63
|
+
for (const expected of [
|
|
64
|
+
'hackernews', 'bbc', 'bilibili', 'v2ex', 'weibo', 'zhihu',
|
|
65
|
+
'twitter', 'reddit', 'xueqiu', 'reuters', 'youtube',
|
|
66
|
+
'smzdm', 'boss', 'ctrip', 'coupang', 'xiaohongshu',
|
|
67
|
+
'yahoo-finance',
|
|
68
|
+
]) {
|
|
69
|
+
expect(sites.has(expected)).toBe(true);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
});
|
package/tsconfig.json
CHANGED