Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions packages/backend/src/__tests__/climb-queries.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import type { ParsedBoardRouteParameters, ClimbSearchParams } from '../db/querie
import { getSizeEdges } from '../db/queries/util/product-sizes-data.js';

describe('Climb Query Functions', () => {
// Use valid size_id for kilter (7 = 12x14 Commercial)
// See packages/backend/src/db/queries/util/product-sizes-data.ts for valid IDs
const testParams: ParsedBoardRouteParameters = {
board_name: 'kilter',
layout_id: 1,
size_id: 1,
size_id: 7,
set_ids: [1, 2],
angle: 40,
};
Expand Down Expand Up @@ -231,7 +233,7 @@ describe('Climb Query Functions', () => {
const result = await getClimbByUuid({
board_name: 'kilter',
layout_id: 1,
size_id: 1,
size_id: 7, // Valid kilter size_id
angle: 40,
climb_uuid: 'non-existent-uuid-12345',
});
Expand All @@ -240,16 +242,16 @@ describe('Climb Query Functions', () => {
});

it('should handle different board names', async () => {
// Test with kilter
// Test with kilter (size_id 7 = 12x14 Commercial)
const kilterResult = await getClimbByUuid({
board_name: 'kilter',
layout_id: 1,
size_id: 1,
size_id: 7,
angle: 40,
climb_uuid: 'test-uuid',
});

// Test with tension
// Test with tension (size_id 1 = Full Wall)
const tensionResult = await getClimbByUuid({
board_name: 'tension',
layout_id: 1,
Expand Down
11 changes: 7 additions & 4 deletions packages/backend/src/__tests__/integration.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { describe, it, expect, beforeAll, afterAll, afterEach } from 'vitest';
import { createClient, Client } from 'graphql-ws';
import WebSocket from 'ws';
import { v4 as uuidv4 } from 'uuid';
import { startServer } from '../server.js';
import type { ClimbQueueItem } from '@boardsesh/shared-schema';

Expand All @@ -12,12 +13,14 @@
let testCounter = 0;
const createTestSessionId = () => `test-session-${Date.now()}-${testCounter++}`;

const createTestClimb = (uuid: string): ClimbQueueItem => ({
uuid,
// Creates a test climb with valid UUIDs
// The label parameter is just for debugging/naming, not used as UUID
const createTestClimb = (label: string): ClimbQueueItem => ({
uuid: uuidv4(),
climb: {
uuid: `climb-${uuid}`,
uuid: uuidv4(),
setter_username: 'test-setter',
name: `Test Climb ${uuid}`,
name: `Test Climb ${label}`,
description: 'A test climb',
frames: 'test-frames',
angle: 40,
Expand Down Expand Up @@ -49,7 +52,7 @@
next: (data) => {
if (data.errors) {
console.error('GraphQL errors:', JSON.stringify(data.errors, null, 2));
reject(new Error(data.errors[0].message));

Check failure on line 55 in packages/backend/src/__tests__/integration.test.ts

View workflow job for this annotation

GitHub Actions / test

src/__tests__/integration.test.ts > Daemon Integration Tests > Multi-Client Sync > should sync queue reordering across clients

Error: Invalid index: queue has 0 items ❯ Object.next src/__tests__/integration.test.ts:55:18 ❯ Object.<anonymous> node_modules/graphql-ws/dist/client.js:333:22 ❯ emit node_modules/graphql-ws/dist/client.js:75:58 ❯ Object.emit node_modules/graphql-ws/dist/client.js:100:11 ❯ WebSocket.socket2.onmessage node_modules/graphql-ws/dist/client.js:203:21 ❯ callListener ../../node_modules/ws/lib/event-target.js:290:14 ❯ WebSocket.onMessage ../../node_modules/ws/lib/event-target.js:209:9 ❯ Receiver.receiverOnMessage ../../node_modules/ws/lib/websocket.js:1220:20

Check failure on line 55 in packages/backend/src/__tests__/integration.test.ts

View workflow job for this annotation

GitHub Actions / test

src/__tests__/integration.test.ts > Daemon Integration Tests > Queue Operations > should reorder queue items

Error: Invalid index: queue has 0 items ❯ Object.next src/__tests__/integration.test.ts:55:18 ❯ Object.<anonymous> node_modules/graphql-ws/dist/client.js:333:22 ❯ emit node_modules/graphql-ws/dist/client.js:75:58 ❯ Object.emit node_modules/graphql-ws/dist/client.js:100:11 ❯ WebSocket.socket2.onmessage node_modules/graphql-ws/dist/client.js:203:21 ❯ callListener ../../node_modules/ws/lib/event-target.js:290:14 ❯ WebSocket.onMessage ../../node_modules/ws/lib/event-target.js:209:9 ❯ Receiver.receiverOnMessage ../../node_modules/ws/lib/websocket.js:1220:20
return;
}
result = data.data as T;
Expand Down Expand Up @@ -285,7 +288,7 @@
variables: { item: testClimb },
});

expect(result.addQueueItem.uuid).toBe('test-climb-1');

Check failure on line 291 in packages/backend/src/__tests__/integration.test.ts

View workflow job for this annotation

GitHub Actions / test

src/__tests__/integration.test.ts > Daemon Integration Tests > Queue Operations > should add a queue item successfully

AssertionError: expected '1a27fec6-87e5-4b9b-a25b-e6d2d1e36e8e' to be 'test-climb-1' // Object.is equality Expected: "test-climb-1" Received: "1a27fec6-87e5-4b9b-a25b-e6d2d1e36e8e" ❯ src/__tests__/integration.test.ts:291:40
expect(result.addQueueItem.climb.name).toBe('Test Climb test-climb-1');
});

Expand All @@ -309,7 +312,7 @@
variables: { item: testClimb },
});

expect(result.setCurrentClimb.uuid).toBe('current-test');

Check failure on line 315 in packages/backend/src/__tests__/integration.test.ts

View workflow job for this annotation

GitHub Actions / test

src/__tests__/integration.test.ts > Daemon Integration Tests > Queue Operations > should set current climb successfully

AssertionError: expected 'bf81571b-28d1-4bce-b1c1-62d987330231' to be 'current-test' // Object.is equality Expected: "current-test" Received: "bf81571b-28d1-4bce-b1c1-62d987330231" ❯ src/__tests__/integration.test.ts:315:43
});

it('should mirror current climb successfully', async () => {
Expand All @@ -333,7 +336,7 @@
query: `mutation { mirrorCurrentClimb(mirrored: true) { uuid climb { mirrored } } }`,
});

expect(result.mirrorCurrentClimb.climb.mirrored).toBe(true);

Check failure on line 339 in packages/backend/src/__tests__/integration.test.ts

View workflow job for this annotation

GitHub Actions / test

src/__tests__/integration.test.ts > Daemon Integration Tests > Queue Operations > should mirror current climb successfully

TypeError: Cannot read properties of null (reading 'climb') ❯ src/__tests__/integration.test.ts:339:40
});

it('should remove a queue item', async () => {
Expand Down Expand Up @@ -436,7 +439,7 @@
// First event should be FullSync, second should be QueueItemAdded
expect(events[0].__typename).toBe('FullSync');
expect(events[1].__typename).toBe('QueueItemAdded');
expect(events[1].item.uuid).toBe('sync-test');

Check failure on line 442 in packages/backend/src/__tests__/integration.test.ts

View workflow job for this annotation

GitHub Actions / test

src/__tests__/integration.test.ts > Daemon Integration Tests > Multi-Client Sync > should sync queue additions across clients

AssertionError: expected 'ebe37a71-cc85-4c03-a3eb-eff65fbae95f' to be 'sync-test' // Object.is equality Expected: "sync-test" Received: "ebe37a71-cc85-4c03-a3eb-eff65fbae95f" ❯ src/__tests__/integration.test.ts:442:35
});

it('should sync current climb changes across clients', async () => {
Expand Down Expand Up @@ -470,7 +473,7 @@
const events = await eventPromise;

expect(events[1].__typename).toBe('CurrentClimbChanged');
expect(events[1].item.uuid).toBe('current-sync');

Check failure on line 476 in packages/backend/src/__tests__/integration.test.ts

View workflow job for this annotation

GitHub Actions / test

src/__tests__/integration.test.ts > Daemon Integration Tests > Multi-Client Sync > should sync current climb changes across clients

AssertionError: expected 'e65ddcf5-e0ce-49e6-ad27-d0be48914692' to be 'current-sync' // Object.is equality Expected: "current-sync" Received: "e65ddcf5-e0ce-49e6-ad27-d0be48914692" ❯ src/__tests__/integration.test.ts:476:35
});

it('should sync queue reordering across clients', async () => {
Expand Down Expand Up @@ -761,7 +764,7 @@
query: `query { session(sessionId: "${sessionId}") { queueState { queue { uuid } } users { id } } }`,
});

expect(result.session.queueState.queue).toHaveLength(1);

Check failure on line 767 in packages/backend/src/__tests__/integration.test.ts

View workflow job for this annotation

GitHub Actions / test

src/__tests__/integration.test.ts > Daemon Integration Tests > Disconnect Handling > should continue session when one of multiple clients disconnects

AssertionError: expected [] to have a length of 1 but got +0 - Expected + Received - 1 + 0 ❯ src/__tests__/integration.test.ts:767:47
expect(result.session.queueState.queue[0].uuid).toBe('persist-test');
expect(result.session.users).toHaveLength(1); // Only client2 remains
});
Expand Down
69 changes: 40 additions & 29 deletions packages/backend/src/__tests__/session-persistence.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,17 @@ const createTestClimb = (): ClimbQueueItem => ({
suggested: false,
});

// Helper to register client and join session
const registerAndJoin = async (
clientId: string,
sessionId: string,
boardPath: string,
username: string
) => {
roomManager.registerClient(clientId);
return roomManager.joinSession(clientId, sessionId, boardPath, username);
};

describe('Session Persistence - Hybrid Redis + Postgres', () => {
let mockRedis: Redis;

Expand All @@ -177,7 +188,7 @@ describe('Session Persistence - Hybrid Redis + Postgres', () => {
const boardPath = '/kilter/1/2/3/40';

// Create and join session
await roomManager.joinSession('client-1', sessionId, boardPath, 'User1');
await registerAndJoin('client-1', sessionId, boardPath, 'User1');

// Verify active status
let session = await db
Expand Down Expand Up @@ -207,7 +218,7 @@ describe('Session Persistence - Hybrid Redis + Postgres', () => {
const boardPath = '/kilter/1/2/3/40';

// Create session, join, and leave
await roomManager.joinSession('client-1', sessionId, boardPath, 'User1');
await registerAndJoin('client-1', sessionId, boardPath, 'User1');
await roomManager.leaveSession('client-1');

// Verify inactive
Expand All @@ -219,7 +230,7 @@ describe('Session Persistence - Hybrid Redis + Postgres', () => {
expect(session[0]?.status).toBe('inactive');

// Rejoin
await roomManager.joinSession('client-2', sessionId, boardPath, 'User2');
await registerAndJoin('client-2', sessionId, boardPath, 'User2');

// Verify back to active
session = await db
Expand All @@ -235,7 +246,7 @@ describe('Session Persistence - Hybrid Redis + Postgres', () => {
const boardPath = '/kilter/1/2/3/40';

// Create session
await roomManager.joinSession('client-1', sessionId, boardPath, 'User1');
await registerAndJoin('client-1', sessionId, boardPath, 'User1');

// End session
await roomManager.endSession(sessionId);
Expand All @@ -260,7 +271,7 @@ describe('Session Persistence - Hybrid Redis + Postgres', () => {
const climb = createTestClimb();

// Create session and add climb to queue
await roomManager.joinSession('client-1', sessionId, boardPath, 'User1');
await registerAndJoin('client-1', sessionId, boardPath, 'User1');
const currentState = await roomManager.getQueueState(sessionId);
await roomManager.updateQueueState(sessionId, [climb], null, currentState.version);

Expand All @@ -272,7 +283,7 @@ describe('Session Persistence - Hybrid Redis + Postgres', () => {
await roomManager.initialize(mockRedis);

// Rejoin should restore from Redis
const result = await roomManager.joinSession('client-2', sessionId, boardPath, 'User2');
const result = await registerAndJoin('client-2', sessionId, boardPath, 'User2');

// Verify queue was restored from Redis
expect(result.queue).toHaveLength(1);
Expand All @@ -284,7 +295,7 @@ describe('Session Persistence - Hybrid Redis + Postgres', () => {
const boardPath = '/kilter/1/2/3/40';

// Create session and make it inactive
await roomManager.joinSession('client-1', sessionId, boardPath, 'User1');
await registerAndJoin('client-1', sessionId, boardPath, 'User1');
await roomManager.leaveSession('client-1');

// Clear in-memory state
Expand All @@ -293,9 +304,9 @@ describe('Session Persistence - Hybrid Redis + Postgres', () => {

// Multiple users join concurrently
const results = await Promise.all([
roomManager.joinSession('client-2', sessionId, boardPath, 'User2'),
roomManager.joinSession('client-3', sessionId, boardPath, 'User3'),
roomManager.joinSession('client-4', sessionId, boardPath, 'User4'),
registerAndJoin('client-2', sessionId, boardPath, 'User2'),
registerAndJoin('client-3', sessionId, boardPath, 'User3'),
registerAndJoin('client-4', sessionId, boardPath, 'User4'),
]);

// Verify all joined successfully
Expand All @@ -312,7 +323,7 @@ describe('Session Persistence - Hybrid Redis + Postgres', () => {
const climb = createTestClimb();

// Create session and add climb to queue
await roomManager.joinSession('client-1', sessionId, boardPath, 'User1');
await registerAndJoin('client-1', sessionId, boardPath, 'User1');
const currentState = await roomManager.getQueueState(sessionId);
await roomManager.updateQueueState(sessionId, [climb], null, currentState.version);

Expand All @@ -330,7 +341,7 @@ describe('Session Persistence - Hybrid Redis + Postgres', () => {
await roomManager.initialize(mockRedis);

// Rejoin should restore from Postgres
const result = await roomManager.joinSession('client-2', sessionId, boardPath, 'User2');
const result = await registerAndJoin('client-2', sessionId, boardPath, 'User2');

// Verify queue was restored from Postgres
expect(result.queue).toHaveLength(1);
Expand All @@ -342,15 +353,15 @@ describe('Session Persistence - Hybrid Redis + Postgres', () => {
const boardPath = '/kilter/1/2/3/40';

// Create and end session
await roomManager.joinSession('client-1', sessionId, boardPath, 'User1');
await registerAndJoin('client-1', sessionId, boardPath, 'User1');
await roomManager.endSession(sessionId);

// Clear in-memory state
roomManager.reset();
await roomManager.initialize(mockRedis);

// Try to rejoin ended session - should create a new session instead of restoring
const result = await roomManager.joinSession('client-2', sessionId, boardPath, 'User2');
const result = await registerAndJoin('client-2', sessionId, boardPath, 'User2');

// Session should be created fresh (empty queue)
expect(result.queue).toHaveLength(0);
Expand All @@ -373,7 +384,7 @@ describe('Session Persistence - Hybrid Redis + Postgres', () => {
const climb2 = createTestClimb();

// Create session
await roomManager.joinSession('client-1', sessionId, boardPath, 'User1');
await registerAndJoin('client-1', sessionId, boardPath, 'User1');

// Add multiple climbs rapidly
let currentState = await roomManager.getQueueState(sessionId);
Expand Down Expand Up @@ -414,7 +425,7 @@ describe('Session Persistence - Hybrid Redis + Postgres', () => {
const climb = createTestClimb();

// Create session and update queue
await roomManager.joinSession('client-1', sessionId, boardPath, 'User1');
await registerAndJoin('client-1', sessionId, boardPath, 'User1');
const currentState = await roomManager.getQueueState(sessionId);
await roomManager.updateQueueState(sessionId, [climb], null, currentState.version);

Expand All @@ -439,7 +450,7 @@ describe('Session Persistence - Hybrid Redis + Postgres', () => {
const climb2 = createTestClimb();

// Create session
await roomManager.joinSession('client-1', sessionId, boardPath, 'User1');
await registerAndJoin('client-1', sessionId, boardPath, 'User1');

// First update
let currentState = await roomManager.getQueueState(sessionId);
Expand Down Expand Up @@ -496,7 +507,7 @@ describe('Session Persistence - Hybrid Redis + Postgres', () => {
'Test Session'
);

await roomManager.joinSession('client-1', sessionId, boardPath, 'User1');
await registerAndJoin('client-1', sessionId, boardPath, 'User1');

// Query nearby sessions
const nearby = await roomManager.findNearbySessions(37.7749, -122.4194, 10000);
Expand All @@ -520,7 +531,7 @@ describe('Session Persistence - Hybrid Redis + Postgres', () => {
'Test Session'
);

await roomManager.joinSession('client-1', sessionId, boardPath, 'User1');
await registerAndJoin('client-1', sessionId, boardPath, 'User1');
await roomManager.leaveSession('client-1');

// Clear in-memory state but Redis still has it
Expand Down Expand Up @@ -549,7 +560,7 @@ describe('Session Persistence - Hybrid Redis + Postgres', () => {
'Test Session'
);

await roomManager.joinSession('client-1', sessionId, boardPath, 'User1');
await registerAndJoin('client-1', sessionId, boardPath, 'User1');
await roomManager.leaveSession('client-1');

// Clear both in-memory and Redis (simulate TTL expiry)
Expand Down Expand Up @@ -581,7 +592,7 @@ describe('Session Persistence - Hybrid Redis + Postgres', () => {
'Test Session'
);

await roomManager.joinSession('client-1', sessionId, boardPath, 'User1');
await registerAndJoin('client-1', sessionId, boardPath, 'User1');
await roomManager.endSession(sessionId);

// Query nearby sessions
Expand All @@ -603,7 +614,7 @@ describe('Session Persistence - Hybrid Redis + Postgres', () => {
const climb = createTestClimb();

// Should still work
await roomManager.joinSession('client-1', sessionId, boardPath, 'User1');
await registerAndJoin('client-1', sessionId, boardPath, 'User1');
const currentState = await roomManager.getQueueState(sessionId);
await roomManager.updateQueueState(sessionId, [climb], null, currentState.version);

Expand Down Expand Up @@ -633,15 +644,15 @@ describe('Session Persistence - Hybrid Redis + Postgres', () => {
const boardPath = '/kilter/1/2/3/40';

// Create session and leave
await roomManager.joinSession('client-1', sessionId, boardPath, 'User1');
await registerAndJoin('client-1', sessionId, boardPath, 'User1');
await roomManager.leaveSession('client-1');

// Reset (simulate server restart)
roomManager.reset();
await roomManager.initialize();

// Try to rejoin - should create new session (no restoration in Postgres-only mode)
const result = await roomManager.joinSession('client-2', sessionId, boardPath, 'User2');
const result = await registerAndJoin('client-2', sessionId, boardPath, 'User2');

// Should be fresh session
expect(result.queue).toHaveLength(0);
Expand All @@ -655,7 +666,7 @@ describe('Session Persistence - Hybrid Redis + Postgres', () => {
const climb = createTestClimb();

// Create session and update queue
await roomManager.joinSession('client-1', sessionId, boardPath, 'User1');
await registerAndJoin('client-1', sessionId, boardPath, 'User1');
const currentState = await roomManager.getQueueState(sessionId);
await roomManager.updateQueueState(sessionId, [climb], null, currentState.version);

Expand All @@ -681,8 +692,8 @@ describe('Session Persistence - Hybrid Redis + Postgres', () => {
const boardPath = '/kilter/1/2/3/40';

// Create session
await roomManager.joinSession('client-1', sessionId, boardPath, 'User1');
await roomManager.joinSession('client-2', sessionId, boardPath, 'User2');
await registerAndJoin('client-1', sessionId, boardPath, 'User1');
await registerAndJoin('client-2', sessionId, boardPath, 'User2');

// Verify users in Redis
const redisHashes = (mockRedis as any)._hashes as Map<string, Record<string, string>>;
Expand All @@ -700,7 +711,7 @@ describe('Session Persistence - Hybrid Redis + Postgres', () => {
const sessionId = uuidv4();
const boardPath = '/kilter/1/2/3/40';

await roomManager.joinSession('client-1', sessionId, boardPath, 'User1');
await registerAndJoin('client-1', sessionId, boardPath, 'User1');

// Simulate concurrent updates with same version
const currentState = await roomManager.getQueueState(sessionId);
Expand Down Expand Up @@ -733,7 +744,7 @@ describe('Session Persistence - Hybrid Redis + Postgres', () => {

// Should not crash, might fall back to Postgres-only behavior
await expect(async () => {
await roomManager.joinSession('client-1', sessionId, boardPath, 'User1');
await registerAndJoin('client-1', sessionId, boardPath, 'User1');
}).rejects.toThrow();
});
});
Expand Down
Loading
Loading