131 lines
5.6 KiB
TypeScript
131 lines
5.6 KiB
TypeScript
// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0
|
|
// The web shell's stores of the mariadb and mongodb profiles (apps/web/src/server/dialect/) on the
|
|
// real engines: the readiness probes answer, the migration level the migrate job wrote is what
|
|
// readiness expects, the MongoDB store refuses a standalone server at boot (AT-D15), and the BullMQ
|
|
// queue enqueues on Valkey under the app's own prefix.
|
|
//
|
|
// It needs ACCOUNT_TEST_MARIADB_URL, ACCOUNT_TEST_MONGODB_URL (a replica set) and ACCOUNT_TEST_REDIS_URL,
|
|
// the databases test/dialect/account.test.ts migrated; without them each block is skipped by name.
|
|
|
|
import { randomUUID } from "node:crypto"
|
|
import { type AppEnv, expectedDialectMigrations, liveCaptureMigrations, loadAppEnv } from "@app/config"
|
|
import { notes } from "@app/modules/notes"
|
|
import { buildRegistry } from "@conusai-kit/core"
|
|
import { entitiesOf } from "@conusai-kit/data"
|
|
import { KIT_JOBS_ENTITIES } from "@conusai-kit/jobs-pgboss"
|
|
import { Redis } from "ioredis"
|
|
import { describe, expect, it } from "vitest"
|
|
import { dialectStores, queueSlug } from "../../apps/web/src/server/dialect/stores"
|
|
import { engineFor } from "../../apps/worker/src/dialect/runtime.js"
|
|
import { freshDialectModules } from "./fixture.js"
|
|
|
|
const redisUrl = process.env.ACCOUNT_TEST_REDIS_URL
|
|
const platform = {
|
|
NODE_ENV: "test",
|
|
CONUSAI_API_URL: "https://api.example.test/api",
|
|
CONUSAI_API_TOKEN: "token",
|
|
CONUSAI_PROJECT_ID: "w06-stores",
|
|
}
|
|
|
|
const ENGINES = [
|
|
[
|
|
"mariadb",
|
|
{ MARIADB_URL: process.env.ACCOUNT_TEST_MARIADB_URL, DATABASE_URL: process.env.ACCOUNT_TEST_MARIADB_URL },
|
|
],
|
|
["mongodb", { MONGODB_URL: process.env.ACCOUNT_TEST_MONGODB_URL }],
|
|
] as const
|
|
|
|
for (const [profile, store] of ENGINES) {
|
|
const registry = buildRegistry(freshDialectModules(profile))
|
|
const url = Object.values(store)[0]
|
|
describe.skipIf(!url || !redisUrl)(`the web shell's ${profile} store`, () => {
|
|
it("answers readiness at the migrate job's level and enqueues on its own BullMQ prefix", async () => {
|
|
const raw = { ...platform, ...store, REDIS_URL: redisUrl }
|
|
await engineFor(profile).migrate(raw, registry)
|
|
const env: AppEnv = loadAppEnv("web", raw, profile)
|
|
const stores = dialectStores(
|
|
env,
|
|
[...entitiesOf(registry, profile), ...KIT_JOBS_ENTITIES],
|
|
liveCaptureMigrations(registry, profile),
|
|
)
|
|
if (!stores) throw new Error("no dialect store")
|
|
try {
|
|
await stores.probes.boot()
|
|
await stores.probes.store()
|
|
const applied = await stores.probes.applied()
|
|
for (const name of await expectedDialectMigrations(registry, profile))
|
|
expect(applied.has(name), name).toBe(true)
|
|
// A request's enqueue reaches Valkey under this app's prefix.
|
|
const id = await stores
|
|
.queue()
|
|
.enqueue(
|
|
{ level: "org", orgId: "00000000-0000-4000-8000-000000000001", actor: { userId: "u" } },
|
|
"files.recover",
|
|
{ probe: true },
|
|
)
|
|
expect(id).toBeTruthy()
|
|
const redis = new Redis(redisUrl as string)
|
|
try {
|
|
const keys = await redis.keys(`kit-jobs-${queueSlug(env)}*`)
|
|
expect(keys.length).toBeGreaterThan(0)
|
|
} finally {
|
|
await redis.quit()
|
|
}
|
|
// The live log: the migrate job prepared notes_note (MariaDB row triggers, MongoDB pre-images),
|
|
// so a note written through the repository is in the log, ids only, in its own organization.
|
|
{
|
|
const log = stores.liveLog?.()
|
|
if (!log) throw new Error(`the ${profile} store has no live log`)
|
|
await log.start()
|
|
const scope = {
|
|
level: "project",
|
|
orgId: randomUUID(),
|
|
workspaceId: randomUUID(),
|
|
projectId: randomUUID(),
|
|
actor: { userId: randomUUID() },
|
|
} as const
|
|
const since = profile === "mongodb" ? (await log.head(scope.orgId)).position : { txid: 0n, seq: 0n }
|
|
const note = await stores.data
|
|
.forScope(scope)
|
|
.of(notes)
|
|
.insert({ title: "live", body: "b", createdBy: scope.actor.userId })
|
|
let rows: Awaited<ReturnType<typeof log.read>>["final"] = []
|
|
for (let attempt = 0; attempt < 50 && rows.length === 0; attempt++) {
|
|
const read = await log.read(scope.orgId, since, 10)
|
|
rows = [...read.final, ...read.early]
|
|
if (rows.length === 0) await new Promise((resolve) => setTimeout(resolve, 100))
|
|
}
|
|
expect(rows.map((row) => [row.entity, row.id, row.op, row.scope.ws])).toEqual([
|
|
["notes.note", note.id, "u", scope.workspaceId],
|
|
])
|
|
expect((await log.read(randomUUID(), since, 10)).final).toEqual([])
|
|
await log.close()
|
|
}
|
|
} finally {
|
|
await stores.close()
|
|
}
|
|
}, 60_000)
|
|
})
|
|
}
|
|
|
|
describe.skipIf(!process.env.ACCOUNT_TEST_MONGODB_STANDALONE_URL)(
|
|
"the web shell's mongodb store on a standalone server",
|
|
() => {
|
|
it("refuses to boot with the typed replica_set error (AT-D15)", async () => {
|
|
const env = loadAppEnv(
|
|
"web",
|
|
{ ...platform, MONGODB_URL: process.env.ACCOUNT_TEST_MONGODB_STANDALONE_URL, REDIS_URL: "redis://127.0.0.1:1" },
|
|
"mongodb",
|
|
)
|
|
const registry = buildRegistry(freshDialectModules("mongodb"))
|
|
const stores = dialectStores(env, entitiesOf(registry, "mongodb"))
|
|
if (!stores) throw new Error("no dialect store")
|
|
try {
|
|
await expect(stores.probes.boot()).rejects.toMatchObject({ name: "MongoTopologyError", setting: "replica_set" })
|
|
} finally {
|
|
await stores.close()
|
|
}
|
|
}, 60_000)
|
|
},
|
|
)
|