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

110 lines
5.0 KiB
JavaScript

import { describe, expect, it, vi } from 'vitest';
import { getRegistry } from '@jackwener/opencli/registry';
import { AuthRequiredError, CliError, CommandExecutionError, EmptyResultError } from '@jackwener/opencli/errors';
import './connections.js';
const { mapConnection } = await import('./connections.js').then((m) => m.__test__);
function makePage({ evaluateResults = [false], cookies = [{ name: 'JSESSIONID', value: '"ajax:12345"' }] } = {}) {
const evaluate = vi.fn();
for (const result of evaluateResults) evaluate.mockResolvedValueOnce(result);
return {
goto: vi.fn().mockResolvedValue(undefined),
wait: vi.fn().mockResolvedValue(undefined),
getCookies: vi.fn().mockResolvedValue(cookies),
evaluate,
};
}
const conn = (id) => ({
createdAt: 1700000000000 + id,
miniProfile: { firstName: 'First' + id, lastName: 'Last' + id, occupation: 'Job ' + id, publicIdentifier: 'user' + id },
});
describe('linkedin connections', () => {
it('maps a connection element to a row', () => {
expect(mapConnection(conn(1), 0)).toEqual({
rank: 1,
name: 'First1 Last1',
occupation: 'Job 1',
public_id: 'user1',
connected_at: 1700000000001,
url: 'https://www.linkedin.com/in/user1',
});
});
it('throws when an element has no miniProfile', () => {
expect(() => mapConnection({ createdAt: 1 }, 0)).toThrow(CommandExecutionError);
});
it('requires a stable public profile identity for every row', () => {
expect(() => mapConnection({
createdAt: 1,
miniProfile: { firstName: 'Only', lastName: 'Name', occupation: 'No id' },
}, 0)).toThrow(CommandExecutionError);
expect(() => mapConnection({
createdAt: 1,
miniProfile: { firstName: 'Bad', lastName: 'Id', publicIdentifier: 'bad/id' },
}, 0)).toThrow(CommandExecutionError);
});
it('fails closed for malformed miniProfile scalar fields', () => {
expect(() => mapConnection({
createdAt: 1,
miniProfile: { firstName: { text: 'Alice' }, lastName: 'Example', publicIdentifier: 'alice' },
}, 0)).toThrow(CommandExecutionError);
});
it('returns rows from the voyager connections API', async () => {
const cmd = getRegistry().get('linkedin/connections');
expect(cmd?.func).toBeTypeOf('function');
const page = makePage({ evaluateResults: [false, { json: { elements: [conn(1), conn(2)] } }] });
const rows = await cmd.func(page, { limit: 2 });
expect(rows.map((r) => r.public_id)).toEqual(['user1', 'user2']);
expect(page.evaluate.mock.calls[1][0]).toContain('/voyager/api/relationships/connections?start=0&count=2');
expect(page.evaluate.mock.calls[1][0]).toContain('ajax:12345');
});
it('throws AuthRequiredError when the connections page is an auth wall', async () => {
const cmd = getRegistry().get('linkedin/connections');
const page = makePage({ evaluateResults: [true] });
await expect(cmd.func(page, { limit: 2 })).rejects.toBeInstanceOf(AuthRequiredError);
});
it('throws AuthRequiredError when JSESSIONID is missing', async () => {
const cmd = getRegistry().get('linkedin/connections');
const page = makePage({ evaluateResults: [false], cookies: [] });
await expect(cmd.func(page, { limit: 2 })).rejects.toBeInstanceOf(AuthRequiredError);
});
it('maps an authRequired fetch result to AuthRequiredError', async () => {
const cmd = getRegistry().get('linkedin/connections');
const page = makePage({ evaluateResults: [false, { authRequired: true, error: 'HTTP 403' }] });
await expect(cmd.func(page, { limit: 2 })).rejects.toBeInstanceOf(AuthRequiredError);
});
it('maps HTML checkpoint responses to AuthRequiredError', async () => {
const cmd = getRegistry().get('linkedin/connections');
const page = makePage({ evaluateResults: [false, { authRequired: true, error: 'HTML auth/checkpoint response' }] });
await expect(cmd.func(page, { limit: 2 })).rejects.toBeInstanceOf(AuthRequiredError);
});
it('fails closed for non-JSON API drift', async () => {
const cmd = getRegistry().get('linkedin/connections');
const page = makePage({ evaluateResults: [false, { error: 'response was not valid JSON' }] });
await expect(cmd.func(page, { limit: 2 })).rejects.toBeInstanceOf(CommandExecutionError);
});
it('throws EmptyResultError when there are no connections', async () => {
const cmd = getRegistry().get('linkedin/connections');
const page = makePage({ evaluateResults: [false, { json: { elements: [] } }] });
await expect(cmd.func(page, { limit: 2 })).rejects.toBeInstanceOf(EmptyResultError);
});
it('rejects invalid limits', async () => {
const cmd = getRegistry().get('linkedin/connections');
const page = makePage();
await expect(cmd.func(page, { limit: 0 })).rejects.toBeInstanceOf(CliError);
});
});