feat(M03-A): 完成门店与房间基础管理
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
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.');
|
||||
Reference in New Issue
Block a user