import assert from 'node:assert/strict'; import { buildApp } from '../dist/app.js'; import { signAccessToken } from '../dist/auth/jwt.js'; import { StoreRoomError, StoreRoomRepository } from '../dist/stores/store-room-repository.js'; const tenantActor = { tenantId: '7', userId: '21', access: { roles: ['TENANT_ADMIN'], capabilities: ['tenant.manage'], storeIds: [] }, traceId: 'trace', ip: '127.0.0.1', userAgent: 'test' }; const storeActor = { tenantId: '7', userId: '22', access: { roles: ['STORE_ADMIN'], capabilities: ['store.operation.read', 'store.operation.write'], storeIds: ['11'] }, traceId: 'trace', ip: '127.0.0.1', userAgent: 'test' }; const repository = new StoreRoomRepository({ async execute(sql) { if (sql.includes('FROM qipai_stores')) return [[{ id: 11, name: 'A', longitude: null, latitude: null }], []]; return [[], []]; } }); assert.equal((await repository.listStores(storeActor))[0].id, '11'); await assert.rejects( () => repository.listRooms({ ...storeActor, access: { ...storeActor.access, storeIds: ['12'] } }, '11'), (error) => error instanceof StoreRoomError && error.code === 'STORE_SCOPE_FORBIDDEN' ); const secret = 'test-only-jwt-secret-with-at-least-32-characters'; const token = signAccessToken({ sub: '21', sid: '5c4d3af8-c63c-4edb-bf95-b84127bb3f6e', tid: '7', aid: '9', rv: 1 }, secret, 900); let roomInput; const app = await buildApp({ storeRoom: { jwtSecret: secret, authRepository: { async validateSession() { return { id: '5c4d3af8-c63c-4edb-bf95-b84127bb3f6e', tenantId: '7', platformAppId: '9', expiresAt: new Date(Date.now() + 60000), user: { id: '21', tenantId: '7', userType: 'STAFF', status: 'ACTIVE', roleVersion: 1, nickname: '', avatarUrl: '', phone: '' } }; } }, accessControl: { async getAccessProfile() { return tenantActor.access; } }, repository: { async listStores() { return []; }, async createStore() { return { storeId: '11' }; }, async updateStore() { return { storeId: '11' }; }, async archiveStore() { return { storeId: '11', archived: true }; }, async listRooms() { return []; }, async createRoom(_actor, input) { roomInput = input; return { roomId: '31' }; }, async updateRoom() { return { roomId: '31' }; }, async archiveRoom() { return { roomId: '31', archived: true }; }, async addDisabledPeriod() { return { disabledPeriodId: '41' }; } } } }); const created = await app.inject({ method: 'POST', url: '/admin-api/rooms', headers: { authorization: `Bearer ${token}` }, payload: { storeId: '11', categoryName: '标准间', name: 'A01', roomNo: 'A01', capacity: 4, basePriceCents: 3000, weekdayPriceCents: 2800, holidayPriceCents: 3500, overnightPriceCents: 12000, depositCents: 0, minimumMinutes: 60, maxAdvanceStartMinutes: 30, maxAdvanceDays: 30, configurationStatus: 'ENABLED', operationalStatus: 'AVAILABLE', tags: ['麻将'], images: [], sortOrder: 1 } }); assert.equal(created.statusCode, 201); assert.equal(created.json().data.roomId, '31'); assert.equal(roomInput.weekdayPriceCents, 2800); const invalid = await app.inject({ method: 'POST', url: '/admin-api/rooms/31/disabled-periods', headers: { authorization: `Bearer ${token}` }, payload: { storeId: '11', startsAt: '2026-06-20T12:00:00Z', endsAt: '2026-06-20T11:00:00Z' } }); assert.equal(invalid.statusCode, 400); await app.close(); console.log('PASS: M03-A store scope, room pricing/status validation and management routes are present.');