This commit is contained in:
@@ -5,6 +5,8 @@ import { afterEach, expect, test, vi } from "vitest";
|
||||
import { buildApp } from "../src/app.js";
|
||||
import { loadConfig } from "../src/config.js";
|
||||
import { MemoryCatalogRepository } from "../src/catalog/memory-repository.js";
|
||||
import { CatalogOperationCoordinator } from "../src/catalog/operation-coordinator.js";
|
||||
import type { CatalogPostgresAccess } from "../src/catalog/postgres-access.js";
|
||||
import type { ObservedSchemaSnapshot } from "../src/catalog/types.js";
|
||||
import { WorkspaceSecretStore } from "../src/workspaces/secret-store.js";
|
||||
import type { WorkspaceRegistry, WorkspaceRevision } from "../src/workspaces/registry.js";
|
||||
@@ -28,7 +30,13 @@ const workspace: WorkspaceDescriptor = {
|
||||
};
|
||||
const revision: WorkspaceRevision = { id: "psd-clinical", commit: "a".repeat(40), blob: "b".repeat(40), snapshotPath: "/tmp/psd.yaml" };
|
||||
|
||||
function setup(environment: Record<string, string> = {}) {
|
||||
function setup(
|
||||
environment: Record<string, string> = {},
|
||||
catalogDependencies: {
|
||||
catalogOperationCoordinator?: CatalogOperationCoordinator;
|
||||
catalogPostgresAccess?: CatalogPostgresAccess;
|
||||
} = {},
|
||||
) {
|
||||
const secretRoot = mkdtempSync(join(tmpdir(), "catalog-secret-"));
|
||||
const runtimeRoot = mkdtempSync(join(tmpdir(), "catalog-secret-runtime-"));
|
||||
roots.push(secretRoot, runtimeRoot);
|
||||
@@ -49,6 +57,7 @@ function setup(environment: Record<string, string> = {}) {
|
||||
workspaceSecretStore: secretStore,
|
||||
catalogRepository: repository,
|
||||
workspaceDiagnoser: vi.fn(),
|
||||
...catalogDependencies,
|
||||
});
|
||||
return { app, secretStore, repository };
|
||||
}
|
||||
@@ -130,6 +139,29 @@ test("lists orphaned records and takes the REST diagnostic path from workspace Y
|
||||
]));
|
||||
});
|
||||
|
||||
test.each([
|
||||
"https://reader:secret@psd.example/api",
|
||||
"https://psd.example/api?token=secret",
|
||||
"https://psd.example/api#secret",
|
||||
"ftp://psd.example/api",
|
||||
])("rejects unsafe REST base URL %s before persistence", async (baseUrl) => {
|
||||
const { app, repository } = setup();
|
||||
const response = await app.inject({
|
||||
method: "POST",
|
||||
url: "/catalog/databases",
|
||||
payload: {
|
||||
...direct,
|
||||
binding: { transport: "rest_api", baseUrl, restPath: "/health", restAuth: "bearer" },
|
||||
},
|
||||
});
|
||||
expect(response.statusCode).toBe(400);
|
||||
expect(response.json()).toEqual({
|
||||
code: "database_invalid",
|
||||
message: "Database configuration is invalid.",
|
||||
});
|
||||
expect(await repository.getByWorkspace("psd-clinical")).toBeUndefined();
|
||||
});
|
||||
|
||||
test("uses optimistic versions, keeps secrets write-only, and hard-deletes only local configuration", async () => {
|
||||
const { app, secretStore } = setup();
|
||||
const created = (await app.inject({ method: "POST", url: "/catalog/databases", payload: direct })).json();
|
||||
@@ -151,6 +183,44 @@ test("uses optimistic versions, keeps secrets write-only, and hard-deletes only
|
||||
expect((await app.inject({ method: "GET", url: "/catalog/databases" })).json()).toMatchObject([{ configured: false }]);
|
||||
});
|
||||
|
||||
test("rejects a connection test while another catalog operation owns the database", async () => {
|
||||
const coordinator = new CatalogOperationCoordinator();
|
||||
const postgres: CatalogPostgresAccess = {
|
||||
connect: vi.fn(async () => { throw new Error("connection must not start"); }),
|
||||
};
|
||||
const { app } = setup({}, {
|
||||
catalogOperationCoordinator: coordinator,
|
||||
catalogPostgresAccess: postgres,
|
||||
});
|
||||
const created = (await app.inject({
|
||||
method: "POST",
|
||||
url: "/catalog/databases",
|
||||
payload: direct,
|
||||
})).json();
|
||||
const release = coordinator.reserve(created.id);
|
||||
|
||||
try {
|
||||
const response = await app.inject({
|
||||
method: "POST",
|
||||
url: `/catalog/databases/${created.id}/test`,
|
||||
payload: { version: created.version },
|
||||
});
|
||||
|
||||
expect(response.statusCode).toBe(409);
|
||||
expect(response.json()).toEqual({
|
||||
code: "database_operation_in_progress",
|
||||
message: "A database operation is already in progress.",
|
||||
});
|
||||
expect(postgres.connect).not.toHaveBeenCalled();
|
||||
expect((await app.inject({
|
||||
method: "GET",
|
||||
url: `/catalog/databases/${created.id}`,
|
||||
})).json()).toMatchObject({ connectionStatus: "untested" });
|
||||
} finally {
|
||||
release();
|
||||
}
|
||||
});
|
||||
|
||||
test("returns exact global and per-database fleet metrics", async () => {
|
||||
const { app, repository } = setup();
|
||||
const database = await repository.create(direct);
|
||||
|
||||
Reference in New Issue
Block a user