Files
sdk/sdks/javascript/test/client.test.ts
T

83 lines
3.2 KiB
TypeScript
Raw Normal View History

2026-05-29 12:46:42 -05:00
import { describe, expect, it, vi } from 'vitest';
import { SocialhoseClient, SocialhoseError } from '../src/index';
const ok = (body: unknown, status = 200) =>
new Response(JSON.stringify(body), { status, headers: { 'content-type': 'application/json' } });
describe('SocialhoseClient', () => {
it('sends Api-Key auth, browser-like user-agent, and query params', async () => {
const fetchMock = vi.fn(async () => ok({ count: 0, next: null, previous: null, results: [] }));
const client = new SocialhoseClient({ apiKey: 'test-key', fetch: fetchMock, cacheTtlMs: 0 });
await client.getMentions({ page: 2, platforms: 'twitter', content_search: 'hospital' });
expect(fetchMock).toHaveBeenCalledTimes(1);
const [url, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit];
expect(url).toBe(
'https://socialhose.net/api/public/v1/mentions/?page=2&platforms=twitter&content_search=hospital',
);
expect(init.headers).toMatchObject({
Authorization: 'Api-Key test-key',
Accept: 'application/json',
});
expect((init.headers as Record<string, string>)['User-Agent']).toContain('Mozilla/5.0');
});
it('caches identical GET requests inside the configured TTL', async () => {
const fetchMock = vi.fn(async () => ok({ count: 1, next: null, previous: null, results: [] }));
const client = new SocialhoseClient({ apiKey: 'test-key', fetch: fetchMock, cacheTtlMs: 60_000 });
await client.getMentions({ page: 1 });
await client.getMentions({ page: 1 });
expect(fetchMock).toHaveBeenCalledTimes(1);
});
it('retries rate limits and transient server failures', async () => {
const fetchMock = vi
.fn()
.mockResolvedValueOnce(new Response('rate limited', { status: 429 }))
.mockResolvedValueOnce(new Response('bad gateway', { status: 502 }))
.mockResolvedValueOnce(ok({ results: [] }));
const client = new SocialhoseClient({
apiKey: 'test-key',
fetch: fetchMock,
cacheTtlMs: 0,
retryDelayMs: () => 0,
});
const campaigns = await client.getCampaigns();
expect(campaigns).toEqual([]);
expect(fetchMock).toHaveBeenCalledTimes(3);
});
it('throws a structured SocialhoseError for non-ok responses', async () => {
const fetchMock = vi.fn(async () => new Response('{"detail":"forbidden"}', { status: 403 }));
const client = new SocialhoseClient({ apiKey: 'test-key', fetch: fetchMock, cacheTtlMs: 0 });
await expect(client.getCampaign('abc')).rejects.toMatchObject({
name: 'SocialhoseError',
status: 403,
path: '/campaigns/abc/',
} satisfies Partial<SocialhoseError>);
});
it('normalizes mailing-list invite outcomes', async () => {
const fetchMock = vi.fn(async () => ok({ detail: 'already invited' }, 409));
const client = new SocialhoseClient({ apiKey: 'test-key', fetch: fetchMock });
await expect(client.inviteMailingListMember('list-1', { email: 'a@example.com' })).resolves.toEqual({
outcome: 'already',
detail: 'already invited',
});
});
it('defers missing apiKey errors until the first request', async () => {
const client = new SocialhoseClient({ apiKey: '', fetch: vi.fn() });
await expect(client.getCampaigns()).rejects.toThrow('Socialhose apiKey is required');
});
});