From 00f8e8e727c1922312446cb08f3b6d8e4fe215cb Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 31 Dec 2025 07:55:32 +0000 Subject: [PATCH 1/6] fix: Add test user to backend test setup to satisfy foreign key constraint The session-persistence tests use 'user-123' as the userId for createDiscoverableSession, but the board_sessions.created_by_user_id column has a foreign key constraint referencing the users table. This fix: - Truncates the users table in beforeEach to ensure clean state - Creates the 'user-123' test user before each test --- packages/backend/src/__tests__/setup.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/backend/src/__tests__/setup.ts b/packages/backend/src/__tests__/setup.ts index 138aa28e..edf94698 100644 --- a/packages/backend/src/__tests__/setup.ts +++ b/packages/backend/src/__tests__/setup.ts @@ -116,6 +116,15 @@ beforeEach(async () => { await db.execute(sql`TRUNCATE TABLE board_session_queues CASCADE`); await db.execute(sql`TRUNCATE TABLE board_session_clients CASCADE`); await db.execute(sql`TRUNCATE TABLE board_sessions CASCADE`); + await db.execute(sql`TRUNCATE TABLE users CASCADE`); + + // Create test users that are referenced by tests + // The session-persistence tests use 'user-123' for discoverable sessions + await db.execute(sql` + INSERT INTO users (id, email, name) + VALUES ('user-123', 'test@example.com', 'Test User') + ON CONFLICT (id) DO NOTHING + `); }); afterAll(async () => { From 7097d35aef8fb51a939d4b94daa44a309e99a89c Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 31 Dec 2025 08:03:22 +0000 Subject: [PATCH 2/6] fix: Use shared db instance in test setup to fix FK constraint error The test setup was creating its own database connection, while roomManager uses the singleton db from @boardsesh/db/client. This caused the test user insertion to not be visible to roomManager when it tried to create discoverable sessions. Changes: - Import and use sharedDb (same instance as roomManager) for all data operations in beforeEach - Remove unused local db connection and related imports - Keep raw postgres client only for initial schema creation --- packages/backend/src/__tests__/setup.ts | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/packages/backend/src/__tests__/setup.ts b/packages/backend/src/__tests__/setup.ts index edf94698..b63dc356 100644 --- a/packages/backend/src/__tests__/setup.ts +++ b/packages/backend/src/__tests__/setup.ts @@ -1,8 +1,7 @@ import { beforeAll, beforeEach, afterAll } from 'vitest'; -import { drizzle } from 'drizzle-orm/postgres-js'; import postgres from 'postgres'; import { sql } from 'drizzle-orm'; -import * as schema from '../db/schema.js'; +import { db as sharedDb } from '../db/client.js'; import { roomManager } from '../services/room-manager.js'; const TEST_DB_NAME = 'boardsesh_backend_test'; @@ -13,7 +12,6 @@ const connectionString = const baseConnectionString = connectionString.replace(/\/[^/]+$/, '/postgres'); let migrationClient: ReturnType; -let db: ReturnType; // SQL to create only the tables needed for backend tests const createTablesSQL = ` @@ -100,9 +98,8 @@ beforeAll(async () => { await adminClient.end(); } - // Now connect to the test database + // Now connect to the test database for schema creation migrationClient = postgres(connectionString, { max: 1, onnotice: () => {} }); - db = drizzle(migrationClient, { schema }); // Create tables directly (backend tests only need session tables) await migrationClient.unsafe(createTablesSQL); @@ -113,14 +110,15 @@ beforeEach(async () => { roomManager.reset(); // Clear all tables in correct order (respect foreign keys) - await db.execute(sql`TRUNCATE TABLE board_session_queues CASCADE`); - await db.execute(sql`TRUNCATE TABLE board_session_clients CASCADE`); - await db.execute(sql`TRUNCATE TABLE board_sessions CASCADE`); - await db.execute(sql`TRUNCATE TABLE users CASCADE`); + // Use sharedDb (same instance as roomManager) to ensure consistency + await sharedDb.execute(sql`TRUNCATE TABLE board_session_queues CASCADE`); + await sharedDb.execute(sql`TRUNCATE TABLE board_session_clients CASCADE`); + await sharedDb.execute(sql`TRUNCATE TABLE board_sessions CASCADE`); + await sharedDb.execute(sql`TRUNCATE TABLE users CASCADE`); // Create test users that are referenced by tests // The session-persistence tests use 'user-123' for discoverable sessions - await db.execute(sql` + await sharedDb.execute(sql` INSERT INTO users (id, email, name) VALUES ('user-123', 'test@example.com', 'Test User') ON CONFLICT (id) DO NOTHING From 753f54d8b56e039bb45800c829b6b6b141d316d7 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 31 Dec 2025 10:25:40 +0000 Subject: [PATCH 3/6] fix: Add kilter/tension tables and fix test parameters for climb queries Changes: - Add kilter_climbs, kilter_climb_stats, kilter_difficulty_grades tables to test setup (required for climb query tests) - Add equivalent tension tables for tension board tests - Fix test parameters to use valid size_id values: - kilter: size_id 7 (12x14 Commercial) instead of 1 - tension: size_id 1 (Full Wall) is already valid The climb-queries tests require these tables to exist even if empty, as the queries need to execute against actual database tables. --- .../src/__tests__/climb-queries.test.ts | 12 +-- packages/backend/src/__tests__/setup.ts | 90 ++++++++++++++++++- 2 files changed, 96 insertions(+), 6 deletions(-) diff --git a/packages/backend/src/__tests__/climb-queries.test.ts b/packages/backend/src/__tests__/climb-queries.test.ts index b27987cb..a8306257 100644 --- a/packages/backend/src/__tests__/climb-queries.test.ts +++ b/packages/backend/src/__tests__/climb-queries.test.ts @@ -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, }; @@ -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', }); @@ -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, diff --git a/packages/backend/src/__tests__/setup.ts b/packages/backend/src/__tests__/setup.ts index b63dc356..2cf61690 100644 --- a/packages/backend/src/__tests__/setup.ts +++ b/packages/backend/src/__tests__/setup.ts @@ -20,6 +20,12 @@ const createTablesSQL = ` DROP TABLE IF EXISTS "board_session_clients" CASCADE; DROP TABLE IF EXISTS "board_sessions" CASCADE; DROP TABLE IF EXISTS "users" CASCADE; + DROP TABLE IF EXISTS "kilter_climbs" CASCADE; + DROP TABLE IF EXISTS "kilter_climb_stats" CASCADE; + DROP TABLE IF EXISTS "kilter_difficulty_grades" CASCADE; + DROP TABLE IF EXISTS "tension_climbs" CASCADE; + DROP TABLE IF EXISTS "tension_climb_stats" CASCADE; + DROP TABLE IF EXISTS "tension_difficulty_grades" CASCADE; -- Create users table (minimal, needed for FK reference) CREATE TABLE IF NOT EXISTS "users" ( @@ -66,7 +72,89 @@ const createTablesSQL = ` "updated_at" timestamp DEFAULT now() NOT NULL ); - -- Create indexes + -- Create kilter tables for climb query tests + CREATE TABLE IF NOT EXISTS "kilter_climbs" ( + "uuid" text PRIMARY KEY NOT NULL, + "layout_id" integer, + "setter_id" integer, + "setter_username" text, + "name" text, + "description" text, + "hsm" integer, + "edge_left" integer, + "edge_right" integer, + "edge_bottom" integer, + "edge_top" integer, + "frames_count" integer DEFAULT 1, + "frames_pace" integer, + "frames" text, + "is_draft" boolean DEFAULT false, + "is_listed" boolean DEFAULT true, + "created_at" text + ); + + CREATE TABLE IF NOT EXISTS "kilter_climb_stats" ( + "climb_uuid" text NOT NULL, + "angle" integer NOT NULL, + "display_difficulty" double precision, + "benchmark_difficulty" double precision, + "ascensionist_count" bigint, + "difficulty_average" double precision, + "quality_average" double precision, + "fa_username" text, + "fa_at" timestamp, + PRIMARY KEY ("climb_uuid", "angle") + ); + + CREATE TABLE IF NOT EXISTS "kilter_difficulty_grades" ( + "difficulty" integer PRIMARY KEY NOT NULL, + "boulder_name" text, + "route_name" text, + "is_listed" boolean DEFAULT true + ); + + -- Create tension tables for climb query tests + CREATE TABLE IF NOT EXISTS "tension_climbs" ( + "uuid" text PRIMARY KEY NOT NULL, + "layout_id" integer, + "setter_id" integer, + "setter_username" text, + "name" text, + "description" text, + "hsm" integer, + "edge_left" integer, + "edge_right" integer, + "edge_bottom" integer, + "edge_top" integer, + "frames_count" integer DEFAULT 1, + "frames_pace" integer, + "frames" text, + "is_draft" boolean DEFAULT false, + "is_listed" boolean DEFAULT true, + "created_at" text + ); + + CREATE TABLE IF NOT EXISTS "tension_climb_stats" ( + "climb_uuid" text NOT NULL, + "angle" integer NOT NULL, + "display_difficulty" double precision, + "benchmark_difficulty" double precision, + "ascensionist_count" bigint, + "difficulty_average" double precision, + "quality_average" double precision, + "fa_username" text, + "fa_at" timestamp, + PRIMARY KEY ("climb_uuid", "angle") + ); + + CREATE TABLE IF NOT EXISTS "tension_difficulty_grades" ( + "difficulty" integer PRIMARY KEY NOT NULL, + "boulder_name" text, + "route_name" text, + "is_listed" boolean DEFAULT true + ); + + -- Create indexes for board_sessions CREATE INDEX IF NOT EXISTS "board_sessions_location_idx" ON "board_sessions" ("latitude", "longitude"); CREATE INDEX IF NOT EXISTS "board_sessions_discoverable_idx" ON "board_sessions" ("discoverable"); CREATE INDEX IF NOT EXISTS "board_sessions_user_idx" ON "board_sessions" ("created_by_user_id"); From 1f32ad1c129df61dfd2b7d7ee946e6ba355aeb01 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 31 Dec 2025 23:06:10 +0000 Subject: [PATCH 4/6] fix: Add kilter/tension ascents and bids tables for user-specific queries The climb query tests with userId parameter use subqueries that reference kilter_ascents, kilter_bids, tension_ascents, and tension_bids tables to count user's ascents and attempts. --- packages/backend/src/__tests__/setup.ts | 68 +++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/packages/backend/src/__tests__/setup.ts b/packages/backend/src/__tests__/setup.ts index 2cf61690..ececc291 100644 --- a/packages/backend/src/__tests__/setup.ts +++ b/packages/backend/src/__tests__/setup.ts @@ -23,9 +23,13 @@ const createTablesSQL = ` DROP TABLE IF EXISTS "kilter_climbs" CASCADE; DROP TABLE IF EXISTS "kilter_climb_stats" CASCADE; DROP TABLE IF EXISTS "kilter_difficulty_grades" CASCADE; + DROP TABLE IF EXISTS "kilter_ascents" CASCADE; + DROP TABLE IF EXISTS "kilter_bids" CASCADE; DROP TABLE IF EXISTS "tension_climbs" CASCADE; DROP TABLE IF EXISTS "tension_climb_stats" CASCADE; DROP TABLE IF EXISTS "tension_difficulty_grades" CASCADE; + DROP TABLE IF EXISTS "tension_ascents" CASCADE; + DROP TABLE IF EXISTS "tension_bids" CASCADE; -- Create users table (minimal, needed for FK reference) CREATE TABLE IF NOT EXISTS "users" ( @@ -113,6 +117,38 @@ const createTablesSQL = ` "is_listed" boolean DEFAULT true ); + CREATE TABLE IF NOT EXISTS "kilter_ascents" ( + "uuid" text PRIMARY KEY NOT NULL, + "climb_uuid" text, + "angle" integer, + "is_mirror" boolean, + "user_id" integer, + "attempt_id" integer, + "bid_count" integer DEFAULT 1, + "quality" integer, + "difficulty" integer, + "is_benchmark" integer DEFAULT 0, + "comment" text DEFAULT '', + "climbed_at" text, + "created_at" text, + "synced" boolean DEFAULT true NOT NULL, + "sync_error" text + ); + + CREATE TABLE IF NOT EXISTS "kilter_bids" ( + "uuid" text PRIMARY KEY NOT NULL, + "user_id" integer, + "climb_uuid" text, + "angle" integer, + "is_mirror" boolean, + "bid_count" integer DEFAULT 1, + "comment" text DEFAULT '', + "climbed_at" text, + "created_at" text, + "synced" boolean DEFAULT true NOT NULL, + "sync_error" text + ); + -- Create tension tables for climb query tests CREATE TABLE IF NOT EXISTS "tension_climbs" ( "uuid" text PRIMARY KEY NOT NULL, @@ -154,6 +190,38 @@ const createTablesSQL = ` "is_listed" boolean DEFAULT true ); + CREATE TABLE IF NOT EXISTS "tension_ascents" ( + "uuid" text PRIMARY KEY NOT NULL, + "climb_uuid" text, + "angle" integer, + "is_mirror" boolean, + "user_id" integer, + "attempt_id" integer, + "bid_count" integer DEFAULT 1, + "quality" integer, + "difficulty" integer, + "is_benchmark" integer DEFAULT 0, + "comment" text DEFAULT '', + "climbed_at" text, + "created_at" text, + "synced" boolean DEFAULT true NOT NULL, + "sync_error" text + ); + + CREATE TABLE IF NOT EXISTS "tension_bids" ( + "uuid" text PRIMARY KEY NOT NULL, + "user_id" integer, + "climb_uuid" text, + "angle" integer, + "is_mirror" boolean, + "bid_count" integer DEFAULT 1, + "comment" text DEFAULT '', + "climbed_at" text, + "created_at" text, + "synced" boolean DEFAULT true NOT NULL, + "sync_error" text + ); + -- Create indexes for board_sessions CREATE INDEX IF NOT EXISTS "board_sessions_location_idx" ON "board_sessions" ("latitude", "longitude"); CREATE INDEX IF NOT EXISTS "board_sessions_discoverable_idx" ON "board_sessions" ("discoverable"); From d369876ad311be9eaa66c1f93cc19def9245d8dd Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 1 Jan 2026 04:38:43 +0000 Subject: [PATCH 5/6] fix: Register clients before joining sessions in persistence tests The roomManager.joinSession() requires clients to be registered first via registerClient(). Added a registerAndJoin helper function and updated all test calls to use it. --- .../src/__tests__/session-persistence.test.ts | 69 +++++++++++-------- 1 file changed, 40 insertions(+), 29 deletions(-) diff --git a/packages/backend/src/__tests__/session-persistence.test.ts b/packages/backend/src/__tests__/session-persistence.test.ts index d41c44a5..b0529eea 100644 --- a/packages/backend/src/__tests__/session-persistence.test.ts +++ b/packages/backend/src/__tests__/session-persistence.test.ts @@ -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; @@ -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 @@ -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 @@ -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 @@ -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); @@ -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); @@ -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); @@ -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 @@ -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 @@ -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); @@ -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); @@ -342,7 +353,7 @@ 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 @@ -350,7 +361,7 @@ describe('Session Persistence - Hybrid Redis + Postgres', () => { 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); @@ -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); @@ -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); @@ -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); @@ -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); @@ -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 @@ -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) @@ -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 @@ -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); @@ -633,7 +644,7 @@ 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) @@ -641,7 +652,7 @@ describe('Session Persistence - Hybrid Redis + Postgres', () => { 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); @@ -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); @@ -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>; @@ -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); @@ -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(); }); }); From 8d7029a75bf49d8663b77b16cd1e025237599083 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 1 Jan 2026 04:39:49 +0000 Subject: [PATCH 6/6] fix: Use valid UUIDs in integration test fixtures The queue item validation requires proper UUID format. Updated createTestClimb() to generate valid UUIDs using uuidv4() instead of using the label parameter as the UUID value. --- packages/backend/src/__tests__/integration.test.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/backend/src/__tests__/integration.test.ts b/packages/backend/src/__tests__/integration.test.ts index 9fe2b3eb..f838519f 100644 --- a/packages/backend/src/__tests__/integration.test.ts +++ b/packages/backend/src/__tests__/integration.test.ts @@ -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'; @@ -12,12 +13,14 @@ const TEST_PORT = 8082; 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,