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 = <A, E>(fn: (dir: string) => Effect.Effect<A, E, FileSystem.FileSystem>) => 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(); }), ), );});
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 = <A, E>(fn: (dir: string) => Effect.Effect<A, E, FileSystem.FileSystem>) =>
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();
}),
),
);
});