Ctrl K
ComunidadGuías…

scripts/setup-config.test.mjs

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
import assert from "node:assert/strict";
import { spawnSync } from "node:child_process";
import {
  mkdtempSync,
  readFileSync,
  readdirSync,
  statSync,
  writeFileSync,
} from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import test from "node:test";
import {
  createCloudflareConfig,
  getCloudflareWorkersSubdomain,
  readCloudflareConfig,
  readEnv,
  readJsonConfig,
  upsertEnv,
  validateUrl,
  validateMcpUrl,
} from "./setup-config.mjs";

test("configuration CLI rejects public HTTP origins despite an HTTPS MCP override", () => {
  const fixture = join(
    mkdtempSync(join(tmpdir(), "devpush-url-validation-")),
    "config.env",
  );
  writeFileSync(fixture, "");
  for (const key of ["DEVPUSH_BASE_URL", "BETTER_AUTH_URL"]) {
    for (const value of [
      "http://devpush.example",
      "http://localhost:7429",
      "http://host.lima.internal:7429",
    ]) {
      const env = Object.fromEntries(
        Object.entries(process.env).filter(
          ([name]) =>
            !name.startsWith("DEVPUSH_") && !name.startsWith("BETTER_AUTH_"),
        ),
      );
      Object.assign(env, {
        BETTER_AUTH_SECRET: "a".repeat(40),
        DEVPUSH_BASE_URL: "http://localhost:7429",
        DEVPUSH_MCP_URL: "https://devpush.example/mcp",
        [key]: value,
      });
      const result = spawnSync(
        process.execPath,
        ["scripts/validate-config.mjs", "--env", fixture, "--smoke"],
        { env, encoding: "utf8" },
      );
      assert.equal(
        result.status,
        value === "http://devpush.example" ? 1 : 0,
        result.stderr,
      );
      if (result.status === 1)
        assert.match(
          result.stderr,
          /must use HTTPS except for local development hosts/,
        );
    }
  }
});

test("validates canonical MCP endpoints independently of the control-plane origin", () => {
  for (const value of [
    "http://localhost:7429/mcp",
    "http://host.lima.internal:7429/mcp",
    "http://[::1]:7429/mcp",
    "https://mcp.example.com/mcp",
  ])
    assert.equal(validateMcpUrl(value), undefined);
  for (const value of [
    "https://user:secret@example.com/mcp",
    "https://example.com/mcp?secret=yes",
    "https://example.com/mcp?",
    "https://example.com/mcp#",
    "https://example.com/",
    "https://example.com/mcp/",
    "http://example.com/mcp",
    "ftp://example.com/mcp",
  ])
    assert.equal(typeof validateMcpUrl(value), "string");
});

test("creates a deployment-specific Cloudflare config", () => {
  const config = JSON.parse(
    createCloudflareConfig({
      accountId: "account-123",
      bucketName: "devpush-assets",
      customDomain: "devpush.example.com",
      databaseId: "11111111-1111-1111-1111-111111111111",
      logDatabaseId: "22222222-2222-2222-2222-222222222222",
      template: {
        compatibility_date: "2026-08-29",
        main: "src/server.ts",
      },
      vars: { DEVPUSH_BASE_URL: "https://devpush.example.com" },
      workerName: "devpush-production",
    }),
  );

  assert.equal(config.name, "devpush-production");
  assert.equal(config.account_id, "account-123");
  assert.equal(config.main, "src/server.ts");
  assert.equal(config.compatibility_date, "2026-08-29");
  assert.equal(config.workers_dev, false);
  assert.deepEqual(config.routes, [
    { pattern: "devpush.example.com", custom_domain: true },
  ]);
  assert.deepEqual(config.d1_databases, [
    {
      binding: "DEVPUSH_DATABASE",
      database_id: "11111111-1111-1111-1111-111111111111",
      database_name: "devpush",
      migrations_dir: "src/server/db/migrations-sqlite",
    },
    {
      binding: "DEVPUSH_LOG_DATABASE",
      database_id: "22222222-2222-2222-2222-222222222222",
      database_name: "devpush-logs",
      migrations_dir: "src/server/db/migrations-logs",
    },
  ]);
  assert.equal(config.hyperdrive, undefined);
  assert.deepEqual(config.r2_buckets, [
    { binding: "DEVPUSH_OBJECTS", bucket_name: "devpush-assets" },
  ]);
  assert.deepEqual(config.vars, {
    DEVPUSH_BASE_URL: "https://devpush.example.com",
  });
  assert.equal(JSON.stringify(config).includes("DATABASE_URL"), false);
});

