import { describe, test, expect, beforeEach, mock } from 'bun:test' import { readFileSync } from 'node:fs' import { resolve } from 'node:path' import { mockConfigModule } from './helpers/mock-config' // --------------------------------------------------------------------------- // Mock config before any other imports // --------------------------------------------------------------------------- mock.module('../config', () => mockConfigModule({ pollTimeout: 1 })) import { Hono } from 'hono' import { storeReset } from '../store' import { issueToken } from '../auth/token' import { hasAcpRelayAuth } from '../routes/acp' import { sessionAuth } from '../auth/middleware' // Source file paths for static analysis const STORE_PATH = resolve(import.meta.dir, '../store.ts') const ACP_INDEX_PATH = resolve(import.meta.dir, '../routes/acp/index.ts') const MIDDLEWARE_PATH = resolve(import.meta.dir, '../auth/middleware.ts') // --------------------------------------------------------------------------- // Test app for sessionAuth behavior // --------------------------------------------------------------------------- function createTestApp() { const app = new Hono() app.get('/protected', async (c, next) => { const result = await sessionAuth(c, next) if (result) return result return c.json({ userId: c.get('userId'), isAdmin: c.get('isAdmin') }) }) return app } // --------------------------------------------------------------------------- // 1. sessionOwners Map removed // --------------------------------------------------------------------------- describe('sessionOwners Map removed', () => { const storeSource = readFileSync(STORE_PATH, 'utf8') test('store.ts does not declare a sessionOwners Map variable', () => { // Match `const sessionOwners = new Map` or similar declarations const hasMapDecl = /(?:const|let|var)\s+sessionOwners\s*=\s*new\s+Map/.test( storeSource, ) expect(hasMapDecl).toBe(false) }) test('storeBindSession uses SQLite instead of in-memory Map', () => { // The function should reference db/sqlite or a SQL INSERT statement, // NOT sessionOwners.set or sessionOwners.get const bindFnMatch = storeSource.match( /export\s+function\s+storeBindSession[\s\S]*?^}/m, ) if (!bindFnMatch) { // If function was removed entirely, that's also acceptable expect(true).toBe(true) return } const fnBody = bindFnMatch[0] // Should NOT use sessionOwners Map expect(/sessionOwners\.(set|get)/.test(fnBody)).toBe(false) }) test('storeListSessionsByOwnerUuid is removed or migrated to SQLite', () => { // If the function still exists, it must NOT iterate over sessionOwners Map const fnMatch = storeSource.match( /export\s+function\s+storeListSessionsByOwnerUuid[\s\S]*?^}/m, ) if (!fnMatch) { // Function removed entirely — acceptable expect(true).toBe(true) return } const fnBody = fnMatch[0] // Should NOT reference sessionOwners Map expect(/sessionOwners\.(get|has|entries|forEach)/.test(fnBody)).toBe(false) }) test('storeIsSessionOwner uses SQLite instead of in-memory Map', () => { const fnMatch = storeSource.match( /export\s+function\s+storeIsSessionOwner[\s\S]*?^}/m, ) if (!fnMatch) { expect(true).toBe(true) return } const fnBody = fnMatch[0] expect(/sessionOwners\.(get|has)/.test(fnBody)).toBe(false) }) test('storeGetSessionOwners uses SQLite instead of in-memory Map', () => { const fnMatch = storeSource.match( /export\s+function\s+storeGetSessionOwners[\s\S]*?^}/m, ) if (!fnMatch) { expect(true).toBe(true) return } const fnBody = fnMatch[0] expect(/sessionOwners\.(get|has)/.test(fnBody)).toBe(false) }) test('storeReset does not reference sessionOwners.clear()', () => { const resetMatch = storeSource.match( /export\s+function\s+storeReset[\s\S]*?^}/m, ) if (!resetMatch) { expect(true).toBe(true) return } expect(/sessionOwners\.clear\(\)/.test(resetMatch[0])).toBe(false) }) }) // --------------------------------------------------------------------------- // 2. ACP relay WS UUID bypass removed // --------------------------------------------------------------------------- describe('ACP relay WS UUID bypass removed', () => { const acpSource = readFileSync(ACP_INDEX_PATH, 'utf8') test('hasAcpRelayAuth does not accept any non-empty token as UUID', () => { // The current implementation has a comment "Treat any other non-empty token // as a web UI UUID" and returns true unconditionally after validateApiKey. // After cleanup, it should NOT have this pattern. const fnMatch = acpSource.match( /export\s+function\s+hasAcpRelayAuth[\s\S]*?^}/m, ) expect(fnMatch).not.toBeNull() const fnBody = fnMatch![0] // Should NOT contain the UUID bypass comment or unconditional return true const hasUuidBypass = /Treat any other non-empty token as a.*UUID/i.test(fnBody) || /web UI UUID/i.test(fnBody) expect(hasUuidBypass).toBe(false) }) test('hasAcpRelayAuth rejects when no token is provided', () => { // Create a mock context with no auth const c = { req: { header: (name: string) => undefined, }, } as any const result = hasAcpRelayAuth(c) expect(result).toBe(false) }) test('hasAcpRelayAuth accepts valid admin API key', () => { const c = { req: { header: (name: string) => { if (name === 'Authorization') return 'Bearer test-api-key' return undefined }, }, } as any const result = hasAcpRelayAuth(c) expect(result).toBe(true) }) test('hasAcpRelayAuth rejects invalid non-empty token that is not an API key or session token', () => { // After cleanup, an arbitrary string like a UUID should NOT be accepted const c = { req: { header: (name: string) => { if (name === 'Authorization') return 'Bearer some-random-uuid-not-an-api-key' return undefined }, }, } as any // This test verifies the function no longer blindly accepts non-empty tokens. // After cleanup: only admin API key or valid session token should pass. const result = hasAcpRelayAuth(c) expect(result).toBe(false) }) }) // --------------------------------------------------------------------------- // 3. uuidAuth middleware removed / sessionAuth UUID fallback removed // --------------------------------------------------------------------------- describe('uuidAuth middleware removed', () => { const middlewareSource = readFileSync(MIDDLEWARE_PATH, 'utf8') test('uuidAuth function does not exist in middleware.ts', () => { const hasUuidAuthExport = /export\s+(?:async\s+)?function\s+uuidAuth/.test( middlewareSource, ) expect(hasUuidAuthExport).toBe(false) }) test('sessionAuth does not have UUID fallback', () => { const sessionAuthMatch = middlewareSource.match( /export\s+async\s+function\s+sessionAuth[\s\S]*?^}/m, ) if (!sessionAuthMatch) { // If sessionAuth doesn't exist, something is very wrong expect(true).toBe(false) return } const fnBody = sessionAuthMatch[0] // Should NOT contain UUID backward compatibility code const hasUuidFallback = /Phase 1 backward compatibility.*UUID/i.test(fnBody) || /getUuidFromRequest/.test(fnBody) expect(hasUuidFallback).toBe(false) }) test('getUuidFromRequest function does not exist in middleware.ts', () => { const hasGetUuid = /export\s+(?:async\s+)?function\s+getUuidFromRequest/.test( middlewareSource, ) expect(hasGetUuid).toBe(false) }) }) // --------------------------------------------------------------------------- // 3b. sessionAuth behavioral tests (anonymous / UUID → 401) // --------------------------------------------------------------------------- describe('sessionAuth blocks anonymous and UUID-only access', () => { let app: Hono beforeEach(() => { storeReset() app = createTestApp() }) test('anonymous request (no cookie, no Bearer, no UUID) returns 401', async () => { const res = await app.request('/protected') expect(res.status).toBe(401) }) test('request with ?uuid=xxx but no token returns 401', async () => { const res = await app.request('/protected?uuid=some-uuid-value') expect(res.status).toBe(401) }) test('request with X-UUID header but no token returns 401', async () => { const res = await app.request('/protected', { headers: { 'X-UUID': 'some-uuid-value' }, }) expect(res.status).toBe(401) }) test('valid admin API key Bearer returns 200', async () => { const res = await app.request('/protected', { headers: { Authorization: 'Bearer test-api-key' }, }) expect(res.status).toBe(200) const body = await res.json() expect(body.userId).toBe('__system__') expect(body.isAdmin).toBe(true) }) test('valid session token via Bearer returns 200', async () => { const { token } = issueToken('testuser') const res = await app.request('/protected', { headers: { Authorization: `Bearer ${token}` }, }) expect(res.status).toBe(200) const body = await res.json() expect(body.userId).toBe('testuser') }) test('valid session token via cookie returns 200', async () => { const { token } = issueToken('cookieuser') const res = await app.request('/protected', { headers: { Cookie: `rcs_access=${token}` }, }) expect(res.status).toBe(200) const body = await res.json() expect(body.userId).toBe('cookieuser') }) test('invalid Bearer token returns 401', async () => { const res = await app.request('/protected', { headers: { Authorization: 'Bearer invalid-token-xyz' }, }) expect(res.status).toBe(401) }) })