Slug-based scenarios, quests and offers per the environments contract; regenerated models; e2e seed passes environment to admin mirrors
Claude-Session: https://claude.ai/code/session_01SMCvdwDmuxoaqGgvGBLk1V
This commit is contained in:
@@ -184,7 +184,7 @@ describe('lazy scenario runtime', () => {
|
||||
return Promise.resolve(new Response(JSON.stringify({
|
||||
effects: [{
|
||||
runId: 'run-1',
|
||||
scenarioId: 'scenario-1',
|
||||
scenarioSlug: 'scenario-1',
|
||||
nodeId: 'start',
|
||||
type: 'notification',
|
||||
data: { message: 'Welcome!' },
|
||||
|
||||
@@ -88,7 +88,7 @@ describe('ScenarioService', () => {
|
||||
});
|
||||
}
|
||||
if (path === '/sdk/v1/stores/starter') {
|
||||
return jsonResponse({ slug: 'starter', name: 'Starter', offers: [{ id: 'pack_1', name: 'Pack' }] });
|
||||
return jsonResponse({ slug: 'starter', name: 'Starter', offers: [{ id: 'pack_1', slug: 'pack-1', name: 'Pack' }] });
|
||||
}
|
||||
return jsonResponse({});
|
||||
});
|
||||
@@ -203,7 +203,7 @@ describe('ScenarioService', () => {
|
||||
return url.includes('/sdk/v1/scenarios/callback');
|
||||
});
|
||||
expect(JSON.parse(callbackCall![1]!.body as string)).toEqual({
|
||||
scenarioId: 'scenario-1',
|
||||
scenarioSlug: 'scenario-1',
|
||||
nodeId: 'n1',
|
||||
handle: 'output',
|
||||
runId: 'run-1',
|
||||
@@ -227,7 +227,7 @@ describe('ScenarioService', () => {
|
||||
expect(onCompleted).toHaveBeenCalledOnce();
|
||||
expect(onCompleted.mock.calls[0][0]).toEqual({
|
||||
runId: 'run-1',
|
||||
scenarioId: 'scenario-1',
|
||||
scenarioSlug: 'scenario-1',
|
||||
});
|
||||
expect(scenarios.isRunning).toBe(false);
|
||||
});
|
||||
@@ -359,10 +359,10 @@ describe('ScenarioService', () => {
|
||||
return jsonResponse({
|
||||
name: 'Starter',
|
||||
slug: 'starter',
|
||||
offers: [{ id: 'pack_1', name: 'Starter Pack' }],
|
||||
offers: [{ id: 'pack_1', slug: 'pack-1', name: 'Starter Pack' }],
|
||||
});
|
||||
}
|
||||
if (method === 'POST' && path === '/sdk/v1/stores/starter/offers/pack_1/purchase') {
|
||||
if (method === 'POST' && path === '/sdk/v1/stores/starter/offers/pack-1/purchase') {
|
||||
return jsonResponse({ success: true, purchaseId: 'purchase-1' });
|
||||
}
|
||||
return jsonResponse({});
|
||||
@@ -387,7 +387,7 @@ describe('ScenarioService', () => {
|
||||
return jsonResponse({ effects: [effect('store', { storeSlug: 'starter' })] });
|
||||
}
|
||||
if (path === '/sdk/v1/stores/starter') {
|
||||
return jsonResponse({ slug: 'starter', offers: [{ id: 'pack_1' }] });
|
||||
return jsonResponse({ slug: 'starter', offers: [{ id: 'pack_1', slug: 'pack-1' }] });
|
||||
}
|
||||
return jsonResponse({});
|
||||
});
|
||||
|
||||
@@ -82,7 +82,7 @@ export async function grantCurrency(
|
||||
amount: number,
|
||||
): Promise<void> {
|
||||
await adminApi(artifact).post(
|
||||
`/platform/v1/projects/${artifact.projectId}/players/wallet/adjust`,
|
||||
`/platform/v1/projects/${artifact.projectId}/players/wallet/adjust?environment=${artifact.environment}`,
|
||||
{ items: [{ playerId, currencyCode, amount, reason: 'e2e grant' }] },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ interface ReleaseResponse {
|
||||
|
||||
interface QuestResponse {
|
||||
id: string;
|
||||
slug: string;
|
||||
name: string;
|
||||
status: string;
|
||||
objectives?: Array<{ id: string; metric: string; target: number }>;
|
||||
@@ -28,10 +29,10 @@ interface QuestResponse {
|
||||
interface StoreResponse {
|
||||
id: string;
|
||||
name: string;
|
||||
slug?: string;
|
||||
data?: { slug?: string };
|
||||
slug: string;
|
||||
offers?: Array<{
|
||||
id: string;
|
||||
slug: string;
|
||||
name: string;
|
||||
contents?: Array<{ itemId: string; amount: number }>;
|
||||
}>;
|
||||
@@ -176,18 +177,24 @@ export async function runSeed(
|
||||
});
|
||||
|
||||
// 7. Store with a free offer and a paid (currency) offer with a purchase limit.
|
||||
// The runtime resolves a store's slug from data.slug, and the CreateStore API has
|
||||
// no slug field — so we set it via data.
|
||||
// Stores and offers carry an authored slug that survives promotes.
|
||||
const storeSlug = 'starter-shop';
|
||||
const paidOfferSlug = 'gold-pack';
|
||||
const store = await api.post<StoreResponse>(`${projPath}/stores${q}`, {
|
||||
projectId: project.id,
|
||||
environment: authoringEnv,
|
||||
slug: storeSlug,
|
||||
name: 'Starter Shop',
|
||||
description: 'E2E shop',
|
||||
data: { slug: storeSlug },
|
||||
offers: [
|
||||
{ name: 'Welcome Gift', contents: [{ itemId: item.id, amount: 10 }], price: { currency, amount: 0 } },
|
||||
{
|
||||
slug: 'welcome-gift',
|
||||
name: 'Welcome Gift',
|
||||
contents: [{ itemId: item.id, amount: 10 }],
|
||||
price: { currency, amount: 0 },
|
||||
},
|
||||
{
|
||||
slug: paidOfferSlug,
|
||||
name: 'Gold Pack',
|
||||
contents: [{ itemId: item.id, amount: 50 }],
|
||||
price: { currency, amount: 100 },
|
||||
@@ -196,20 +203,18 @@ export async function runSeed(
|
||||
],
|
||||
});
|
||||
log(`store ${storeSlug} (${store.id}) with ${store.offers?.length ?? 0} offers`);
|
||||
const paidOffer = store.offers?.find((o) => o.name === 'Gold Pack');
|
||||
if (!paidOffer?.id) {
|
||||
throw new Error('seeded paid offer did not return an id');
|
||||
}
|
||||
|
||||
// 8. Global quest: buying the paid offer completes it; claim grants extra item reward.
|
||||
const questName = 'Buy Gold Pack Quest';
|
||||
const questObjectiveId = 'buy-gold-pack';
|
||||
const questTarget = 1;
|
||||
const questMetric = `purchase.offer:${paidOffer.id}`;
|
||||
const questSlug = 'buy-gold-pack-quest';
|
||||
const questMetric = `purchase.offer:${paidOfferSlug}`;
|
||||
const questRewardAmount = 7;
|
||||
const quest = await api.post<QuestResponse>(`${projPath}/quests${q}`, {
|
||||
projectId: project.id,
|
||||
environment: authoringEnv,
|
||||
slug: questSlug,
|
||||
name: questName,
|
||||
objectives: [{ id: questObjectiveId, metric: questMetric, target: questTarget }],
|
||||
rewards: [{ itemId: item.id, amount: questRewardAmount }],
|
||||
@@ -220,9 +225,11 @@ export async function runSeed(
|
||||
const startAt = new Date(ts - 3600_000).toISOString();
|
||||
const endAt = new Date(ts + 365 * 24 * 3600_000).toISOString();
|
||||
const scenarioEvent = 'session_start';
|
||||
const scenarioSlug = 'onboarding';
|
||||
const scenario = await api.post<{ id: string }>(`${projPath}/scenarios${q}`, {
|
||||
projectId: project.id,
|
||||
environment: authoringEnv,
|
||||
slug: scenarioSlug,
|
||||
name: 'Onboarding',
|
||||
description: 'e2e onboarding',
|
||||
startAt,
|
||||
@@ -230,9 +237,8 @@ export async function runSeed(
|
||||
});
|
||||
|
||||
// 10. Scenario flow (trigger -> notification -> rc override -> store -> condition -> notification).
|
||||
await api.put(`${projPath}/scenarios/${scenario.id}`, {
|
||||
await api.put(`${projPath}/scenarios/${scenario.id}?environment=${authoringEnv}`, {
|
||||
projectId: project.id,
|
||||
scenarioId: scenario.id,
|
||||
flow: buildFlow(storeSlug),
|
||||
});
|
||||
log(`scenario ${scenario.id} flow set`);
|
||||
@@ -245,15 +251,15 @@ export async function runSeed(
|
||||
});
|
||||
await pollRelease(api, projPath, runtimeEnv, release.id, log);
|
||||
|
||||
// Promotion re-creates every entity with fresh ids — read the prod ones back.
|
||||
// Promotion upserts by slug: prod keeps its own ids, so read the prod rows back by slug.
|
||||
const prodStores = await api.get<StoreResponse[]>(`${projPath}/stores${prodQ}`);
|
||||
const prodStore = prodStores.find((s) => s.data?.slug === storeSlug || s.name === 'Starter Shop');
|
||||
const prodStore = prodStores.find((s) => s.slug === storeSlug);
|
||||
if (!prodStore) {
|
||||
throw new Error(`promoted store ${storeSlug} was not found in ${runtimeEnv}`);
|
||||
}
|
||||
const prodPaidOffer = prodStore.offers?.find((o) => o.name === 'Gold Pack');
|
||||
if (!prodPaidOffer?.id) {
|
||||
throw new Error('promoted paid offer did not return an id');
|
||||
const prodPaidOffer = prodStore.offers?.find((o) => o.slug === paidOfferSlug);
|
||||
if (!prodPaidOffer?.slug) {
|
||||
throw new Error('promoted paid offer did not return a slug');
|
||||
}
|
||||
const prodRewardItemId = prodPaidOffer.contents?.[0]?.itemId;
|
||||
if (!prodRewardItemId) {
|
||||
@@ -261,25 +267,25 @@ export async function runSeed(
|
||||
}
|
||||
|
||||
const prodQuests = await api.get<QuestResponse[]>(`${projPath}/quests${prodQ}`);
|
||||
const prodQuest = prodQuests.find((q) => q.name === questName);
|
||||
if (!prodQuest?.id) {
|
||||
throw new Error(`promoted quest ${questName} was not found in ${runtimeEnv}`);
|
||||
const prodQuest = prodQuests.find((qq) => qq.slug === questSlug);
|
||||
if (!prodQuest?.slug) {
|
||||
throw new Error(`promoted quest ${questSlug} was not found in ${runtimeEnv}`);
|
||||
}
|
||||
const prodQuestMetric = prodQuest.objectives?.find((o) => o.id === questObjectiveId)?.metric;
|
||||
if (!prodQuestMetric) {
|
||||
throw new Error(`promoted quest ${questName} did not return metric ${questObjectiveId}`);
|
||||
throw new Error(`promoted quest ${questSlug} did not return metric ${questObjectiveId}`);
|
||||
}
|
||||
const prodQuestRewardItemId = prodQuest.rewards?.[0]?.itemId;
|
||||
if (!prodQuestRewardItemId) {
|
||||
throw new Error(`promoted quest ${questName} did not return reward item id`);
|
||||
throw new Error(`promoted quest ${questSlug} did not return reward item id`);
|
||||
}
|
||||
|
||||
const prodScenarios = await api.get<Array<{ id: string; name: string }>>(
|
||||
const prodScenarios = await api.get<Array<{ id: string; slug: string; name: string }>>(
|
||||
`${projPath}/scenarios${prodQ}`,
|
||||
);
|
||||
const prodScenario = prodScenarios.find((s) => s.name === 'Onboarding');
|
||||
if (!prodScenario?.id) {
|
||||
throw new Error(`promoted scenario Onboarding was not found in ${runtimeEnv}`);
|
||||
const prodScenario = prodScenarios.find((s) => s.slug === scenarioSlug);
|
||||
if (!prodScenario?.slug) {
|
||||
throw new Error(`promoted scenario ${scenarioSlug} was not found in ${runtimeEnv}`);
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -296,14 +302,14 @@ export async function runSeed(
|
||||
slug: storeSlug,
|
||||
freeOfferName: 'Welcome Gift',
|
||||
paidOfferName: 'Gold Pack',
|
||||
paidOfferId: prodPaidOffer.id,
|
||||
paidOfferSlug: prodPaidOffer.slug,
|
||||
paidPrice: { currency, amount: 100 },
|
||||
paidMaxPurchases: 2,
|
||||
grantedItemName: 'Gold Coin',
|
||||
paidGrantAmount: 50,
|
||||
},
|
||||
quest: {
|
||||
id: prodQuest.id,
|
||||
slug: prodQuest.slug,
|
||||
name: questName,
|
||||
objectiveId: questObjectiveId,
|
||||
metric: prodQuestMetric,
|
||||
@@ -321,7 +327,7 @@ export async function runSeed(
|
||||
shopLayout: { cols: 3 },
|
||||
spawnRateOverride: 3.0,
|
||||
},
|
||||
scenario: { id: prodScenario.id, event: scenarioEvent },
|
||||
scenario: { slug: prodScenario.slug, event: scenarioEvent },
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -16,14 +16,14 @@ export interface SeedArtifact {
|
||||
slug: string;
|
||||
freeOfferName: string;
|
||||
paidOfferName: string;
|
||||
paidOfferId: string;
|
||||
paidOfferSlug: string;
|
||||
paidPrice: { currency: string; amount: number };
|
||||
paidMaxPurchases: number;
|
||||
grantedItemName: string;
|
||||
paidGrantAmount: number;
|
||||
};
|
||||
quest: {
|
||||
id: string;
|
||||
slug: string;
|
||||
name: string;
|
||||
objectiveId: string;
|
||||
metric: string;
|
||||
@@ -42,5 +42,5 @@ export interface SeedArtifact {
|
||||
/** spawn_rate value the scenario's remote_config_override applies. */
|
||||
spawnRateOverride: number;
|
||||
};
|
||||
scenario: { id: string; event: string };
|
||||
scenario: { slug: string; event: string };
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ describe('e2e-prod: purchase (wallet/inventory/limits)', () => {
|
||||
|
||||
// 3. Inventory credited (verified via admin player details).
|
||||
const details = await api.get<AdminPlayerDetails>(
|
||||
`/platform/v1/projects/${artifact.projectId}/players/${playerId}`,
|
||||
`/platform/v1/projects/${artifact.projectId}/players/${playerId}?environment=${artifact.environment}`,
|
||||
);
|
||||
const inv = details.inventory?.find((i) => i.slug === artifact.item.id);
|
||||
expect(Number(inv?.amount)).toBe(artifact.store.paidGrantAmount);
|
||||
|
||||
@@ -24,7 +24,7 @@ describe('E2E: boundary nodes (server-side continuation)', () => {
|
||||
stores: [{
|
||||
slug: 'starter',
|
||||
name: 'Starter',
|
||||
offers: [{ id: 'pack_1', name: 'Starter Pack' }],
|
||||
offers: [{ id: 'pack_1', slug: 'pack-1', name: 'Starter Pack' }],
|
||||
}],
|
||||
});
|
||||
gateway.install();
|
||||
@@ -35,12 +35,12 @@ describe('E2E: boundary nodes (server-side continuation)', () => {
|
||||
|
||||
it('purchase crosses the boundary → callback fires and continues the scenario', async () => {
|
||||
const offerEffect = effect('store', { storeSlug: 'starter', message: 'Buy the starter pack!' }, {
|
||||
scenarioId: 'offer_flow',
|
||||
scenarioSlug: 'offer_flow',
|
||||
runId: 'offer_flow-run',
|
||||
nodeId: 'offer',
|
||||
});
|
||||
const reward = effect('notification', { message: 'Reward granted: 500 gems!' }, {
|
||||
scenarioId: 'offer_flow',
|
||||
scenarioSlug: 'offer_flow',
|
||||
runId: 'offer_flow-run',
|
||||
nodeId: 'reward',
|
||||
});
|
||||
@@ -52,7 +52,7 @@ describe('E2E: boundary nodes (server-side continuation)', () => {
|
||||
let offer: StoreOfferEffect | undefined;
|
||||
client.effects.onStoreOffer((e) => { offer = e; });
|
||||
client.effects.onNotification((e) => { messages.push(e.message); });
|
||||
client.effects.onScenarioCompleted((e) => { completed.push(e.scenarioId); });
|
||||
client.effects.onScenarioCompleted((e) => { completed.push(e.scenarioSlug); });
|
||||
|
||||
const token = (await client.auth.loginWithDevice()).accessToken;
|
||||
await vi.waitFor(() => expect(offer).toBeDefined());
|
||||
@@ -61,7 +61,7 @@ describe('E2E: boundary nodes (server-side continuation)', () => {
|
||||
|
||||
const callback = gateway.recorded.find((r) => r.path === '/sdk/v1/scenarios/callback');
|
||||
expect(callback?.body).toEqual({
|
||||
scenarioId: 'offer_flow',
|
||||
scenarioSlug: 'offer_flow',
|
||||
nodeId: 'offer',
|
||||
handle: 'onPurchase',
|
||||
runId: 'offer_flow-run',
|
||||
@@ -75,12 +75,12 @@ describe('E2E: boundary nodes (server-side continuation)', () => {
|
||||
|
||||
it('decline posts callback and continues with the server-supplied next effect', async () => {
|
||||
const offerEffect = effect('store', { storeSlug: 'starter', message: 'Buy the starter pack!' }, {
|
||||
scenarioId: 'offer_flow',
|
||||
scenarioSlug: 'offer_flow',
|
||||
runId: 'offer_flow-run',
|
||||
nodeId: 'offer',
|
||||
});
|
||||
const consolation = effect('notification', { message: 'Maybe next time!' }, {
|
||||
scenarioId: 'offer_flow',
|
||||
scenarioSlug: 'offer_flow',
|
||||
runId: 'offer_flow-run',
|
||||
nodeId: 'consolation',
|
||||
});
|
||||
@@ -102,7 +102,7 @@ describe('E2E: boundary nodes (server-side continuation)', () => {
|
||||
|
||||
it('a failing callback does not fail or crash the run', async () => {
|
||||
const offerEffect = effect('store', { storeSlug: 'starter' }, {
|
||||
scenarioId: 'offer_flow',
|
||||
scenarioSlug: 'offer_flow',
|
||||
runId: 'offer_flow-run',
|
||||
nodeId: 'offer',
|
||||
});
|
||||
|
||||
@@ -18,29 +18,29 @@ function makeClient(): RudderClient {
|
||||
|
||||
function offerScenario(now: number) {
|
||||
const waitIntro = effect('wait', {}, {
|
||||
scenarioId: 'offer_flow',
|
||||
scenarioSlug: 'offer_flow',
|
||||
runId: 'offer_flow-run',
|
||||
nodeId: 'wait_intro',
|
||||
waitDeadline: new Date(now + MINUTE).toISOString(),
|
||||
});
|
||||
const offer = effect('store', { storeSlug: 'starter', offerId: 'pack_1', message: 'Limited starter pack!' }, {
|
||||
scenarioId: 'offer_flow',
|
||||
const offer = effect('store', { storeSlug: 'starter', offerSlug: 'pack-1', message: 'Limited starter pack!' }, {
|
||||
scenarioSlug: 'offer_flow',
|
||||
runId: 'offer_flow-run',
|
||||
nodeId: 'offer',
|
||||
});
|
||||
const waitReminder = effect('wait', {}, {
|
||||
scenarioId: 'offer_flow',
|
||||
scenarioSlug: 'offer_flow',
|
||||
runId: 'offer_flow-run',
|
||||
nodeId: 'wait_reminder',
|
||||
waitDeadline: new Date(now + 2 * MINUTE).toISOString(),
|
||||
});
|
||||
const reminder = effect('notification', { message: 'Your offer is still available!' }, {
|
||||
scenarioId: 'offer_flow',
|
||||
scenarioSlug: 'offer_flow',
|
||||
runId: 'offer_flow-run',
|
||||
nodeId: 'reminder',
|
||||
});
|
||||
const thanks = effect('notification', { message: 'Thanks for your purchase!' }, {
|
||||
scenarioId: 'offer_flow',
|
||||
scenarioSlug: 'offer_flow',
|
||||
runId: 'offer_flow-run',
|
||||
nodeId: 'thanks',
|
||||
});
|
||||
@@ -58,7 +58,7 @@ describe('E2E: player offer journey', () => {
|
||||
stores: [{
|
||||
slug: 'starter',
|
||||
name: 'Starter',
|
||||
offers: [{ id: 'pack_1', name: 'Starter Pack' }],
|
||||
offers: [{ id: 'pack_1', slug: 'pack-1', name: 'Starter Pack' }],
|
||||
}],
|
||||
});
|
||||
gateway.install();
|
||||
@@ -139,7 +139,7 @@ describe('E2E: player offer journey', () => {
|
||||
expect(purchase.success).toBe(true);
|
||||
|
||||
expect(gateway.purchases).toEqual([
|
||||
{ storeSlug: 'starter', offerId: 'pack_1', idempotencyKey: 'idem-key-123', authToken: accessToken },
|
||||
{ storeSlug: 'starter', offerSlug: 'pack-1', idempotencyKey: 'idem-key-123', authToken: accessToken },
|
||||
]);
|
||||
|
||||
expect(notifications).toHaveLength(1);
|
||||
|
||||
@@ -24,17 +24,17 @@ describe('E2E: pending effects resume after a new login', () => {
|
||||
stubDeterministicUuid();
|
||||
vi.useFakeTimers();
|
||||
gateway = createFakeGateway({
|
||||
stores: [{ slug: 'starter', name: 'Starter', offers: [{ id: 'pack_1', name: 'Pack' }] }],
|
||||
stores: [{ slug: 'starter', name: 'Starter', offers: [{ id: 'pack_1', slug: 'pack-1', name: 'Pack' }] }],
|
||||
});
|
||||
const now = Date.now();
|
||||
const wait = effect('wait', {}, {
|
||||
scenarioId: 'offer_flow',
|
||||
scenarioSlug: 'offer_flow',
|
||||
runId: 'offer_flow-run',
|
||||
nodeId: 'wait_intro',
|
||||
waitDeadline: new Date(now + MINUTE).toISOString(),
|
||||
});
|
||||
const offer = effect('store', { message: 'Limited starter pack!', storeSlug: 'starter' }, {
|
||||
scenarioId: 'offer_flow',
|
||||
scenarioSlug: 'offer_flow',
|
||||
runId: 'offer_flow-run',
|
||||
nodeId: 'offer',
|
||||
});
|
||||
|
||||
@@ -33,8 +33,8 @@ export interface FakeGatewayState {
|
||||
export interface FakeGateway {
|
||||
state: FakeGatewayState;
|
||||
recorded: RecordedRequest[];
|
||||
purchases: Array<{ storeSlug: string; offerId: string; idempotencyKey: string; authToken: string | null }>;
|
||||
questClaims: Array<{ questId: string; authToken: string | null }>;
|
||||
purchases: Array<{ storeSlug: string; offerSlug: string; idempotencyKey: string; authToken: string | null }>;
|
||||
questClaims: Array<{ questSlug: string; authToken: string | null }>;
|
||||
install(): void;
|
||||
onEvent(event: string, ...effects: PendingEffect[]): void;
|
||||
onCallback(nodeId: string, handle: string, next: PendingEffect | null): void;
|
||||
@@ -175,7 +175,7 @@ export function createFakeGateway(seed?: Partial<FakeGatewayState>): FakeGateway
|
||||
const b = body as { idempotencyKey?: string };
|
||||
purchases.push({
|
||||
storeSlug: decodeURIComponent(purchaseMatch[1]),
|
||||
offerId: decodeURIComponent(purchaseMatch[2]),
|
||||
offerSlug: decodeURIComponent(purchaseMatch[2]),
|
||||
idempotencyKey: b.idempotencyKey ?? '',
|
||||
authToken: req.authToken,
|
||||
});
|
||||
@@ -192,10 +192,10 @@ export function createFakeGateway(seed?: Partial<FakeGatewayState>): FakeGateway
|
||||
return json(state.quests);
|
||||
}
|
||||
if (method === 'POST' && path === '/sdk/v1/quests/claim') {
|
||||
const b = body as { questId?: string };
|
||||
const questId = b.questId ?? '';
|
||||
questClaims.push({ questId, authToken: req.authToken });
|
||||
return json(state.questClaims.get(questId) ?? { success: false, error: 'not found' });
|
||||
const b = body as { questSlug?: string };
|
||||
const questSlug = b.questSlug ?? '';
|
||||
questClaims.push({ questSlug, authToken: req.authToken });
|
||||
return json(state.questClaims.get(questSlug) ?? { success: false, error: 'not found' });
|
||||
}
|
||||
|
||||
if (path === '/sdk/v1/storage') {
|
||||
|
||||
@@ -7,7 +7,7 @@ export function effect(
|
||||
): PendingEffect {
|
||||
return {
|
||||
runId: 'run-1',
|
||||
scenarioId: 'scenario-1',
|
||||
scenarioSlug: 'scenario-1',
|
||||
nodeId: `${type}-1`,
|
||||
type,
|
||||
data,
|
||||
|
||||
Reference in New Issue
Block a user