mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-18 21:16:47 +00:00
test(server): seed the admin integration suite through the factories
Converts the raw prepare() fixtures and reads in the admin integration
suite to the ORM factories and readers. The user-deletion case that seeds
one row per foreign key pointing at the departing user now writes each of
them through insertRow and checks what survived through findRow; the
expiry timestamps that used datetime('now', ...) are computed with dbNow.
The OAuth client in that case uses the same string for its row id and its
public client_id, so the token and consent rows reference it the same way
whichever column the relation keys on.
This commit is contained in:
@@ -23,17 +23,60 @@ vi.mock('../../src/config', () => ({
|
||||
vi.mock('../../src/websocket', () => ({ broadcast: vi.fn(), broadcastToUser: vi.fn() }));
|
||||
|
||||
import { db as testDb } from '../../src/db/database';
|
||||
import { MikroORM } from '@mikro-orm/core';
|
||||
import { buildApp } from '../../src/bootstrap';
|
||||
import { resetTestDb, resetRateLimits } from '../helpers/test-db';
|
||||
import type { FactoryOrm } from '../helpers/factories/context';
|
||||
import { createUser, createAdmin, createInviteToken, createTrip, createBudgetItem, createJourney, createJourneyEntry, addJourneyContributor, addTripPhoto, createCategory, createTag, createTodoItem, createMcpToken, createBucketListItem, createVisitedCountry, createCollabNote, addTripMember } from '../helpers/factories';
|
||||
import { authCookie } from '../helpers/auth';
|
||||
import { findRow, insertRow, insertRowIgnoringConflict, updateRows } from '../helpers/factories/rows';
|
||||
import { makeShareToken } from '../helpers/factories/trips';
|
||||
import { makeVacayPlan } from '../helpers/factories/vacay';
|
||||
import { readUser } from '../helpers/factories/users';
|
||||
import { dbNow } from '../../src/db/types/db-timestamp.type';
|
||||
import { AuditLog } from '../../src/db/entities/AuditLog.entity';
|
||||
import { BucketList } from '../../src/db/entities/BucketList.entity';
|
||||
import { BudgetItems } from '../../src/db/entities/BudgetItems.entity';
|
||||
import { Categories } from '../../src/db/entities/Categories.entity';
|
||||
import { CollabNotes } from '../../src/db/entities/CollabNotes.entity';
|
||||
import { InviteTokens } from '../../src/db/entities/InviteTokens.entity';
|
||||
import { JourneyContributors } from '../../src/db/entities/JourneyContributors.entity';
|
||||
import { JourneyEntries } from '../../src/db/entities/JourneyEntries.entity';
|
||||
import { JourneyShareTokens } from '../../src/db/entities/JourneyShareTokens.entity';
|
||||
import { Journeys } from '../../src/db/entities/Journeys.entity';
|
||||
import { McpTokens } from '../../src/db/entities/McpTokens.entity';
|
||||
import { NotificationChannelPreferences } from '../../src/db/entities/NotificationChannelPreferences.entity';
|
||||
import { Notifications } from '../../src/db/entities/Notifications.entity';
|
||||
import { OauthClients } from '../../src/db/entities/OauthClients.entity';
|
||||
import { OauthConsents } from '../../src/db/entities/OauthConsents.entity';
|
||||
import { OauthTokens } from '../../src/db/entities/OauthTokens.entity';
|
||||
import { PackingBags } from '../../src/db/entities/PackingBags.entity';
|
||||
import { PackingTemplates } from '../../src/db/entities/PackingTemplates.entity';
|
||||
import { PasswordResetTokens } from '../../src/db/entities/PasswordResetTokens.entity';
|
||||
import { Settings } from '../../src/db/entities/Settings.entity';
|
||||
import { ShareTokens } from '../../src/db/entities/ShareTokens.entity';
|
||||
import { Tags } from '../../src/db/entities/Tags.entity';
|
||||
import { TodoItems } from '../../src/db/entities/TodoItems.entity';
|
||||
import { TrekPhotos } from '../../src/db/entities/TrekPhotos.entity';
|
||||
import { TripFiles } from '../../src/db/entities/TripFiles.entity';
|
||||
import { TripMembers } from '../../src/db/entities/TripMembers.entity';
|
||||
import { TripPhotos } from '../../src/db/entities/TripPhotos.entity';
|
||||
import { Trips } from '../../src/db/entities/Trips.entity';
|
||||
import { UserNoticeDismissals } from '../../src/db/entities/UserNoticeDismissals.entity';
|
||||
import { Users } from '../../src/db/entities/Users.entity';
|
||||
import { VacayPlanMembers } from '../../src/db/entities/VacayPlanMembers.entity';
|
||||
import { VacayPlans } from '../../src/db/entities/VacayPlans.entity';
|
||||
import { VisitedCountries } from '../../src/db/entities/VisitedCountries.entity';
|
||||
import { VisitedRegions } from '../../src/db/entities/VisitedRegions.entity';
|
||||
|
||||
let nestApp: INestApplication;
|
||||
let app: Application;
|
||||
let orm: FactoryOrm;
|
||||
|
||||
beforeAll(async () => {
|
||||
nestApp = await buildApp();
|
||||
app = nestApp.getHttpAdapter().getInstance();
|
||||
orm = nestApp.get(MikroORM);
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
@@ -128,8 +171,8 @@ describe('Admin user management', () => {
|
||||
expect(res.body.success).toBe(true);
|
||||
|
||||
// Verify the row is actually gone from the DB
|
||||
const deleted = testDb.prepare('SELECT id FROM users WHERE id = ?').get(user.id);
|
||||
expect(deleted).toBeUndefined();
|
||||
const deleted = await findRow(orm, Users, { id: user.id });
|
||||
expect(deleted).toBeNull();
|
||||
});
|
||||
|
||||
it('ADMIN-005b — DELETE /admin/users/:id succeeds when user has FK references', async () => {
|
||||
@@ -141,14 +184,14 @@ describe('Admin user management', () => {
|
||||
// trip_members.invited_by: target invited thirdUser to otherUser's trip
|
||||
// (trip survives deletion; only invited_by should become NULL)
|
||||
const otherTrip = createTrip(testDb, otherUser.id);
|
||||
testDb.prepare('INSERT INTO trip_members (trip_id, user_id, invited_by) VALUES (?, ?, ?)').run(otherTrip.id, thirdUser.id, target.id);
|
||||
await insertRow(orm, TripMembers, { trip: otherTrip.id, user: thirdUser.id, invitedByRef: target.id });
|
||||
|
||||
// share_tokens.created_by: target created a share token for otherUser's trip
|
||||
testDb.prepare("INSERT INTO share_tokens (trip_id, token, created_by) VALUES (?, 'tok-admin-test', ?)").run(otherTrip.id, target.id);
|
||||
await makeShareToken(orm, otherTrip.id, target.id, { token: 'tok-admin-test' });
|
||||
|
||||
// budget_items.paid_by_user_id: target paid for an expense on otherUser's trip
|
||||
const budgetItem = createBudgetItem(testDb, otherTrip.id);
|
||||
testDb.prepare('UPDATE budget_items SET paid_by_user_id = ? WHERE id = ?').run(target.id, budgetItem.id);
|
||||
await updateRows(orm, BudgetItems, { id: budgetItem.id }, { paidByUser: target.id });
|
||||
|
||||
// journey_contributors: target is a contributor on otherUser's journey
|
||||
const otherJourney = createJourney(testDb, otherUser.id);
|
||||
@@ -158,35 +201,29 @@ describe('Admin user management', () => {
|
||||
createJourneyEntry(testDb, otherJourney.id, target.id);
|
||||
|
||||
// journey_share_tokens: target created a share token for otherUser's journey
|
||||
testDb.prepare("INSERT INTO journey_share_tokens (journey_id, token, created_by) VALUES (?, 'jst-admin-test', ?)").run(otherJourney.id, target.id);
|
||||
await insertRow(orm, JourneyShareTokens, { journey: otherJourney.id, token: 'jst-admin-test', createdByRef: target.id });
|
||||
|
||||
// notifications.sender_id (SET NULL): target sent a notification to otherUser
|
||||
const sentNotif = testDb.prepare(
|
||||
"INSERT INTO notifications (type, scope, target, sender_id, recipient_id, title_key, text_key) VALUES ('simple', 'trip', ?, ?, ?, 'k', 'k')"
|
||||
).run(otherTrip.id, target.id, otherUser.id);
|
||||
const sentNotif = await insertRow(orm, Notifications, {
|
||||
type: 'simple', scope: 'trip', target: otherTrip.id, sender: target.id, recipient: otherUser.id, title_key: 'k', text_key: 'k',
|
||||
});
|
||||
// notifications.recipient_id (CASCADE): otherUser sent a notification to target
|
||||
testDb.prepare(
|
||||
"INSERT INTO notifications (type, scope, target, sender_id, recipient_id, title_key, text_key) VALUES ('simple', 'trip', ?, ?, ?, 'k', 'k')"
|
||||
).run(otherTrip.id, otherUser.id, target.id);
|
||||
await insertRow(orm, Notifications, {
|
||||
type: 'simple', scope: 'trip', target: otherTrip.id, sender: otherUser.id, recipient: target.id, title_key: 'k', text_key: 'k',
|
||||
});
|
||||
|
||||
// user_notice_dismissals (CASCADE): target dismissed a notice
|
||||
testDb.prepare(
|
||||
"INSERT INTO user_notice_dismissals (user_id, notice_id, dismissed_at) VALUES (?, 'test-notice', ?)"
|
||||
).run(target.id, Date.now());
|
||||
await insertRow(orm, UserNoticeDismissals, { user: target.id, notice_id: 'test-notice', dismissed_at: Date.now() });
|
||||
|
||||
// owned journey: target owns a journey with an entry (cascade-deletes on journey deletion)
|
||||
const ownedJourney = createJourney(testDb, target.id);
|
||||
createJourneyEntry(testDb, ownedJourney.id, target.id);
|
||||
|
||||
// trip_files.uploaded_by (SET NULL): target uploaded a file to otherUser's trip
|
||||
const fileRow = testDb.prepare(
|
||||
"INSERT INTO trip_files (trip_id, filename, original_name, uploaded_by) VALUES (?, 'f.pdf', 'file.pdf', ?)"
|
||||
).run(otherTrip.id, target.id);
|
||||
const fileId = await insertRow(orm, TripFiles, { trip: otherTrip.id, filename: 'f.pdf', original_name: 'file.pdf', uploadedByRef: target.id });
|
||||
|
||||
// trek_photos.owner_id (SET NULL): target owns a photo in the central registry
|
||||
const trekPhotoRow = testDb.prepare(
|
||||
"INSERT INTO trek_photos (provider, asset_id, owner_id) VALUES ('immich', 'asset-admin-test', ?)"
|
||||
).run(target.id);
|
||||
const trekPhotoId = await insertRow(orm, TrekPhotos, { provider: 'immich', asset_id: 'asset-admin-test', owner: target.id });
|
||||
|
||||
// trip_photos.user_id (CASCADE): target added a photo to otherUser's trip
|
||||
addTripPhoto(testDb, otherTrip.id, target.id, 'asset-tp-admin', 'immich');
|
||||
@@ -205,33 +242,33 @@ describe('Admin user management', () => {
|
||||
|
||||
// todo_items.assigned_user_id (SET NULL): target is assigned to a todo on otherUser's trip
|
||||
const todoItem = createTodoItem(testDb, otherTrip.id);
|
||||
testDb.prepare('UPDATE todo_items SET assigned_user_id = ? WHERE id = ?').run(target.id, todoItem.id);
|
||||
await updateRows(orm, TodoItems, { id: todoItem.id }, { assignedUser: target.id });
|
||||
|
||||
// packing_bags.user_id (SET NULL): target owns a packing bag on otherUser's trip
|
||||
const packBagRow = testDb.prepare(
|
||||
"INSERT INTO packing_bags (trip_id, name, color, user_id) VALUES (?, 'Bag', '#ff0000', ?)"
|
||||
).run(otherTrip.id, target.id);
|
||||
const packBagId = await insertRow(orm, PackingBags, { trip: otherTrip.id, name: 'Bag', color: '#ff0000', user: target.id });
|
||||
|
||||
// mcp_tokens.user_id (CASCADE): target has an MCP API token
|
||||
createMcpToken(testDb, target.id);
|
||||
|
||||
// oauth_tokens/consents.user_id (CASCADE): target has tokens from otherUser's OAuth client
|
||||
testDb.prepare(
|
||||
"INSERT INTO oauth_clients (id, user_id, name, client_id, client_secret_hash) VALUES ('cl-admin-test', ?, 'App', 'cid-admin-test', 'h')"
|
||||
).run(otherUser.id);
|
||||
testDb.prepare(
|
||||
"INSERT INTO oauth_tokens (client_id, user_id, access_token_hash, refresh_token_hash, access_token_expires_at, refresh_token_expires_at) VALUES ('cid-admin-test', ?, 'ath-admin', 'rth-admin', datetime('now','+1 hour'), datetime('now','+30 days'))"
|
||||
).run(target.id);
|
||||
testDb.prepare(
|
||||
"INSERT INTO oauth_consents (client_id, user_id) VALUES ('cid-admin-test', ?)"
|
||||
).run(target.id);
|
||||
// The row id and the public client_id are the same string here, so the
|
||||
// tokens' client reference resolves to it whichever of the two it keys on.
|
||||
await insertRow(orm, OauthClients, {
|
||||
id: 'cid-admin-test', user: otherUser.id, name: 'App', client_id: 'cid-admin-test', client_secret_hash: 'h',
|
||||
});
|
||||
await insertRow(orm, OauthTokens, {
|
||||
client: 'cid-admin-test', user: target.id, access_token_hash: 'ath-admin', refresh_token_hash: 'rth-admin',
|
||||
access_token_expires_at: dbNow(new Date(Date.now() + 60 * 60 * 1000)),
|
||||
refresh_token_expires_at: dbNow(new Date(Date.now() + 30 * 24 * 60 * 60 * 1000)),
|
||||
});
|
||||
await insertRow(orm, OauthConsents, { client: 'cid-admin-test', user: target.id });
|
||||
|
||||
// vacay_plans.owner_id (CASCADE): target owns a vacation plan
|
||||
const vacayPlanRow = testDb.prepare("INSERT INTO vacay_plans (owner_id) VALUES (?)").run(target.id);
|
||||
const vacayPlanId = (await makeVacayPlan(orm, target.id)).id;
|
||||
|
||||
// vacay_plan_members.user_id (CASCADE): target is a member of otherUser's vacay plan
|
||||
const otherVacayPlanRow = testDb.prepare("INSERT INTO vacay_plans (owner_id) VALUES (?)").run(otherUser.id);
|
||||
testDb.prepare("INSERT INTO vacay_plan_members (plan_id, user_id) VALUES (?, ?)").run(otherVacayPlanRow.lastInsertRowid, target.id);
|
||||
const otherVacayPlanId = (await makeVacayPlan(orm, otherUser.id)).id;
|
||||
await insertRow(orm, VacayPlanMembers, { plan: otherVacayPlanId, user: target.id });
|
||||
|
||||
// bucket_list.user_id (CASCADE): target has a bucket list item
|
||||
createBucketListItem(testDb, target.id);
|
||||
@@ -240,14 +277,10 @@ describe('Admin user management', () => {
|
||||
createVisitedCountry(testDb, target.id, 'JP');
|
||||
|
||||
// visited_regions.user_id (CASCADE): target has visited a region
|
||||
testDb.prepare(
|
||||
"INSERT INTO visited_regions (user_id, region_code, region_name, country_code) VALUES (?, 'JP-13', 'Tokyo', 'JP')"
|
||||
).run(target.id);
|
||||
await insertRow(orm, VisitedRegions, { user: target.id, region_code: 'JP-13', region_name: 'Tokyo', country_code: 'JP' });
|
||||
|
||||
// packing_templates.created_by (CASCADE): target created a packing template
|
||||
const packTemplateRow = testDb.prepare(
|
||||
"INSERT INTO packing_templates (name, created_by) VALUES ('My Template', ?)"
|
||||
).run(target.id);
|
||||
const packTemplateId = await insertRow(orm, PackingTemplates, { name: 'My Template', createdByRef: target.id });
|
||||
|
||||
// invite_tokens.created_by (CASCADE): target created an invite token
|
||||
createInviteToken(testDb, { created_by: target.id });
|
||||
@@ -256,20 +289,18 @@ describe('Admin user management', () => {
|
||||
createCollabNote(testDb, otherTrip.id, target.id);
|
||||
|
||||
// settings.user_id (CASCADE): target has a user setting
|
||||
testDb.prepare("INSERT INTO settings (user_id, key, value) VALUES (?, 'theme', 'dark')").run(target.id);
|
||||
await insertRow(orm, Settings, { user: target.id, key: 'theme', value: 'dark' });
|
||||
|
||||
// password_reset_tokens.user_id (CASCADE): target has a pending password reset
|
||||
testDb.prepare(
|
||||
"INSERT INTO password_reset_tokens (user_id, token_hash, expires_at) VALUES (?, 'prt-hash-admin', datetime('now','+1 hour'))"
|
||||
).run(target.id);
|
||||
await insertRow(orm, PasswordResetTokens, {
|
||||
user: target.id, token_hash: 'prt-hash-admin', expires_at: dbNow(new Date(Date.now() + 60 * 60 * 1000)),
|
||||
});
|
||||
|
||||
// audit_log.user_id (SET NULL): target performed an audited action
|
||||
const auditRow = testDb.prepare(
|
||||
"INSERT INTO audit_log (user_id, action, ip) VALUES (?, 'test.action', '127.0.0.1')"
|
||||
).run(target.id);
|
||||
const auditId = await insertRow(orm, AuditLog, { user: target.id, action: 'test.action', ip: '127.0.0.1' });
|
||||
|
||||
// notification_channel_preferences.user_id (CASCADE): target has notification preferences
|
||||
testDb.prepare("INSERT OR IGNORE INTO notification_channel_preferences (user_id, event_type, channel) VALUES (?, 'trip_invite', 'email')").run(target.id);
|
||||
await insertRowIgnoringConflict(orm, NotificationChannelPreferences, { user: target.id, event_type: 'trip_invite', channel: 'email' });
|
||||
|
||||
const res = await request(app)
|
||||
.delete(`/api/admin/users/${target.id}`)
|
||||
@@ -277,69 +308,69 @@ describe('Admin user management', () => {
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body.success).toBe(true);
|
||||
|
||||
expect(testDb.prepare('SELECT id FROM users WHERE id = ?').get(target.id)).toBeUndefined();
|
||||
expect(await findRow(orm, Users, { id: target.id })).toBeNull();
|
||||
// trip_members row survives but invited_by is now NULL
|
||||
expect((testDb.prepare('SELECT invited_by FROM trip_members WHERE trip_id = ? AND user_id = ?').get(otherTrip.id, thirdUser.id) as any).invited_by).toBeNull();
|
||||
expect(testDb.prepare('SELECT id FROM share_tokens WHERE created_by = ?').get(target.id)).toBeUndefined();
|
||||
expect((testDb.prepare('SELECT paid_by_user_id FROM budget_items WHERE id = ?').get(budgetItem.id) as any).paid_by_user_id).toBeNull();
|
||||
expect(testDb.prepare('SELECT user_id FROM journey_contributors WHERE journey_id = ? AND user_id = ?').get(otherJourney.id, target.id)).toBeUndefined();
|
||||
expect(testDb.prepare('SELECT id FROM journey_entries WHERE author_id = ?').get(target.id)).toBeUndefined();
|
||||
expect(testDb.prepare('SELECT id FROM journey_share_tokens WHERE created_by = ?').get(target.id)).toBeUndefined();
|
||||
expect((await findRow(orm, TripMembers, { trip: otherTrip.id, user: thirdUser.id }))?.invited_by).toBeNull();
|
||||
expect(await findRow(orm, ShareTokens, { createdByRef: target.id })).toBeNull();
|
||||
expect((await findRow(orm, BudgetItems, { id: budgetItem.id }))?.paid_by_user_id).toBeNull();
|
||||
expect(await findRow(orm, JourneyContributors, { journey: otherJourney.id, user: target.id })).toBeNull();
|
||||
expect(await findRow(orm, JourneyEntries, { author: target.id })).toBeNull();
|
||||
expect(await findRow(orm, JourneyShareTokens, { createdByRef: target.id })).toBeNull();
|
||||
// sent notification survives but sender_id becomes NULL
|
||||
expect((testDb.prepare('SELECT sender_id FROM notifications WHERE id = ?').get(sentNotif.lastInsertRowid) as any).sender_id).toBeNull();
|
||||
expect((await findRow(orm, Notifications, { id: sentNotif }))?.sender_id).toBeNull();
|
||||
// received notification is cascade-deleted
|
||||
expect(testDb.prepare('SELECT id FROM notifications WHERE recipient_id = ?').get(target.id)).toBeUndefined();
|
||||
expect(await findRow(orm, Notifications, { recipient: target.id })).toBeNull();
|
||||
// notice dismissals are cascade-deleted
|
||||
expect(testDb.prepare("SELECT user_id FROM user_notice_dismissals WHERE user_id = ? AND notice_id = 'test-notice'").get(target.id)).toBeUndefined();
|
||||
expect(await findRow(orm, UserNoticeDismissals, { user: target.id, notice_id: 'test-notice' })).toBeNull();
|
||||
// owned journey and its entries are cascade-deleted
|
||||
expect(testDb.prepare('SELECT id FROM journeys WHERE user_id = ?').get(target.id)).toBeUndefined();
|
||||
expect(testDb.prepare('SELECT id FROM journey_entries WHERE journey_id = ?').get(ownedJourney.id)).toBeUndefined();
|
||||
expect(await findRow(orm, Journeys, { user: target.id })).toBeNull();
|
||||
expect(await findRow(orm, JourneyEntries, { journey: ownedJourney.id })).toBeNull();
|
||||
// uploaded file survives but uploaded_by is now NULL
|
||||
expect((testDb.prepare('SELECT uploaded_by FROM trip_files WHERE id = ?').get(fileRow.lastInsertRowid) as any).uploaded_by).toBeNull();
|
||||
expect((await findRow(orm, TripFiles, { id: fileId }))?.uploaded_by).toBeNull();
|
||||
// trek_photos row survives but owner_id is now NULL
|
||||
expect((testDb.prepare('SELECT owner_id FROM trek_photos WHERE id = ?').get(trekPhotoRow.lastInsertRowid) as any).owner_id).toBeNull();
|
||||
expect((await findRow(orm, TrekPhotos, { id: trekPhotoId }))?.owner_id).toBeNull();
|
||||
// trip_photos row for target is cascade-deleted
|
||||
expect(testDb.prepare("SELECT id FROM trip_photos WHERE trip_id = ? AND user_id = ?").get(otherTrip.id, target.id)).toBeUndefined();
|
||||
expect(await findRow(orm, TripPhotos, { trip: otherTrip.id, user: target.id })).toBeNull();
|
||||
// owned trip is cascade-deleted
|
||||
expect(testDb.prepare('SELECT id FROM trips WHERE id = ?').get(ownedTrip.id)).toBeUndefined();
|
||||
expect(await findRow(orm, Trips, { id: ownedTrip.id })).toBeNull();
|
||||
// trip membership on others' trips is removed
|
||||
expect(testDb.prepare('SELECT id FROM trip_members WHERE trip_id = ? AND user_id = ?').get(otherTrip.id, target.id)).toBeUndefined();
|
||||
expect(await findRow(orm, TripMembers, { trip: otherTrip.id, user: target.id })).toBeNull();
|
||||
// category survives but user_id is NULL
|
||||
expect((testDb.prepare('SELECT user_id FROM categories WHERE id = ?').get(userCategory.id) as any).user_id).toBeNull();
|
||||
expect((await findRow(orm, Categories, { id: userCategory.id }))?.user_id).toBeNull();
|
||||
// tag is deleted
|
||||
expect(testDb.prepare('SELECT id FROM tags WHERE id = ?').get(userTag.id)).toBeUndefined();
|
||||
expect(await findRow(orm, Tags, { id: userTag.id })).toBeNull();
|
||||
// todo assigned_user_id is NULL
|
||||
expect((testDb.prepare('SELECT assigned_user_id FROM todo_items WHERE id = ?').get(todoItem.id) as any).assigned_user_id).toBeNull();
|
||||
expect((await findRow(orm, TodoItems, { id: todoItem.id }))?.assigned_user_id).toBeNull();
|
||||
// packing bag survives but user_id is NULL
|
||||
expect((testDb.prepare('SELECT user_id FROM packing_bags WHERE id = ?').get(packBagRow.lastInsertRowid) as any).user_id).toBeNull();
|
||||
expect((await findRow(orm, PackingBags, { id: packBagId }))?.user_id).toBeNull();
|
||||
// MCP tokens are deleted
|
||||
expect(testDb.prepare('SELECT id FROM mcp_tokens WHERE user_id = ?').get(target.id)).toBeUndefined();
|
||||
expect(await findRow(orm, McpTokens, { user: target.id })).toBeNull();
|
||||
// OAuth tokens and consents are deleted
|
||||
expect(testDb.prepare('SELECT id FROM oauth_tokens WHERE user_id = ?').get(target.id)).toBeUndefined();
|
||||
expect(testDb.prepare('SELECT id FROM oauth_consents WHERE user_id = ?').get(target.id)).toBeUndefined();
|
||||
expect(await findRow(orm, OauthTokens, { user: target.id })).toBeNull();
|
||||
expect(await findRow(orm, OauthConsents, { user: target.id })).toBeNull();
|
||||
// owned vacay plan is deleted
|
||||
expect(testDb.prepare('SELECT id FROM vacay_plans WHERE id = ?').get(vacayPlanRow.lastInsertRowid)).toBeUndefined();
|
||||
expect(await findRow(orm, VacayPlans, { id: vacayPlanId })).toBeNull();
|
||||
// vacay plan membership on others' plans is removed
|
||||
expect(testDb.prepare('SELECT id FROM vacay_plan_members WHERE plan_id = ? AND user_id = ?').get(otherVacayPlanRow.lastInsertRowid, target.id)).toBeUndefined();
|
||||
expect(await findRow(orm, VacayPlanMembers, { plan: otherVacayPlanId, user: target.id })).toBeNull();
|
||||
// bucket list items are deleted
|
||||
expect(testDb.prepare('SELECT id FROM bucket_list WHERE user_id = ?').get(target.id)).toBeUndefined();
|
||||
expect(await findRow(orm, BucketList, { user: target.id })).toBeNull();
|
||||
// travel history is deleted
|
||||
expect(testDb.prepare('SELECT user_id FROM visited_countries WHERE user_id = ? AND country_code = ?').get(target.id, 'JP')).toBeUndefined();
|
||||
expect(testDb.prepare('SELECT id FROM visited_regions WHERE user_id = ?').get(target.id)).toBeUndefined();
|
||||
expect(await findRow(orm, VisitedCountries, { user: target.id, country_code: 'JP' })).toBeNull();
|
||||
expect(await findRow(orm, VisitedRegions, { user: target.id })).toBeNull();
|
||||
// packing template is deleted
|
||||
expect(testDb.prepare('SELECT id FROM packing_templates WHERE id = ?').get(packTemplateRow.lastInsertRowid)).toBeUndefined();
|
||||
expect(await findRow(orm, PackingTemplates, { id: packTemplateId })).toBeNull();
|
||||
// invite tokens created by target are deleted
|
||||
expect(testDb.prepare('SELECT id FROM invite_tokens WHERE created_by = ?').get(target.id)).toBeUndefined();
|
||||
expect(await findRow(orm, InviteTokens, { createdByRef: target.id })).toBeNull();
|
||||
// collab content is deleted
|
||||
expect(testDb.prepare('SELECT id FROM collab_notes WHERE user_id = ? AND trip_id = ?').get(target.id, otherTrip.id)).toBeUndefined();
|
||||
expect(await findRow(orm, CollabNotes, { user: target.id, trip: otherTrip.id })).toBeNull();
|
||||
// user settings are deleted
|
||||
expect(testDb.prepare("SELECT id FROM settings WHERE user_id = ?").get(target.id)).toBeUndefined();
|
||||
expect(await findRow(orm, Settings, { user: target.id })).toBeNull();
|
||||
// password reset tokens are deleted
|
||||
expect(testDb.prepare('SELECT id FROM password_reset_tokens WHERE user_id = ?').get(target.id)).toBeUndefined();
|
||||
expect(await findRow(orm, PasswordResetTokens, { user: target.id })).toBeNull();
|
||||
// audit log entry survives but user_id is NULL
|
||||
expect((testDb.prepare('SELECT user_id FROM audit_log WHERE id = ?').get(auditRow.lastInsertRowid) as any).user_id).toBeNull();
|
||||
expect((await findRow(orm, AuditLog, { id: auditId }))?.user_id).toBeNull();
|
||||
// notification channel preferences are deleted
|
||||
expect(testDb.prepare("SELECT user_id FROM notification_channel_preferences WHERE user_id = ? AND event_type = 'trip_invite'").get(target.id)).toBeUndefined();
|
||||
expect(await findRow(orm, NotificationChannelPreferences, { user: target.id, event_type: 'trip_invite' })).toBeNull();
|
||||
});
|
||||
|
||||
it('ADMIN-006 — admin cannot delete their own account', async () => {
|
||||
@@ -367,7 +398,7 @@ describe('Admin user management — whitespace normalization', () => {
|
||||
.send({ username: ' trimmedadmin ' });
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
const row = testDb.prepare('SELECT username FROM users WHERE id = ?').get(user.id) as { username: string };
|
||||
const row = await readUser(orm, user.id);
|
||||
expect(row.username).toBe('trimmedadmin');
|
||||
});
|
||||
|
||||
@@ -381,7 +412,7 @@ describe('Admin user management — whitespace normalization', () => {
|
||||
.send({ email: ' newemail@example.com ' });
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
const row = testDb.prepare('SELECT email FROM users WHERE id = ?').get(user.id) as { email: string };
|
||||
const row = await readUser(orm, user.id);
|
||||
expect(row.email).toBe('newemail@example.com');
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user