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

173 lines
6.8 KiB
JavaScript

import { describe, expect, it, vi } from 'vitest';
import { getRegistry } from '@jackwener/opencli/registry';
import { CommandExecutionError } from '@jackwener/opencli/errors';
import './salesnav-message.js';
const {
parseSalesProfileUrn,
parseRecipient,
salesLeadUrlFromParts,
profileApiUrl,
buildCreateMessagePayload,
extractRemainingCredits,
profileSummary,
requireProfileSummary,
salesPageShowsSentMessage,
} = await import('./salesnav-message.js').then((m) => m.__test__);
function createPageMock(evaluateResults = []) {
const evaluate = vi.fn();
for (const result of evaluateResults) evaluate.mockResolvedValueOnce(result);
evaluate.mockResolvedValue(undefined);
return {
goto: vi.fn().mockResolvedValue(undefined),
wait: vi.fn().mockResolvedValue(undefined),
evaluate,
getCookies: vi.fn().mockResolvedValue([{ name: 'JSESSIONID', value: '"csrf"', domain: '.linkedin.com' }]),
};
}
describe('linkedin salesnav-message command', () => {
it('parses Sales Navigator profile urns and lead URLs', () => {
const urn = 'urn:li:fs_salesProfile:(ACwAAAJS8TABxyz,NAME_SEARCH,Enlo)';
expect(parseSalesProfileUrn(urn)).toMatchObject({
profileId: 'ACwAAAJS8TABxyz',
authType: 'NAME_SEARCH',
authToken: 'Enlo',
entityUrn: urn,
});
expect(parseSalesProfileUrn('urn:li:fs_salesProfile:(ACwAAAJS8TABxyz,undefined,undefined)')).toBeNull();
const parsed = parseRecipient('https://www.linkedin.com/sales/lead/ACwAAAJS8TABxyz,NAME_SEARCH,Enlo');
expect(parsed).toMatchObject({ profileId: 'ACwAAAJS8TABxyz', authType: 'NAME_SEARCH', authToken: 'Enlo' });
expect(parsed.entityUrn).toBe(urn);
expect(salesLeadUrlFromParts(parsed)).toBe('https://www.linkedin.com/sales/lead/ACwAAAJS8TABxyz,NAME_SEARCH,Enlo');
});
it('accepts LinkedIn /in tokens as unresolved recipients', () => {
expect(parseRecipient('https://www.linkedin.com/in/ACwAAAJS8TABxyz/')).toMatchObject({
profileId: 'ACwAAAJS8TABxyz',
authType: '',
authToken: '',
entityUrn: '',
});
});
it('builds profile API URLs with the Sales Navigator auth key', () => {
const url = profileApiUrl({ profileId: 'P1', authType: 'NAME_SEARCH', authToken: 'T1' });
expect(url).toContain('/sales-api/salesApiProfiles/(profileId:P1,authType:NAME_SEARCH,authToken:T1)');
expect(url).toContain('decoration=');
});
it('constructs the createMessage action payload used by Sales Navigator', () => {
const payload = buildCreateMessagePayload({
recipientUrn: 'urn:li:fs_salesProfile:(P1,NAME_SEARCH,T1)',
subject: 'Quick QA doc question',
body: 'Hi Jane, can I ask a quick question?',
trackingId: '0123456789abcdef',
copyToCrm: false,
});
expect(payload).toEqual({
createMessageRequest: {
recipients: ['urn:li:fs_salesProfile:(P1,NAME_SEARCH,T1)'],
subject: 'Quick QA doc question',
body: 'Hi Jane, can I ask a quick question?',
copyToCrm: false,
trackingId: '0123456789abcdef',
},
});
});
it('validates payload fields before any send attempt', () => {
expect(() => buildCreateMessagePayload({ recipientUrn: '', subject: 's', body: 'b' })).toThrow();
expect(() => buildCreateMessagePayload({ recipientUrn: 'urn:li:fs_salesProfile:(P,A,T)', subject: '', body: 'b' })).toThrow();
expect(() => buildCreateMessagePayload({ recipientUrn: 'urn:li:fs_salesProfile:(P,A,T)', subject: 's', body: '' })).toThrow();
expect(() => buildCreateMessagePayload({ recipientUrn: 'urn:li:fs_salesProfile:(P,A,T)', subject: 'x'.repeat(201), body: 'b' })).toThrow();
expect(() => buildCreateMessagePayload({ recipientUrn: 'urn:li:fs_salesProfile:(P,undefined,undefined)', subject: 's', body: 'b' })).toThrow();
});
it('detects the Sales Navigator sent activity on a verified lead page', () => {
expect(salesPageShowsSentMessage('5/18/2026 You sent a Sales Navigator message to Jane', 'Jane Q')).toBe(true);
expect(salesPageShowsSentMessage('No recent activity', 'Jane Q')).toBe(false);
});
it('extracts a plausible remaining InMail credit count', () => {
expect(extractRemainingCredits({ elements: [{ type: 'LSS_INMAIL', value: 149, id: 1 }], paging: { count: 10 } })).toBe(149);
expect(extractRemainingCredits({ data: { remaining: 149, used: 1 } })).toBe(149);
expect(extractRemainingCredits({ elements: [{ availableCount: 12 }] })).toBe(12);
expect(extractRemainingCredits({})).toBe(null);
});
it('summarizes decorated Sales Navigator profile data', () => {
expect(profileSummary({ data: {
fullName: 'Rayki Goh',
headline: 'Food Safety',
degree: 3,
defaultPosition: { title: 'FSQA Manager', companyName: 'Acme Foods' },
memberBadges: { openLink: false },
} })).toMatchObject({
recipient: 'Rayki Goh',
title: 'FSQA Manager',
company: 'Acme Foods',
degree: '3',
open_link: false,
});
});
it('fails typed when decorated profile data has no recipient identity', () => {
expect(() => requireProfileSummary({ data: { defaultPosition: { title: 'FSQA' } } }))
.toThrow(CommandExecutionError);
});
it('keeps manifest columns aligned with dry-run rows and fails typed on malformed profile API', async () => {
const cmd = getRegistry().get('linkedin/salesnav-message');
expect(cmd?.columns).toEqual([
'status',
'recipient',
'title',
'company',
'credits_remaining',
'credits_before',
'credits_after',
'sent_in_salesnav',
'message_chars',
'subject_chars',
'recipient_urn',
'degree',
'inmail_restriction',
'open_link',
]);
const goodPage = createPageMock([
{ status: 200, json: { data: { fullName: 'Jane Doe', defaultPosition: { title: 'QA', companyName: 'Acme' }, degree: 2, inmailRestriction: 'NO_RESTRICTION', memberBadges: { openLink: true } } } },
{ status: 200, json: { elements: [{ type: 'LSS_INMAIL', value: 12 }] } },
]);
const rows = await cmd.func(goodPage, {
recipient: 'urn:li:fs_salesProfile:(P1,NAME_SEARCH,T1)',
subject: 'Hello',
body: 'Quick question',
});
expect(Object.keys(rows[0]).sort()).toEqual([...cmd.columns].sort());
expect(rows[0]).toMatchObject({
status: 'validated_dry_run',
recipient: 'Jane Doe',
credits_remaining: 12,
credits_before: 12,
credits_after: '',
sent_in_salesnav: false,
degree: '2',
inmail_restriction: 'NO_RESTRICTION',
open_link: true,
});
const malformedPage = createPageMock([
{ status: 200, json: { data: { defaultPosition: { title: 'QA' } } } },
]);
await expect(cmd.func(malformedPage, {
recipient: 'urn:li:fs_salesProfile:(P1,NAME_SEARCH,T1)',
subject: 'Hello',
body: 'Quick question',
})).rejects.toThrow(CommandExecutionError);
});
});