Files
rudder-js-sdk/test/e2e-prod/_setup/harness.ts
T

108 lines
3.4 KiB
TypeScript
Raw Normal View History

2026-08-12 14:02:25 +03:00
// Test-side helpers: load the seed artifact, build SDK clients against prod, and
// reach the admin API for wallet grants / player inspection.
import { RudderClient } from '../../../src/client/RudderClient.js';
import type { RemoteConfigShape } from '../../../src/state/RemoteConfigState.js';
import type { TokenStore } from '../../../src/token/TokenStore.js';
import { createAdminClient, type AdminClient } from './adminClient.js';
import { readArtifact } from './artifact.js';
import type { SeedArtifact } from './types.js';
type RuntimeOptions = {
loginEvent?: string | null;
};
/** Loads the seed artifact; RUDDER_E2E_* env vars override file values. */
export function loadArtifact(): SeedArtifact {
const file = readArtifact();
if (!file) {
throw new Error(
'No seed artifact (.e2e-prod.local.json). Run `npm run test:e2e-prod` (globalSetup seeds automatically).',
);
}
return {
...file,
baseUrl: process.env.RUDDER_E2E_BASE_URL ?? file.baseUrl,
projectKey: process.env.RUDDER_E2E_PROJECT_KEY ?? file.projectKey,
};
}
export function createInMemoryTokenStore(): TokenStore {
let access: string | null = null;
let refresh: string | null = null;
return {
getAccessToken: () => access,
getRefreshToken: () => refresh,
saveTokens: (a, r) => {
access = a;
refresh = r;
},
clear: () => {
access = null;
refresh = null;
},
};
}
/** A client with no auth yet (in-memory tokens, no IndexedDB persistence). */
export function makeProdClient<TConfig extends RemoteConfigShape = RemoteConfigShape>(
artifact: SeedArtifact = loadArtifact(),
runtime: RuntimeOptions = { loginEvent: null },
2026-08-12 14:02:25 +03:00
): RudderClient<TConfig> {
return new RudderClient<TConfig>({
baseUrl: artifact.baseUrl,
projectKey: artifact.projectKey,
tokenStore: createInMemoryTokenStore(),
runtime,
});
}
/** A device-authenticated client. In Node each call mints a fresh player (ephemeral device id). */
export async function freshPlayer<TConfig extends RemoteConfigShape = RemoteConfigShape>(
artifact: SeedArtifact = loadArtifact(),
runtime?: RuntimeOptions,
): Promise<RudderClient<TConfig>> {
const client = makeProdClient<TConfig>(artifact, runtime);
await client.auth.loginWithDevice({ region: 'en', language: 'en' });
return client;
}
/** Admin (operator) API client authenticated with the seeded operator token. */
export function adminApi(artifact: SeedArtifact = loadArtifact()): AdminClient {
const api = createAdminClient(artifact.baseUrl);
api.setToken(artifact.adminToken);
return api;
}
/** Credits a player's wallet through the admin batch endpoint. */
export async function grantCurrency(
artifact: SeedArtifact,
playerId: string,
currencyCode: string,
amount: number,
): Promise<void> {
await adminApi(artifact).post(
`/platform/v1/projects/${artifact.projectId}/players/wallet/adjust`,
{ items: [{ playerId, currencyCode, amount, reason: 'e2e grant' }] },
);
}
2026-08-12 14:02:25 +03:00
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
/** Retries a read until ok(result) holds — absorbs release/cache reload lag. */
export async function withReadRetry<T>(
fn: () => Promise<T>,
ok: (value: T) => boolean,
tries = 8,
gapMs = 1500,
): Promise<T> {
let last = await fn();
if (ok(last)) return last;
for (let i = 1; i < tries; i++) {
await sleep(gapMs);
last = await fn();
if (ok(last)) return last;
}
return last;
}