@@ -0,0 +1,99 @@
|
||||
// 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 = {
|
||||
planStateStore?: {
|
||||
state: string | null;
|
||||
load(): Promise<void>;
|
||||
save(state: string | null): Promise<void>;
|
||||
} | null;
|
||||
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 = { planStateStore: null, loginEvent: null },
|
||||
): 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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user