test("reads deployment resources from a generated Cloudflare config", () => {
  const directory = mkdtempSync(join(tmpdir(), "devpush-setup-"));
  const path = join(directory, "wrangler.jsonc");
  writeFileSync(
    path,
    createCloudflareConfig({
      accountId: "account-123",
      bucketName: "devpush-assets",
      databaseId: "11111111-1111-1111-1111-111111111111",
      logDatabaseId: "22222222-2222-2222-2222-222222222222",
      workersDev: true,
      vars: {
        BETTER_AUTH_URL: "https://auth.example.com",
        DEVPUSH_ADMIN_EMAIL: "admin@example.com",
        DEVPUSH_BASE_URL: "https://devpush.example.com",
      },
      workerName: "devpush-production",
    }),
  );

  assert.deepEqual(readCloudflareConfig(path), {
    accountId: "account-123",
    bucketName: "devpush-assets",
    customDomain: "",
    databaseId: "11111111-1111-1111-1111-111111111111",
    databaseName: "devpush",
    logDatabaseId: "22222222-2222-2222-2222-222222222222",
    logDatabaseName: "devpush-logs",
    vars: {
      BETTER_AUTH_URL: "https://auth.example.com",
      DEVPUSH_ADMIN_EMAIL: "admin@example.com",
      DEVPUSH_BASE_URL: "https://devpush.example.com",
    },
    workerName: "devpush-production",
    workersDev: true,
  });
  assert.equal(readJsonConfig(path).name, "devpush-production");
});

test("discovers the workers.dev account subdomain", async () => {
  const requests = [];
  const subdomain = await getCloudflareWorkersSubdomain({
    accountId: "account-123",
    apiToken: "secret-token",
    fetchImpl: async (url, options) => {
      requests.push({ url, options });
      return {
        ok: true,
        json: async () => ({
          result: { subdomain: "example-account" },
          success: true,
        }),
      };
    },
  });

  assert.equal(subdomain, "example-account");
  assert.equal(
    requests[0].url,
    "https://api.cloudflare.com/client/v4/accounts/account-123/workers/subdomain",
  );
  assert.equal(
    requests[0].options.headers.Authorization,
    "Bearer secret-token",
  );
});

test("explains when the Cloudflare account has no workers.dev subdomain", async () => {
  await assert.rejects(
    getCloudflareWorkersSubdomain({
      accountId: "account-123",
      apiToken: "secret-token",
      fetchImpl: async () => ({
        ok: false,
        json: async () => ({
          errors: [{ code: 10007, message: "Subdomain not found" }],
          success: false,
        }),
      }),
    }),
    /has no workers\.dev subdomain/u,
  );
});

test("reads comments and trailing commas from a JSONC template", () => {
  const directory = mkdtempSync(join(tmpdir(), "devpush-setup-"));
  const path = join(directory, "wrangler.template.jsonc");
  writeFileSync(
    path,
    `{
      // Shared Worker defaults.
      "name": "devpush",
      "observability": {
        "enabled": true,
      },
    }`,
  );

  assert.deepEqual(readJsonConfig(path), {
    name: "devpush",
    observability: { enabled: true },
  });
});

test("upserts env values without discarding unrelated configuration", () => {
  const directory = mkdtempSync(join(tmpdir(), "devpush-setup-"));
  const path = join(directory, ".env");
  upsertEnv(path, { EXISTING: "first", MULTILINE: "line one\nline two" });
  upsertEnv(path, { EXISTING: "second", NEW_VALUE: "hello world" });

  assert.deepEqual(readEnv(path), {
    EXISTING: "second",
    MULTILINE: "line one\nline two",
    NEW_VALUE: "hello world",
  });
  assert.equal(readFileSync(path, "utf8").match(/^EXISTING=/gmu)?.length, 1);
  assert.equal(statSync(path).mode & 0o777, 0o600);
  assert.deepEqual(readdirSync(directory), [".env"]);
});

test("setup reads standard dotenv quoting and replaces whole multiline assignments", () => {
  const directory = mkdtempSync(join(tmpdir(), "devpush-env-quotes-"));
  const path = join(directory, ".env");
  writeFileSync(path, "# Keep this comment\nexport KEY='line one\nFAKE=value\nline three'\nUNRELATED=`literal \\\\n`\n");
  assert.equal(readEnv(path).KEY, "line one\nFAKE=value\nline three");
  const unrelated = readEnv(path).UNRELATED;
  upsertEnv(path, { KEY: 'new "quoted" value', NEXT: "ready" });
  assert.deepEqual(readEnv(path), { KEY: 'new "quoted" value', UNRELATED: unrelated, NEXT: "ready" });
  assert.ok(readFileSync(path, "utf8").startsWith("# Keep this comment\n"));
  assert.ok(!readFileSync(path, "utf8").includes("FAKE=value"));
});

