From 0fea1fff14e08b225503b710d2b6d340e8ae5f1d Mon Sep 17 00:00:00 2001 From: Devin Zuczek Date: Tue, 9 Jun 2026 00:58:37 -0400 Subject: [PATCH] stub /goto/room --- apps/match/README.md | 5 +-- apps/match/src/match.app.ts | 37 +++++++++++++++++++-- apps/match/src/test/integration/api.test.ts | 26 +++++++++++++-- 3 files changed, 61 insertions(+), 7 deletions(-) diff --git a/apps/match/README.md b/apps/match/README.md index e4ac062..99a38e9 100644 --- a/apps/match/README.md +++ b/apps/match/README.md @@ -13,8 +13,9 @@ C# `MatchmakingController`. EF Core (`AppDbContext`) queries are stubbed for now (the C# fell back to that file when the account/room instance wasn't found). - **`POST /goto/none`** returns the static offline-dorm instance with a fresh `photonRoomId`. -- **`POST /goto/room/:room`** 404s — without a Rooms binding the room can't be - resolved, matching the C# `NotFound` path. +- **`POST /goto/room/:room`** synthesizes the room-instance response (no Rooms + binding yet). The dorm gets its known scene id and a private instance; other + rooms get an empty `location` and respect the posted `JoinMode` (2 = private). - **`POST /player/heartbeat`** echoes the posted heartbeat fields; `roomInstance` is always null and `isOnline` false until there's a DB binding. - **`/player/login`, `/player/statusvisibility`, `/roominstance/:id/reportjoinresult`** diff --git a/apps/match/src/match.app.ts b/apps/match/src/match.app.ts index 7cac812..a447c37 100644 --- a/apps/match/src/match.app.ts +++ b/apps/match/src/match.app.ts @@ -123,9 +123,40 @@ const app = new Hono() .post('/goto/room/:room', async (c) => { const id = await authedId(c) if (id === null) return unauthorized(c) - // No Rooms binding → the room can never be found (C# returns NotFound here). - // TODO: resolve the Room, upsert a RoomInstance, and return it. - return c.text('Room not found', 404) + + const room = c.req.param('room') + const isDorm = room.toLowerCase() === 'dormroom' + + const body = await c.req.parseBody().catch(() => ({}) as Record) + const joinMode = typeof body.JoinMode === 'string' ? Number.parseInt(body.JoinMode, 10) || 0 : 0 + const isPrivate = joinMode === 2 || isDorm + + // No Rooms/SubRooms/RoomInstances bindings yet, so synthesize the instance + // the C# would build. The dorm scene id is known; other rooms get an empty + // location until there's real Room data. + // TODO: resolve the Room + SubRoom and upsert a RoomInstance once a DB binding exists. + return c.json({ + errorCode: 0, + roomInstance: { + roomInstanceId: 1, + roomId: isDorm ? 1 : Number.parseInt(room, 10) || 1, + subRoomId: isDorm ? 1 : 0, + roomInstanceType: 2, + location: isDorm ? '76d98498-60a1-430c-ab76-b54a29b7a163' : '', + dataBlob: '', + eventId: 0, + clubId: 0, + roomCode: '', + photonRegionId: 'us', + photonRoomId: crypto.randomUUID(), + name: isDorm ? 'DormRoom' : room, + maxCapacity: 4, + isFull: false, + isPrivate, + isInProgress: false, + EncryptVoiceChat: false, + }, + }) }) .post('/goto/none', (c) => diff --git a/apps/match/src/test/integration/api.test.ts b/apps/match/src/test/integration/api.test.ts index 8f956d9..03a4a31 100644 --- a/apps/match/src/test/integration/api.test.ts +++ b/apps/match/src/test/integration/api.test.ts @@ -81,12 +81,34 @@ describe('auth-gated endpoints', () => { expect(res.status).toBe(401) }) - test('POST /goto/room/:room 404s with a valid token (no DB)', async () => { + test('POST /goto/room/dormroom returns the dorm instance', async () => { const res = await exports.default.fetch(`${ORIGIN}/goto/room/dormroom`, { method: 'POST', headers: await bearer(), }) - expect(res.status).toBe(404) + expect(res.status).toBe(200) + const body = (await res.json()) as { + errorCode: number + roomInstance: { name: string; location: string; isPrivate: boolean; roomId: number } + } + expect(body.errorCode).toBe(0) + expect(body.roomInstance).toMatchObject({ + name: 'DormRoom', + location: '76d98498-60a1-430c-ab76-b54a29b7a163', + isPrivate: true, + roomId: 1, + }) + }) + + test('POST /goto/room/:id synthesizes an instance and honors JoinMode', async () => { + const res = await exports.default.fetch(`${ORIGIN}/goto/room/42`, { + method: 'POST', + headers: { ...(await bearer()), 'Content-Type': 'application/x-www-form-urlencoded' }, + body: new URLSearchParams({ JoinMode: '2' }).toString(), + }) + expect(res.status).toBe(200) + const body = (await res.json()) as { roomInstance: { roomId: number; isPrivate: boolean; name: string } } + expect(body.roomInstance).toMatchObject({ roomId: 42, isPrivate: true, name: '42' }) }) test('POST /player/heartbeat 401s without a token', async () => {