Track HiveGrid Cloud planning reference
This commit is contained in:
1 parent
b3d5ea6764
commit
e341c5938a
5784 files changed
+1104929
-3
No files matched your search
@@ -0,0 +1,135 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { runApiKeyMigrationCli } from "./migrate-api-keys.js";
|
||||
import type { ApiKeyMigrationCliDependencies } from "./migrate-api-keys.js";
|
||||
|
||||
function createOutputBuffer(): Pick<typeof process.stdout, "write"> & { text: () => string } {
|
||||
const chunks: string[] = [];
|
||||
return {
|
||||
write: vi.fn((chunk: string | Uint8Array) => {
|
||||
chunks.push(String(chunk));
|
||||
return true;
|
||||
}) as unknown as typeof process.stdout.write,
|
||||
text: () => chunks.join(""),
|
||||
};
|
||||
}
|
||||
|
||||
function createDependencies(overrides: Partial<ApiKeyMigrationCliDependencies> = {}) {
|
||||
const stdout = createOutputBuffer();
|
||||
const stderr = createOutputBuffer();
|
||||
const backfillLegacyApiKeyHashes = vi.fn().mockResolvedValue({ scanned: 0, updated: 0, skipped: 0, failed: 0, failures: [] });
|
||||
const dependencies = {
|
||||
stdout,
|
||||
stderr,
|
||||
backfillLegacyApiKeyHashes,
|
||||
...overrides,
|
||||
};
|
||||
|
||||
return dependencies as typeof dependencies & {
|
||||
stdout: typeof stdout;
|
||||
stderr: typeof stderr;
|
||||
backfillLegacyApiKeyHashes: typeof backfillLegacyApiKeyHashes;
|
||||
};
|
||||
}
|
||||
|
||||
describe("runApiKeyMigrationCli", () => {
|
||||
it("defaults to dry-run and performs no writes", async () => {
|
||||
const dependencies = createDependencies();
|
||||
|
||||
await expect(runApiKeyMigrationCli([], { API_KEY_HASH_SECRET: "fixture-hash-secret" }, dependencies)).resolves.toBe(0);
|
||||
|
||||
expect(dependencies.backfillLegacyApiKeyHashes).not.toHaveBeenCalled();
|
||||
expect(JSON.parse(dependencies.stdout.text())).toMatchObject({
|
||||
mode: "dry-run",
|
||||
scanned: 0,
|
||||
updated: 0,
|
||||
skipped: 0,
|
||||
failed: 0,
|
||||
});
|
||||
expect(dependencies.stdout.text()).not.toContain("fixture-hash-secret");
|
||||
expect(dependencies.stderr.text()).toBe("");
|
||||
});
|
||||
|
||||
it("executes the backfill with caller batch size and resolved hash secret", async () => {
|
||||
const dependencies = createDependencies({
|
||||
backfillLegacyApiKeyHashes: vi.fn().mockResolvedValue({
|
||||
scanned: 3,
|
||||
updated: 2,
|
||||
skipped: 1,
|
||||
failed: 0,
|
||||
failures: [],
|
||||
}),
|
||||
});
|
||||
|
||||
await expect(
|
||||
runApiKeyMigrationCli(["--execute", "--batch-size", "3"], { API_KEY_HASH_SECRET: "fixture-hash-secret", JWT_SECRET: "fallback-secret" }, dependencies),
|
||||
).resolves.toBe(0);
|
||||
|
||||
expect(dependencies.backfillLegacyApiKeyHashes).toHaveBeenCalledWith({ batchSize: 3, hashSecret: "fixture-hash-secret" });
|
||||
expect(JSON.parse(dependencies.stdout.text())).toEqual({
|
||||
mode: "execute",
|
||||
batchSize: 3,
|
||||
scanned: 3,
|
||||
updated: 2,
|
||||
skipped: 1,
|
||||
failed: 0,
|
||||
failureIds: [],
|
||||
});
|
||||
expect(dependencies.stdout.text()).not.toContain("fixture-hash-secret");
|
||||
expect(dependencies.stdout.text()).not.toContain("fallback-secret");
|
||||
});
|
||||
|
||||
it("falls back to JWT_SECRET when API_KEY_HASH_SECRET is blank", async () => {
|
||||
const dependencies = createDependencies();
|
||||
|
||||
await expect(runApiKeyMigrationCli(["--execute"], { API_KEY_HASH_SECRET: " ", JWT_SECRET: "jwt-fallback-secret" }, dependencies)).resolves.toBe(0);
|
||||
|
||||
expect(dependencies.backfillLegacyApiKeyHashes).toHaveBeenCalledWith({ batchSize: 100, hashSecret: "jwt-fallback-secret" });
|
||||
expect(dependencies.stdout.text()).not.toContain("jwt-fallback-secret");
|
||||
});
|
||||
|
||||
it("rejects invalid batch-size before DB writes", async () => {
|
||||
const dependencies = createDependencies();
|
||||
|
||||
await expect(runApiKeyMigrationCli(["--execute", "--batch-size", "0"], { API_KEY_HASH_SECRET: "fixture-hash-secret" }, dependencies)).resolves.toBe(1);
|
||||
|
||||
expect(dependencies.backfillLegacyApiKeyHashes).not.toHaveBeenCalled();
|
||||
expect(dependencies.stderr.text()).toContain("Invalid --batch-size");
|
||||
expect(dependencies.stderr.text()).not.toContain("fixture-hash-secret");
|
||||
expect(dependencies.stdout.text()).toBe("");
|
||||
});
|
||||
|
||||
it("redacts raw fixture secrets from failed summaries", async () => {
|
||||
const rawKey = "api_test_plaintext_secret";
|
||||
const dependencies = createDependencies({
|
||||
backfillLegacyApiKeyHashes: vi.fn().mockResolvedValue({
|
||||
scanned: 1,
|
||||
updated: 0,
|
||||
skipped: 0,
|
||||
failed: 1,
|
||||
failures: [{ id: "api_key_1", error: `database leaked ${rawKey}` }],
|
||||
}),
|
||||
});
|
||||
|
||||
await expect(runApiKeyMigrationCli(["--execute"], { JWT_SECRET: "jwt-fallback-secret" }, dependencies)).resolves.toBe(1);
|
||||
|
||||
expect(dependencies.stdout.text()).toContain("api_key_1");
|
||||
expect(dependencies.stdout.text()).not.toContain(rawKey);
|
||||
expect(dependencies.stdout.text()).not.toContain("jwt-fallback-secret");
|
||||
expect(dependencies.stderr.text()).toBe("");
|
||||
});
|
||||
|
||||
it("redacts thrown migration helper errors", async () => {
|
||||
const rawKey = "api_test_plaintext_secret";
|
||||
const dependencies = createDependencies({
|
||||
backfillLegacyApiKeyHashes: vi.fn().mockRejectedValue(new Error(`connection failed for ${rawKey}`)),
|
||||
});
|
||||
|
||||
await expect(runApiKeyMigrationCli(["--execute"], { API_KEY_HASH_SECRET: "fixture-hash-secret" }, dependencies)).resolves.toBe(1);
|
||||
|
||||
expect(dependencies.stderr.text()).toContain("Error");
|
||||
expect(dependencies.stderr.text()).not.toContain(rawKey);
|
||||
expect(dependencies.stderr.text()).not.toContain("fixture-hash-secret");
|
||||
expect(dependencies.stdout.text()).toBe("");
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user