feat: add feedback operations report

This commit is contained in:
Codex
2026-06-30 15:38:47 +08:00
parent 87f4cf9c4e
commit ddad2ac04d
17 changed files with 593 additions and 44 deletions

View File

@@ -7770,12 +7770,15 @@ async function testTenantAdminOps() {
assert.equal(fakeWechat.requests.at(-1)?.query?.js_code, 'integration-code-001', 'wechat code should be exchanged server-side');
assert.ok(!JSON.stringify(miniappLogin).includes('session-key-integration-code-001'), 'login response must not leak WeChat session_key');
assert.ok(!JSON.stringify(miniappLogin).includes('wechat-app-secret-smoke'), 'login response must not leak app secret');
assert.equal(miniappLogin.user?.avatarUrl, null, 'wechat miniapp login must not persist client avatarUrl');
assert.ok(!JSON.stringify(miniappLogin).includes('https://example.test/avatar.png'), 'wechat miniapp login response must not echo client avatarUrl');
const miniappMe = await request('/api/auth/me', {
userId: false,
headers: { authorization: `Bearer ${miniappLogin.session.token}` },
});
assert.equal(miniappMe.user?.id, miniappLogin.user.id, 'wechat session should work with auth/me');
assert.equal(miniappMe.user?.avatarUrl, null, 'wechat session auth/me should keep avatarUrl empty');
const wechatWebProvider = await request('/api/tenant-admin/auth-providers', {
userId: TENANT_ADMIN_USER_ID,
@@ -7814,6 +7817,7 @@ async function testTenantAdminOps() {
assert.ok(fakeWechat.requests.some(item => item.pathname === '/sns/userinfo'), 'wechat web login should fetch userinfo server-side');
assert.ok(!JSON.stringify(wechatWebLogin).includes('wechat-web-token-web-code-001'), 'wechat web response must not leak access_token');
assert.ok(!JSON.stringify(wechatWebLogin).includes('wechat-app-secret-smoke'), 'wechat web response must not leak app secret');
assert.ok(!JSON.stringify(wechatWebLogin).includes('wechat-web-avatar.png'), 'wechat web response must not echo provider avatar');
const qqProvider = await request('/api/tenant-admin/auth-providers', {
userId: TENANT_ADMIN_USER_ID,
@@ -7854,6 +7858,8 @@ async function testTenantAdminOps() {
assert.ok(fakeQq.requests.some(item => item.pathname === '/user/get_user_info'), 'qq login should fetch userinfo server-side');
assert.ok(!JSON.stringify(qqLogin).includes('qq-token-qq-code-001'), 'qq response must not leak access_token');
assert.ok(!JSON.stringify(qqLogin).includes('qq-client-secret-smoke'), 'qq response must not leak client secret');
assert.equal(qqLogin.user?.avatarUrl, null, 'qq login must not persist provider avatarUrl');
assert.ok(!JSON.stringify(qqLogin).includes('qq-avatar.png'), 'qq login response must not echo provider avatar');
const paymentAccount = await request('/api/tenant-admin/payment-accounts', {
userId: TENANT_ADMIN_USER_ID,
@@ -8283,6 +8289,26 @@ async function testTenantAdminOps() {
assert.ok(feedbackEvents.items?.some(item => item.toStatus === 'pending'), 'feedback events should include initial pending event');
assert.ok(feedbackEvents.items?.some(item => item.toStatus === 'resolved'), 'feedback events should include resolved event');
const feedbackReport = await request('/api/tenant-admin/feedbacks/report', {
userId: TENANT_ADMIN_USER_ID,
query: { timeRange: '30d', limit: 20 },
});
assert.ok(feedbackReport.item?.summary?.total >= 1, 'tenant admin feedback report should count created feedback');
assert.ok(feedbackReport.item?.summary?.handledInRange >= 1, 'tenant admin feedback report should count handled feedback');
assert.ok(feedbackReport.item?.summary?.rewardPoints >= 5, 'tenant admin feedback report should include feedback reward points');
assert.ok(
feedbackReport.item?.byStatus?.some(item => item.status === 'resolved'),
'tenant admin feedback report should group by resolved status',
);
assert.ok(Array.isArray(feedbackReport.item?.recentUnhandled), 'tenant admin feedback report should expose recent unhandled list');
const invalidFeedbackReportRange = await request('/api/tenant-admin/feedbacks/report', {
userId: TENANT_ADMIN_USER_ID,
query: { timeRange: '365d' },
expectStatus: 400,
});
assert.equal(invalidFeedbackReportRange.code, 'INVALID_FEEDBACK_REPORT_RANGE', 'feedback report should reject unsupported range');
const partnerFeedbackDenied = await request('/api/tenant-admin/feedbacks', {
tenantId: PARTNER_TENANT_ID,
userId: TENANT_ADMIN_USER_ID,
@@ -8290,6 +8316,13 @@ async function testTenantAdminOps() {
});
assert.equal(partnerFeedbackDenied.code, 'TENANT_ADMIN_REQUIRED', 'feedback admin must be tenant isolated');
const partnerFeedbackReportDenied = await request('/api/tenant-admin/feedbacks/report', {
tenantId: PARTNER_TENANT_ID,
userId: TENANT_ADMIN_USER_ID,
expectStatus: 403,
});
assert.equal(partnerFeedbackReportDenied.code, 'TENANT_ADMIN_REQUIRED', 'feedback report must be tenant isolated');
const batch = await request('/api/tenant-admin/code-batches', {
userId: TENANT_ADMIN_USER_ID,
method: 'PUT',