import { describe, expect, it } from "@effect/vitest";import { Effect } from "effect";import { collectTables } from "@executor-js/sdk";import { createSqliteTestFumaDb } from "@executor-js/sdk/testing";import type { SqliteDataMigrationClient } from "@executor-js/sdk/core"; import { runSqliteGoogleOpenApiOwnershipMigration } from "./openapi-ownership-migration"; const now = 1_780_000_000_000; const insertIntegration = ( client: SqliteDataMigrationClient, row: { readonly rowId: string; readonly tenant: string; readonly slug: string; readonly pluginId: string; readonly config: unknown; },) => client.execute({ sql: `INSERT INTO integration (row_id, tenant, slug, plugin_id, name, description, config, can_remove, can_refresh, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, 1, 1, ?, ?)`, args: [ row.rowId, row.tenant, row.slug, row.pluginId, row.slug, row.slug, JSON.stringify(row.config), now, now, ], }); const insertIntegrationRawConfig = ( client: SqliteDataMigrationClient, row: { readonly rowId: string; readonly tenant: string; readonly slug: string; readonly pluginId: string; readonly config: string; },) => client.execute({ sql: `INSERT INTO integration (row_id, tenant, slug, plugin_id, name, description, config, can_remove, can_refresh, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, 1, 1, ?, ?)`, args: [row.rowId, row.tenant, row.slug, row.pluginId, row.slug, row.slug, row.config, now, now], }); const insertBlob = ( client: SqliteDataMigrationClient, row: { readonly namespace: string; readonly key: string; readonly value: string; },) => client.execute({ sql: "INSERT INTO blob (namespace, key, value, row_id, id) VALUES (?, ?, ?, ?, ?)", args: [ row.namespace, row.key, row.value, `blob-${row.namespace}-${row.key}`, JSON.stringify([row.namespace, row.key]), ], }); const insertOperationStorage = ( client: SqliteDataMigrationClient, row: { readonly tenant: string; readonly pluginId: string; readonly integration: string; readonly toolName: string; },) => client.execute({ sql: `INSERT INTO plugin_storage (tenant, owner, subject, plugin_id, collection, key, data, created_at, updated_at, row_id) VALUES (?, 'org', '', ?, 'operation', ?, ?, ?, ?, ?)`, args: [ row.tenant, row.pluginId, `${row.integration}.${row.toolName}`, JSON.stringify({ integration: row.integration, toolName: row.toolName, binding: { method: "get", pathTemplate: "/items" }, }), now, now, `storage-${row.pluginId}-${row.integration}-${row.toolName}`, ], }); const insertTool = ( client: SqliteDataMigrationClient, row: { readonly tenant: string; readonly pluginId: string; readonly integration: string; },) => client.execute({ sql: `INSERT INTO tool (tenant, owner, subject, integration, connection, plugin_id, name, description, input_schema, output_schema, annotations, created_at, updated_at, row_id) VALUES (?, 'org', '', ?, 'default', ?, 'items.list', 'List items', NULL, NULL, NULL, ?, ?, ?)`, args: [row.tenant, row.integration, row.pluginId, now, now, `tool-${row.integration}`], }); const insertDefinition = ( client: SqliteDataMigrationClient, row: { readonly tenant: string; readonly pluginId: string; readonly integration: string; },) => client.execute({ sql: `INSERT INTO definition (tenant, owner, subject, integration, connection, plugin_id, name, schema, created_at, row_id) VALUES (?, 'org', '', ?, 'default', ?, 'Item', '{}', ?, ?)`, args: [row.tenant, row.integration, row.pluginId, now, `definition-${row.integration}`], }); describe("runSqliteGoogleOpenApiOwnershipMigration", () => { it.effect("skips malformed legacy integration config rows", () => Effect.gen(function* () { const db = yield* Effect.promise(() => createSqliteTestFumaDb({ tables: collectTables() })); yield* Effect.promise(() => insertIntegrationRawConfig(db.client, { rowId: "malformed-row", tenant: "org_1", slug: "broken", pluginId: "openapi", config: "", }), ); expect(yield* runSqliteGoogleOpenApiOwnershipMigration(db.client)).toBe(0); const integrations = yield* Effect.promise(() => db.client.execute("SELECT slug, plugin_id, config FROM integration"), ); expect(integrations.rows).toEqual([{ slug: "broken", plugin_id: "openapi", config: "" }]); yield* Effect.promise(() => db.close()); }), ); it.effect("moves OpenAPI-owned Google bundle rows to the Google plugin", () => Effect.gen(function* () { const db = yield* Effect.promise(() => createSqliteTestFumaDb({ tables: collectTables() })); const client = db.client; yield* Effect.promise(() => insertIntegration(client, { rowId: "google-row", tenant: "org_1", slug: "google", pluginId: "openapi", config: { specHash: "googlehash", googleDiscoveryUrls: ["https://www.googleapis.com/discovery/v1/apis/calendar/v3/rest"], }, }), ); yield* Effect.promise(() => insertIntegration(client, { rowId: "stripe-row", tenant: "org_1", slug: "stripe", pluginId: "openapi", config: { specHash: "stripehash", specUrl: "https://stripe.example/openapi.json", }, }), ); yield* Effect.promise(() => insertBlob(client, { namespace: "o:org_1/openapi", key: "spec/googlehash", value: "google spec", }), ); yield* Effect.promise(() => insertBlob(client, { namespace: "o:org_1/openapi", key: "spec/stripehash", value: "stripe spec", }), ); for (const integration of ["google", "stripe"] as const) { yield* Effect.promise(() => insertOperationStorage(client, { tenant: "org_1", pluginId: "openapi", integration, toolName: "items.list", }), ); yield* Effect.promise(() => insertTool(client, { tenant: "org_1", pluginId: "openapi", integration, }), ); yield* Effect.promise(() => insertDefinition(client, { tenant: "org_1", pluginId: "openapi", integration, }), ); } expect(yield* runSqliteGoogleOpenApiOwnershipMigration(client)).toBe(1); expect(yield* runSqliteGoogleOpenApiOwnershipMigration(client)).toBe(0); const integrations = yield* Effect.promise(() => client.execute("SELECT slug, plugin_id FROM integration ORDER BY slug"), ); expect(integrations.rows).toEqual([ { slug: "google", plugin_id: "google" }, { slug: "stripe", plugin_id: "openapi" }, ]); const googleBlob = yield* Effect.promise(() => client.execute({ sql: "SELECT value FROM blob WHERE id = ?", args: [JSON.stringify(["o:org_1/google", "spec/googlehash"])], }), ); expect(googleBlob.rows).toEqual([{ value: "google spec" }]); const openApiBlobs = yield* Effect.promise(() => client.execute("SELECT key FROM blob WHERE namespace = 'o:org_1/openapi' ORDER BY key"), ); expect(openApiBlobs.rows).toEqual([{ key: "spec/googlehash" }, { key: "spec/stripehash" }]); const storage = yield* Effect.promise(() => client.execute( "SELECT plugin_id, key FROM plugin_storage WHERE collection = 'operation' ORDER BY plugin_id, key", ), ); expect(storage.rows).toEqual([ { plugin_id: "google", key: "google.items.list" }, { plugin_id: "openapi", key: "stripe.items.list" }, ]); const tools = yield* Effect.promise(() => client.execute("SELECT integration, plugin_id FROM tool ORDER BY integration"), ); expect(tools.rows).toEqual([ { integration: "google", plugin_id: "google" }, { integration: "stripe", plugin_id: "openapi" }, ]); const definitions = yield* Effect.promise(() => client.execute("SELECT integration, plugin_id FROM definition ORDER BY integration"), ); expect(definitions.rows).toEqual([ { integration: "google", plugin_id: "google" }, { integration: "stripe", plugin_id: "openapi" }, ]); yield* Effect.promise(() => db.close()); }), );});
import { describe, expect, it } from "@effect/vitest";
import { Effect } from "effect";
import { collectTables } from "@executor-js/sdk";
import { createSqliteTestFumaDb } from "@executor-js/sdk/testing";
import type { SqliteDataMigrationClient } from "@executor-js/sdk/core";
import { runSqliteGoogleOpenApiOwnershipMigration } from "./openapi-ownership-migration";
const now = 1_780_000_000_000;
const insertIntegration = (
client: SqliteDataMigrationClient,
row: {
readonly rowId: string;
readonly tenant: string;
readonly slug: string;
readonly pluginId: string;
readonly config: unknown;
},
) =>
client.execute({
sql: `INSERT INTO integration
(row_id, tenant, slug, plugin_id, name, description, config, can_remove, can_refresh, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, 1, 1, ?, ?)`,
args: [
row.rowId,
row.tenant,
row.slug,
row.pluginId,
row.slug,
row.slug,
JSON.stringify(row.config),
now,
now,
],
});
const insertIntegrationRawConfig = (
client: SqliteDataMigrationClient,
row: {
readonly rowId: string;
readonly tenant: string;
readonly slug: string;
readonly pluginId: string;
readonly config: string;
},
) =>
client.execute({
sql: `INSERT INTO integration
(row_id, tenant, slug, plugin_id, name, description, config, can_remove, can_refresh, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, 1, 1, ?, ?)`,
args: [row.rowId, row.tenant, row.slug, row.pluginId, row.slug, row.slug, row.config, now, now],
});
const insertBlob = (
client: SqliteDataMigrationClient,
row: {
readonly namespace: string;
readonly key: string;
readonly value: string;
},
) =>
client.execute({
sql: "INSERT INTO blob (namespace, key, value, row_id, id) VALUES (?, ?, ?, ?, ?)",
args: [
row.namespace,
row.key,
row.value,
`blob-${row.namespace}-${row.key}`,
JSON.stringify([row.namespace, row.key]),
],
});
const insertOperationStorage = (
client: SqliteDataMigrationClient,
row: {
readonly tenant: string;
readonly pluginId: string;
readonly integration: string;
readonly toolName: string;
},
) =>
client.execute({
sql: `INSERT INTO plugin_storage
(tenant, owner, subject, plugin_id, collection, key, data, created_at, updated_at, row_id)
VALUES (?, 'org', '', ?, 'operation', ?, ?, ?, ?, ?)`,
args: [
row.tenant,
row.pluginId,
`${row.integration}.${row.toolName}`,
JSON.stringify({
integration: row.integration,
toolName: row.toolName,
binding: { method: "get", pathTemplate: "/items" },
}),
now,
now,
`storage-${row.pluginId}-${row.integration}-${row.toolName}`,
],
});
const insertTool = (
client: SqliteDataMigrationClient,
row: {
readonly tenant: string;
readonly pluginId: string;
readonly integration: string;
},
) =>
client.execute({
sql: `INSERT INTO tool
(tenant, owner, subject, integration, connection, plugin_id, name, description, input_schema, output_schema, annotations, created_at, updated_at, row_id)
VALUES (?, 'org', '', ?, 'default', ?, 'items.list', 'List items', NULL, NULL, NULL, ?, ?, ?)`,
args: [row.tenant, row.integration, row.pluginId, now, now, `tool-${row.integration}`],
});
const insertDefinition = (
client: SqliteDataMigrationClient,
row: {
readonly tenant: string;
readonly pluginId: string;
readonly integration: string;
},
) =>
client.execute({
sql: `INSERT INTO definition
(tenant, owner, subject, integration, connection, plugin_id, name, schema, created_at, row_id)
VALUES (?, 'org', '', ?, 'default', ?, 'Item', '{}', ?, ?)`,
args: [row.tenant, row.integration, row.pluginId, now, `definition-${row.integration}`],
});
describe("runSqliteGoogleOpenApiOwnershipMigration", () => {
it.effect("skips malformed legacy integration config rows", () =>
Effect.gen(function* () {
const db = yield* Effect.promise(() => createSqliteTestFumaDb({ tables: collectTables() }));
yield* Effect.promise(() =>
insertIntegrationRawConfig(db.client, {
rowId: "malformed-row",
tenant: "org_1",
slug: "broken",
pluginId: "openapi",
config: "",
}),
);
expect(yield* runSqliteGoogleOpenApiOwnershipMigration(db.client)).toBe(0);
const integrations = yield* Effect.promise(() =>
db.client.execute("SELECT slug, plugin_id, config FROM integration"),
);
expect(integrations.rows).toEqual([{ slug: "broken", plugin_id: "openapi", config: "" }]);
yield* Effect.promise(() => db.close());
}),
);
it.effect("moves OpenAPI-owned Google bundle rows to the Google plugin", () =>
Effect.gen(function* () {
const db = yield* Effect.promise(() => createSqliteTestFumaDb({ tables: collectTables() }));
const client = db.client;
yield* Effect.promise(() =>
insertIntegration(client, {
rowId: "google-row",
tenant: "org_1",
slug: "google",
pluginId: "openapi",
config: {
specHash: "googlehash",
googleDiscoveryUrls: ["https://www.googleapis.com/discovery/v1/apis/calendar/v3/rest"],
},
}),
);
yield* Effect.promise(() =>
insertIntegration(client, {
rowId: "stripe-row",
tenant: "org_1",
slug: "stripe",
pluginId: "openapi",
config: {
specHash: "stripehash",
specUrl: "https://stripe.example/openapi.json",
},
}),
);
yield* Effect.promise(() =>
insertBlob(client, {
namespace: "o:org_1/openapi",
key: "spec/googlehash",
value: "google spec",
}),
);
yield* Effect.promise(() =>
insertBlob(client, {
namespace: "o:org_1/openapi",
key: "spec/stripehash",
value: "stripe spec",
}),
);
for (const integration of ["google", "stripe"] as const) {
yield* Effect.promise(() =>
insertOperationStorage(client, {
tenant: "org_1",
pluginId: "openapi",
integration,
toolName: "items.list",
}),
);
yield* Effect.promise(() =>
insertTool(client, {
tenant: "org_1",
pluginId: "openapi",
integration,
}),
);
yield* Effect.promise(() =>
insertDefinition(client, {
tenant: "org_1",
pluginId: "openapi",
integration,
}),
);
}
expect(yield* runSqliteGoogleOpenApiOwnershipMigration(client)).toBe(1);
expect(yield* runSqliteGoogleOpenApiOwnershipMigration(client)).toBe(0);
const integrations = yield* Effect.promise(() =>
client.execute("SELECT slug, plugin_id FROM integration ORDER BY slug"),
);
expect(integrations.rows).toEqual([
{ slug: "google", plugin_id: "google" },
{ slug: "stripe", plugin_id: "openapi" },
]);
const googleBlob = yield* Effect.promise(() =>
client.execute({
sql: "SELECT value FROM blob WHERE id = ?",
args: [JSON.stringify(["o:org_1/google", "spec/googlehash"])],
}),
);
expect(googleBlob.rows).toEqual([{ value: "google spec" }]);
const openApiBlobs = yield* Effect.promise(() =>
client.execute("SELECT key FROM blob WHERE namespace = 'o:org_1/openapi' ORDER BY key"),
);
expect(openApiBlobs.rows).toEqual([{ key: "spec/googlehash" }, { key: "spec/stripehash" }]);
const storage = yield* Effect.promise(() =>
client.execute(
"SELECT plugin_id, key FROM plugin_storage WHERE collection = 'operation' ORDER BY plugin_id, key",
),
);
expect(storage.rows).toEqual([
{ plugin_id: "google", key: "google.items.list" },
{ plugin_id: "openapi", key: "stripe.items.list" },
]);
const tools = yield* Effect.promise(() =>
client.execute("SELECT integration, plugin_id FROM tool ORDER BY integration"),
);
expect(tools.rows).toEqual([
{ integration: "google", plugin_id: "google" },
{ integration: "stripe", plugin_id: "openapi" },
]);
const definitions = yield* Effect.promise(() =>
client.execute("SELECT integration, plugin_id FROM definition ORDER BY integration"),
);
expect(definitions.rows).toEqual([
{ integration: "google", plugin_id: "google" },
{ integration: "stripe", plugin_id: "openapi" },
]);
yield* Effect.promise(() => db.close());
}),
);
});