import assert from 'node:assert/strict'; import { buildApp } from '../dist/app.js'; import { signAccessToken } from '../dist/auth/jwt.js'; const secret = 'test-only-member-profile-route-secret'; const token = signAccessToken({ sub: '21', sid: '5c4d3af8-c63c-4edb-bf95-b84127bb3f6e', tid: '7', aid: '9', rv: 1 }, secret, 900); const forbiddenToken = signAccessToken({ sub: '22', sid: '6c4d3af8-c63c-4edb-bf95-b84127bb3f6e', tid: '7', aid: '9', rv: 1 }, secret, 900); const managerToken = signAccessToken({ sub: '23', sid: '7c4d3af8-c63c-4edb-bf95-b84127bb3f6e', tid: '7', aid: '9', rv: 1 }, secret, 900); let profileInput; let benefitsInput; let memberListInput; let memberDetailInput; const app = await buildApp({ members: { jwtSecret: secret, authRepository: { async validateSession(sessionId) { return { id: sessionId, tenantId: '7', platformAppId: '9', expiresAt: new Date(Date.now() + 60000), user: { id: sessionId === '6c4d3af8-c63c-4edb-bf95-b84127bb3f6e' ? '22' : sessionId === '7c4d3af8-c63c-4edb-bf95-b84127bb3f6e' ? '23' : '21', tenantId: '7', userType: 'CUSTOMER', status: 'ACTIVE', roleVersion: 1, nickname: '', avatarUrl: '', phone: '' } }; } }, accessControl: { async getAccessProfile(tenantId, userId) { return userId === '23' ? { roles: ['STORE_ADMIN'], capabilities: ['user.read'], storeIds: ['11'] } : userId === '21' ? { roles: ['CUSTOMER'], capabilities: ['profile.read', 'order.self.read'], storeIds: [] } : { roles: ['CUSTOMER'], capabilities: ['order.self.read'], storeIds: [] }; } }, service: { async listMembers(input) { memberListInput = input; return { items: [{ memberId: '31', nickname: '门店会员', maskedPhone: '138****8000', wallet: { totalBalanceCents: 1200 }, orders: { orderCount: 2 } }], total: 1 }; }, async getMember(input) { memberDetailInput = input; return { memberId: input.memberId, nickname: '门店会员', recentLedger: [] }; }, async getMyBenefits(input) { benefitsInput = input; return { coupons: [{ couponGrantId: '401', name: '30元抵扣券', status: 'AVAILABLE', discountAmountCents: 3000 }], packages: [{ packageHoldingId: '501', name: '三小时畅玩包', status: 'ACTIVE', remainingMinutes: 120 }] }; }, async getMyProfile(input) { profileInput = input; return { memberId: input.userId, nickname: 'Alice', maskedPhone: '138****8000', wallet: { cashBalanceCents: 1000, giftBalanceCents: 200, totalBalanceCents: 1200 }, benefits: { availableCoupons: 2, activePackages: 1, packageMinutes: 90 }, recentLedger: [] }; } } } }); const profile = await app.inject({ method: 'GET', url: '/app-api/profile', headers: { authorization: `Bearer ${token}` } }); assert.equal(profile.statusCode, 200); assert.equal(profileInput.tenantId, '7'); assert.equal(profileInput.userId, '21'); assert.equal(profile.json().data.wallet.totalBalanceCents, 1200); const benefits = await app.inject({ method: 'GET', url: '/app-api/profile/benefits', headers: { authorization: `Bearer ${token}` } }); assert.equal(benefits.statusCode, 200); assert.equal(benefitsInput.tenantId, '7'); assert.equal(benefitsInput.userId, '21'); assert.equal(benefits.json().data.coupons[0].couponGrantId, '401'); assert.equal(benefits.json().data.packages[0].packageHoldingId, '501'); const members = await app.inject({ method: 'GET', url: '/app-api/management/members?page=1&pageSize=20&status=ACTIVE&search=138', headers: { authorization: `Bearer ${managerToken}` } }); assert.equal(members.statusCode, 200); assert.equal(memberListInput.actor.userId, '23'); assert.deepEqual(memberListInput.actor.access.storeIds, ['11']); assert.equal(memberListInput.status, 'ACTIVE'); assert.equal(memberListInput.search, '138'); assert.equal(members.json().data.items[0].memberId, '31'); const memberDetail = await app.inject({ method: 'GET', url: '/app-api/management/members/31', headers: { authorization: `Bearer ${managerToken}` } }); assert.equal(memberDetail.statusCode, 200); assert.equal(memberDetailInput.memberId, '31'); const memberForbidden = await app.inject({ method: 'GET', url: '/app-api/management/members', headers: { authorization: `Bearer ${token}` } }); assert.equal(memberForbidden.statusCode, 403); const unauthorized = await app.inject({ method: 'GET', url: '/app-api/profile' }); assert.equal(unauthorized.statusCode, 401); const forbidden = await app.inject({ method: 'GET', url: '/app-api/profile', headers: { authorization: `Bearer ${forbiddenToken}` } }); assert.equal(forbidden.statusCode, 403); await app.close(); console.log('PASS: M08-A customer profile route exposes only the current member.');