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>
69 lines
2.1 KiB
TypeScript
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);
|
|
});
|
|
});
|