forked from wangziqi/gongxue-base
feat: notify tenants about public bank syncs
This commit is contained in:
@@ -4238,6 +4238,46 @@ async function testPublicQuestionBankAdoption() {
|
||||
'public bank sync should insert newly published source questions',
|
||||
);
|
||||
|
||||
const syncNotifications = await request('/api/tenant-content/notifications', {
|
||||
tenantId: PARTNER_TENANT_ID,
|
||||
userId: PARTNER_TENANT_ADMIN_USER_ID,
|
||||
query: { notificationType: 'public_question_bank_synced', adoptionId: adopted.item.id },
|
||||
});
|
||||
const syncNotification = syncNotifications.items?.find(item => item.adoptionId === adopted.item.id && item.status === 'unread');
|
||||
assert.ok(syncNotification?.id, 'public bank sync with inserted questions should create an unread tenant notification');
|
||||
assert.equal(syncNotification?.severity, 'success', 'successful public bank sync notification should be success severity');
|
||||
assert.equal(syncNotifications.summary?.unread >= 1, true, 'notification summary should include unread public bank sync notification');
|
||||
assert.ok(!JSON.stringify(syncNotification).includes('新增公共题应同步到已采纳租户'), 'public bank notification should not leak question explanations');
|
||||
|
||||
const markedSyncNotification = await request('/api/tenant-content/notifications/status', {
|
||||
tenantId: PARTNER_TENANT_ID,
|
||||
userId: PARTNER_TENANT_ADMIN_USER_ID,
|
||||
method: 'POST',
|
||||
body: { notificationIds: [syncNotification.id], status: 'read' },
|
||||
});
|
||||
assert.equal(markedSyncNotification.item?.updatedCount, 1, 'tenant admin should mark content notification as read');
|
||||
const invalidNotificationId = await request('/api/tenant-content/notifications/status', {
|
||||
tenantId: PARTNER_TENANT_ID,
|
||||
userId: PARTNER_TENANT_ADMIN_USER_ID,
|
||||
method: 'POST',
|
||||
body: { notificationIds: ['not-a-uuid'], status: 'read' },
|
||||
expectStatus: 400,
|
||||
});
|
||||
assert.equal(invalidNotificationId.code, 'CONTENT_NOTIFICATION_ID_INVALID', 'notification status API should reject malformed UUIDs before querying');
|
||||
|
||||
const crossTenantNotifications = await request('/api/tenant-content/notifications', {
|
||||
userId: TENANT_ADMIN_USER_ID,
|
||||
query: { adoptionId: adopted.item.id },
|
||||
});
|
||||
assert.equal(crossTenantNotifications.items?.length, 0, 'content notifications must not expose partner tenant rows to another tenant admin');
|
||||
const crossTenantNotificationsDenied = await request('/api/tenant-content/notifications', {
|
||||
tenantId: PARTNER_TENANT_ID,
|
||||
userId: TENANT_ADMIN_USER_ID,
|
||||
query: { adoptionId: adopted.item.id },
|
||||
expectStatus: 403,
|
||||
});
|
||||
assert.equal(crossTenantNotificationsDenied.code, 'TENANT_CONTENT_EDITOR_REQUIRED', 'content notifications must require tenant content permission in the current tenant');
|
||||
|
||||
const collectionAfterSync = await request('/api/catalog/question-collections/questions', {
|
||||
tenantId: PARTNER_TENANT_ID,
|
||||
userId: false,
|
||||
@@ -4302,6 +4342,16 @@ async function testPublicQuestionBankAdoption() {
|
||||
'public bank sync should identify the source question that conflicts with tenant edits',
|
||||
);
|
||||
|
||||
const conflictNotifications = await request('/api/tenant-content/notifications', {
|
||||
tenantId: PARTNER_TENANT_ID,
|
||||
userId: PARTNER_TENANT_ADMIN_USER_ID,
|
||||
query: { notificationType: 'public_question_bank_conflict', adoptionId: adopted.item.id },
|
||||
});
|
||||
const conflictNotification = conflictNotifications.items?.find(item => item.adoptionId === adopted.item.id && item.status === 'unread');
|
||||
assert.ok(conflictNotification?.id, 'public bank conflicts should create an unread tenant notification');
|
||||
assert.equal(conflictNotification?.severity, 'warning', 'public bank conflict notification should be warning severity');
|
||||
assert.equal(conflictNotification?.metadata?.syncStatus, 'conflict', 'public bank conflict notification should expose conflict status metadata');
|
||||
|
||||
const conflictList = await request('/api/tenant-content/public-question-banks/conflicts', {
|
||||
tenantId: PARTNER_TENANT_ID,
|
||||
userId: PARTNER_TENANT_ADMIN_USER_ID,
|
||||
@@ -4369,6 +4419,15 @@ async function testPublicQuestionBankAdoption() {
|
||||
query: { adoptionId: adopted.item.id },
|
||||
});
|
||||
assert.equal(conflictListAfterAccept.item?.conflictCount, 0, 'resolved conflict list should have no remaining conflicts');
|
||||
const conflictNotificationsAfterAccept = await request('/api/tenant-content/notifications', {
|
||||
tenantId: PARTNER_TENANT_ID,
|
||||
userId: PARTNER_TENANT_ADMIN_USER_ID,
|
||||
query: { notificationType: 'public_question_bank_conflict', adoptionId: adopted.item.id, status: 'resolved' },
|
||||
});
|
||||
assert.ok(
|
||||
conflictNotificationsAfterAccept.items?.some(item => item.id === conflictNotification.id && item.status === 'resolved'),
|
||||
'resolving all public bank conflicts should mark related notifications resolved',
|
||||
);
|
||||
|
||||
const keepLocalContent = `租户再次保留本地公共题副本 ${Date.now()}`;
|
||||
await request('/api/tenant-content/questions', {
|
||||
|
||||
@@ -207,6 +207,26 @@ async function main() {
|
||||
assert.equal(audit.rows[0]?.action, 'content.public_question_bank.synced', 'worker should write sync audit');
|
||||
assert.equal(audit.rows[0]?.details?.triggeredBy, 'worker', 'worker audit should record trigger source');
|
||||
|
||||
const notifications = await pool.query(
|
||||
`
|
||||
select notification_type, status, severity, metadata
|
||||
from public.tenant_content_notifications
|
||||
where tenant_id = $1 and adoption_id = $2
|
||||
order by created_at desc
|
||||
limit 5
|
||||
`,
|
||||
[PARTNER_TENANT_ID, ids.adoption],
|
||||
);
|
||||
assert.ok(
|
||||
notifications.rows.some(row => row.notification_type === 'public_question_bank_synced' && row.status === 'unread' && row.severity === 'success'),
|
||||
'worker public bank sync should create an unread success notification',
|
||||
);
|
||||
assert.equal(
|
||||
notifications.rows.find(row => row.notification_type === 'public_question_bank_synced')?.metadata?.triggeredBy,
|
||||
'worker',
|
||||
'worker public bank sync notification should record trigger source',
|
||||
);
|
||||
|
||||
const secondOutput = await runWorkerOnce();
|
||||
assert.match(secondOutput, /processed=0/, 'worker should skip already synced public bank when source has not changed');
|
||||
|
||||
|
||||
Reference in New Issue
Block a user