mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 21:46:49 +00:00
510 lines
23 KiB
TypeScript
510 lines
23 KiB
TypeScript
/**
|
|
* System Notices API integration tests.
|
|
* Covers GET /api/system-notices/active and POST /api/system-notices/:id/dismiss.
|
|
*/
|
|
import { ADDON_IDS } from '../../src/addons';
|
|
import { buildApp } from '../../src/bootstrap';
|
|
import { db as testDb } from '../../src/db/database';
|
|
import { SYSTEM_NOTICES } from '../../src/systemNotices/registry';
|
|
import { getCurrentAppVersion } from '../../src/systemNotices/service';
|
|
import type { SystemNotice } from '../../src/systemNotices/types';
|
|
import { authCookie } from '../helpers/auth';
|
|
import { createUser, createAdmin } from '../helpers/factories';
|
|
import { resetTestDb } from '../helpers/test-db';
|
|
import type { INestApplication } from '@nestjs/common';
|
|
|
|
import type { Application } from 'express';
|
|
import request from 'supertest';
|
|
import { describe, it, expect, vi, beforeAll, beforeEach, afterAll, afterEach } from 'vitest';
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// Bare in-memory DB — schema applied in beforeAll after mocks register
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
vi.mock('../../src/db/database', async () => {
|
|
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
|
|
return buildDbMock(createSnapshotTestDb());
|
|
});
|
|
vi.mock('../../src/config', () => ({
|
|
JWT_SECRET: 'test-jwt-secret-for-trek-testing-only',
|
|
ENCRYPTION_KEY: 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2a3b4c5d6a7b8c9d0e1f2',
|
|
updateJwtSecret: () => {},
|
|
SESSION_DURATION: '24h',
|
|
SESSION_DURATION_MS: 86400000,
|
|
SESSION_DURATION_SECONDS: 86400,
|
|
DEFAULT_LANGUAGE: 'en',
|
|
}));
|
|
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() }));
|
|
|
|
let nestApp: INestApplication;
|
|
let app: Application;
|
|
|
|
// Test notice injected into the registry for notice-specific tests
|
|
const TEST_NOTICE: SystemNotice = {
|
|
id: 'test-first-login-notice',
|
|
display: 'modal',
|
|
severity: 'info',
|
|
titleKey: 'system_notice.test_first_login_notice.title',
|
|
bodyKey: 'system_notice.test_first_login_notice.body',
|
|
dismissible: true,
|
|
conditions: [{ kind: 'firstLogin' }],
|
|
publishedAt: '2026-01-01T00:00:00Z',
|
|
priority: 0,
|
|
};
|
|
|
|
// Test notice gated on an addon, so the addonFlags lookup in getActiveNoticesFor
|
|
// (built once per call from every addonEnabled condition in SYSTEM_NOTICES) is
|
|
// exercised for both the enabled and the disabled case.
|
|
const TEST_NOTICE_ADDON: SystemNotice = {
|
|
id: 'test-addon-notice',
|
|
display: 'modal',
|
|
severity: 'info',
|
|
titleKey: 'system_notice.test_addon_notice.title',
|
|
bodyKey: 'system_notice.test_addon_notice.body',
|
|
dismissible: true,
|
|
conditions: [{ kind: 'addonEnabled', addonId: ADDON_IDS.JOURNEY }],
|
|
publishedAt: '2026-01-01T00:00:00Z',
|
|
priority: 0,
|
|
};
|
|
|
|
beforeAll(async () => {
|
|
nestApp = await buildApp();
|
|
app = nestApp.getHttpAdapter().getInstance();
|
|
});
|
|
|
|
beforeEach(() => {
|
|
resetTestDb(testDb);
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await nestApp.close();
|
|
testDb.close();
|
|
});
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// GET /api/system-notices/active
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
describe('GET /api/system-notices/active', () => {
|
|
it('returns 401 without auth', async () => {
|
|
const res = await request(app).get('/api/system-notices/active');
|
|
expect(res.status).toBe(401);
|
|
});
|
|
|
|
it('returns no login/version-gated notices for an established user', async () => {
|
|
const { user } = createUser(testDb);
|
|
// login_count > 1 means firstLogin does not match; first_seen_version >= 3.0.0 means
|
|
// existingUserBeforeVersion('3.0.0') does not match either. Notices that gate on
|
|
// nothing but the install being self-hosted still apply, and which ones those are
|
|
// changes every release (the thank-you modal handed over to the release notes at 4.0.0).
|
|
// Read the set out of the registry rather than naming them, or this ages out again.
|
|
testDb.prepare('UPDATE users SET login_count = 5, first_seen_version = ? WHERE id = ?').run('3.0.0', user.id);
|
|
const alwaysOn = new Set(
|
|
SYSTEM_NOTICES.filter((n) => (n.conditions ?? []).every((c) => c.kind === 'managed')).map((n) => n.id),
|
|
);
|
|
const res = await request(app).get('/api/system-notices/active').set('Cookie', authCookie(user.id));
|
|
expect(res.status).toBe(200);
|
|
expect(res.body.filter((n: { id: string }) => !alwaysOn.has(n.id))).toEqual([]);
|
|
});
|
|
|
|
it('returns firstLogin notice for user with login_count <= 1', async () => {
|
|
SYSTEM_NOTICES.push(TEST_NOTICE);
|
|
try {
|
|
const { user } = createUser(testDb);
|
|
// Set login_count to 1 (first login)
|
|
testDb.prepare('UPDATE users SET login_count = 1 WHERE id = ?').run(user.id);
|
|
|
|
const res = await request(app).get('/api/system-notices/active').set('Cookie', authCookie(user.id));
|
|
expect(res.status).toBe(200);
|
|
// The always-on thank-you notice may also be present, so just assert TEST_NOTICE is there
|
|
const testNotice = res.body.find((n: { id: string }) => n.id === TEST_NOTICE.id);
|
|
expect(testNotice).toBeDefined();
|
|
// DTO should not expose conditions, publishedAt, minVersion, maxVersion, priority
|
|
expect(testNotice.conditions).toBeUndefined();
|
|
expect(testNotice.publishedAt).toBeUndefined();
|
|
expect(testNotice.minVersion).toBeUndefined();
|
|
expect(testNotice.maxVersion).toBeUndefined();
|
|
} finally {
|
|
const idx = SYSTEM_NOTICES.indexOf(TEST_NOTICE);
|
|
if (idx !== -1) SYSTEM_NOTICES.splice(idx, 1);
|
|
}
|
|
});
|
|
|
|
it('an addonEnabled-gated notice appears only while the addon is on', async () => {
|
|
SYSTEM_NOTICES.push(TEST_NOTICE_ADDON);
|
|
try {
|
|
const { user } = createUser(testDb);
|
|
testDb.prepare('UPDATE addons SET enabled = 0 WHERE id = ?').run(ADDON_IDS.JOURNEY);
|
|
|
|
const off = await request(app).get('/api/system-notices/active').set('Cookie', authCookie(user.id));
|
|
expect(off.status).toBe(200);
|
|
expect(off.body.find((n: { id: string }) => n.id === TEST_NOTICE_ADDON.id)).toBeUndefined();
|
|
|
|
testDb.prepare('UPDATE addons SET enabled = 1 WHERE id = ?').run(ADDON_IDS.JOURNEY);
|
|
|
|
const on = await request(app).get('/api/system-notices/active').set('Cookie', authCookie(user.id));
|
|
expect(on.status).toBe(200);
|
|
expect(on.body.find((n: { id: string }) => n.id === TEST_NOTICE_ADDON.id)).toBeDefined();
|
|
} finally {
|
|
const idx = SYSTEM_NOTICES.indexOf(TEST_NOTICE_ADDON);
|
|
if (idx !== -1) SYSTEM_NOTICES.splice(idx, 1);
|
|
}
|
|
});
|
|
|
|
it('does not return firstLogin notice for user with login_count > 1', async () => {
|
|
SYSTEM_NOTICES.push(TEST_NOTICE);
|
|
try {
|
|
const { user } = createUser(testDb);
|
|
testDb.prepare('UPDATE users SET login_count = 5, first_seen_version = ? WHERE id = ?').run('3.0.0', user.id);
|
|
|
|
const res = await request(app).get('/api/system-notices/active').set('Cookie', authCookie(user.id));
|
|
expect(res.status).toBe(200);
|
|
expect(res.body.find((n: { id: string }) => n.id === TEST_NOTICE.id)).toBeUndefined();
|
|
} finally {
|
|
const idx = SYSTEM_NOTICES.indexOf(TEST_NOTICE);
|
|
if (idx !== -1) SYSTEM_NOTICES.splice(idx, 1);
|
|
}
|
|
});
|
|
|
|
it('filters out dismissed notices', async () => {
|
|
SYSTEM_NOTICES.push(TEST_NOTICE);
|
|
try {
|
|
const { user } = createUser(testDb);
|
|
testDb.prepare('UPDATE users SET login_count = 1 WHERE id = ?').run(user.id);
|
|
|
|
// Dismiss the notice directly in DB
|
|
testDb
|
|
.prepare('INSERT INTO user_notice_dismissals (user_id, notice_id, dismissed_at) VALUES (?, ?, ?)')
|
|
.run(user.id, TEST_NOTICE.id, Date.now());
|
|
|
|
const res = await request(app).get('/api/system-notices/active').set('Cookie', authCookie(user.id));
|
|
expect(res.status).toBe(200);
|
|
// TEST_NOTICE should be filtered out; the thank-you notice may still appear
|
|
const found = res.body.find((n: { id: string }) => n.id === TEST_NOTICE.id);
|
|
expect(found).toBeUndefined();
|
|
} finally {
|
|
const idx = SYSTEM_NOTICES.indexOf(TEST_NOTICE);
|
|
if (idx !== -1) SYSTEM_NOTICES.splice(idx, 1);
|
|
}
|
|
});
|
|
|
|
// Drives the recurrence mechanism through a notice this test owns. It used to
|
|
// ride on thank-you-support, which stopped applying the moment that notice was
|
|
// capped at 4.0.0 — the mechanism was fine, the fixture had retired.
|
|
it('re-surfaces a per-version notice after an upgrade but hides it within the same version', async () => {
|
|
const RECURRING: SystemNotice = {
|
|
id: 'test-per-version-notice',
|
|
display: 'modal',
|
|
severity: 'info',
|
|
titleKey: 'system_notice.test_per_version_notice.title',
|
|
bodyKey: 'system_notice.test_per_version_notice.body',
|
|
dismissible: true,
|
|
conditions: [],
|
|
recurring: 'per-version',
|
|
publishedAt: '2026-01-01T00:00:00Z',
|
|
priority: 0,
|
|
};
|
|
SYSTEM_NOTICES.push(RECURRING);
|
|
try {
|
|
const { user } = createUser(testDb);
|
|
testDb.prepare('UPDATE users SET login_count = 5, first_seen_version = ? WHERE id = ?').run('3.0.0', user.id);
|
|
|
|
const shows = async () => {
|
|
const res = await request(app).get('/api/system-notices/active').set('Cookie', authCookie(user.id));
|
|
expect(res.status).toBe(200);
|
|
return res.body.some((n: { id: string }) => n.id === RECURRING.id);
|
|
};
|
|
|
|
// Fresh user with no dismissal: the recurring notice shows.
|
|
expect(await shows()).toBe(true);
|
|
|
|
// Dismissed at an old version → it returns once the running version is newer.
|
|
testDb
|
|
.prepare(
|
|
'INSERT INTO user_notice_dismissals (user_id, notice_id, dismissed_at, dismissed_app_version) VALUES (?, ?, ?, ?)',
|
|
)
|
|
.run(user.id, RECURRING.id, Date.now(), '0.0.1');
|
|
expect(await shows()).toBe(true);
|
|
|
|
// Dismissed at a version >= the running one → stays hidden until the next upgrade.
|
|
testDb
|
|
.prepare('UPDATE user_notice_dismissals SET dismissed_app_version = ? WHERE user_id = ? AND notice_id = ?')
|
|
.run('99.0.0', user.id, RECURRING.id);
|
|
expect(await shows()).toBe(false);
|
|
} finally {
|
|
const idx = SYSTEM_NOTICES.indexOf(RECURRING);
|
|
if (idx !== -1) SYSTEM_NOTICES.splice(idx, 1);
|
|
}
|
|
});
|
|
|
|
// The release notes carry the support links, so the promise is exact: once per
|
|
// update for every user, whatever they did with it before, and never again on a
|
|
// reload of the same version. Driven through the real registry entry and the real
|
|
// dismiss route, so neither half can drift away from the other unnoticed.
|
|
it('shows the release notes once per update, however often they were closed before', async () => {
|
|
const { user } = createUser(testDb);
|
|
testDb.prepare('UPDATE users SET login_count = 5, first_seen_version = ? WHERE id = ?').run('3.0.0', user.id);
|
|
|
|
// The client announces the release layout and the version it was built as; a
|
|
// bundle that predates that, or one built for another version, never gets the
|
|
// notice (pinned separately below).
|
|
const shows = async () => {
|
|
const res = await request(app)
|
|
.get('/api/system-notices/active')
|
|
.query({ supports: 'release', ui: getCurrentAppVersion() })
|
|
.set('Cookie', authCookie(user.id));
|
|
expect(res.status).toBe(200);
|
|
return res.body.some((n: { id: string }) => n.id === 'release-notes');
|
|
};
|
|
const dismiss = async () => {
|
|
const res = await request(app)
|
|
.post('/api/system-notices/release-notes/dismiss')
|
|
.set('Cookie', authCookie(user.id));
|
|
expect(res.status).toBe(204);
|
|
};
|
|
|
|
expect(await shows()).toBe(true);
|
|
|
|
// Closed on this version: gone for every reload after it.
|
|
await dismiss();
|
|
expect(await shows()).toBe(false);
|
|
expect(await shows()).toBe(false);
|
|
|
|
// The next update brings it back, although it was closed before...
|
|
testDb
|
|
.prepare('UPDATE user_notice_dismissals SET dismissed_app_version = ? WHERE user_id = ? AND notice_id = ?')
|
|
.run('4.0.0', user.id, 'release-notes');
|
|
expect(await shows()).toBe(true);
|
|
expect(await shows()).toBe(true);
|
|
|
|
// ...and closing it again holds until the one after that.
|
|
await dismiss();
|
|
expect(await shows()).toBe(false);
|
|
});
|
|
|
|
// Right after an update the browser still runs the bundle the service worker cached,
|
|
// which has neither the copy for the new release notice nor a way to tell. It would
|
|
// draw the keys, or its own older texts, and on close spend the notice for this whole
|
|
// version. So the notice waits for a client that announces the release layout AND
|
|
// was built for the version now running; the reload brings one.
|
|
it('keeps the release notes from a client that does not announce the release layout for this version', async () => {
|
|
const { user } = createUser(testDb);
|
|
testDb.prepare('UPDATE users SET login_count = 5, first_seen_version = ? WHERE id = ?').run('3.0.0', user.id);
|
|
|
|
const fetchActive = (query: Record<string, string>) =>
|
|
request(app).get('/api/system-notices/active').query(query).set('Cookie', authCookie(user.id));
|
|
|
|
const without = await fetchActive({});
|
|
expect(without.status).toBe(200);
|
|
expect(without.body.some((n: { id: string }) => n.id === 'release-notes')).toBe(false);
|
|
expect(without.body.some((n: { release?: unknown }) => n.release !== undefined)).toBe(false);
|
|
|
|
// The layout alone is the shell of the previous version after an update.
|
|
const layoutOnly = await fetchActive({ supports: 'release' });
|
|
expect(layoutOnly.body.some((n: { id: string }) => n.id === 'release-notes')).toBe(false);
|
|
const olderBundle = await fetchActive({ supports: 'release', ui: '4.0.0' });
|
|
expect(olderBundle.body.some((n: { id: string }) => n.id === 'release-notes')).toBe(false);
|
|
|
|
const matching = await fetchActive({ supports: 'release', ui: getCurrentAppVersion() });
|
|
expect(matching.status).toBe(200);
|
|
expect(matching.body.some((n: { id: string }) => n.id === 'release-notes')).toBe(true);
|
|
});
|
|
});
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// POST /api/system-notices/:id/dismiss
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
describe('POST /api/system-notices/:id/dismiss', () => {
|
|
it('returns 401 without auth', async () => {
|
|
const res = await request(app).post('/api/system-notices/test-id/dismiss');
|
|
expect(res.status).toBe(401);
|
|
});
|
|
|
|
it('returns 404 for unknown notice id', async () => {
|
|
const { user } = createUser(testDb);
|
|
const res = await request(app)
|
|
.post('/api/system-notices/nonexistent-id/dismiss')
|
|
.set('Cookie', authCookie(user.id));
|
|
expect(res.status).toBe(404);
|
|
expect(res.body.error).toBe('NOTICE_NOT_FOUND');
|
|
});
|
|
|
|
it('returns 204 for valid notice id', async () => {
|
|
SYSTEM_NOTICES.push(TEST_NOTICE);
|
|
try {
|
|
const { user } = createUser(testDb);
|
|
const res = await request(app)
|
|
.post(`/api/system-notices/${TEST_NOTICE.id}/dismiss`)
|
|
.set('Cookie', authCookie(user.id));
|
|
expect(res.status).toBe(204);
|
|
} finally {
|
|
const idx = SYSTEM_NOTICES.indexOf(TEST_NOTICE);
|
|
if (idx !== -1) SYSTEM_NOTICES.splice(idx, 1);
|
|
}
|
|
});
|
|
|
|
it('is idempotent — second dismiss also returns 204', async () => {
|
|
SYSTEM_NOTICES.push(TEST_NOTICE);
|
|
try {
|
|
const { user } = createUser(testDb);
|
|
const first = await request(app)
|
|
.post(`/api/system-notices/${TEST_NOTICE.id}/dismiss`)
|
|
.set('Cookie', authCookie(user.id));
|
|
expect(first.status).toBe(204);
|
|
|
|
const second = await request(app)
|
|
.post(`/api/system-notices/${TEST_NOTICE.id}/dismiss`)
|
|
.set('Cookie', authCookie(user.id));
|
|
expect(second.status).toBe(204);
|
|
} finally {
|
|
const idx = SYSTEM_NOTICES.indexOf(TEST_NOTICE);
|
|
if (idx !== -1) SYSTEM_NOTICES.splice(idx, 1);
|
|
}
|
|
});
|
|
|
|
it('dismiss appears in GET /active as filtered out', async () => {
|
|
SYSTEM_NOTICES.push(TEST_NOTICE);
|
|
try {
|
|
const { user } = createUser(testDb);
|
|
testDb.prepare('UPDATE users SET login_count = 1 WHERE id = ?').run(user.id);
|
|
|
|
// Confirm TEST_NOTICE is visible before dismiss
|
|
const before = await request(app).get('/api/system-notices/active').set('Cookie', authCookie(user.id));
|
|
expect(before.body.find((n: { id: string }) => n.id === TEST_NOTICE.id)).toBeDefined();
|
|
|
|
// Dismiss it
|
|
await request(app).post(`/api/system-notices/${TEST_NOTICE.id}/dismiss`).set('Cookie', authCookie(user.id));
|
|
|
|
// Confirm TEST_NOTICE is gone; other notices (e.g. welcome-v1) may still appear
|
|
const after = await request(app).get('/api/system-notices/active').set('Cookie', authCookie(user.id));
|
|
expect(after.status).toBe(200);
|
|
expect(after.body.find((n: { id: string }) => n.id === TEST_NOTICE.id)).toBeUndefined();
|
|
} finally {
|
|
const idx = SYSTEM_NOTICES.indexOf(TEST_NOTICE);
|
|
if (idx !== -1) SYSTEM_NOTICES.splice(idx, 1);
|
|
}
|
|
});
|
|
});
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// v3014-whitespace-collision notice
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* Helper: creates an admin user whose first_seen_version is before 3.0.14
|
|
* (so existingUserBeforeVersion('3.0.14') passes) and whose login_count is
|
|
* high enough to suppress the firstLogin and v3-upgrade notice conditions.
|
|
*/
|
|
function setupCollisionAdmin() {
|
|
const { user } = createAdmin(testDb);
|
|
testDb.prepare('UPDATE users SET login_count = 5, first_seen_version = ? WHERE id = ?').run('3.0.0', user.id);
|
|
return user;
|
|
}
|
|
|
|
describe('v3014-whitespace-collision notice', () => {
|
|
const NOTICE_ID = 'v3014-whitespace-collision';
|
|
const originalAppVersion = process.env.APP_VERSION;
|
|
|
|
beforeEach(() => {
|
|
process.env.APP_VERSION = '3.0.14';
|
|
});
|
|
|
|
afterEach(() => {
|
|
if (originalAppVersion === undefined) {
|
|
delete process.env.APP_VERSION;
|
|
} else {
|
|
process.env.APP_VERSION = originalAppVersion;
|
|
}
|
|
});
|
|
|
|
it('SN-COLLISION-1 — shown to admin when collision flag is set and user predates 3.0.14', async () => {
|
|
const user = setupCollisionAdmin();
|
|
testDb
|
|
.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES ('whitespace_migration_collision', 'true')")
|
|
.run();
|
|
|
|
const res = await request(app).get('/api/system-notices/active').set('Cookie', authCookie(user.id));
|
|
|
|
expect(res.status).toBe(200);
|
|
expect(res.body.find((n: { id: string }) => n.id === NOTICE_ID)).toBeDefined();
|
|
});
|
|
|
|
it('SN-COLLISION-2 — hidden when collision flag is absent', async () => {
|
|
const user = setupCollisionAdmin();
|
|
|
|
const res = await request(app).get('/api/system-notices/active').set('Cookie', authCookie(user.id));
|
|
|
|
expect(res.status).toBe(200);
|
|
expect(res.body.find((n: { id: string }) => n.id === NOTICE_ID)).toBeUndefined();
|
|
});
|
|
|
|
it('SN-COLLISION-3 — hidden when collision flag is explicitly false', async () => {
|
|
const user = setupCollisionAdmin();
|
|
testDb
|
|
.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES ('whitespace_migration_collision', 'false')")
|
|
.run();
|
|
|
|
const res = await request(app).get('/api/system-notices/active').set('Cookie', authCookie(user.id));
|
|
|
|
expect(res.status).toBe(200);
|
|
expect(res.body.find((n: { id: string }) => n.id === NOTICE_ID)).toBeUndefined();
|
|
});
|
|
|
|
it('SN-COLLISION-4 — hidden for non-admin user even when collision flag is set', async () => {
|
|
const { user } = createUser(testDb);
|
|
testDb.prepare('UPDATE users SET login_count = 5, first_seen_version = ? WHERE id = ?').run('3.0.0', user.id);
|
|
testDb
|
|
.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES ('whitespace_migration_collision', 'true')")
|
|
.run();
|
|
|
|
const res = await request(app).get('/api/system-notices/active').set('Cookie', authCookie(user.id));
|
|
|
|
expect(res.status).toBe(200);
|
|
expect(res.body.find((n: { id: string }) => n.id === NOTICE_ID)).toBeUndefined();
|
|
});
|
|
|
|
it('SN-COLLISION-5 — hidden for user whose first_seen_version is >= 3.0.14 (new account)', async () => {
|
|
const { user } = createAdmin(testDb);
|
|
testDb.prepare('UPDATE users SET login_count = 5, first_seen_version = ? WHERE id = ?').run('3.0.14', user.id);
|
|
testDb
|
|
.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES ('whitespace_migration_collision', 'true')")
|
|
.run();
|
|
|
|
const res = await request(app).get('/api/system-notices/active').set('Cookie', authCookie(user.id));
|
|
|
|
expect(res.status).toBe(200);
|
|
expect(res.body.find((n: { id: string }) => n.id === NOTICE_ID)).toBeUndefined();
|
|
});
|
|
|
|
it('SN-COLLISION-6 — hidden when app version is below 3.0.14', async () => {
|
|
process.env.APP_VERSION = '3.0.13';
|
|
const user = setupCollisionAdmin();
|
|
testDb
|
|
.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES ('whitespace_migration_collision', 'true')")
|
|
.run();
|
|
|
|
const res = await request(app).get('/api/system-notices/active').set('Cookie', authCookie(user.id));
|
|
|
|
expect(res.status).toBe(200);
|
|
expect(res.body.find((n: { id: string }) => n.id === NOTICE_ID)).toBeUndefined();
|
|
});
|
|
|
|
it('SN-COLLISION-7 — hidden after admin dismisses it', async () => {
|
|
const user = setupCollisionAdmin();
|
|
testDb
|
|
.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES ('whitespace_migration_collision', 'true')")
|
|
.run();
|
|
|
|
const before = await request(app).get('/api/system-notices/active').set('Cookie', authCookie(user.id));
|
|
expect(before.body.find((n: { id: string }) => n.id === NOTICE_ID)).toBeDefined();
|
|
|
|
const dismiss = await request(app)
|
|
.post(`/api/system-notices/${NOTICE_ID}/dismiss`)
|
|
.set('Cookie', authCookie(user.id));
|
|
expect(dismiss.status).toBe(204);
|
|
|
|
const after = await request(app).get('/api/system-notices/active').set('Cookie', authCookie(user.id));
|
|
expect(after.body.find((n: { id: string }) => n.id === NOTICE_ID)).toBeUndefined();
|
|
});
|
|
});
|