import { expect } from "@std/expect"; import { createConsumer } from "@cistern/consumer"; import { createProducer } from "@cistern/producer"; import type { Handle } from "@atcute/lexicons"; /** * End-to-end integration test for Cistern * * This test requires the following environment variables: * - CISTERN_HANDLE: Your Bluesky handle (e.g., "user.bsky.social") * - CISTERN_APP_PASSWORD: Your app password * * To run this test: * ```bash * CISTERN_HANDLE="your.handle" CISTERN_APP_PASSWORD="your-app-password" deno test --allow-env --allow-net e2e.test.ts * ``` */ const SKIP_E2E = !Deno.env.get("CISTERN_HANDLE") || !Deno.env.get("CISTERN_APP_PASSWORD"); Deno.test({ name: "E2E: Full encryption workflow", ignore: SKIP_E2E, async fn(t) { const handle = Deno.env.get("CISTERN_HANDLE") as Handle; const appPassword = Deno.env.get("CISTERN_APP_PASSWORD")!; let consumer: Awaited>; let producer: Awaited>; let keypair: Awaited>; let memoUri: string; let testMessage: string; await t.step("Create consumer", async () => { consumer = await createConsumer({ handle, appPassword, }); expect(consumer.did).toBeDefined(); expect(consumer.rpc).toBeDefined(); }); await t.step("Generate keypair", async () => { keypair = await consumer.generateKeyPair(); expect(keypair.privateKey).toBeInstanceOf(Uint8Array); expect(keypair.publicKey).toBeDefined(); expect(keypair.publicKey).toContain("app.cistern.pubkey"); }); try { await t.step("Create producer with public key", async () => { const publicKeyRkey = keypair.publicKey.split("/").pop()!; producer = await createProducer({ handle, appPassword, publicKey: publicKeyRkey, }); expect(producer.publicKey).toBeDefined(); expect(producer.publicKey?.uri).toEqual(keypair.publicKey); }); await t.step("Create encrypted memo", async () => { testMessage = `E2E Test - ${new Date().toISOString()}`; memoUri = await producer.createMemo(testMessage); expect(memoUri).toBeDefined(); expect(memoUri).toContain("app.cistern.memo"); }); await t.step("List and decrypt memos", async () => { const memos = []; for await (const memo of consumer.listMemos()) { memos.push(memo); } expect(memos.length).toBeGreaterThan(0); const ourMemo = memos.find((memo) => memo.text === testMessage); expect(ourMemo).toBeDefined(); expect(ourMemo!.text).toEqual(testMessage); }); await t.step("Delete memo", async () => { const memoRkey = memoUri.split("/").pop()!; await consumer.deleteMemo(memoRkey); // Verify deletion const memosAfterDelete = []; for await (const memo of consumer.listMemos()) { memosAfterDelete.push(memo); } const deletedMemo = memosAfterDelete.find( (memo) => memo.text === testMessage, ); expect(deletedMemo).toBeUndefined(); }); await t.step("List public keys", async () => { const keys = []; for await (const key of producer.listPublicKeys()) { keys.push(key); } expect(keys.length).toBeGreaterThan(0); const ourKey = keys.find((key) => key.uri === keypair.publicKey); expect(ourKey).toBeDefined(); expect(ourKey!.uri).toEqual(keypair.publicKey); }); } finally { await t.step("Cleanup: Delete test keypair", async () => { const publicKeyRkey = keypair.publicKey.split("/").pop()!; const res = await consumer.rpc.post("com.atproto.repo.deleteRecord", { input: { collection: "app.cistern.pubkey", repo: consumer.did, rkey: publicKeyRkey, }, }); expect(res.ok).toBe(true); }); } }, }); Deno.test({ name: "E2E: Multiple memos with same keypair", ignore: SKIP_E2E, async fn(t) { const handle = Deno.env.get("CISTERN_HANDLE") as Handle; const appPassword = Deno.env.get("CISTERN_APP_PASSWORD")!; let consumer: Awaited>; let producer: Awaited>; let keypair: Awaited>; let messages: string[]; let memoUris: string[]; await t.step("Create consumer and generate keypair", async () => { consumer = await createConsumer({ handle, appPassword, }); keypair = await consumer.generateKeyPair(); expect(keypair.privateKey).toBeInstanceOf(Uint8Array); expect(keypair.publicKey).toBeDefined(); }); try { await t.step("Create producer", async () => { const publicKeyRkey = keypair.publicKey.split("/").pop()!; producer = await createProducer({ handle, appPassword, publicKey: publicKeyRkey, }); expect(producer.publicKey?.uri).toEqual(keypair.publicKey); }); await t.step("Create multiple encrypted memos", async () => { messages = [ `E2E Memo 1 - ${new Date().toISOString()}`, `E2E Memo 2 - ${new Date().toISOString()}`, `E2E Memo 3 - ${new Date().toISOString()}`, ]; memoUris = []; for (const message of messages) { const uri = await producer.createMemo(message); memoUris.push(uri); } expect(memoUris).toHaveLength(3); }); await t.step("Decrypt all memos", async () => { const memos = []; for await (const memo of consumer.listMemos()) { memos.push(memo); } expect(memos.length).toBeGreaterThanOrEqual(3); // Verify all test messages are present for (const message of messages) { const memo = memos.find((m) => m.text === message); expect(memo).toBeDefined(); expect(memo!.text).toEqual(message); } }); await t.step("Cleanup: Delete test memos", async () => { for (const uri of memoUris) { const rkey = uri.split("/").pop()!; await consumer.deleteMemo(rkey); } // Verify all memos deleted const remainingMemos = []; for await (const memo of consumer.listMemos()) { remainingMemos.push(memo); } for (const message of messages) { const memo = remainingMemos.find((m) => m.text === message); expect(memo).toBeUndefined(); } }); } finally { await t.step("Cleanup: Delete test keypair", async () => { const publicKeyRkey = keypair.publicKey.split("/").pop()!; const res = await consumer.rpc.post("com.atproto.repo.deleteRecord", { input: { collection: "app.cistern.pubkey", repo: consumer.did, rkey: publicKeyRkey, }, }); expect(res.ok).toBe(true); }); } }, }); if (SKIP_E2E) { console.log(` ⚠️ E2E tests skipped - missing environment variables To run E2E tests, set the following environment variables: CISTERN_HANDLE="your.bsky.social" CISTERN_APP_PASSWORD="xxxx-xxxx-xxxx-xxxx" Then run: deno test --allow-env --allow-net e2e.test.ts `); }