mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-23 15:36:33 +00:00
ed18c37903
Every file the release touches was read once more, and what survived an independent check against the code is fixed here, with a test for each. Data and credentials - A document sync connection edited to a new address no longer carries the stored token along: a blank form under another origin is refused, the same rule the probe route already had. Dawarich and AirTrail testConnection send the stored key only to the host it was stored for. - The key rotation script now covers document_connections.secrets and trip_document_links.webhook_secret, which it had skipped. - Turning a booked night into a pause in the road trip popup asks first and says that the booking and its expense go with it. Deleting a place says the same when a night is booked there, on the desktop and on the phone. - A moved version marker over unchanged bytes no longer replaces the TREK file and its attachments; a real revision carries the attachments over. Planner - A drop in the day plan names the row it lands ahead of in the full day, so a hidden hotel stop no longer shifts it one slot early; the store sorts before it splices. Via re-anchoring, the booked-night guard and the sidebar's day membership read the stored day, the index space the server uses. - A hotel stop placed mid-day re-pins the road trip vias of that day, and the booking session receives the new stop order too. The Public API and the iCal feed list a booked hotel once. A plugin can no longer write a stay against another trip's day. Road trip and map - The route overview paces its requests to the public routers and retries a rate limit, and it says which legs it could not route instead of quietly reporting a shorter trip. A click on a via handle no longer drops a second via; handles hide when zooming out. Read-only members cannot edit the driving limits. The phone shows a way back when no day is picked and prints the night drive departure in the chosen clock format. Integrations and search - Google transit keeps conventional trains when the train chip is on. TREK_PLACES_ENABLED=false stops the details and enrichment calls too, and accepts the whole boolean family. A caller-supplied description no longer lands in the instance-wide enrichment cache. Nominatim searches carry a timeout. An OSM place id is checked for shape before it reaches Overpass. - Dawarich puts a stay on the trip whose dates hold it, the Atlas hand-off knows what the Atlas already shows, and a host change with a new key clears the old instance's state. - Document sync: a webhook address pasted by hand works on the token alone, deleting a connection unregisters its subscriptions, Nextcloud under a sub-path connects, Synology answers a name collision before uploading and verifies a move into the bin, Paperless titles are capped at 128 characters, a DNS failure reads as unreachable rather than as a policy verdict, videos from the store are accepted like uploads, and remote_missing has a way out. Conflict buttons only for the owner, a refused choice is reported, a refused credential gets a Reconnect button, disconnecting asks first on the desktop as it does on the phone, and the folder picker no longer spins forever. Release - The server holds the release notice back from a bundle that cannot draw it, so the previous shell served from the service worker cache after an update no longer shows bare keys and no longer lets a click use the notice up. - Import into an existing list gets the same body limit as a new list. Chat images say when the limit is reached and the new controls are translated. The transit backend switch reports a failed save. The Helm chart carries the Amap credentials in its Secret; the shipped templates document the Places API and Amap variables and the Overpass default. The wiki follows.
505 lines
21 KiB
TypeScript
505 lines
21 KiB
TypeScript
/**
|
|
* System Notices API integration tests.
|
|
* Covers GET /api/system-notices/active and POST /api/system-notices/:id/dismiss.
|
|
*/
|
|
import { describe, it, expect, vi, beforeAll, beforeEach, afterAll, afterEach } from 'vitest';
|
|
import request from 'supertest';
|
|
import type { Application } from 'express';
|
|
import type { INestApplication } from '@nestjs/common';
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// Bare in-memory DB — schema applied in beforeAll after mocks register
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
const { testDb, dbMock } = vi.hoisted(() => {
|
|
const Database = require('better-sqlite3');
|
|
const db = new Database(':memory:');
|
|
db.exec('PRAGMA journal_mode = WAL');
|
|
db.exec('PRAGMA foreign_keys = ON');
|
|
db.exec('PRAGMA busy_timeout = 5000');
|
|
|
|
const mock = {
|
|
db,
|
|
closeDb: () => {},
|
|
reinitialize: () => {},
|
|
getPlaceWithTags: () => null,
|
|
canAccessTrip: () => null,
|
|
isOwner: () => false,
|
|
};
|
|
|
|
return { testDb: db, dbMock: mock };
|
|
});
|
|
|
|
vi.mock('../../src/db/database', () => dbMock);
|
|
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() }));
|
|
|
|
import { buildApp } from '../../src/bootstrap';
|
|
import { createTables } from '../../src/db/schema';
|
|
import { runMigrations } from '../../src/db/migrations';
|
|
import { resetTestDb } from '../helpers/test-db';
|
|
import { createUser, createAdmin } from '../helpers/factories';
|
|
import { authCookie } from '../helpers/auth';
|
|
import { SYSTEM_NOTICES } from '../../src/systemNotices/registry';
|
|
import type { SystemNotice } from '../../src/systemNotices/types';
|
|
|
|
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,
|
|
};
|
|
|
|
beforeAll(async () => {
|
|
createTables(testDb);
|
|
runMigrations(testDb);
|
|
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('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 with `?supports=release`; a bundle that
|
|
// predates it never gets the notice (pinned separately below).
|
|
const shows = async () => {
|
|
const res = await request(app)
|
|
.get('/api/system-notices/active')
|
|
.query({ supports: 'release' })
|
|
.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 and, on close, spend the notice for this whole version. So the notice
|
|
// waits for a client that announces the release layout; the reload brings one.
|
|
it('keeps the release notes from a client that does not announce the release layout', 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);
|
|
|
|
const withLayout = await fetchActive({ supports: 'release' });
|
|
expect(withLayout.status).toBe(200);
|
|
expect(withLayout.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();
|
|
});
|
|
});
|