Files

425 lines
16 KiB
JavaScript
Raw Permalink Normal View History

const { test } = require("node:test");
const assert = require("node:assert/strict");
const { renderMockResponse, createMockState } = require("../providers/_mock");
const TEMPLATES = require("../mocks/tool-responses.json");
// ---------------------------------------------------------------------------
// createMockState
// ---------------------------------------------------------------------------
test("createMockState returns fresh empty state each call", () => {
const a = createMockState();
const b = createMockState();
assert.deepEqual(a, { configs: {}, flags: {} });
a.configs["x"] = {};
assert.deepEqual(b.configs, {}); // independent
});
// ---------------------------------------------------------------------------
// setup-ai-config (stateful write)
// ---------------------------------------------------------------------------
test("setup-ai-config stores config in state and returns it", () => {
const state = createMockState();
const result = renderMockResponse(TEMPLATES["setup-ai-config"], {
key: "my-bot",
name: "My Bot",
mode: "agent",
variationKey: "v1",
variationName: "V1",
modelConfigKey: "Anthropic.claude-sonnet-4-6",
modelName: "claude-sonnet-4-6",
instructions: "Be helpful.",
}, "setup-ai-config", state);
assert.equal(result.key, "my-bot");
assert.equal(result.mode, "agent");
assert.equal(state.configs["my-bot"].name, "My Bot");
assert.equal(state.configs["my-bot"].variations.length, 1);
assert.equal(state.configs["my-bot"].variations[0].instructions, "Be helpful.");
});
// ---------------------------------------------------------------------------
// create-ai-config (stateful write)
// ---------------------------------------------------------------------------
test("create-ai-config stores config with empty variations", () => {
const state = createMockState();
renderMockResponse(TEMPLATES["create-ai-config"], {
key: "new-config",
name: "New Config",
mode: "completion",
}, "create-ai-config", state);
assert.ok(state.configs["new-config"]);
assert.deepEqual(state.configs["new-config"].variations, []);
});
// ---------------------------------------------------------------------------
// create-ai-config-variation
// ---------------------------------------------------------------------------
test("create-ai-config-variation appends to existing config", () => {
const state = createMockState();
renderMockResponse(TEMPLATES["create-ai-config"], {
key: "my-config", name: "My Config", mode: "agent",
}, "create-ai-config", state);
renderMockResponse(TEMPLATES["create-ai-config-variation"], {
configKey: "my-config",
key: "v1",
name: "Variation 1",
modelConfigKey: "OpenAI.gpt-4o",
modelName: "gpt-4o",
instructions: "Help the user.",
}, "create-ai-config-variation", state);
assert.equal(state.configs["my-config"].variations.length, 1);
assert.equal(state.configs["my-config"].variations[0].key, "v1");
});
test("create-ai-config-variation creates stub when parent config missing", () => {
const state = createMockState();
renderMockResponse(TEMPLATES["create-ai-config-variation"], {
configKey: "orphan-config",
key: "v1",
name: "V1",
modelConfigKey: "OpenAI.gpt-4o",
modelName: "gpt-4o",
}, "create-ai-config-variation", state);
assert.ok(state.configs["orphan-config"], "stub config should be created");
assert.equal(state.configs["orphan-config"].variations.length, 1);
});
// ---------------------------------------------------------------------------
// update-ai-config-variation
// ---------------------------------------------------------------------------
test("update-ai-config-variation patches only variation fields", () => {
const state = createMockState();
renderMockResponse(TEMPLATES["setup-ai-config"], {
key: "bot",
name: "Bot",
mode: "agent",
variationKey: "default",
variationName: "Default",
modelConfigKey: "OpenAI.gpt-4o",
modelName: "gpt-4o",
instructions: "Original.",
}, "setup-ai-config", state);
renderMockResponse(TEMPLATES["update-ai-config-variation"], {
configKey: "bot",
variationKey: "default",
modelConfigKey: "OpenAI.gpt-4o-mini",
modelName: "gpt-4o-mini",
}, "update-ai-config-variation", state);
const v = state.configs["bot"].variations[0];
assert.equal(v.modelConfigKey, "OpenAI.gpt-4o-mini");
assert.equal(v.modelName, "gpt-4o-mini");
assert.equal(v.instructions, "Original."); // unchanged
// input-only fields must NOT bleed into the variation
assert.ok(!("configKey" in v), "configKey must not appear on variation");
assert.ok(!("variationKey" in v), "variationKey must not appear on variation");
});
test("update-ai-config-variation is a no-op when config not in state", () => {
const state = createMockState();
// Should not throw
assert.doesNotThrow(() =>
renderMockResponse(TEMPLATES["update-ai-config-variation"], {
configKey: "nonexistent",
variationKey: "default",
modelConfigKey: "OpenAI.gpt-4o-mini",
}, "update-ai-config-variation", state)
);
assert.deepEqual(state.configs, {});
});
// ---------------------------------------------------------------------------
// get-ai-config (stateful read)
// ---------------------------------------------------------------------------
test("get-ai-config returns state config when present", () => {
const state = createMockState();
renderMockResponse(TEMPLATES["setup-ai-config"], {
key: "bot", name: "Bot", mode: "agent",
variationKey: "default", variationName: "Default",
modelConfigKey: "OpenAI.gpt-4o", modelName: "gpt-4o",
}, "setup-ai-config", state);
const result = renderMockResponse(TEMPLATES["get-ai-config"], {
configKey: "bot",
}, "get-ai-config", state);
assert.equal(result.key, "bot");
assert.equal(result.name, "Bot");
});
test("get-ai-config loads SEED_CONFIGS when key matches", () => {
const state = createMockState();
const result = renderMockResponse(TEMPLATES["get-ai-config"], {
configKey: "support-chatbot",
}, "get-ai-config", state);
assert.equal(result.key, "support-chatbot");
assert.ok(result.variations.length > 0);
// seed should now be in state for subsequent calls
assert.ok(state.configs["support-chatbot"]);
});
test("get-ai-config falls back to template for unknown key", () => {
const state = createMockState();
const result = renderMockResponse(TEMPLATES["get-ai-config"], {
configKey: "mystery-config",
}, "get-ai-config", state);
assert.equal(result.key, "mystery-config"); // template substitution
});
// ---------------------------------------------------------------------------
// list-ai-configs (stateful merge)
// ---------------------------------------------------------------------------
test("list-ai-configs returns static list when state is empty", () => {
const state = createMockState();
const result = renderMockResponse(TEMPLATES["list-ai-configs"], {}, "list-ai-configs", state);
assert.ok(result.configs.length > 0);
});
test("list-ai-configs merges state configs, state wins for duplicate keys", () => {
const state = createMockState();
// write over a seed key
renderMockResponse(TEMPLATES["setup-ai-config"], {
key: "support-chatbot",
name: "My Custom Chatbot",
mode: "completion",
variationKey: "default",
variationName: "Default",
modelConfigKey: "OpenAI.gpt-4o",
modelName: "gpt-4o",
}, "setup-ai-config", state);
const result = renderMockResponse(TEMPLATES["list-ai-configs"], {}, "list-ai-configs", state);
const entry = result.configs.find((c) => c.key === "support-chatbot");
assert.ok(entry, "support-chatbot should appear in list");
assert.equal(entry.name, "My Custom Chatbot", "state entry should win over static template");
// should appear exactly once
const count = result.configs.filter((c) => c.key === "support-chatbot").length;
assert.equal(count, 1);
});
test("list-ai-configs adds newly created configs not in static list", () => {
const state = createMockState();
renderMockResponse(TEMPLATES["create-ai-config"], {
key: "brand-new", name: "Brand New", mode: "agent",
}, "create-ai-config", state);
const result = renderMockResponse(TEMPLATES["list-ai-configs"], {}, "list-ai-configs", state);
const entry = result.configs.find((c) => c.key === "brand-new");
assert.ok(entry);
assert.equal(result.totalCount, result.configs.length);
});
// ---------------------------------------------------------------------------
// get-ai-config-health
// ---------------------------------------------------------------------------
test("get-ai-config-health reflects state when config is present", () => {
const state = createMockState();
renderMockResponse(TEMPLATES["setup-ai-config"], {
key: "bot", name: "Bot", mode: "agent",
variationKey: "default", variationName: "Default",
modelConfigKey: "OpenAI.gpt-4o", modelName: "gpt-4o",
instructions: "Be helpful.",
}, "setup-ai-config", state);
const health = renderMockResponse(TEMPLATES["get-ai-config-health"], {
configKey: "bot",
}, "get-ai-config-health", state);
assert.equal(health.key, "bot");
assert.equal(health.health, "healthy");
assert.equal(health.variationsCount, 1);
assert.equal(health.variations[0].hasModel, true);
assert.equal(health.variations[0].hasPrompts, true);
});
test("get-ai-config-health falls back to template for unknown key", () => {
const state = createMockState();
const health = renderMockResponse(TEMPLATES["get-ai-config-health"], {
configKey: "unknown-config",
}, "get-ai-config-health", state);
// template fallback — key should be substituted
assert.equal(health.key, "unknown-config");
});
// ---------------------------------------------------------------------------
// clone-ai-config-variation
// ---------------------------------------------------------------------------
test("clone-ai-config-variation appends cloned variation to state", () => {
const state = createMockState();
renderMockResponse(TEMPLATES["setup-ai-config"], {
key: "my-bot", name: "My Bot", mode: "agent",
variationKey: "default", variationName: "Default",
modelConfigKey: "OpenAI.gpt-4o", modelName: "gpt-4o",
instructions: "Original instructions.",
}, "setup-ai-config", state);
const result = renderMockResponse(TEMPLATES["clone-ai-config-variation"], {
configKey: "my-bot",
sourceVariationKey: "default",
key: "cost-test",
name: "Cost Test",
modelConfigKey: "OpenAI.gpt-4o-mini",
modelName: "gpt-4o-mini",
}, "clone-ai-config-variation", state);
assert.equal(result.created.key, "cost-test");
assert.equal(result.created.modelConfigKey, "OpenAI.gpt-4o-mini");
assert.equal(result.source.key, "default");
assert.equal(state.configs["my-bot"].variations.length, 2);
assert.equal(state.configs["my-bot"].variations[1].key, "cost-test");
});
test("clone-ai-config-variation reflects in subsequent get-ai-config", () => {
const state = createMockState();
renderMockResponse(TEMPLATES["setup-ai-config"], {
key: "bot", name: "Bot", mode: "agent",
variationKey: "default", variationName: "Default",
modelConfigKey: "OpenAI.gpt-4o", modelName: "gpt-4o",
instructions: "Help.",
}, "setup-ai-config", state);
renderMockResponse(TEMPLATES["clone-ai-config-variation"], {
configKey: "bot",
sourceVariationKey: "default",
key: "new-var",
name: "New Variation",
}, "clone-ai-config-variation", state);
const cfg = renderMockResponse(TEMPLATES["get-ai-config"], {
configKey: "bot",
}, "get-ai-config", state);
assert.equal(cfg.variations.length, 2);
assert.ok(cfg.variations.find((v) => v.key === "new-var"), "cloned variation should appear in get-ai-config");
});
test("clone-ai-config-variation works with SEED_CONFIGS source", () => {
const state = createMockState();
const result = renderMockResponse(TEMPLATES["clone-ai-config-variation"], {
configKey: "support-chatbot",
sourceVariationKey: "default",
key: "experimental",
name: "Experimental",
}, "clone-ai-config-variation", state);
assert.equal(result.created.key, "experimental");
assert.equal(result.source.key, "default");
assert.ok(state.configs["support-chatbot"], "seed config should be loaded into state");
assert.equal(state.configs["support-chatbot"].variations.length, 2);
});
test("clone-ai-config-variation falls back to template when source variation not found", () => {
const state = createMockState();
renderMockResponse(TEMPLATES["create-ai-config"], {
key: "my-config", name: "My Config", mode: "agent",
}, "create-ai-config", state);
// source variation doesn't exist — should fall back to template render, not throw
assert.doesNotThrow(() =>
renderMockResponse(TEMPLATES["clone-ai-config-variation"], {
configKey: "my-config",
sourceVariationKey: "nonexistent",
key: "new-var",
name: "New",
}, "clone-ai-config-variation", state)
);
});
// ---------------------------------------------------------------------------
// create-flag / get-flag (flag state)
// ---------------------------------------------------------------------------
test("create-flag stores flag in state", () => {
const state = createMockState();
renderMockResponse(TEMPLATES["create-flag"], {
key: "my-new-flag",
name: "My New Flag",
}, "create-flag", state);
assert.ok(state.flags["my-new-flag"]);
});
test("get-flag returns state flag when present", () => {
const state = createMockState();
renderMockResponse(TEMPLATES["create-flag"], {
key: "my-flag",
name: "My Flag",
}, "create-flag", state);
const result = renderMockResponse(TEMPLATES["get-flag"], {
flagKey: "my-flag",
}, "get-flag", state);
assert.equal(result.key, "my-flag");
});
test("get-flag falls back to template substitution for unknown flag", () => {
const state = createMockState();
const result = renderMockResponse(TEMPLATES["get-flag"], {
flagKey: "unknown-flag",
flagName: "Unknown Flag",
}, "get-flag", state);
assert.equal(result.key, "unknown-flag");
});
feat: add launchdarkly-flag-drift skill (#119) * feat: add launchdarkly-flag-drift skill Detect and reconcile drift between a feature flag's in-code SDK fallback default and its LaunchDarkly default rule (fallthrough), updating only the default argument without removing the flag or changing its evaluation. Co-authored-by: Cursor <cursoragent@cursor.com> * test(evals): add eval suite for launchdarkly-flag-drift Add a promptfoo suite covering drift reconciliation, the no-drift (no code change, no PR) case, and a registry-declared default, asserting the agent resolves the fallthrough via get-flag and never mutates the flag. Register the suite in the manifest and add npm scripts. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(flag-drift): check fallthrough across all critical environments Address review feedback that the skill only reconciled the in-code default against a single environment. The in-code fallback default is a single value that must stand in for every environment the build serves, so the fallthrough is now resolved in each critical environment. When critical environments agree, that shared value is the expected default and reconciliation proceeds as before. When they disagree (e.g. EU serves true but Federal serves false), the skill surfaces the per-environment divergence and confirms which environment is authoritative instead of silently reconciling to one. Updates the prerequisites, workflow, edge cases, summary fields, and PR template accordingly. Co-authored-by: Ramon Niebla <nieblara@users.noreply.github.com> * test(evals): cover cross-environment fallthrough divergence for flag-drift Add a divergence eval where one build serves both production and federal environments that disagree on the fallthrough. The mock now returns the opposite default (variation 0) for any /federal/i environment key, so the skill must query each critical environment and surface the divergence rather than blindly reconciling. Adds mock unit tests for the hook. Co-authored-by: Ramon Niebla <nieblara@users.noreply.github.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Ramon Niebla <nieblara@users.noreply.github.com>
2026-07-28 12:30:54 -07:00
test("get-flag resolves the template fallthrough (variation 1) for a non-federal env", () => {
const state = createMockState();
const result = renderMockResponse(TEMPLATES["get-flag"], {
flagKey: "billing-v2",
environmentKey: "production",
}, "get-flag", state);
assert.equal(result.environment.fallthrough.variation, 1); // -> false
});
test("get-flag returns the opposite fallthrough (variation 0) for a federal env", () => {
const state = createMockState();
const result = renderMockResponse(TEMPLATES["get-flag"], {
flagKey: "billing-v2",
environmentKey: "federal",
}, "get-flag", state);
assert.equal(result.environment.fallthrough.variation, 0); // -> true (diverges)
});
// ---------------------------------------------------------------------------
// stateless template rendering ({{placeholder}} substitution)
// ---------------------------------------------------------------------------
test("template substitution fills {{configKey}} and {{configName}}", () => {
const state = createMockState();
const result = renderMockResponse(TEMPLATES["create-ai-config"], {
key: "my-config",
name: "My Config",
mode: "completion",
}, "create-ai-config", state);
// result from stateful handler, not template — check state
assert.equal(state.configs["my-config"].key, "my-config");
});
test("update-ai-config uses template substitution", () => {
const state = createMockState();
const result = renderMockResponse(TEMPLATES["update-ai-config"], {
configKey: "bot",
configName: "Bot",
}, "update-ai-config", state);
assert.equal(result.key, "bot");
assert.equal(result.name, "Bot");
});