mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-06-10 00:04:48 -07:00
refactor login flow and hooks, address autologin issues
This commit is contained in:
parent
dcd6dc00f4
commit
bd2382c94e
43 changed files with 2179 additions and 484 deletions
33
webclient/integration/src/app/helpers.tsx
Normal file
33
webclient/integration/src/app/helpers.tsx
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
// Shared render helper for the app integration suite.
|
||||
//
|
||||
// Two non-obvious choices:
|
||||
//
|
||||
// 1. WebClientContext is provided directly (not via production
|
||||
// <WebClientProvider>) because the shared integration setup.ts already
|
||||
// instantiates the WebClient singleton in beforeEach. The production
|
||||
// provider would `new WebClient(...)` a second time and throw.
|
||||
//
|
||||
// 2. We pass the REAL Redux store from @app/store — not renderWithProviders'
|
||||
// default test-local store. The real WebClient dispatches against the
|
||||
// real store (that's what createWebClientResponse wires to). Asserting
|
||||
// against a different in-memory store would silently miss every
|
||||
// dispatch. setup.ts's resetAll + afterEach clears the real store
|
||||
// between tests, so each test still starts from a clean slate.
|
||||
|
||||
import { ReactElement } from 'react';
|
||||
|
||||
import { renderWithProviders } from '../../../src/__test-utils__';
|
||||
import { store } from '@app/store';
|
||||
import { WebClientContext } from '@app/hooks';
|
||||
import { WebClient } from '@app/websocket';
|
||||
|
||||
export function renderAppScreen(ui: ReactElement) {
|
||||
return renderWithProviders(
|
||||
<WebClientContext.Provider value={WebClient.instance}>
|
||||
{ui}
|
||||
</WebClientContext.Provider>,
|
||||
{ store }
|
||||
);
|
||||
}
|
||||
|
||||
export { store };
|
||||
192
webclient/integration/src/app/login-autoconnect.spec.tsx
Normal file
192
webclient/integration/src/app/login-autoconnect.spec.tsx
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
// Full-stack autoconnect integration. Only outbound surfaces are mocked
|
||||
// (WebSocket via the shared setup, IndexedDB via fake-indexeddb in setup).
|
||||
// Everything in between — Dexie, DTOs, useSettings/useKnownHosts, useAutoLogin,
|
||||
// the Login container, WebClient, Redux — runs as shipped code.
|
||||
//
|
||||
// We assert auto-login via `connectionAttemptMade` on the real server slice,
|
||||
// not via the WebSocket mock's call count: KnownHosts fires a testConnection
|
||||
// on mount for the UX indicator, which also constructs sockets, so raw
|
||||
// socket counts are noisy. Only the login path dispatches CONNECTION_ATTEMPTED.
|
||||
|
||||
import { act, waitFor } from '@testing-library/react';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
// Store loads notify React subscribers synchronously when the Dexie
|
||||
// promise resolves. Awaiting whenReady() directly would let those
|
||||
// notifications fire outside an act() scope, which trips React's
|
||||
// "update was not wrapped in act" warning. Wrapping here captures
|
||||
// both the store resolution and any resulting component re-renders.
|
||||
const flushStoresAndEffects = async (): Promise<void> => {
|
||||
await act(async () => {
|
||||
await settingsStore.whenReady();
|
||||
await knownHostsStore.whenReady();
|
||||
// Let dependent effects (host-sync, settings-sync) commit.
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
});
|
||||
};
|
||||
|
||||
import { autoLoginSession } from '../../../src/hooks/useAutoLogin';
|
||||
import { settingsStore } from '../../../src/hooks/useSettings';
|
||||
import { knownHostsStore } from '../../../src/hooks/useKnownHosts';
|
||||
import Login from '../../../src/containers/Login/Login';
|
||||
import { HostDTO, SettingDTO } from '@app/services';
|
||||
import { App } from '@app/types';
|
||||
import { ServerSelectors, ServerDispatch } from '@app/store';
|
||||
import { StatusEnum } from '@app/websocket';
|
||||
|
||||
import { resetDexie } from '../services/dexie/resetDexie';
|
||||
import { renderAppScreen, store } from './helpers';
|
||||
|
||||
// Mimics the production "user logged out / connection dropped" transition:
|
||||
// dispatching updateStatus(DISCONNECTED) is what the real reducer uses to
|
||||
// clear connectionAttemptMade (clearStore intentionally preserves status).
|
||||
const simulateLogout = () => {
|
||||
ServerDispatch.updateStatus(StatusEnum.DISCONNECTED, null);
|
||||
};
|
||||
|
||||
const seedAutoConnect = async () => {
|
||||
const setting = new SettingDTO(App.APP_USER);
|
||||
setting.autoConnect = true;
|
||||
await setting.save();
|
||||
|
||||
const id = (await HostDTO.add({
|
||||
name: 'Test Server',
|
||||
host: 'server.example',
|
||||
port: '4748',
|
||||
editable: false,
|
||||
})) as number;
|
||||
const host = (await HostDTO.get(id))!;
|
||||
host.remember = true;
|
||||
host.userName = 'alice';
|
||||
host.hashedPassword = 'stored-hash';
|
||||
host.lastSelected = true;
|
||||
await host.save();
|
||||
};
|
||||
|
||||
const attempted = (): boolean =>
|
||||
ServerSelectors.getConnectionAttemptMade(store.getState());
|
||||
|
||||
afterEach(async () => {
|
||||
// Absorb any state updates that lingered past the test body (stores
|
||||
// resolving after unmount, trailing effect commits) so they're wrapped
|
||||
// in act and don't trip React's warning during teardown.
|
||||
await flushStoresAndEffects();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
// setup.ts's beforeEach installs fake timers and re-creates the WebClient
|
||||
// singleton. Dexie + React async need real timers; module caches persist
|
||||
// across tests and need explicit reset.
|
||||
vi.useRealTimers();
|
||||
await resetDexie();
|
||||
|
||||
// Reset the module-level caches that load from Dexie. Without this, a
|
||||
// test would read the PREVIOUS test's snapshot (the Dexie clear only
|
||||
// truncates storage, not the useSettings / useKnownHosts subscribers'
|
||||
// cached values).
|
||||
settingsStore.reset();
|
||||
knownHostsStore.reset();
|
||||
autoLoginSession.startupCheckRan = false;
|
||||
});
|
||||
|
||||
describe('autoconnect — cold start', () => {
|
||||
it('auto-logs in when Dexie has autoConnect=true + host with stored credentials', async () => {
|
||||
await seedAutoConnect();
|
||||
|
||||
renderAppScreen(<Login />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(attempted()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('does NOT attempt login when Dexie has no settings row', async () => {
|
||||
renderAppScreen(<Login />);
|
||||
|
||||
await flushStoresAndEffects();
|
||||
|
||||
expect(attempted()).toBe(false);
|
||||
});
|
||||
|
||||
it('does NOT attempt login when autoConnect=true but lastSelected host lacks credentials', async () => {
|
||||
const setting = new SettingDTO(App.APP_USER);
|
||||
setting.autoConnect = true;
|
||||
await setting.save();
|
||||
await HostDTO.add({
|
||||
name: 'Unremembered',
|
||||
host: 'server.example',
|
||||
port: '4748',
|
||||
editable: false,
|
||||
lastSelected: true,
|
||||
});
|
||||
|
||||
renderAppScreen(<Login />);
|
||||
|
||||
await flushStoresAndEffects();
|
||||
|
||||
expect(attempted()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('autoconnect — logout cycle (same session)', () => {
|
||||
it('does not auto-reconnect after first auto-login + logout within the same JS session', async () => {
|
||||
await seedAutoConnect();
|
||||
|
||||
const first = renderAppScreen(<Login />);
|
||||
await waitFor(() => {
|
||||
expect(attempted()).toBe(true);
|
||||
});
|
||||
|
||||
// Simulate "logged out and returned to /login": unmount, clear the
|
||||
// store's connectionAttemptMade flag (the app-level equivalent of
|
||||
// DISCONNECTED → status.connectionAttemptMade reset), remount.
|
||||
first.unmount();
|
||||
simulateLogout();
|
||||
|
||||
renderAppScreen(<Login />);
|
||||
await flushStoresAndEffects();
|
||||
|
||||
// The session gate must have kept useAutoLogin silent; the flag stays
|
||||
// false.
|
||||
expect(attempted()).toBe(false);
|
||||
});
|
||||
|
||||
it('does not auto-connect when the user enabled autoConnect mid-session and then logged out', async () => {
|
||||
// First mount with autoConnect=false — gate latches without firing.
|
||||
const first = renderAppScreen(<Login />);
|
||||
await flushStoresAndEffects();
|
||||
expect(attempted()).toBe(false);
|
||||
first.unmount();
|
||||
|
||||
// Mid-session: user ticked the checkbox → Dexie flipped to autoConnect=true.
|
||||
await seedAutoConnect();
|
||||
|
||||
// Remount (post-logout). The gate MUST keep useAutoLogin silent.
|
||||
renderAppScreen(<Login />);
|
||||
await flushStoresAndEffects();
|
||||
|
||||
expect(attempted()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('autoconnect — refresh', () => {
|
||||
it('auto-connects again after resetting the session gate (page-refresh equivalent)', async () => {
|
||||
await seedAutoConnect();
|
||||
|
||||
const first = renderAppScreen(<Login />);
|
||||
await waitFor(() => {
|
||||
expect(attempted()).toBe(true);
|
||||
});
|
||||
first.unmount();
|
||||
|
||||
// Simulate a browser refresh: the session gate naturally resets on a
|
||||
// fresh JS context, and the real connection flag resets too.
|
||||
simulateLogout();
|
||||
autoLoginSession.startupCheckRan = false;
|
||||
|
||||
renderAppScreen(<Login />);
|
||||
await waitFor(() => {
|
||||
expect(attempted()).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -9,6 +9,10 @@
|
|||
|
||||
import '@testing-library/jest-dom/vitest';
|
||||
import '../../../src/polyfills';
|
||||
// fake-indexeddb polyfills globalThis.indexedDB. MUST be imported before any
|
||||
// module that opens a Dexie database (Dexie opens on first table access).
|
||||
// Harmless for the websocket suite, which doesn't touch Dexie.
|
||||
import 'fake-indexeddb/auto';
|
||||
|
||||
import { create } from '@bufbuild/protobuf';
|
||||
import { afterEach, beforeEach, vi } from 'vitest';
|
||||
|
|
|
|||
91
webclient/integration/src/services/dexie/hosts.spec.ts
Normal file
91
webclient/integration/src/services/dexie/hosts.spec.ts
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
// Real round-trip tests for HostDTO through Dexie into fake-indexeddb.
|
||||
// Exercises the full static method surface (add, get, getAll, bulkAdd,
|
||||
// delete) plus instance save().
|
||||
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { HostDTO } from '@app/services';
|
||||
import type { App } from '@app/types';
|
||||
|
||||
import { resetDexie } from './resetDexie';
|
||||
|
||||
const makeRow = (overrides: Partial<App.Host> = {}): App.Host => ({
|
||||
name: 'Test',
|
||||
host: 'host.example',
|
||||
port: '4747',
|
||||
editable: false,
|
||||
...overrides,
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
// Shared setup.ts installs fake timers for the websocket suite's
|
||||
// KeepAliveService; Dexie / fake-indexeddb need real timers.
|
||||
vi.useRealTimers();
|
||||
await resetDexie();
|
||||
});
|
||||
|
||||
describe('HostDTO (real Dexie)', () => {
|
||||
it('getAll returns empty on a fresh store', async () => {
|
||||
const all = await HostDTO.getAll();
|
||||
expect(all).toEqual([]);
|
||||
});
|
||||
|
||||
it('add returns an auto-incremented id and makes the row retrievable by get(id)', async () => {
|
||||
const id = (await HostDTO.add(makeRow({ name: 'A' }))) as number;
|
||||
expect(typeof id).toBe('number');
|
||||
|
||||
const loaded = await HostDTO.get(id);
|
||||
expect(loaded).toBeDefined();
|
||||
expect(loaded!.name).toBe('A');
|
||||
expect(loaded!.id).toBe(id);
|
||||
expect(loaded).toBeInstanceOf(HostDTO);
|
||||
});
|
||||
|
||||
it('bulkAdd seeds multiple rows and they are all retrievable via getAll', async () => {
|
||||
await HostDTO.bulkAdd([
|
||||
makeRow({ name: 'A' }),
|
||||
makeRow({ name: 'B' }),
|
||||
makeRow({ name: 'C' }),
|
||||
]);
|
||||
|
||||
const all = await HostDTO.getAll();
|
||||
expect(all.map((h) => h.name).sort()).toEqual(['A', 'B', 'C']);
|
||||
});
|
||||
|
||||
it('save() on a loaded instance upserts the same row (does not duplicate)', async () => {
|
||||
const id = (await HostDTO.add(makeRow({ name: 'A', remember: false }))) as number;
|
||||
|
||||
const loaded = await HostDTO.get(id);
|
||||
loaded!.remember = true;
|
||||
loaded!.userName = 'alice';
|
||||
loaded!.hashedPassword = 'stored';
|
||||
await loaded!.save();
|
||||
|
||||
const all = await HostDTO.getAll();
|
||||
expect(all).toHaveLength(1);
|
||||
expect(all[0].remember).toBe(true);
|
||||
expect(all[0].userName).toBe('alice');
|
||||
expect(all[0].hashedPassword).toBe('stored');
|
||||
});
|
||||
|
||||
it('delete removes the row by id', async () => {
|
||||
const idA = (await HostDTO.add(makeRow({ name: 'A' }))) as number;
|
||||
await HostDTO.add(makeRow({ name: 'B' }));
|
||||
|
||||
await HostDTO.delete(idA as unknown as string);
|
||||
|
||||
const all = await HostDTO.getAll();
|
||||
expect(all.map((h) => h.name)).toEqual(['B']);
|
||||
});
|
||||
|
||||
it('lastSelected round-trips as a boolean column', async () => {
|
||||
const idA = (await HostDTO.add(makeRow({ name: 'A', lastSelected: true }))) as number;
|
||||
await HostDTO.add(makeRow({ name: 'B', lastSelected: false }));
|
||||
|
||||
const all = await HostDTO.getAll();
|
||||
const selected = all.find((h) => h.id === idA)!;
|
||||
expect(selected.lastSelected).toBe(true);
|
||||
const other = all.find((h) => h.name === 'B')!;
|
||||
expect(other.lastSelected).toBe(false);
|
||||
});
|
||||
});
|
||||
12
webclient/integration/src/services/dexie/resetDexie.ts
Normal file
12
webclient/integration/src/services/dexie/resetDexie.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
// Clears every table the services suite touches so each test starts from
|
||||
// empty storage. Dexie is a real singleton, the database a real (fake-
|
||||
// indexeddb) instance, so state leaks between tests otherwise.
|
||||
|
||||
import { dexieService } from '@app/services';
|
||||
|
||||
export async function resetDexie(): Promise<void> {
|
||||
await Promise.all([
|
||||
dexieService.settings.clear(),
|
||||
dexieService.hosts.clear(),
|
||||
]);
|
||||
}
|
||||
69
webclient/integration/src/services/dexie/settings.spec.ts
Normal file
69
webclient/integration/src/services/dexie/settings.spec.ts
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
// Real round-trip tests for SettingDTO through Dexie into fake-indexeddb.
|
||||
// Nothing is mocked past the IndexedDB boundary — the DTO class, the Dexie
|
||||
// schema, and the table's put/where/first pipeline all run as shipped code.
|
||||
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { SettingDTO } from '@app/services';
|
||||
import { App } from '@app/types';
|
||||
|
||||
import { resetDexie } from './resetDexie';
|
||||
|
||||
beforeEach(async () => {
|
||||
// Shared setup.ts installs vi.useFakeTimers() for the websocket suite's
|
||||
// KeepAliveService needs. Dexie + fake-indexeddb rely on real microtasks
|
||||
// and will hang under fake timers, so flip back here.
|
||||
vi.useRealTimers();
|
||||
await resetDexie();
|
||||
});
|
||||
|
||||
describe('SettingDTO (real Dexie)', () => {
|
||||
it('returns undefined for a user with no row yet', async () => {
|
||||
const loaded = await SettingDTO.get(App.APP_USER);
|
||||
expect(loaded).toBeUndefined();
|
||||
});
|
||||
|
||||
it('round-trips a fresh setting via save()', async () => {
|
||||
const dto = new SettingDTO(App.APP_USER);
|
||||
dto.autoConnect = true;
|
||||
await dto.save();
|
||||
|
||||
const loaded = await SettingDTO.get(App.APP_USER);
|
||||
expect(loaded).toBeDefined();
|
||||
expect(loaded!.user).toBe(App.APP_USER);
|
||||
expect(loaded!.autoConnect).toBe(true);
|
||||
});
|
||||
|
||||
it('upserts on repeated save for the same user key', async () => {
|
||||
const first = new SettingDTO(App.APP_USER);
|
||||
first.autoConnect = false;
|
||||
await first.save();
|
||||
|
||||
const loaded = await SettingDTO.get(App.APP_USER);
|
||||
loaded!.autoConnect = true;
|
||||
await loaded!.save();
|
||||
|
||||
const reloaded = await SettingDTO.get(App.APP_USER);
|
||||
expect(reloaded!.autoConnect).toBe(true);
|
||||
});
|
||||
|
||||
it('matches user lookups case-insensitively (equalsIgnoreCase in DTO.get)', async () => {
|
||||
const dto = new SettingDTO(App.APP_USER);
|
||||
await dto.save();
|
||||
|
||||
const loaded = await SettingDTO.get(App.APP_USER.toUpperCase());
|
||||
expect(loaded).toBeDefined();
|
||||
expect(loaded!.user).toBe(App.APP_USER);
|
||||
});
|
||||
|
||||
it('preserves the SettingDTO class on load (mapToClass binding)', async () => {
|
||||
const dto = new SettingDTO(App.APP_USER);
|
||||
await dto.save();
|
||||
|
||||
const loaded = await SettingDTO.get(App.APP_USER);
|
||||
expect(loaded).toBeInstanceOf(SettingDTO);
|
||||
// The save() instance method must be present on the retrieved row so
|
||||
// call sites (useSettings.update) can round-trip without reinstantiation.
|
||||
expect(typeof loaded!.save).toBe('function');
|
||||
});
|
||||
});
|
||||
|
|
@ -8,14 +8,14 @@ import { Data } from '@app/types';
|
|||
import { store } from '@app/store';
|
||||
import { AdminCommands } from '@app/websocket';
|
||||
|
||||
import { connectAndLogin } from './helpers/setup';
|
||||
import { connectAndLogin } from '../helpers/setup';
|
||||
import {
|
||||
buildResponse,
|
||||
buildResponseMessage,
|
||||
buildSessionEventMessage,
|
||||
deliverMessage,
|
||||
} from './helpers/protobuf-builders';
|
||||
import { findLastAdminCommand } from './helpers/command-capture';
|
||||
} from '../helpers/protobuf-builders';
|
||||
import { findLastAdminCommand } from '../helpers/command-capture';
|
||||
|
||||
describe('admin commands', () => {
|
||||
it('adjustMod modifies the user level bitflags on success', () => {
|
||||
|
|
@ -8,13 +8,13 @@ import { Data } from '@app/types';
|
|||
import { store } from '@app/store';
|
||||
import { StatusEnum, WebSocketConnectReason } from '@app/websocket';
|
||||
|
||||
import { connectAndHandshake, connectAndHandshakeWithSalt } from './helpers/setup';
|
||||
import { connectAndHandshake, connectAndHandshakeWithSalt } from '../helpers/setup';
|
||||
import {
|
||||
buildResponse,
|
||||
buildResponseMessage,
|
||||
deliverMessage,
|
||||
} from './helpers/protobuf-builders';
|
||||
import { findLastSessionCommand } from './helpers/command-capture';
|
||||
} from '../helpers/protobuf-builders';
|
||||
import { findLastSessionCommand } from '../helpers/command-capture';
|
||||
|
||||
function makeUser(name: string): Data.ServerInfo_User {
|
||||
return create(Data.ServerInfo_UserSchema, {
|
||||
|
|
@ -9,7 +9,7 @@ import { Data } from '@app/types';
|
|||
import { store } from '@app/store';
|
||||
import { StatusEnum } from '@app/websocket';
|
||||
|
||||
import { PROTOCOL_VERSION } from '../../src/websocket/config';
|
||||
import { PROTOCOL_VERSION } from '../../../src/websocket/config';
|
||||
|
||||
import {
|
||||
getMockWebSocket,
|
||||
|
|
@ -17,14 +17,14 @@ import {
|
|||
openMockWebSocket,
|
||||
setPendingOptions,
|
||||
connectAndHandshake,
|
||||
} from './helpers/setup';
|
||||
} from '../helpers/setup';
|
||||
import type { WebSocketConnectOptions } from '@app/websocket';
|
||||
import { WebSocketConnectReason } from '@app/websocket';
|
||||
import {
|
||||
buildSessionEventMessage,
|
||||
deliverMessage,
|
||||
} from './helpers/protobuf-builders';
|
||||
import { findLastSessionCommand } from './helpers/command-capture';
|
||||
} from '../helpers/protobuf-builders';
|
||||
import { findLastSessionCommand } from '../helpers/command-capture';
|
||||
|
||||
function loginOptions(overrides: Partial<{ userName: string; password: string }> = {}): WebSocketConnectOptions {
|
||||
return {
|
||||
|
|
@ -8,13 +8,13 @@ import { Data } from '@app/types';
|
|||
import { store } from '@app/store';
|
||||
import { SessionCommands } from '@app/websocket';
|
||||
|
||||
import { connectAndLogin } from './helpers/setup';
|
||||
import { connectAndLogin } from '../helpers/setup';
|
||||
import {
|
||||
buildResponse,
|
||||
buildResponseMessage,
|
||||
deliverMessage,
|
||||
} from './helpers/protobuf-builders';
|
||||
import { findLastSessionCommand } from './helpers/command-capture';
|
||||
} from '../helpers/protobuf-builders';
|
||||
import { findLastSessionCommand } from '../helpers/command-capture';
|
||||
|
||||
describe('deck operations', () => {
|
||||
it('populates backendDecks from deckList response', () => {
|
||||
|
|
@ -8,7 +8,7 @@ import { Data } from '@app/types';
|
|||
import { store } from '@app/store';
|
||||
import { GameCommands, RoomCommands } from '@app/websocket';
|
||||
|
||||
import { connectAndHandshake, connectAndLogin } from './helpers/setup';
|
||||
import { connectAndHandshake, connectAndLogin } from '../helpers/setup';
|
||||
import {
|
||||
buildResponse,
|
||||
buildResponseMessage,
|
||||
|
|
@ -16,8 +16,8 @@ import {
|
|||
buildRoomEventMessage,
|
||||
buildGameEventMessage,
|
||||
deliverMessage,
|
||||
} from './helpers/protobuf-builders';
|
||||
import { findLastGameCommand, findLastRoomCommand, findLastSessionCommand } from './helpers/command-capture';
|
||||
} from '../helpers/protobuf-builders';
|
||||
import { findLastGameCommand, findLastRoomCommand, findLastSessionCommand } from '../helpers/command-capture';
|
||||
|
||||
function joinGame(gameId: number): void {
|
||||
deliverMessage(buildSessionEventMessage(
|
||||
|
|
@ -6,13 +6,13 @@ import { Data } from '@app/types';
|
|||
import { store } from '@app/store';
|
||||
import { StatusEnum } from '@app/websocket';
|
||||
|
||||
import { connectRaw, getMockWebSocket } from './helpers/setup';
|
||||
import { connectRaw, getMockWebSocket } from '../helpers/setup';
|
||||
import {
|
||||
buildResponse,
|
||||
buildResponseMessage,
|
||||
deliverMessage,
|
||||
} from './helpers/protobuf-builders';
|
||||
import { findLastSessionCommand } from './helpers/command-capture';
|
||||
} from '../helpers/protobuf-builders';
|
||||
import { findLastSessionCommand } from '../helpers/command-capture';
|
||||
|
||||
describe('keep-alive', () => {
|
||||
it('sends a Command_Ping on every keepalive interval tick', () => {
|
||||
|
|
@ -9,13 +9,13 @@ import { Data } from '@app/types';
|
|||
import { store } from '@app/store';
|
||||
import { ModeratorCommands } from '@app/websocket';
|
||||
|
||||
import { connectAndLogin } from './helpers/setup';
|
||||
import { connectAndLogin } from '../helpers/setup';
|
||||
import {
|
||||
buildResponse,
|
||||
buildResponseMessage,
|
||||
deliverMessage,
|
||||
} from './helpers/protobuf-builders';
|
||||
import { findLastModeratorCommand } from './helpers/command-capture';
|
||||
} from '../helpers/protobuf-builders';
|
||||
import { findLastModeratorCommand } from '../helpers/command-capture';
|
||||
|
||||
describe('moderator commands', () => {
|
||||
it('getBanHistory populates server.banHistory on success', () => {
|
||||
|
|
@ -8,13 +8,13 @@ import { Data } from '@app/types';
|
|||
import { store } from '@app/store';
|
||||
import { StatusEnum, WebSocketConnectReason } from '@app/websocket';
|
||||
|
||||
import { connectAndHandshake } from './helpers/setup';
|
||||
import { connectAndHandshake } from '../helpers/setup';
|
||||
import {
|
||||
buildResponse,
|
||||
buildResponseMessage,
|
||||
deliverMessage,
|
||||
} from './helpers/protobuf-builders';
|
||||
import { findLastSessionCommand } from './helpers/command-capture';
|
||||
} from '../helpers/protobuf-builders';
|
||||
import { findLastSessionCommand } from '../helpers/command-capture';
|
||||
|
||||
describe('password reset', () => {
|
||||
it('forgotPasswordRequest sends command and disconnects on success', () => {
|
||||
|
|
@ -8,15 +8,15 @@ import { Data } from '@app/types';
|
|||
import { store } from '@app/store';
|
||||
import { RoomCommands } from '@app/websocket';
|
||||
|
||||
import { connectAndHandshake } from './helpers/setup';
|
||||
import { connectAndHandshake } from '../helpers/setup';
|
||||
import {
|
||||
buildResponse,
|
||||
buildResponseMessage,
|
||||
buildRoomEventMessage,
|
||||
buildSessionEventMessage,
|
||||
deliverMessage,
|
||||
} from './helpers/protobuf-builders';
|
||||
import { findLastSessionCommand, findLastRoomCommand, captureAllOutbound } from './helpers/command-capture';
|
||||
} from '../helpers/protobuf-builders';
|
||||
import { findLastSessionCommand, findLastRoomCommand, captureAllOutbound } from '../helpers/command-capture';
|
||||
import { fromBinary, hasExtension, getExtension } from '@bufbuild/protobuf';
|
||||
|
||||
function makeRoom(overrides: Partial<{
|
||||
|
|
@ -8,11 +8,11 @@ import { Data } from '@app/types';
|
|||
import { store } from '@app/store';
|
||||
import { StatusEnum } from '@app/websocket';
|
||||
|
||||
import { connectAndHandshake } from './helpers/setup';
|
||||
import { connectAndHandshake } from '../helpers/setup';
|
||||
import {
|
||||
buildSessionEventMessage,
|
||||
deliverMessage,
|
||||
} from './helpers/protobuf-builders';
|
||||
} from '../helpers/protobuf-builders';
|
||||
|
||||
describe('server events', () => {
|
||||
it('writes the server banner into server.info.message on Event_ServerMessage', () => {
|
||||
|
|
@ -6,14 +6,14 @@ import { describe, expect, it } from 'vitest';
|
|||
import { Data } from '@app/types';
|
||||
import { store } from '@app/store';
|
||||
|
||||
import { connectAndLogin } from './helpers/setup';
|
||||
import { connectAndLogin } from '../helpers/setup';
|
||||
import {
|
||||
buildResponse,
|
||||
buildResponseMessage,
|
||||
buildSessionEventMessage,
|
||||
deliverMessage,
|
||||
} from './helpers/protobuf-builders';
|
||||
import { findLastSessionCommand } from './helpers/command-capture';
|
||||
} from '../helpers/protobuf-builders';
|
||||
import { findLastSessionCommand } from '../helpers/command-capture';
|
||||
|
||||
function makeUser(name: string): Data.ServerInfo_User {
|
||||
return create(Data.ServerInfo_UserSchema, {
|
||||
Loading…
Add table
Add a link
Reference in a new issue