Files

67 lines
3.8 KiB
JavaScript

import { test } from 'node:test';
import assert from 'node:assert/strict';
import { createClient } from '../dist/index.js';
const identity = { anonymous_id: 'anonymous', master_id: 'master', anonymous_token: 'signed-anonymous-token' };
const response = (body, status = 200) => new Response(JSON.stringify(body), { status, headers: { 'Content-Type': 'application/json' } });
test('concurrent tracks initialize once, reuse identity and replay the same event envelope', async () => {
const calls = [];
globalThis.fetch = async (url, options) => {
calls.push({ url, options, body: JSON.parse(options.body) });
if (url.endsWith('/anonymous')) return response(identity);
return response({ ...identity, event_id: JSON.parse(options.body).event_id, duplicate: false });
};
const values = new Map();
const client = createClient({ baseUrl: 'https://api.example/', appId: 'shop', collectionKey: 'collection-demo', storage: { getItem: (key) => values.get(key) ?? null, setItem: (key, value) => values.set(key, value) } });
const properties = { product_id: 'camera', quantity: 1 };
const prepared = client.prepareEvent('add_cart', properties);
properties.quantity = 999;
await Promise.all([client.sendEvent(prepared), client.track('page', { page_id: 'home' })]);
await client.sendEvent(prepared);
assert.equal(calls.filter((call) => call.url.endsWith('/anonymous')).length, 1);
const events = calls.filter((call) => call.url.endsWith('/events'));
assert.equal(events[0].body.properties.quantity, 1);
assert.equal(events[0].body.event_id, events[2].body.event_id);
assert.equal(events[0].body.occurred_at, events[2].body.occurred_at);
assert.equal(events[0].body.anonymous_token, identity.anonymous_token);
assert.equal(events[0].options.headers['X-Adcloud-Key'], 'collection-demo');
assert.equal(events[0].options.credentials, 'include');
assert.equal(values.size, 1);
});
test('initialization failure can retry and persisted anonymous token is restored', async () => {
let calls = 0;
globalThis.fetch = async (_url, options) => {
calls++;
assert.equal(JSON.parse(options.body).anonymous_token, identity.anonymous_token);
return calls === 1 ? response({ detail: 'Unavailable' }, 503) : response(identity);
};
const client = createClient({ baseUrl: 'https://api.example', storage: { getItem: () => identity.anonymous_token, setItem: () => {} } });
await assert.rejects(client.initialize(), /HTTP 503/);
assert.deepEqual(await client.initialize(), identity);
});
test('tracking requires collection configuration and cannot send another app envelope', async () => {
assert.throws(() => createClient({ baseUrl: 'https://api.example', storage: null }).prepareEvent('page', {}), /requires appId/);
const client = createClient({ baseUrl: 'https://api.example', appId: 'shop', collectionKey: 'collection-demo', storage: null });
await assert.rejects(client.sendEvent({ ...client.prepareEvent('page', {}), app_id: 'other' }), /configured application/);
});
test('login identity update and logout rotation replace the cached tracking identity', async () => {
const requests = [];
const next = { ...identity, anonymous_id: 'new-anonymous', anonymous_token: 'new-signed-token' };
globalThis.fetch = async (url, options) => {
requests.push({ url, body: JSON.parse(options.body) });
if (url.endsWith('?rotate=true')) return response(next);
return response(identity);
};
const client = createClient({ baseUrl: 'https://api.example', appId: 'shop', collectionKey: 'collection-demo', storage: null });
client.updateIdentity(next);
await client.track('page', { page_id: 'home' });
assert.equal(requests[0].body.anonymous_token, next.anonymous_token);
await client.resetIdentity();
assert.equal(requests[1].url, 'https://api.example/v1/identity/anonymous?rotate=true');
assert.throws(() => client.updateIdentity({}), /Invalid anonymous identity/);
});