feat!: client storage is read-only, storage.save and storage.delete removed
This commit is contained in:
@@ -117,7 +117,7 @@ describe('transport error mapping', () => {
|
||||
vi.stubGlobal('fetch', vi.fn().mockResolvedValue(
|
||||
new Response(null, { status: 204, statusText: 'No Content' }),
|
||||
));
|
||||
const result = await client.storage.save('t', '{}');
|
||||
const result = await client.auth.linkWithGoogle('id-token');
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
|
||||
@@ -2,18 +2,10 @@ import { describe, it, expect } from 'vitest';
|
||||
import { freshPlayer } from './_setup/harness.js';
|
||||
|
||||
describe('e2e-prod: storage', () => {
|
||||
it('save -> get -> delete round-trip', async () => {
|
||||
it('a fresh player reads empty storage', async () => {
|
||||
const client = await freshPlayer();
|
||||
const payload = JSON.stringify({ level: 7 });
|
||||
|
||||
await client.storage.save('savegame', payload);
|
||||
|
||||
const got = await client.storage.reload();
|
||||
expect(got.items?.some((i) => i.type === 'savegame' && i.data === payload)).toBe(true);
|
||||
|
||||
await client.storage.delete('savegame');
|
||||
|
||||
const after = await client.storage.reload();
|
||||
expect(after.items?.some((i) => i.type === 'savegame')).toBeFalsy();
|
||||
expect(got.items ?? []).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,56 +8,36 @@ describe('E2E: player storage', () => {
|
||||
let client: RudderClient;
|
||||
|
||||
beforeEach(() => {
|
||||
gateway = createFakeGateway();
|
||||
gateway = createFakeGateway({
|
||||
storage: new Map([
|
||||
['progress', { id: 'id-progress', type: 'progress', data: JSON.stringify({ level: 7, gold: 1200 }) }],
|
||||
['prefs', { id: 'id-prefs', type: 'prefs', data: JSON.stringify({ sound: false }) }],
|
||||
]),
|
||||
});
|
||||
gateway.install();
|
||||
client = createTestClient();
|
||||
});
|
||||
|
||||
afterEach(() => vi.unstubAllGlobals());
|
||||
|
||||
it('saves items then reads them back (round-trip through the gateway)', async () => {
|
||||
it('reads the items written for the player', async () => {
|
||||
await client.auth.loginWithDevice();
|
||||
|
||||
await client.storage.save('progress', JSON.stringify({ level: 7, gold: 1200 }));
|
||||
await client.storage.save('prefs', JSON.stringify({ sound: false }));
|
||||
|
||||
const all = await client.storage.reload();
|
||||
expect(all.items).toHaveLength(2);
|
||||
|
||||
const progress = all.items!.find((i) => i.type === 'progress');
|
||||
expect(JSON.parse(progress!.data!)).toEqual({ level: 7, gold: 1200 });
|
||||
|
||||
const put = gateway.recorded.find((r) => r.method === 'PUT' && r.path === '/sdk/v1/storage');
|
||||
expect(put?.body).toEqual({ items: [{ type: 'progress', data: '{"level":7,"gold":1200}' }] });
|
||||
|
||||
const getReq = gateway.recorded.findLast((r) => r.method === 'GET' && r.path === '/sdk/v1/storage');
|
||||
expect(getReq?.query.get('limit')).toBe('100');
|
||||
});
|
||||
|
||||
it('save overwrites the item of the same type', async () => {
|
||||
await client.auth.loginWithDevice();
|
||||
await client.storage.save('progress', '1');
|
||||
await client.storage.save('progress', '2');
|
||||
const all = await client.storage.reload();
|
||||
expect(all.items?.map((i) => i.data)).toEqual(['2']);
|
||||
});
|
||||
|
||||
it('deletes a type and the item is gone', async () => {
|
||||
await client.auth.loginWithDevice();
|
||||
await client.storage.save('progress', '{}');
|
||||
await client.storage.save('prefs', '{}');
|
||||
|
||||
await client.storage.delete('progress');
|
||||
|
||||
const remaining = await client.storage.reload();
|
||||
expect(remaining.items?.map((i) => i.type)).toEqual(['prefs']);
|
||||
});
|
||||
|
||||
it('storage writes carry the auth token', async () => {
|
||||
it('storage reads carry the auth token', async () => {
|
||||
const token = (await client.auth.loginWithDevice()).accessToken;
|
||||
await client.storage.save('t', '{}');
|
||||
await client.storage.reload();
|
||||
|
||||
const put = gateway.recorded.find((r) => r.method === 'PUT' && r.path === '/sdk/v1/storage');
|
||||
expect(put?.authToken).toBe(token);
|
||||
const getReq = gateway.recorded.findLast((r) => r.method === 'GET' && r.path === '/sdk/v1/storage');
|
||||
expect(getReq?.authToken).toBe(token);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,7 +15,6 @@ export interface RecordedRequest {
|
||||
export interface FakeGatewayState {
|
||||
storage: Map<string, StorageItem>;
|
||||
currencies: CatalogCurrency[];
|
||||
/** Response per `module/fn`; falls back to `{ result: {} }`. */
|
||||
moduleCalls: Map<string, CallModuleFunctionClientResponse>;
|
||||
}
|
||||
|
||||
@@ -32,7 +31,6 @@ function json(body: unknown, status = 200): Response {
|
||||
});
|
||||
}
|
||||
|
||||
const noContent = (): Response => new Response(null, { status: 204 });
|
||||
|
||||
export function createFakeGateway(seed?: Partial<FakeGatewayState>): FakeGateway {
|
||||
const state: FakeGatewayState = {
|
||||
@@ -79,19 +77,6 @@ export function createFakeGateway(seed?: Partial<FakeGatewayState>): FakeGateway
|
||||
);
|
||||
return json({ items, nextCursor: '' });
|
||||
}
|
||||
if (method === 'PUT') {
|
||||
const items = (body as { items?: StorageItem[] }).items ?? [];
|
||||
for (const item of items) {
|
||||
if (item.type) state.storage.set(item.type, { id: `id-${item.type}`, ...item });
|
||||
}
|
||||
return noContent();
|
||||
}
|
||||
if (method === 'DELETE') {
|
||||
const type = query.get('type');
|
||||
if (type) state.storage.delete(type);
|
||||
else state.storage.clear();
|
||||
return noContent();
|
||||
}
|
||||
}
|
||||
|
||||
return json({ error: `unhandled route: ${method} ${path}` }, 500);
|
||||
|
||||
Reference in New Issue
Block a user