Files
gitmost/apps/server/test/integration/page-template-references-cascade.int-spec.ts
claude code agent 227 04f05626ad test(server): integration harness + deferred coverage vs real Postgres/Redis
Builds the deferred integration tests from docs/backlog/feature-test-coverage-
deferred.md that needed real infra (a test Postgres + real Redis) which the repo
lacked. Runs against an isolated, auto-created docmost_test database and Redis
logical DB 15 — never the dev data.

Harness (apps/server/test/integration/, run via new `pnpm --filter server test:int`
=> jest --config test/jest-integration.json; default unit `jest` is untouched and
excludes these via the *.int-spec.ts name + rootDir):
- db.ts: buildTestDb() mirrors database.module.ts exactly (PostgresJSDialect,
  CamelCasePlugin, bigint to:20/from:[20,1700] parsing) + minimal seed helpers.
- global-setup.ts: DROP/CREATE docmost_test, CREATE EXTENSION vector, migrate to
  latest via Kysely Migrator (fails loud on any errored migration).
- global-teardown.ts: closes the pool.

Coverage (5 suites, 16 tests, all green against live PG+Redis):
- WorkspaceRepo.updateSetting: jsonb-merge persists htmlEmbed without clobbering
  sibling ai/sharing namespaces (the kill-switch write half).
- AiAgentRoleRepo: soft-delete exclusion, cross-workspace tenant isolation,
  duplicate (name,workspace) -> 23505, name reusable after softDelete (partial
  unique index WHERE deleted_at IS NULL), same name across workspaces allowed.
- page_template_references: deleting either source or referenced page cascades
  the link row (onDelete cascade) — real FK, not mocked.
- PublicShareWorkspaceLimiter vs REAL Redis: real ioredis EVAL of the sliding-
  window Lua — max boundary (3 admit / 4th deny), re-admit after the window
  slides, same-ms distinct members. Catches Lua bugs a FakeRedis cannot.
- AiChatRepo.findByCreator: role-badge join (enabled->badge; soft-deleted or
  disabled role -> null).

Review: APPROVE; applied its two hardening suggestions (fail loud on errored
migration result even without a top-level error; TEST_REDIS_URL override + ping
preflight). tsc clean; unit run excludes int-spec (verified).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 07:02:55 +03:00

69 lines
2.1 KiB
TypeScript

import { Kysely } from 'kysely';
import {
getTestDb,
destroyTestDb,
createWorkspace,
createSpace,
createPage,
} from './db';
/**
* C — page_template_references FK onDelete('cascade') (migration
* 20260620T131000-page-template-references.ts). Both reference_page_id and
* source_page_id reference pages.id ON DELETE CASCADE; deleting either page
* must remove the reference row.
*/
describe('page_template_references FK cascade [integration]', () => {
let db: Kysely<any>;
let workspaceId: string;
let spaceId: string;
beforeAll(async () => {
db = getTestDb();
workspaceId = (await createWorkspace(db)).id;
spaceId = (await createSpace(db, workspaceId)).id;
});
afterAll(async () => {
await destroyTestDb();
});
async function seedRef() {
const source = await createPage(db, { workspaceId, spaceId, title: 'source' });
const reference = await createPage(db, { workspaceId, spaceId, title: 'reference' });
const ref = await db
.insertInto('pageTemplateReferences')
.values({ workspaceId, sourcePageId: source.id, referencePageId: reference.id })
.returning(['id'])
.executeTakeFirstOrThrow();
return { source, reference, refId: ref.id as string };
}
async function refExists(refId: string): Promise<boolean> {
const row = await db
.selectFrom('pageTemplateReferences')
.select('id')
.where('id', '=', refId)
.executeTakeFirst();
return Boolean(row);
}
it('deleting the referenced page cascades the reference row away', async () => {
const { reference, refId } = await seedRef();
expect(await refExists(refId)).toBe(true);
await db.deleteFrom('pages').where('id', '=', reference.id).execute();
expect(await refExists(refId)).toBe(false);
});
it('deleting the source page also cascades the reference row away', async () => {
const { source, refId } = await seedRef();
expect(await refExists(refId)).toBe(true);
await db.deleteFrom('pages').where('id', '=', source.id).execute();
expect(await refExists(refId)).toBe(false);
});
});