diff --git a/tests/hash-backend-integration/package.json b/tests/hash-backend-integration/package.json index 8df50911c8a..3eb829df0b1 100644 --- a/tests/hash-backend-integration/package.json +++ b/tests/hash-backend-integration/package.json @@ -10,7 +10,7 @@ "fix:eslint": "eslint --fix .", "lint:eslint": "eslint --report-unused-disable-directives .", "lint:tsc": "tsc --noEmit", - "test:integration": "vitest --run" + "test:integration": "vitest --run && vitest --run --config vitest.snapshot.config.ts" }, "dependencies": { "@apps/hash-api": "workspace:*", diff --git a/tests/hash-backend-integration/src/tests/admin-server.ts b/tests/hash-backend-integration/src/tests/admin-server.ts index 5ae6dbbefe1..b4a25c881d3 100644 --- a/tests/hash-backend-integration/src/tests/admin-server.ts +++ b/tests/hash-backend-integration/src/tests/admin-server.ts @@ -6,6 +6,18 @@ import { StatusCode } from "@local/status"; import type { GraphStatus } from "@rust/hash-graph-type-defs/typescript/status"; +/** + * Throw unless running in the snapshot group (`vitest.snapshot.config.ts`), + * the only group allowed to wipe the shared system graph. + */ +const assertRunningInSnapshotGroup = (operation: string) => { + if (process.env.HASH_TEST_GROUP !== "snapshot") { + throw new Error( + `\`${operation}\` wipes the graph seeded by \`globalSetup\`, so it may only run in the snapshot group (\`vitest.snapshot.config.ts\`). To make a test file destructive, place it under \`src/tests/subgraph/\`.`, + ); + } +}; + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const port = process.env.HASH_GRAPH_ADMIN_PORT || "4001"; @@ -96,8 +108,12 @@ export const deleteEntities = async () => { /** * Restore a snapshot from a file. + * + * Destructive – may only run in the snapshot group. */ export const restoreSnapshot = async (snapshotPath: string) => { + assertRunningInSnapshotGroup("restoreSnapshot"); + await fetch(`http://127.0.0.1:${port}/snapshot`, { method: "POST", body: createReadStream(snapshotPath), @@ -137,8 +153,12 @@ export const deleteUser = async ( * Reset the Graph. * * This is a convenience function for deleting all entities, entity types, property types, data types, and accounts. + * + * Destructive – may only run in the snapshot group. */ export const resetGraph = async () => { + assertRunningInSnapshotGroup("resetGraph"); + await deleteEntities(); await deleteEntityTypes(); await deletePropertyTypes(); diff --git a/tests/hash-backend-integration/tsconfig.json b/tests/hash-backend-integration/tsconfig.json index 8ff51e490d6..31ae08d6037 100644 --- a/tests/hash-backend-integration/tsconfig.json +++ b/tests/hash-backend-integration/tsconfig.json @@ -1,4 +1,9 @@ { "extends": "@local/tsconfig/legacy-base-tsconfig-to-refactor.json", - "include": ["./src/", "codegen.config.ts", "vitest.config.ts"] + "include": [ + "./src/", + "codegen.config.ts", + "vitest.config.ts", + "vitest.snapshot.config.ts" + ] } diff --git a/tests/hash-backend-integration/vitest.config.ts b/tests/hash-backend-integration/vitest.config.ts index 83ada709987..b9bee0c61e8 100644 --- a/tests/hash-backend-integration/vitest.config.ts +++ b/tests/hash-backend-integration/vitest.config.ts @@ -1,29 +1,39 @@ /// import { defineConfig } from "vitest/config"; -import { BaseSequencer } from "vitest/node"; -import type { TestSpecification } from "vitest/node"; +import type { TestUserConfig } from "vitest/config"; /** - * The subgraph tests reset the graph and restore standalone snapshots, - * destroying the shared system graph seeded once per run by `globalSetup`. - * Sort them after all other test files so that they cannot break tests which - * rely on the shared seed – the next run's `globalSetup` re-seeds the graph - * from scratch. + * Test options shared between the seeded group (this config) and the snapshot + * group (`vitest.snapshot.config.ts`), which `test:integration` runs as + * separate, sequential vitest invocations. */ -class DestructiveTestsLastSequencer extends BaseSequencer { - override async sort(files: TestSpecification[]) { - const isDestructive = (file: TestSpecification) => - file.moduleId.includes("/src/tests/subgraph/"); - - const sorted = await super.sort(files); - - return [ - ...sorted.filter((file) => !isDestructive(file)), - ...sorted.filter(isDestructive), - ]; - } -} +export const sharedTestConfig = { + coverage: { + enabled: process.env.TEST_COVERAGE === "true", + provider: "istanbul", + reporter: ["lcov", "text"], + include: ["**/*.{c,m,}{j,t}s{x,}"], + exclude: ["**/node_modules/**", "**/dist/**"], + }, + setupFiles: [ + "@local/hash-backend-utils/environment", + "./src/tests/setup-opentelemetry.ts", + ], + environment: "node", + testTimeout: 60_000, + hookTimeout: 120_000, + sequence: { + hooks: "list", + }, + /** + * These integration tests share a single graph instance, so running files + * in parallel causes graph state races. + */ + fileParallelism: false, + maxWorkers: 1, + maxConcurrency: 1, +} satisfies TestUserConfig; export default defineConfig({ plugins: [], @@ -31,35 +41,8 @@ export default defineConfig({ target: "esnext", }, test: { - coverage: { - enabled: process.env.TEST_COVERAGE === "true", - provider: "istanbul", - reporter: ["lcov", "text"], - include: ["**/*.{c,m,}{j,t}s{x,}"], - exclude: ["**/node_modules/**", "**/dist/**"], - }, + ...sharedTestConfig, globalSetup: ["./src/tests/global-setup.ts"], - setupFiles: [ - "@local/hash-backend-utils/environment", - "./src/tests/setup-opentelemetry.ts", - ], - include: [ - "src/tests/graph/**/*.test.ts", - "src/tests/subgraph/**/*.test.ts", - ], - environment: "node", - testTimeout: 60_000, - hookTimeout: 120_000, - sequence: { - hooks: "list", - sequencer: DestructiveTestsLastSequencer, - }, - /** - * These integration tests share a single graph instance, so running files - * in parallel causes graph state races. - */ - fileParallelism: false, - maxWorkers: 1, - maxConcurrency: 1, + include: ["src/tests/graph/**/*.test.ts"], }, }); diff --git a/tests/hash-backend-integration/vitest.snapshot.config.ts b/tests/hash-backend-integration/vitest.snapshot.config.ts new file mode 100644 index 00000000000..de6abab1b8e --- /dev/null +++ b/tests/hash-backend-integration/vitest.snapshot.config.ts @@ -0,0 +1,31 @@ +/// +import { defineConfig } from "vitest/config"; + +import { sharedTestConfig } from "./vitest.config"; + +/** + * Snapshot group: test files under `src/tests/subgraph/` wipe the graph and + * restore standalone snapshots, so `test:integration` runs this config as a + * separate vitest invocation after the seeded group (`vitest.config.ts`). + * The `HASH_TEST_GROUP` marker lets `admin-server.ts` refuse destructive + * operations outside this group. + */ +export default defineConfig({ + plugins: [], + build: { + target: "esnext", + }, + test: { + ...sharedTestConfig, + coverage: { + ...sharedTestConfig.coverage, + // Sharing `./coverage` would discard the seeded group's report – + // vitest cleans the reports directory at the start of a run. + reportsDirectory: "./coverage-snapshot", + }, + include: ["src/tests/subgraph/**/*.test.ts"], + env: { + HASH_TEST_GROUP: "snapshot", + }, + }, +});