alepha
Version:
Easy-to-use modern TypeScript framework for building many kind of applications.
154 lines (126 loc) • 4.65 kB
text/typescript
import { Alepha } from "alepha";
import {
FileSystemProvider,
MemoryFileSystemProvider,
MemoryShellProvider,
ShellProvider,
} from "alepha/system";
import { describe, test } from "vitest";
import { GitHubSecretStore } from "../providers/GitHubSecretStore.ts";
describe("GitHubSecretStore", () => {
const createTestEnv = () => {
const alepha = Alepha.create()
.with({
provide: ShellProvider,
use: MemoryShellProvider,
})
.with({
provide: FileSystemProvider,
use: MemoryFileSystemProvider,
});
const shell = alepha.inject(MemoryShellProvider);
const fs = alepha.inject(MemoryFileSystemProvider);
const store = alepha.inject(GitHubSecretStore);
return { alepha, shell, fs, store };
};
describe("ensureAvailable", () => {
test("succeeds when gh is installed and authenticated", async ({
expect,
}) => {
const { shell, store } = createTestEnv();
shell.installedCommands.add("gh");
shell.outputs.set("gh auth status", "Logged in");
await expect(store.ensureAvailable()).resolves.not.toThrow();
});
test("throws when gh is not installed", async ({ expect }) => {
const { store } = createTestEnv();
await expect(store.ensureAvailable()).rejects.toThrow(
"GitHub CLI (gh) is not installed",
);
});
test("throws when gh is not authenticated", async ({ expect }) => {
const { shell, store } = createTestEnv();
shell.installedCommands.add("gh");
shell.errors.set("gh auth status", "Not logged in");
await expect(store.ensureAvailable()).rejects.toThrow(
"GitHub CLI is not authenticated",
);
});
});
describe("list", () => {
test("parses JSON output from gh", async ({ expect }) => {
const { shell, store } = createTestEnv();
shell.outputs.set(
"gh secret list --env app-production --json name,updatedAt",
JSON.stringify([
{ name: "API_KEY", updatedAt: "2026-01-01T00:00:00Z" },
{ name: "DATABASE_URL", updatedAt: "2026-01-02T00:00:00Z" },
]),
);
const result = await store.list("app-production");
expect(result).toEqual([
{ name: "API_KEY", updatedAt: "2026-01-01T00:00:00Z" },
{ name: "DATABASE_URL", updatedAt: "2026-01-02T00:00:00Z" },
]);
});
test("returns empty array when no secrets", async ({ expect }) => {
const { shell, store } = createTestEnv();
shell.outputs.set(
"gh secret list --env app-staging --json name,updatedAt",
"[]",
);
const result = await store.list("app-staging");
expect(result).toEqual([]);
});
test("returns empty array on error", async ({ expect }) => {
const { shell, store } = createTestEnv();
shell.errors.set(
"gh secret list --env bad-env --json name,updatedAt",
"Environment not found",
);
const result = await store.list("bad-env");
expect(result).toEqual([]);
});
});
describe("set", () => {
test("writes dotenv file and calls gh secret set --env-file", async ({
expect,
}) => {
const { shell, fs, store } = createTestEnv();
await store.set("app-production", "API_KEY", "abc123");
// Should write dotenv format
const written = fs.writeFileCalls.find((c) =>
/\/tmp\/alepha-secret-API_KEY-/.test(c.path),
);
expect(written).toBeDefined();
expect(written!.data).toBe('API_KEY="abc123"\n');
// Should call gh with --env-file (no positional secret name)
expect(
shell.wasCalledMatching(
/gh secret set -f \/tmp\/alepha-secret-API_KEY-\d+ --env app-production/,
),
).toBe(true);
// Temp file should be cleaned up
expect(
fs.rmCalls.some((c) => /\/tmp\/alepha-secret-API_KEY-/.test(c.path)),
).toBe(true);
});
test("escapes special characters in value", async ({ expect }) => {
const { fs, store } = createTestEnv();
await store.set("app-production", "KEY", 'val"ue\nwith\\special');
const written = fs.writeFileCalls.find((c) =>
/\/tmp\/alepha-secret-KEY-/.test(c.path),
);
expect(written!.data).toBe('KEY="val\\"ue\\nwith\\\\special"\n');
});
});
describe("delete", () => {
test("calls gh secret delete with correct args", async ({ expect }) => {
const { shell, store } = createTestEnv();
await store.delete("app-production", "OLD_KEY");
expect(
shell.wasCalled("gh secret delete OLD_KEY --env app-production"),
).toBe(true);
});
});
});