import { afterEach, beforeEach, describe, expect, test } from "bun:test"; import { mkdirSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; // ---- @ Alias Resolution Tests ---- let testDir: string; let storePath: string; let cliPath: string; beforeEach(() => { // Create unique temp directory for each test testDir = join( tmpdir(), `ocas-cli-test-${Date.now()}-${Math.random().toString(36).slice(2)}`, ); storePath = join(testDir, "store"); cliPath = join(import.meta.dir, "../src/index.ts"); mkdirSync(testDir, { recursive: true }); mkdirSync(storePath, { recursive: true }); }); afterEach(() => { // Clean up test directory try { rmSync(testDir, { recursive: true, force: true }); } catch { // Ignore cleanup errors } }); /** * Run CLI command and return stdout, stderr, and exit code */ async function runCliAlias(...args: string[]): Promise<{ stdout: string; stderr: string; exitCode: number; }> { const proc = Bun.spawn( ["bun", "run", cliPath, "--store", storePath, ...args], { stdout: "pipe", stderr: "pipe", }, ); const [stdout, stderr] = await Promise.all([ new Response(proc.stdout).text(), new Response(proc.stderr).text(), ]); await proc.exited; return { stdout: stdout.trim(), stderr: stderr.trim(), exitCode: proc.exitCode ?? 0, }; } /** Extract the `value` field from a { type, value } envelope JSON string. */ function envValue(json: string): unknown { return (JSON.parse(json.trim()) as { value: unknown }).value; } describe("@ Alias Resolution - put", () => { test("ocas put @string should resolve alias", async () => { await runCliAlias("init"); const payloadFile = join(testDir, "payload.json"); writeFileSync(payloadFile, JSON.stringify("hello world")); const { stdout, stderr, exitCode } = await runCliAlias( "put", "@string", payloadFile, ); expect(exitCode).toBe(0); expect(stderr).toBe(""); // Should output an envelope whose value is a valid hash (13 chars) expect(envValue(stdout)).toMatch(/^[0-9A-HJKMNP-TV-Z]{13}$/); }); test("ocas put @number should resolve alias", async () => { await runCliAlias("init"); const payloadFile = join(testDir, "payload.json"); writeFileSync(payloadFile, "42"); const { stdout, exitCode } = await runCliAlias( "put", "@number", payloadFile, ); expect(exitCode).toBe(0); expect(envValue(stdout)).toMatch(/^[0-9A-HJKMNP-TV-Z]{13}$/); }); test("ocas put @object should resolve alias", async () => { await runCliAlias("init"); const payloadFile = join(testDir, "payload.json"); writeFileSync(payloadFile, JSON.stringify({ foo: "bar" })); const { stdout, exitCode } = await runCliAlias( "put", "@object", payloadFile, ); expect(exitCode).toBe(0); expect(envValue(stdout)).toMatch(/^[0-9A-HJKMNP-TV-Z]{13}$/); }); test("ocas put @invalid should fail", async () => { await runCliAlias("init"); const payloadFile = join(testDir, "payload.json"); writeFileSync(payloadFile, "{}"); const { stderr, exitCode } = await runCliAlias( "put", "@invalid", payloadFile, ); expect(exitCode).not.toBe(0); expect(stderr.length).toBeGreaterThan(0); }); test("ocas put @schema with nested type constraints should succeed", async () => { await runCliAlias("init"); const schemaFile = join(testDir, "constrained-schema.json"); writeFileSync( schemaFile, JSON.stringify({ type: "object", properties: { name: { type: "string", minLength: 1, maxLength: 50 }, age: { type: "number", minimum: 0, maximum: 150 }, tags: { type: "array", items: { type: "string" }, minItems: 1, uniqueItems: true, }, }, required: ["name"], }), ); const { stdout, stderr, exitCode } = await runCliAlias( "put", "@schema", schemaFile, ); expect(exitCode).toBe(0); expect(stderr).toBe(""); expect(envValue(stdout)).toMatch(/^[0-9A-HJKMNP-TV-Z]{13}$/); }); }); describe("@ Alias Resolution - hash", () => { test("ocas hash @string should compute hash without storing", async () => { await runCliAlias("init"); const payloadFile = join(testDir, "payload.json"); writeFileSync(payloadFile, JSON.stringify("test")); const { stdout, stderr, exitCode } = await runCliAlias( "hash", "@string", payloadFile, ); expect(exitCode).toBe(0); expect(stderr).toBe(""); expect(envValue(stdout)).toMatch(/^[0-9A-HJKMNP-TV-Z]{13}$/); }); });