lib/capabilities.test.ts
Rama main · réplica de lectura del repositorio original.
import { describe, expect, it } from "vitest";
import { resolveCapabilities } from "@/lib/capabilities";
const core = {
MINDDY_PUBLIC_SUPABASE_URL: "http://127.0.0.1:54321",
MINDDY_PUBLIC_SUPABASE_ANON_KEY: "anon",
SUPABASE_SERVICE_ROLE_KEY: "service-role",
};
describe("resolveCapabilities", () => {
it("keeps only Supabase and Storage in the minimal configuration", () => {
const capabilities = resolveCapabilities(core);
expect(capabilities.supabase.state).toBe("ready");
expect(capabilities.storage.state).toBe("ready");
for (const [id, value] of Object.entries(capabilities)) {
if (id === "supabase" || id === "storage") continue;
expect(value.configured, `${id}: ${value.diagnostic}`).toBe(false);
}
});
it("does not infer managed services from present secrets", () => {
const capabilities = resolveCapabilities({
...core,
OPENROUTER_API_KEY: "operator-key",
STRIPE_SECRET_KEY: "stripe-key",
});
expect(capabilities.managedAi.state).toBe("disabled");
expect(capabilities.managedBilling.state).toBe("disabled");
});
it("diagnoses each partial configuration with its missing variables", () => {
const capabilities = resolveCapabilities({
...core,
MINDDY_EDITION: "cloud",
MINDDY_MANAGED_AI: "1",
MINDDY_MANAGED_BILLING: "1",
EMAIL_PROVIDER: "resend",
GITHUB_APP_ID: "1",
GITLAB_OAUTH_CLIENT_ID: "1",
MINDDY_PUBLIC_VAPID_PUBLIC_KEY: "public",
APNS_TEAM_ID: "team",
});
for (const id of [
"managedAi",
"managedBilling",
"transactionalEmail",
] as const) {
expect(capabilities[id].state).toBe("incomplete");
expect(capabilities[id].missing.length).toBeGreaterThan(0);
}
for (const id of ["github", "gitlab", "webPush", "apns"] as const) {
expect(capabilities[id].configured).toBe(false);
expect(capabilities[id].missing.length).toBeGreaterThan(0);
}
});
it("requires an explicit choice before any Resend delivery", () => {
const capabilities = resolveCapabilities({
...core,
VERCEL: "1",
RESEND_API_KEY: "resend-key",
FEEDBACK_EMAIL_FROM: "feedback@example.test",
INVITATION_EMAIL_FROM: "invites@example.test",
});
expect(capabilities.transactionalEmail.configured).toBe(false);
});
it("exposes no code-execution capability, whatever legacy runner settings remain", () => {
const capabilities = resolveCapabilities({
...core,
AGENT_EXECUTION_BACKEND: "self-hosted",
AGENT_RUNNER_URL: "http://agent-runner:6464",
AGENT_RUNNER_SECRET: "runner-secret",
});
expect(Object.keys(capabilities)).not.toContain("agentExecution");
expect(Object.keys(capabilities)).not.toContain("vercelSandbox");
});
it("does not activate Cloud providers from deceptive host or Vercel metadata", () => {
const implicit = resolveCapabilities({
...core,
VERCEL: "1",
MINDDY_PUBLIC_APP_URL: "https://www.minddy.app",
VERCEL_PROJECT_PRODUCTION_URL: "minddy.app",
GITHUB_REF_NAME: "production",
OPENROUTER_API_KEY: "managed-key",
RESEND_API_KEY: "resend-key",
STRIPE_SECRET_KEY: "stripe-key",
STRIPE_WEBHOOK_SECRET: "webhook-key",
STRIPE_PRICE_ID_GO: "go",
STRIPE_PRICE_ID_PRO: "pro",
STRIPE_PRICE_ID_GO_YEARLY: "go-year",
STRIPE_PRICE_ID_PRO_YEARLY: "pro-year",
MINDDY_PUBLIC_VAPID_PUBLIC_KEY: "public",
VAPID_PRIVATE_KEY: "private",
APNS_TEAM_ID: "team",
APNS_KEY_ID: "key",
APNS_PRIVATE_KEY: "private",
MINDDY_PUBLIC_POSTHOG_KEY: "posthog-key",
});
for (const id of [
"managedBilling",
"managedAi",
"analytics",
"transactionalEmail",
"webPush",
"apns",
] as const) {
expect(implicit[id].configured, `${id}: ${implicit[id].diagnostic}`).toBe(false);
}
});
it("requires the Cloud edition before managed services can start", () => {
const cloud = resolveCapabilities({
...core,
MINDDY_EDITION: "cloud",
MINDDY_MANAGED_AI: "1",
MINDDY_MANAGED_BILLING: "1",
OPENROUTER_API_KEY: "managed-key",
STRIPE_SECRET_KEY: "stripe-key",
STRIPE_WEBHOOK_SECRET: "webhook-key",
STRIPE_PRICE_ID_GO: "go",
STRIPE_PRICE_ID_PRO: "pro",
STRIPE_PRICE_ID_GO_YEARLY: "go-year",
STRIPE_PRICE_ID_PRO_YEARLY: "pro-year",
OPENROUTER_MANAGEMENT_KEY: "management-key",
});
expect(cloud.managedAi.configured).toBe(true);
expect(cloud.managedBilling.configured).toBe(true);
const selfHosted = resolveCapabilities({
...core,
MINDDY_EDITION: "self-hosted",
MINDDY_MANAGED_AI: "1",
MINDDY_MANAGED_BILLING: "1",
OPENROUTER_API_KEY: "managed-key",
STRIPE_SECRET_KEY: "stripe-key",
});
expect(selfHosted.managedAi.configured).toBe(false);
expect(selfHosted.managedBilling.configured).toBe(false);
});
it("reports billed Cloud AI as incomplete without a management key", () => {
const capabilities = resolveCapabilities({
...core,
MINDDY_EDITION: "cloud",
MINDDY_MANAGED_AI: "1",
MINDDY_MANAGED_BILLING: "1",
OPENROUTER_API_KEY: "managed-key",
STRIPE_SECRET_KEY: "stripe-key",
STRIPE_WEBHOOK_SECRET: "webhook-key",
STRIPE_PRICE_ID_GO: "go",
STRIPE_PRICE_ID_PRO: "pro",
STRIPE_PRICE_ID_GO_YEARLY: "go-year",
STRIPE_PRICE_ID_PRO_YEARLY: "pro-year",
});
expect(capabilities.managedAi).toMatchObject({
state: "incomplete",
missing: ["OPENROUTER_MANAGEMENT_KEY"],
});
});
it("does not combine two partial PostHog pairs", () => {
const capabilities = resolveCapabilities({
...core,
POSTHOG_API_KEY: "server-key",
MINDDY_PUBLIC_POSTHOG_HOST: "https://analytics.example.test",
});
expect(capabilities.analytics.state).toBe("disabled");
expect(capabilities.analytics.configured).toBe(false);
});
it("does not declare Web Push ready with an unusable VAPID subject", () => {
const capabilities = resolveCapabilities({
...core,
MINDDY_PUBLIC_VAPID_PUBLIC_KEY: "public",
VAPID_PRIVATE_KEY: "private",
VAPID_SUBJECT: "push@example.test",
});
expect(capabilities.webPush.configured).toBe(false);
expect(capabilities.webPush.missing).toContain("VAPID_SUBJECT (mailto: or https:)");
});
it("classifies workable replacements outside Resend and the managed services", () => {
const capabilities = resolveCapabilities(core);
expect(capabilities.scheduler.requirement).toBe("replaceable");
expect(capabilities.transactionalEmail.requirement).toBe("replaceable");
expect(capabilities.managedAi.requirement).toBe("replaceable");
});
it("enables scheduler routes with their authentication secret", () => {
expect(resolveCapabilities(core).scheduler)
.toMatchObject({ state: "disabled", configured: false, missing: ["CRON_SECRET"] });
expect(resolveCapabilities({ ...core, CRON_SECRET: "secret" }).scheduler)
.toMatchObject({ state: "external", configured: true });
});
it("serves git providers through the managed forge relay only when fully configured", () => {
const relay = {
MINDDY_FORGE_RELAY_URL: "https://forge-relay.minddy.app",
MINDDY_FORGE_RELAY_INSTANCE_ID: "instance-id",
MINDDY_FORGE_RELAY_SECRET: "instance-secret",
GIT_STATE_SECRET: "state-secret",
// Relayed tokens are stored encrypted instance-side.
GIT_TOKEN_ENCRYPTION_SECRET: "x".repeat(32),
};
const capabilities = resolveCapabilities({ ...core, ...relay });
for (const id of ["github", "gitlab"] as const) {
expect(capabilities[id]).toMatchObject({
requirement: "replaceable",
state: "ready",
configured: true,
});
expect(capabilities[id].diagnostic).toContain("managed forge relay");
}
// Without usable token encryption neither relay path is ready: GitHub
// user grants and GitLab connection grants are both stored at handoff.
const withoutEncryption = resolveCapabilities({
...core,
...relay,
GIT_TOKEN_ENCRYPTION_SECRET: undefined,
});
for (const id of ["github", "gitlab"] as const) {
expect(withoutEncryption[id]).toMatchObject({ state: "incomplete" });
expect(withoutEncryption[id].missing[0]).toContain("GIT_TOKEN_ENCRYPTION_SECRET");
}
const weakEncryption = resolveCapabilities({
...core,
...relay,
GIT_TOKEN_ENCRYPTION_SECRET: "too-short",
});
expect(weakEncryption.github.state).toBe("incomplete");
expect(weakEncryption.gitlab.state).toBe("incomplete");
});
it("keeps the operator-owned app ahead of the relay and provisions automatically by default", () => {
const relay = {
MINDDY_FORGE_RELAY_URL: "https://forge-relay.minddy.app",
MINDDY_FORGE_RELAY_INSTANCE_ID: "instance-id",
MINDDY_FORGE_RELAY_SECRET: "instance-secret",
GIT_STATE_SECRET: "state-secret",
GIT_TOKEN_ENCRYPTION_SECRET: "x".repeat(32),
GITHUB_APP_ID: "1",
GITHUB_APP_SLUG: "example-app",
GITHUB_APP_PRIVATE_KEY: "private-key",
};
const local = resolveCapabilities({ ...core, ...relay });
expect(local.github).toMatchObject({
requirement: "optional",
state: "ready",
diagnostic: expect.stringContaining("operator-owned app"),
});
expect(local.gitlab.diagnostic).toContain("managed forge relay");
// Self-hosted default: no operator-owned app and no relay variables —
// the relay identity is provisioned automatically on first connect.
// The instance-side secrets stay required (the installer generates them).
const automatic = resolveCapabilities({
...core,
GIT_STATE_SECRET: "state-secret",
GIT_TOKEN_ENCRYPTION_SECRET: "x".repeat(32),
});
for (const id of ["github", "gitlab"] as const) {
expect(automatic[id]).toMatchObject({
requirement: "replaceable",
state: "ready",
configured: true,
});
expect(automatic[id].diagnostic).toContain("provisioned automatically");
}
// The explicit opt-out (--no-forge-relay) restores the disabled state.
const optedOut = resolveCapabilities({ ...core, MINDDY_FORGE_RELAY: "0" });
expect(optedOut.github.state).toBe("disabled");
expect(optedOut.gitlab.state).toBe("disabled");
// The cloud edition never self-provisions: it operates the relay instead.
const cloud = resolveCapabilities({ ...core, MINDDY_EDITION: "cloud" });
expect(cloud.github.state).toBe("disabled");
expect(cloud.gitlab.state).toBe("disabled");
});
it("falls back to automatic provisioning when relay variables are only partial", () => {
const capabilities = resolveCapabilities({
...core,
MINDDY_FORGE_RELAY_URL: "https://forge-relay.minddy.app",
GIT_STATE_SECRET: "state-secret",
GIT_TOKEN_ENCRYPTION_SECRET: "x".repeat(32),
});
// Partial explicit variables do not select a pinned control plane
// (all three are required); the instance stays on the automatic default.
expect(capabilities.github).toMatchObject({ state: "ready", configured: true });
const missingState = resolveCapabilities({
...core,
MINDDY_FORGE_RELAY_URL: "https://forge-relay.minddy.app",
MINDDY_FORGE_RELAY_INSTANCE_ID: "instance-id",
MINDDY_FORGE_RELAY_SECRET: "instance-secret",
});
expect(missingState.github).toMatchObject({
state: "incomplete",
missing: [
"GIT_STATE_SECRET",
"GIT_TOKEN_ENCRYPTION_SECRET (at least 32 characters)",
],
});
});
});