import { describe, it, expect } from "@effect/vitest";
import { Effect, Schema } from "effect";
import * as NodeFileSystem from "@effect/platform-node/NodeFileSystem";
import { FileSystem } from "effect";
import { join } from "node:path";
import { ExecutorFileConfig } from "./schema";
import { ConfigParseError, loadConfig } from "./load";
import {
addIntegrationToConfig,
removeIntegrationFromConfig,
writeConfig,
addSecretToConfig,
removeSecretFromConfig,
} from "./write";
const decodeExecutorFileConfig = Schema.decodeUnknownSync(ExecutorFileConfig);
const withTmpDir = (fn: (dir: string) => Effect.Effect) =>
Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const dir = yield* fs.makeTempDirectoryScoped({
prefix: "executor-config-test-",
});
return yield* fn(dir);
}).pipe(Effect.scoped, Effect.provide(NodeFileSystem.layer));
describe("ExecutorFileConfig schema", () => {
it("decodes a minimal config", () => {
const raw = { integrations: [] };
const result = decodeExecutorFileConfig(raw);
expect(result.integrations).toEqual([]);
});
it("decodes a full config with all integration types", () => {
const raw = {
name: "test",
integrations: [
{
kind: "openapi",
spec: "https://example.com/openapi.json",
namespace: "example",
headers: {
Authorization: { value: "secret-public-ref:my-token", prefix: "Bearer " },
},
},
{
kind: "graphql",
endpoint: "https://example.com/graphql",
namespace: "gql",
},
{
kind: "mcp",
transport: "remote",
name: "Remote MCP",
endpoint: "https://mcp.example.com/sse",
},
{
kind: "mcp",
transport: "stdio",
name: "Local MCP",
command: "npx",
args: ["-y", "some-server"],
},
],
secrets: {
"my-token": {
name: "My Token",
provider: "keychain",
purpose: "Auth",
},
},
};
const result = decodeExecutorFileConfig(raw);
expect(result.integrations).toHaveLength(4);
expect(result.name).toBe("test");
expect(result.secrets!["my-token"]!.name).toBe("My Token");
});
it("rejects invalid integration kind", () => {
const raw = {
integrations: [{ kind: "invalid", endpoint: "http://example.com" }],
};
expect(() => decodeExecutorFileConfig(raw)).toThrow();
});
});
describe("loadConfig", () => {
it.effect("returns null for missing file", () =>
withTmpDir((dir) =>
Effect.gen(function* () {
const result = yield* loadConfig(join(dir, "nonexistent.jsonc"));
expect(result).toBeNull();
}),
),
);
it.effect("loads a valid JSONC file with comments", () =>
withTmpDir((dir) =>
Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const path = join(dir, "executor.jsonc");
yield* fs.writeFileString(
path,
`{
// This is a comment
"name": "test",
"integrations": [
{
"kind": "openapi",
"spec": "https://example.com/spec.json"
}
]
}`,
);
const result = yield* loadConfig(path);
expect(result).not.toBeNull();
expect(result!.name).toBe("test");
expect(result!.integrations).toHaveLength(1);
}),
),
);
it.effect("fails on invalid JSON", () =>
withTmpDir((dir) =>
Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const path = join(dir, "executor.jsonc");
yield* fs.writeFileString(path, "{ invalid json }");
const result = yield* loadConfig(path).pipe(
Effect.catchTag("ConfigParseError", (error) => Effect.succeed(error)),
);
expect(result).toBeInstanceOf(ConfigParseError);
}),
),
);
});
describe("write operations", () => {
it.effect("addIntegrationToConfig creates file and adds integration", () =>
withTmpDir((dir) =>
Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const path = join(dir, "executor.jsonc");
yield* addIntegrationToConfig(path, {
kind: "openapi",
spec: "https://example.com/spec.json",
namespace: "example",
});
const content = yield* fs.readFileString(path);
expect(content).toContain("openapi");
expect(content).toContain("example");
const config = yield* loadConfig(path);
expect(config!.integrations).toHaveLength(1);
expect(config!.integrations![0]!.kind).toBe("openapi");
}),
),
);
it.effect("addIntegrationToConfig appends to existing integrations", () =>
withTmpDir((dir) =>
Effect.gen(function* () {
const path = join(dir, "executor.jsonc");
yield* addIntegrationToConfig(path, {
kind: "openapi",
spec: "https://example.com/spec.json",
namespace: "first",
});
yield* addIntegrationToConfig(path, {
kind: "graphql",
endpoint: "https://example.com/graphql",
namespace: "second",
});
const config = yield* loadConfig(path);
expect(config!.integrations).toHaveLength(2);
expect(config!.integrations![0]!.kind).toBe("openapi");
expect(config!.integrations![1]!.kind).toBe("graphql");
}),
),
);
it.effect("addIntegrationToConfig preserves comments", () =>
withTmpDir((dir) =>
Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const path = join(dir, "executor.jsonc");
yield* fs.writeFileString(
path,
`{
// My project config
"name": "test",
"integrations": [
{
"kind": "openapi",
"spec": "https://example.com/spec.json",
"namespace": "first"
}
]
}`,
);
yield* addIntegrationToConfig(path, {
kind: "graphql",
endpoint: "https://example.com/graphql",
namespace: "second",
});
const content = yield* fs.readFileString(path);
expect(content).toContain("// My project config");
expect(content).toContain("graphql");
}),
),
);
it.effect("addIntegrationToConfig replaces existing integration with same namespace", () =>
withTmpDir((dir) =>
Effect.gen(function* () {
const path = join(dir, "executor.jsonc");
yield* addIntegrationToConfig(path, {
kind: "openapi",
spec: "https://example.com/v1.json",
namespace: "example",
});
// Add again with same namespace but different spec
yield* addIntegrationToConfig(path, {
kind: "openapi",
spec: "https://example.com/v2.json",
namespace: "example",
});
const config = yield* loadConfig(path);
// Should have 1, not 2
expect(config!.integrations).toHaveLength(1);
expect((config!.integrations![0] as { spec: string }).spec).toBe(
"https://example.com/v2.json",
);
}),
),
);
it.effect("removeIntegrationFromConfig removes by namespace", () =>
withTmpDir((dir) =>
Effect.gen(function* () {
const path = join(dir, "executor.jsonc");
yield* writeConfig(path, {
integrations: [
{
kind: "openapi",
spec: "https://example.com/spec.json",
namespace: "keep",
},
{
kind: "graphql",
endpoint: "https://example.com/graphql",
namespace: "remove-me",
},
],
});
yield* removeIntegrationFromConfig(path, "remove-me");
const config = yield* loadConfig(path);
expect(config!.integrations).toHaveLength(1);
expect(config!.integrations![0]!.kind).toBe("openapi");
}),
),
);
it.effect("addSecretToConfig and removeSecretFromConfig", () =>
withTmpDir((dir) =>
Effect.gen(function* () {
const path = join(dir, "executor.jsonc");
yield* writeConfig(path, { integrations: [] });
yield* addSecretToConfig(path, "my-token", {
name: "My Token",
provider: "keychain",
});
let config = yield* loadConfig(path);
expect(config!.secrets!["my-token"]!.name).toBe("My Token");
yield* removeSecretFromConfig(path, "my-token");
config = yield* loadConfig(path);
expect(config!.secrets?.["my-token"]).toBeUndefined();
}),
),
);
});