test("setup preserves quote and backslash values when saving existing settings", () => {
  const directory = mkdtempSync(join(tmpdir(), "devpush-env-roundtrip-"));
  const path = join(directory, ".env");
  const values = {
    PRIVATE_KEY: "-----BEGIN KEY-----\nline1\nline2\n-----END KEY-----",
    QUOTES: "a 'single' and \"double\" quote # literal",
    LITERAL: String.raw`literal\npath\value`,
    EMPTY: "",
  };
  upsertEnv(path, values);
  assert.deepEqual(readEnv(path), values);
  upsertEnv(path, { EMPTY: "changed" });
  assert.deepEqual(readEnv(path), { ...values, EMPTY: "changed" });
});

test("setup leaves malformed multiline files untouched", () => {
  const directory = mkdtempSync(join(tmpdir(), "devpush-env-malformed-"));
  const path = join(directory, ".env");
  const source = "KEY='unterminated\nOTHER=value\n";
  writeFileSync(path, source);
  assert.throws(() => upsertEnv(path, { OTHER: "new" }), /unterminated/);
  assert.equal(readFileSync(path, "utf8"), source);
});

test("validates control-plane URLs", () => {
  assert.equal(validateUrl("https://devpush.example.com"), undefined);
  for (const value of [
    "http://localhost:7429",
    "http://host.lima.internal:7429",
    "http://127.0.0.2:7429",
    "http://[::1]:7429",
  ])
    assert.equal(validateUrl(value), undefined);
  for (const value of [
    "http://devpush.example",
    "https://user:secret@devpush.example",
    "https://devpush.example?",
    "https://devpush.example#",
  ])
    assert.notEqual(validateUrl(value), undefined);
  assert.equal(
    validateUrl("ftp://example.com"),
    "Use an http:// or https:// URL.",
  );
  assert.equal(
    validateUrl("https://example.com/path"),
    "Use an origin without a path, query, or fragment.",
  );
});

test("D1 config separates main and log stores and rejects local placeholders for remote use", async () => {
  const { getD1Bindings, localD1Ids } = await import("./d1-config.mjs");
  const config = {
    d1_databases: [
      {
        binding: "DEVPUSH_DATABASE",
        database_name: "main",
        database_id: localD1Ids[0],
        migrations_dir: "main-migrations",
      },
      {
        binding: "DEVPUSH_LOG_DATABASE",
        database_name: "logs",
        database_id: localD1Ids[1],
        migrations_dir: "log-migrations",
      },
    ],
  };
  assert.equal(getD1Bindings(config, { local: true }).length, 2);
  assert.throws(() => getD1Bindings(config), /configured D1 database ID/);
  const identical = structuredClone(config);
  identical.d1_databases[1].database_id = identical.d1_databases[0].database_id;
  assert.throws(
    () => getD1Bindings(identical, { local: true }),
    /different D1/,
  );
  const missing = structuredClone(config);
  missing.d1_databases.pop();
  assert.throws(
    () => getD1Bindings(missing, { local: true }),
    /exactly one DEVPUSH_LOG_DATABASE/,
  );
  const duplicate = structuredClone(config);
  duplicate.d1_databases.push(duplicate.d1_databases[0]);
  assert.throws(
    () => getD1Bindings(duplicate, { local: true }),
    /exactly one DEVPUSH_DATABASE/,
  );
});

test("pending setup preserves requested database names without treating local IDs as configured resources", () => {
  const directory = mkdtempSync(join(tmpdir(), "devpush-d1-setup-"));
  const path = join(directory, "pending.jsonc");
  writeFileSync(
    path,
    createCloudflareConfig({
      databaseName: "custom-main",
      logDatabaseName: "custom-logs",
    }),
  );
  const pending = readCloudflareConfig(path);
  assert.equal(pending.databaseName, "custom-main");
  assert.equal(pending.logDatabaseName, "custom-logs");
  assert.equal(pending.databaseId, "");
  assert.equal(pending.logDatabaseId, "");
});

test("remote database configuration removes template D1 bindings but preserves R2", () => {
  const config = JSON.parse(
    createCloudflareConfig({
      remoteDatabase: true,
      bucketName: "objects",
      template: {
        d1_databases: [{ binding: "DEVPUSH_DATABASE" }],
        compatibility_flags: ["nodejs_compat"],
      },
    }),
  );
  assert.equal(config.d1_databases, undefined);
  assert.equal(config.r2_buckets[0].bucket_name, "objects");
  assert.deepEqual(config.compatibility_flags, ["nodejs_compat"]);
});

test("Cloudflare configuration can omit image storage", () => {
  const config = JSON.parse(createCloudflareConfig({
    template: { r2_buckets: [{ binding: "DEVPUSH_OBJECTS", bucket_name: "old" }] },
    databaseId: "main-id",
    logDatabaseId: "log-id",
  }));
  assert.equal(config.r2_buckets, undefined);
  assert.equal(config.d1_databases.length, 2);
});