node/test/client.test.mjs
Stefan Schmidt-Egermann 1f2d02b916
feat: initial node content (v0.1.0)
- ESM-Package für Node.js 18+
- Client mit Sync, Async (mit Polling), Webhook, Verify, Download
- Typisierte Error-Klassen
- Webhook-Signatur-Verifikation (timingSafeEqual)
- 13 Tests mit Node native test runner
2026-04-25 12:26:04 +02:00

172 lines
6.7 KiB
JavaScript

// Tests für @hightrusted/capture
// Node.js native test runner (Node 18+)
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { createHmac } from 'node:crypto';
import {
Client,
HightrustedError,
InvalidApiKeyError,
QuotaExceededError,
RateLimitedError,
verifyWebhookSignature,
} from '../src/index.mjs';
// ─────────────────────────────────────────────────────────────────────
// Mock-fetch helper
// ─────────────────────────────────────────────────────────────────────
function mockFetch(handler) {
const calls = [];
const fetch = async (url, init) => {
calls.push({ url, init });
return handler(url, init);
};
return { fetch, calls };
}
function jsonResponse(body, { status = 200, headers = {} } = {}) {
return new Response(JSON.stringify(body), {
status,
headers: { 'Content-Type': 'application/json', ...headers },
});
}
// ─────────────────────────────────────────────────────────────────────
test('Client wirft, wenn kein API-Key gesetzt ist', () => {
const old = process.env.HIGHTRUSTED_API_KEY;
delete process.env.HIGHTRUSTED_API_KEY;
assert.throws(() => new Client(), /API-Key fehlt/);
if (old) process.env.HIGHTRUSTED_API_KEY = old;
});
test('Client liest API-Key aus ENV', () => {
const old = process.env.HIGHTRUSTED_API_KEY;
process.env.HIGHTRUSTED_API_KEY = 'ht_test_env';
const c = new Client({ fetch: () => {} });
assert.equal(c.apiKey, 'ht_test_env');
if (old) process.env.HIGHTRUSTED_API_KEY = old;
else delete process.env.HIGHTRUSTED_API_KEY;
});
test('capture() sendet Bearer-Token', async () => {
const { fetch, calls } = mockFetch(() =>
jsonResponse({
id: '550e',
status: 'ready',
verify_url: 'https://verify.hightrusted.net/c/550e',
created_at: '2026-04-25T00:00:00Z',
})
);
const c = new Client({ apiKey: 'ht_test_xyz', fetch });
const result = await c.capture({ url: 'https://example.com' });
assert.equal(result.id, '550e');
assert.equal(calls[0].init.headers.Authorization, 'Bearer ht_test_xyz');
const body = JSON.parse(calls[0].init.body);
assert.equal(body.url, 'https://example.com');
});
test('capture() mit reference + viewport', async () => {
const { fetch, calls } = mockFetch(() =>
jsonResponse({ id: 'x', status: 'ready', created_at: '2026-04-25T00:00:00Z' })
);
const c = new Client({ apiKey: 'ht_test_xyz', fetch });
await c.capture({
url: 'https://example.com',
reference: 'case-001',
viewport: { width: 1920, height: 1080 },
});
const body = JSON.parse(calls[0].init.body);
assert.equal(body.reference, 'case-001');
assert.equal(body.viewport.width, 1920);
});
test('verify() ohne Argumente wirft', async () => {
const c = new Client({ apiKey: 'ht_test_xyz', fetch: () => {} });
await assert.rejects(() => c.verify(), /Genau einer/);
await assert.rejects(() => c.verify({ source: 'x', pdf: Buffer.from('y') }), /Genau einer/);
});
test('verify() per ID', async () => {
const { fetch, calls } = mockFetch(() => jsonResponse({ valid: true, capture_id: '550e' }));
const c = new Client({ apiKey: 'ht_test_xyz', fetch });
const result = await c.verify({ source: '550e8400-e29b-41d4-a716-446655440000' });
assert.equal(result.valid, true);
const body = JSON.parse(calls[0].init.body);
assert.equal(body.source, '550e8400-e29b-41d4-a716-446655440000');
});
test('InvalidApiKeyError bei 401', async () => {
const { fetch } = mockFetch(() =>
jsonResponse(
{ error: { code: 'invalid_api_key', message: 'Bad key', request_id: 'r1' } },
{ status: 401 }
)
);
const c = new Client({ apiKey: 'ht_test_xyz', fetch, maxRetries: 0 });
await assert.rejects(() => c.capture({ url: 'https://example.com' }), (err) => {
assert.ok(err instanceof InvalidApiKeyError);
assert.equal(err.code, 'invalid_api_key');
assert.equal(err.requestId, 'r1');
return true;
});
});
test('QuotaExceededError bei 402', async () => {
const { fetch } = mockFetch(() =>
jsonResponse(
{ error: { code: 'quota_exceeded', message: 'X', request_id: 'r1' } },
{ status: 402 }
)
);
const c = new Client({ apiKey: 'ht_test_xyz', fetch, maxRetries: 0 });
await assert.rejects(() => c.capture({ url: 'https://example.com' }), QuotaExceededError);
});
test('RateLimitedError mit retryAfterSeconds', async () => {
const { fetch } = mockFetch(() =>
jsonResponse(
{ error: { code: 'rate_limited', message: 'X', request_id: 'r1' } },
{ status: 429, headers: { 'Retry-After': '12' } }
)
);
const c = new Client({ apiKey: 'ht_test_xyz', fetch, maxRetries: 0 });
await assert.rejects(() => c.capture({ url: 'https://example.com' }), (err) => {
assert.ok(err instanceof RateLimitedError);
assert.equal(err.retryAfterSeconds, 12);
return true;
});
});
// ─────────────────────────────────────────────────────────────────────
// Webhook-Signatur
// ─────────────────────────────────────────────────────────────────────
test('verifyWebhookSignature akzeptiert korrekte Signatur', () => {
const body = Buffer.from('{"event":"capture.ready"}');
const secret = 'wh_secret_test';
const sig = 'sha256=' + createHmac('sha256', secret).update(body).digest('hex');
assert.equal(verifyWebhookSignature(body, sig, secret), true);
});
test('verifyWebhookSignature lehnt falsche Signatur ab', () => {
const body = Buffer.from('{"event":"capture.ready"}');
const badSig = 'sha256=' + '0'.repeat(64);
assert.equal(verifyWebhookSignature(body, badSig, 'secret'), false);
});
test('verifyWebhookSignature lehnt leere Inputs ab', () => {
assert.equal(verifyWebhookSignature('', 'sha256=x', 'secret'), false);
assert.equal(verifyWebhookSignature('x', '', 'secret'), false);
assert.equal(verifyWebhookSignature('x', 'sha256=x', ''), false);
});
test('verifyWebhookSignature akzeptiert auch String-Body', () => {
const body = '{"event":"capture.ready"}';
const secret = 'wh_secret_test';
const sig = 'sha256=' + createHmac('sha256', secret).update(body).digest('hex');
assert.equal(verifyWebhookSignature(body, sig, secret), true);
});