diff --git a/server/tests/integration/plugins/dev-link.test.ts b/server/tests/integration/plugins/dev-link.test.ts index 87ffe90c2..473f50618 100644 --- a/server/tests/integration/plugins/dev-link.test.ts +++ b/server/tests/integration/plugins/dev-link.test.ts @@ -47,7 +47,7 @@ function writeSource(id: string, opts: { index?: string; native?: boolean; noBui fs.mkdirSync(path.join(dir, 'server'), { recursive: true }); // dev-link requires a `trek` range like any other install front door; `opts.trek` // overrides it to exercise the gate. - fs.writeFileSync(path.join(dir, 'trek-plugin.json'), JSON.stringify({ id, name: id, version: '1.0.0', type: 'integration', permissions: [], trek: opts.trek ?? '>=3.0.0 <4.0.0' })); + fs.writeFileSync(path.join(dir, 'trek-plugin.json'), JSON.stringify({ id, name: id, version: '1.0.0', type: 'integration', permissions: [], trek: opts.trek ?? '>=3.0.0' })); if (!opts.noBuild) fs.writeFileSync(path.join(dir, 'server', 'index.js'), opts.index ?? 'module.exports = {};'); if (opts.native) fs.writeFileSync(path.join(dir, 'server', 'addon.node'), '\0'); return dir; diff --git a/server/tests/integration/plugins/registry.test.ts b/server/tests/integration/plugins/registry.test.ts index 8facfd85f..d10755a46 100644 --- a/server/tests/integration/plugins/registry.test.ts +++ b/server/tests/integration/plugins/registry.test.ts @@ -49,7 +49,7 @@ function tarHeader(name: string, size: number, typeflag = '0'): Buffer { // without it would be testing the version gate rather than whatever the test is about. // Pass an explicit `trek` (or null, spread last) to exercise the gate itself. function makeArtifact(manifest: object): Buffer { - const withTrek = { trek: '>=3.2.0 <4.0.0', ...manifest }; + const withTrek = { trek: '>=3.2.0', ...manifest }; const files = [ { name: 'plug-abc/', type: '5' as const, data: '' }, { name: 'plug-abc/trek-plugin.json', type: '0' as const, data: JSON.stringify(withTrek) }, diff --git a/server/tests/integration/systemNotices.test.ts b/server/tests/integration/systemNotices.test.ts index 56a2a9545..0ecb3e3e4 100644 --- a/server/tests/integration/systemNotices.test.ts +++ b/server/tests/integration/systemNotices.test.ts @@ -95,14 +95,21 @@ describe('GET /api/system-notices/active', () => { 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. The always-on thank-you - // notice (no conditions) may still apply, so only filter it out. + // 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 release-4-0-0 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 }) => n.id !== 'thank-you-support')).toEqual([]); + expect(res.body.filter((n: { id: string }) => !alwaysOn.has(n.id))).toEqual([]); }); it('returns firstLogin notice for user with login_count <= 1', async () => { @@ -171,33 +178,53 @@ describe('GET /api/system-notices/active', () => { } }); + // 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 TY = 'thank-you-support'; - 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 === TY); + 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); - // Fresh user with no dismissal: the recurring thank-you shows. - expect(await shows()).toBe(true); + 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); + }; - // 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, TY, Date.now(), '0.0.1'); - expect(await shows()).toBe(true); + // Fresh user with no dismissal: the recurring notice shows. + 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, TY); - expect(await shows()).toBe(false); + // 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); + } }); });