Files
wehub-resource-sync b3a7f98e5a
CI / E2E Cloudflare (4/8) (push) Failing after 0s
CI / E2E Cloudflare (8/8) (push) Failing after 1s
CI / Lint (push) Failing after 1s
Auto Extract / Extract (push) Failing after 4s
CI / Version Check (push) Failing after 9s
CI / Integration Tests (push) Failing after 1s
CI / E2E tests (1/8) (push) Failing after 1s
CI / E2E tests (2/8) (push) Failing after 2s
CI / E2E tests (3/8) (push) Failing after 2s
CI / Browser Tests (push) Failing after 1s
CI / E2E tests (5/8) (push) Failing after 1s
CI / E2E Cloudflare (2/8) (push) Failing after 2s
CI / Typecheck (push) Failing after 1s
CI / Changeset Validation (push) Failing after 2s
CI / E2E Cloudflare (5/8) (push) Failing after 1s
CI / E2E Cloudflare (6/8) (push) Failing after 1s
CI / E2E Cloudflare (7/8) (push) Failing after 1s
CodeQL / Analyze (javascript-typescript) (push) Failing after 1s
Format / Format (push) Failing after 0s
CodeQL / Analyze (actions) (push) Failing after 4s
CI / E2E tests (4/8) (push) Failing after 1s
CI / E2E tests (6/8) (push) Failing after 1s
CI / E2E tests (7/8) (push) Failing after 2s
CI / E2E tests (8/8) (push) Failing after 1s
CI / E2E Cloudflare (1/8) (push) Failing after 1s
CI / E2E Cloudflare (3/8) (push) Failing after 2s
Preview Releases / Publish Preview (push) Failing after 0s
zizmor / Run zizmor (push) Failing after 1s
Release / Release (push) Failing after 2s
CI / Smoke Tests (push) Failing after 5m36s
CI / Tests (push) Failing after 6m36s
Release / Sync Templates (push) Has been skipped
CI / E2E Tests (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:23:53 +08:00

93 lines
3.9 KiB
TypeScript

/**
* Tests for the `emdash secrets` CLI surface.
*
* Focuses on the file-write helper used by `secrets generate --write`,
* which is the only piece with non-trivial logic. The command runners
* themselves are thin wrappers around `generateEncryptionKey()` and
* `fingerprintKey()` (covered by `tests/unit/config/secrets.test.ts`).
*/
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { describe, it, expect, beforeEach, afterEach } from "vitest";
import { writeEncryptionKeyToFile } from "../../../src/cli/commands/secrets.js";
describe("secrets CLI: writeEncryptionKeyToFile", () => {
let dir: string;
let target: string;
const sample = "emdash_enc_v1_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
const sample2 = "emdash_enc_v1_BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB";
beforeEach(async () => {
dir = await mkdtemp(join(tmpdir(), "emdash-secrets-cli-"));
target = join(dir, ".env");
});
afterEach(async () => {
await rm(dir, { recursive: true, force: true });
});
it("creates a new file with a trailing newline", async () => {
const result = writeEncryptionKeyToFile(target, sample, false);
expect(result).toBe("wrote");
const content = await readFile(target, "utf-8");
expect(content).toBe(`EMDASH_ENCRYPTION_KEY=${sample}\n`);
});
it("appends to an existing file without clobbering other vars", async () => {
await writeFile(target, "OTHER=value\nFOO=bar\n");
const result = writeEncryptionKeyToFile(target, sample, false);
expect(result).toBe("wrote");
const content = await readFile(target, "utf-8");
expect(content).toBe(`OTHER=value\nFOO=bar\nEMDASH_ENCRYPTION_KEY=${sample}\n`);
});
it("appends to a file that lacks a trailing newline", async () => {
await writeFile(target, "OTHER=value");
const result = writeEncryptionKeyToFile(target, sample, false);
expect(result).toBe("wrote");
const content = await readFile(target, "utf-8");
expect(content).toBe(`OTHER=value\nEMDASH_ENCRYPTION_KEY=${sample}\n`);
});
it("refuses to overwrite an existing entry without force", async () => {
await writeFile(target, `EMDASH_ENCRYPTION_KEY=${sample}\nOTHER=value\n`);
const result = writeEncryptionKeyToFile(target, sample2, false);
expect(result).toBe("skipped");
const content = await readFile(target, "utf-8");
// Entry untouched.
expect(content).toBe(`EMDASH_ENCRYPTION_KEY=${sample}\nOTHER=value\n`);
});
it("replaces an existing entry in place when force is true", async () => {
await writeFile(target, `OTHER=value\nEMDASH_ENCRYPTION_KEY=${sample}\nMORE=stuff\n`);
const result = writeEncryptionKeyToFile(target, sample2, true);
expect(result).toBe("wrote");
const content = await readFile(target, "utf-8");
// Other vars untouched, key replaced inline (no duplication).
expect(content).toBe(`OTHER=value\nEMDASH_ENCRYPTION_KEY=${sample2}\nMORE=stuff\n`);
});
it("treats an empty-value entry as not-set and replaces it without --force", async () => {
// Operators sometimes leave `EMDASH_ENCRYPTION_KEY=` as a placeholder.
// A skip in that case would be hostile — they actively want a value.
await writeFile(target, `OTHER=value\nEMDASH_ENCRYPTION_KEY=\nMORE=stuff\n`);
const result = writeEncryptionKeyToFile(target, sample, false);
expect(result).toBe("wrote");
const content = await readFile(target, "utf-8");
expect(content).toBe(`OTHER=value\nEMDASH_ENCRYPTION_KEY=${sample}\nMORE=stuff\n`);
});
it("always ends with a trailing newline, even when replacing in-place in a file without one", async () => {
await writeFile(target, `OTHER=value\nEMDASH_ENCRYPTION_KEY=${sample}`);
const result = writeEncryptionKeyToFile(target, sample2, true);
expect(result).toBe("wrote");
const content = await readFile(target, "utf-8");
expect(content.endsWith("\n")).toBe(true);
expect(content).toBe(`OTHER=value\nEMDASH_ENCRYPTION_KEY=${sample2}\n`);
});
});