154 lines
7.2 KiB
JavaScript
154 lines
7.2 KiB
JavaScript
/**
|
|
* ICD Test Runner — CRUD: Notifications
|
|
* Full preference lifecycle + notification endpoint error paths.
|
|
*/
|
|
(function () {
|
|
'use strict';
|
|
var T = window.ICD;
|
|
if (!T) return;
|
|
if (!T.crud) T.crud = {};
|
|
|
|
T.crud.notifications = async function (testTag) {
|
|
|
|
// ── Preference: Set ──
|
|
await T.test('crud', 'notifications', 'PUT /notifications/preferences/:type (set)', async function () {
|
|
var d = await T.apiPut('/notifications/preferences/kb.ready', {
|
|
in_app: true, email: true
|
|
});
|
|
T.assert(typeof d === 'object', 'expected object');
|
|
T.assert(d.type === 'kb.ready', 'type should be kb.ready, got: ' + d.type);
|
|
T.assert(d.in_app === true, 'in_app should be true');
|
|
T.assert(d.email === true, 'email should be true');
|
|
});
|
|
|
|
// ── Preference: Read back via list ──
|
|
await T.test('crud', 'notifications', 'GET /notifications/preferences (round-trip)', async function () {
|
|
var d = await T.apiGet('/notifications/preferences');
|
|
T.assertHasKey(d, 'data', 'preferences envelope');
|
|
T.assert(Array.isArray(d.data), 'data should be array');
|
|
var found = d.data.find(function (p) { return p.type === 'kb.ready'; });
|
|
T.assert(found, 'kb.ready preference should be in list');
|
|
T.assert(found.in_app === true, 'in_app should be true');
|
|
T.assert(found.email === true, 'email should be true');
|
|
});
|
|
|
|
// ── Preference: Partial update ──
|
|
await T.test('crud', 'notifications', 'PUT /notifications/preferences/:type (partial update)', async function () {
|
|
var d = await T.apiPut('/notifications/preferences/kb.ready', {
|
|
email: false
|
|
});
|
|
T.assert(d.in_app === true, 'in_app should remain true after partial update');
|
|
T.assert(d.email === false, 'email should be false after partial update');
|
|
});
|
|
|
|
// ── Preference: Wildcard ──
|
|
await T.test('crud', 'notifications', 'PUT /notifications/preferences/* (wildcard)', async function () {
|
|
var d = await T.apiPut('/notifications/preferences/*', {
|
|
in_app: false, email: true
|
|
});
|
|
T.assert(d.type === '*', 'type should be *, got: ' + d.type);
|
|
T.assert(d.in_app === false, 'in_app should be false');
|
|
T.assert(d.email === true, 'email should be true');
|
|
});
|
|
|
|
// ── Preference: Delete + verify removed ──
|
|
await T.test('crud', 'notifications', 'DELETE /notifications/preferences/:type (reset)', async function () {
|
|
await T.safeDelete('/notifications/preferences/kb.ready');
|
|
var d = await T.apiGet('/notifications/preferences');
|
|
var found = (d.data || []).find(function (p) { return p.type === 'kb.ready'; });
|
|
T.assert(!found, 'kb.ready should be removed after delete');
|
|
});
|
|
|
|
// ── Preference: Idempotent delete ──
|
|
await T.test('crud', 'notifications', 'DELETE /notifications/preferences/:type (idempotent)', async function () {
|
|
await T.safeDelete('/notifications/preferences/nonexistent.type');
|
|
});
|
|
|
|
// ── Cleanup wildcard preference ──
|
|
await T.test('crud', 'notifications', 'DELETE /notifications/preferences/* (cleanup)', async function () {
|
|
await T.safeDelete('/notifications/preferences/*');
|
|
});
|
|
|
|
// ── Notification list: envelope shape ──
|
|
await T.test('crud', 'notifications', 'GET /notifications (envelope shape)', async function () {
|
|
var d = await T.apiGet('/notifications');
|
|
T.assertHasKey(d, 'data', 'notifications envelope');
|
|
T.assertHasKey(d, 'total', 'notifications total');
|
|
T.assertHasKey(d, 'limit', 'notifications limit');
|
|
T.assertHasKey(d, 'offset', 'notifications offset');
|
|
T.assert(Array.isArray(d.data), 'data should be array');
|
|
T.assert(typeof d.total === 'number', 'total should be number');
|
|
T.assert(typeof d.limit === 'number', 'limit should be number');
|
|
T.assert(typeof d.offset === 'number', 'offset should be number');
|
|
});
|
|
|
|
// ── Notification list: pagination params ──
|
|
await T.test('crud', 'notifications', 'GET /notifications?limit=2 (pagination)', async function () {
|
|
var d = await T.apiGet('/notifications?limit=2&offset=0');
|
|
T.assert(d.limit === 2, 'limit should be 2, got: ' + d.limit);
|
|
T.assert(d.offset === 0, 'offset should be 0, got: ' + d.offset);
|
|
});
|
|
|
|
// ── Notification list: unread_only filter ──
|
|
await T.test('crud', 'notifications', 'GET /notifications?unread_only=true', async function () {
|
|
var d = await T.apiGet('/notifications?unread_only=true');
|
|
T.assertHasKey(d, 'data', 'unread envelope');
|
|
T.assert(Array.isArray(d.data), 'unread data should be array');
|
|
});
|
|
|
|
// ── Unread count: shape ──
|
|
await T.test('crud', 'notifications', 'GET /notifications/unread-count (shape)', async function () {
|
|
var d = await T.apiGet('/notifications/unread-count');
|
|
T.assertHasKey(d, 'count', 'unread-count');
|
|
T.assert(typeof d.count === 'number', 'count should be number');
|
|
});
|
|
|
|
// ── Mark all read: no-op on empty ──
|
|
await T.test('crud', 'notifications', 'POST /notifications/mark-all-read (no-op)', async function () {
|
|
var d = await T.apiPost('/notifications/mark-all-read');
|
|
T.assertHasKey(d, 'ok', 'mark-all-read');
|
|
T.assert(d.ok === true, 'ok should be true');
|
|
});
|
|
|
|
// ── Mark read: non-existent → 404 ──
|
|
await T.test('crud', 'notifications', 'PATCH /notifications/:id/read (not found → 404)', async function () {
|
|
var token = await T.getAuthToken();
|
|
var d = await T.authFetch(token, 'PATCH', '/notifications/00000000-0000-0000-0000-000000000000/read');
|
|
T.assertStatus(d, 404, 'mark read non-existent');
|
|
});
|
|
|
|
// ── Delete: non-existent → 404 ──
|
|
await T.test('crud', 'notifications', 'DELETE /notifications/:id (not found → 404)', async function () {
|
|
var token = await T.getAuthToken();
|
|
var d = await T.authFetch(token, 'DELETE', '/notifications/00000000-0000-0000-0000-000000000000');
|
|
T.assertStatus(d, 404, 'delete non-existent');
|
|
});
|
|
|
|
// ── Admin broadcast (v0.28.6) ──
|
|
|
|
await T.test('crud', 'notifications', 'POST /admin/notifications/broadcast', async function () {
|
|
var d = await T.apiPost('/admin/notifications/broadcast', {
|
|
title: 'ICD Test Broadcast',
|
|
message: 'Automated test broadcast.',
|
|
level: 'info'
|
|
});
|
|
T.assertHasKey(d, 'message', 'broadcast response');
|
|
T.assertHasKey(d, 'count', 'broadcast response');
|
|
T.assert(d.count >= 1, 'count should be >= 1');
|
|
});
|
|
|
|
await T.test('crud', 'notifications', 'POST /admin/notifications/broadcast (missing title → 400)', async function () {
|
|
var token = await T.getAuthToken();
|
|
var d = await T.authFetch(token, 'POST', '/admin/notifications/broadcast', { message: 'no title' });
|
|
T.assertStatus(d, 400, 'missing title');
|
|
});
|
|
|
|
await T.test('crud', 'notifications', 'POST /admin/notifications/broadcast (invalid level → 400)', async function () {
|
|
var token = await T.getAuthToken();
|
|
var d = await T.authFetch(token, 'POST', '/admin/notifications/broadcast', { title: 'x', message: 'x', level: 'extreme' });
|
|
T.assertStatus(d, 400, 'invalid level');
|
|
});
|
|
|
|
};
|
|
})();
|