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>
61 lines
1.9 KiB
TypeScript
61 lines
1.9 KiB
TypeScript
import { Kysely } from 'kysely';
|
|
import { WorkspaceRepo } from '@docmost/db/repos/workspace/workspace.repo';
|
|
import { getTestDb, destroyTestDb, createWorkspace } from './db';
|
|
|
|
/**
|
|
* A — WorkspaceRepo.updateSetting jsonb-MERGE (the html-embed kill-switch
|
|
* write-half). Setting a single top-level key must NOT clobber sibling
|
|
* settings namespaces. This is real SQL: the repo does
|
|
* `COALESCE(settings,'{}') || jsonb_build_object(key, value)`.
|
|
*/
|
|
describe('WorkspaceRepo.updateSetting (jsonb merge) [integration]', () => {
|
|
let db: Kysely<any>;
|
|
let repo: WorkspaceRepo;
|
|
|
|
beforeAll(() => {
|
|
db = getTestDb();
|
|
// Repos are plain classes taking @InjectKysely() db — instantiate directly.
|
|
repo = new WorkspaceRepo(db as any);
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await destroyTestDb();
|
|
});
|
|
|
|
it('persists htmlEmbed:true without clobbering sibling ai/sharing settings', async () => {
|
|
const ws = await createWorkspace(db, {
|
|
settings: { ai: { chat: true }, sharing: { x: 1 } },
|
|
});
|
|
|
|
const updated = await repo.updateSetting(ws.id, 'htmlEmbed', true);
|
|
|
|
// Returned row carries the merged settings.
|
|
expect(updated.settings).toMatchObject({
|
|
htmlEmbed: true,
|
|
ai: { chat: true },
|
|
sharing: { x: 1 },
|
|
});
|
|
|
|
// Re-read from the DB to confirm it actually persisted (not just returning()).
|
|
const row = await db
|
|
.selectFrom('workspaces')
|
|
.select(['settings'])
|
|
.where('id', '=', ws.id)
|
|
.executeTakeFirstOrThrow();
|
|
|
|
expect(row.settings).toEqual({
|
|
ai: { chat: true },
|
|
sharing: { x: 1 },
|
|
htmlEmbed: true,
|
|
});
|
|
});
|
|
|
|
it('initializes settings from NULL via COALESCE without error', async () => {
|
|
const ws = await createWorkspace(db, { settings: undefined });
|
|
|
|
const updated = await repo.updateSetting(ws.id, 'htmlEmbed', false);
|
|
|
|
expect(updated.settings).toEqual({ htmlEmbed: false });
|
|
});
|
|
});
|