feat: modules and resources domains, drop removed domains, regenerated models

This commit is contained in:
edmand46
2026-09-17 12:08:03 +03:00
parent f45261439f
commit 6f37b00fb4
71 changed files with 516 additions and 4184 deletions
+41 -115
View File
@@ -6,9 +6,30 @@ function jsonResponse(body: unknown): Response {
return new Response(JSON.stringify(body), { status: 200 });
}
function stubKernelFetch(
extra?: (url: string) => Response | undefined,
): ReturnType<typeof vi.fn> {
return vi.fn().mockImplementation((url: string) => {
const custom = extra?.(url);
if (custom) return Promise.resolve(custom);
if (String(url).includes('/sdk/v1/authorization/device')) {
return Promise.resolve(jsonResponse({
accessToken: 'access-token-123',
refreshToken: 'refresh-token-456',
}));
}
if (String(url).includes('/sdk/v1/catalog')) {
return Promise.resolve(jsonResponse({ currencies: [] }));
}
if (String(url).includes('/sdk/v1/sync')) {
return Promise.resolve(jsonResponse({}));
}
return Promise.resolve(jsonResponse({ player: null, wallets: [] }));
});
}
describe('AuthService', () => {
beforeEach(() => {
// Mock crypto.randomUUID for deterministic device ID.
vi.stubGlobal('crypto', {
randomUUID: () => '00000000-0000-4000-a000-000000000001',
});
@@ -17,20 +38,11 @@ describe('AuthService', () => {
it('loginWithDevice sends correct request body', async () => {
const client = createTestClient();
const fetchMock = vi.fn().mockImplementation((url: string) => {
if (url.includes('/sdk/v1/remote-configs')) {
return Promise.resolve(jsonResponse({ configs: {} }));
}
return Promise.resolve(jsonResponse({
accessToken: 'access-token-123',
refreshToken: 'refresh-token-456',
}));
});
const fetchMock = stubKernelFetch();
vi.stubGlobal('fetch', fetchMock);
const response = await client.auth.loginWithDevice({ region: 'us', language: 'en' });
expect(fetchMock).toHaveBeenCalledTimes(9);
const [url, init] = fetchMock.mock.calls[0];
expect(url).toContain('/sdk/v1/authorization/device');
const body = JSON.parse(init.body);
@@ -40,29 +52,24 @@ describe('AuthService', () => {
expect(body.language).toBe('en');
expect(response.accessToken).toBe('access-token-123');
expect(response.refreshToken).toBe('refresh-token-456');
expect(fetchMock.mock.calls.map(([url]) => new URL(String(url)).pathname)).toEqual([
'/sdk/v1/authorization/device',
'/sdk/v1/remote-configs',
'/sdk/v1/player/information',
'/sdk/v1/stores',
'/sdk/v1/catalog',
'/sdk/v1/scenarios/pending',
'/sdk/v1/scenarios/trigger',
'/sdk/v1/player/information',
'/sdk/v1/sync',
]);
const paths = fetchMock.mock.calls.map(([callUrl]) => new URL(String(callUrl)).pathname);
expect(paths[0]).toBe('/sdk/v1/authorization/device');
expect(paths).toContain('/sdk/v1/player/information');
expect(paths).toContain('/sdk/v1/catalog');
expect(paths).toContain('/sdk/v1/sync');
expect(paths).not.toContain('/sdk/v1/remote-configs');
expect(paths).not.toContain('/sdk/v1/stores');
expect(paths).not.toContain('/sdk/v1/scenarios/pending');
expect(paths).not.toContain('/sdk/v1/scenarios/trigger');
});
it('loginWithDevice saves tokens on success', async () => {
const client = createTestClient();
vi.stubGlobal('fetch', vi.fn().mockImplementation((url: string) => {
if (url.includes('/sdk/v1/remote-configs')) {
return Promise.resolve(jsonResponse({ configs: {} }));
vi.stubGlobal('fetch', stubKernelFetch((url) => {
if (url.includes('/sdk/v1/authorization/device')) {
return jsonResponse({ accessToken: 'at', refreshToken: 'rt' });
}
return Promise.resolve(jsonResponse({
accessToken: 'at',
refreshToken: 'rt',
}));
return undefined;
}));
await client.auth.loginWithDevice();
@@ -94,23 +101,16 @@ describe('AuthService', () => {
it('loginWithDevice sends token in Authorization header after login', async () => {
const client = createTestClient();
const fetchMock = vi.fn().mockImplementation((url: string) => {
const fetchMock = stubKernelFetch((url) => {
if (url.includes('/sdk/v1/authorization/device')) {
return Promise.resolve(jsonResponse({
accessToken: 'at2',
refreshToken: 'rt2',
}));
return jsonResponse({ accessToken: 'at2', refreshToken: 'rt2' });
}
if (url.includes('/sdk/v1/remote-configs')) {
return Promise.resolve(jsonResponse({ configs: {} }));
}
return Promise.resolve(jsonResponse({ player: null, wallets: [] }));
return undefined;
});
vi.stubGlobal('fetch', fetchMock);
await client.auth.loginWithDevice();
// Subsequent authenticated calls should include the token.
const response = await client.player.reload();
expect(response).toEqual({ player: null, wallets: [] });
const playerCall = fetchMock.mock.calls.find(([url]) =>
@@ -120,71 +120,9 @@ describe('AuthService', () => {
expect(headers['Authorization']).toBe('Bearer at2');
});
it('loginWithDevice only fires player_login from the client runtime', async () => {
const client = createTestClient();
const fetchMock = vi.fn().mockImplementation((url: string) => {
if (url.includes('/sdk/v1/authorization/device')) {
return Promise.resolve(jsonResponse({
accessToken: 'at-new',
refreshToken: 'rt-new',
}));
}
if (url.includes('/sdk/v1/remote-configs')) {
return Promise.resolve(jsonResponse({ configs: {} }));
}
if (url.includes('/sdk/v1/scenarios/trigger')) {
return Promise.resolve(jsonResponse({ plans: [] }));
}
if (url.includes('/sdk/v1/stores')) {
return Promise.resolve(jsonResponse({ stores: [], total: 0 }));
}
return Promise.resolve(jsonResponse({ player: null, wallets: [] }));
});
vi.stubGlobal('fetch', fetchMock);
await client.auth.loginWithDevice();
const triggerBodies = fetchMock.mock.calls
.filter(([url]) => String(url).includes('/sdk/v1/scenarios/trigger'))
.map(([, init]) => JSON.parse(init.body));
expect(triggerBodies.map((body) => body.event)).toEqual(['player_login']);
});
it('loginWithDevice isolates post-login scenario trigger failures', async () => {
const client = createTestClient();
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
vi.stubGlobal('fetch', vi.fn().mockImplementation((url: string) => {
if (url.includes('/sdk/v1/authorization/device')) {
return Promise.resolve(jsonResponse({
accessToken: 'at',
refreshToken: 'rt',
}));
}
if (url.includes('/sdk/v1/remote-configs')) {
return Promise.resolve(jsonResponse({ configs: {} }));
}
if (url.includes('/sdk/v1/scenarios/trigger')) {
return Promise.resolve(new Response('scenario failed', { status: 500, statusText: 'Internal Server Error' }));
}
if (url.includes('/sdk/v1/stores')) {
return Promise.resolve(jsonResponse({ stores: [], total: 0 }));
}
return Promise.resolve(jsonResponse({ player: null, wallets: [] }));
}));
await expect(client.auth.loginWithDevice()).resolves.toMatchObject({ accessToken: 'at' });
expect(warn).toHaveBeenCalled();
warn.mockRestore();
});
it('loginWithDevice defaults to region global and language en', async () => {
const client = createTestClient();
const fetchMock = vi.fn().mockImplementation((url: string) => {
if (url.includes('/sdk/v1/remote-configs')) {
return Promise.resolve(jsonResponse({ configs: {} }));
}
return Promise.resolve(jsonResponse({ accessToken: 'at', refreshToken: 'rt' }));
});
const fetchMock = stubKernelFetch();
vi.stubGlobal('fetch', fetchMock);
await client.auth.loginWithDevice();
@@ -203,21 +141,9 @@ describe('auth state', () => {
localStorage.clear();
});
function stubLoginFlow(): void {
vi.stubGlobal('fetch', vi.fn().mockImplementation((url: string) => {
if (url.includes('/sdk/v1/remote-configs')) {
return Promise.resolve(jsonResponse({ configs: {} }));
}
if (url.includes('/sdk/v1/scenarios/trigger')) {
return Promise.resolve(jsonResponse({ plans: [] }));
}
return Promise.resolve(jsonResponse({ accessToken: 'at', refreshToken: 'rt' }));
}));
}
it('onAuthStateChange fires immediately, then on login and logout', async () => {
const client = createTestClient();
stubLoginFlow();
vi.stubGlobal('fetch', stubKernelFetch());
const states: string[] = [];
const unsubscribe = client.auth.onAuthStateChange((state) => states.push(state));