Files
jackwener__opencli/clis/pixiv/illusts.test.ts
jakevin 4fe9a73ebc refactor: migrate adapter imports to package exports (#795)
* refactor: migrate adapter imports to package exports

Replace all relative imports (../../src/registry.js, ../../browser/cdp.js, etc.)
with package exports (@jackwener/opencli/registry, @jackwener/opencli/errors, etc.)
across all 484 adapter files.

This decouples adapter import resolution from directory structure:
- User CLIs in ~/.opencli/clis/ resolve via node_modules symlink
- Internal adapters resolve via Node.js self-referencing
- No more shim files needed for import resolution

Changes:
- package.json: add sub-path exports for all public modules
- clis/**: replace relative imports with @jackwener/opencli/...
- discovery.ts: simplify ensureUserCliCompatShims to symlink-only
- registry-api.ts: export CommandArgs type
- Remove root-level shim directories (browser/, download/, pipeline/)
- Remove shim entries from tsconfig.json include and package.json files

* test: add regression tests for package exports

Prevents regressions like #788/#791 by:
1. Scanning all adapter files for forbidden relative imports
   (../../src/, ../../browser/, etc.) — fails if any remain
2. Verifying every package.json export maps to an existing source file

18 new test cases.

* fix: use junction on Windows + broaden test patterns

- discovery.ts: use 'junction' symlink type on Windows (no admin required)
- package-exports.test.ts: generalize forbidden patterns to catch any
  depth of ../ traversal (not just ../../ and ../../../)

* fix: update stale vi.mock/importActual paths in adapter tests

Test files still used old relative paths for vi.mock() and
vi.importActual() calls. Updated 5 test files to use package exports.
Also broadened regression test patterns to catch mock/importActual paths.

* fix: use rm instead of unlink for symlink cleanup, add warn on failure

Addresses review feedback from Astro-Han:
- rm() handles both symlinks and stale directories (unlink fails on dirs)
- Log a warning when symlink creation fails instead of silent catch

* docs: update import examples to use package exports

Update all documentation, contributing guides, and skills to use
@jackwener/opencli/registry instead of ../../src/registry.js.

Without this, users following the docs would write adapters with
broken imports since the old shim files are no longer created.
2026-04-05 16:02:17 +08:00

116 lines
3.4 KiB
TypeScript

import { beforeAll, describe, expect, it } from 'vitest';
import type { CliCommand } from '@jackwener/opencli/registry';
import { getRegistry } from '@jackwener/opencli/registry';
import { AuthRequiredError, CommandExecutionError } from '@jackwener/opencli/errors';
import { createPageMock } from './test-utils.js';
import './illusts.js';
let cmd: CliCommand;
beforeAll(() => {
cmd = getRegistry().get('pixiv/illusts')!;
expect(cmd?.func).toBeTypeOf('function');
});
describe('pixiv illusts', () => {
it('throws CommandExecutionError on invalid user ID', async () => {
const page = createPageMock([]);
await expect(cmd.func!(page, { 'user-id': 'abc', limit: 5 })).rejects.toThrow(CommandExecutionError);
});
it('throws AuthRequiredError on 401', async () => {
const page = createPageMock([{ __httpError: 401 }]);
await expect(cmd.func!(page, { 'user-id': '11', limit: 5 })).rejects.toThrow(AuthRequiredError);
});
it('throws generic error on non-auth HTTP failure', async () => {
const page = createPageMock([{ __httpError: 500 }]);
await expect(cmd.func!(page, { 'user-id': '11', limit: 5 })).rejects.toThrow(CommandExecutionError);
});
it('returns empty array when user has no illusts', async () => {
const page = createPageMock([
{ body: { illusts: {} } },
]);
const result = await cmd.func!(page, { 'user-id': '11', limit: 5 });
expect(result).toEqual([]);
});
it('fetches illust IDs then batch-fetches details', async () => {
const page = createPageMock([
// Step 1: profile/all returns illust IDs
{
body: {
illusts: { '99999': null, '88888': null, '77777': null },
},
},
// Step 2: batch detail response
{
body: {
works: {
'99999': {
id: '99999',
title: 'Latest Work',
pageCount: 2,
bookmarkCount: 300,
tags: ['original', 'fantasy'],
createDate: '2025-01-15T12:00:00+09:00',
},
'88888': {
id: '88888',
title: 'Older Work',
pageCount: 1,
bookmarkCount: 150,
tags: ['landscape'],
createDate: '2024-12-01T10:00:00+09:00',
},
},
},
},
]);
const result = (await cmd.func!(page, { 'user-id': '11', limit: 3 })) as any[];
// Should be sorted newest first (99999 > 88888 > 77777)
expect(result).toHaveLength(2); // 77777 has no detail data, filtered out
expect(result[0]).toMatchObject({
rank: 1,
title: 'Latest Work',
illust_id: '99999',
pages: 2,
bookmarks: 300,
created: '2025-01-15',
});
expect(result[1]).toMatchObject({
rank: 2,
title: 'Older Work',
illust_id: '88888',
});
});
it('respects the limit on illust IDs fetched', async () => {
const page = createPageMock([
{
body: {
illusts: { '100': null, '200': null, '300': null, '400': null, '500': null },
},
},
{
body: {
works: {
'500': { id: '500', title: 'W5', pageCount: 1, bookmarkCount: 0, tags: [], createDate: '' },
'400': { id: '400', title: 'W4', pageCount: 1, bookmarkCount: 0, tags: [], createDate: '' },
},
},
},
]);
const result = (await cmd.func!(page, { 'user-id': '11', limit: 2 })) as any[];
expect(result).toHaveLength(2);
});
});