Spaces:
Sleeping
Sleeping
| import crypto from "node:crypto"; | |
| import fs from "node:fs/promises"; | |
| import os from "node:os"; | |
| import path from "node:path"; | |
| import { describe, expect, it, vi } from "vitest"; | |
| import { resolveOAuthDir } from "../config/paths.js"; | |
| import { listChannelPairingRequests, upsertChannelPairingRequest } from "./pairing-store.js"; | |
| async function withTempStateDir<T>(fn: (stateDir: string) => Promise<T>) { | |
| const previous = process.env.OPENCLAW_STATE_DIR; | |
| const dir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-pairing-")); | |
| process.env.OPENCLAW_STATE_DIR = dir; | |
| try { | |
| return await fn(dir); | |
| } finally { | |
| if (previous === undefined) { | |
| delete process.env.OPENCLAW_STATE_DIR; | |
| } else { | |
| process.env.OPENCLAW_STATE_DIR = previous; | |
| } | |
| await fs.rm(dir, { recursive: true, force: true }); | |
| } | |
| } | |
| describe("pairing store", () => { | |
| it("reuses pending code and reports created=false", async () => { | |
| await withTempStateDir(async () => { | |
| const first = await upsertChannelPairingRequest({ | |
| channel: "discord", | |
| id: "u1", | |
| }); | |
| const second = await upsertChannelPairingRequest({ | |
| channel: "discord", | |
| id: "u1", | |
| }); | |
| expect(first.created).toBe(true); | |
| expect(second.created).toBe(false); | |
| expect(second.code).toBe(first.code); | |
| const list = await listChannelPairingRequests("discord"); | |
| expect(list).toHaveLength(1); | |
| expect(list[0]?.code).toBe(first.code); | |
| }); | |
| }); | |
| it("expires pending requests after TTL", async () => { | |
| await withTempStateDir(async (stateDir) => { | |
| const created = await upsertChannelPairingRequest({ | |
| channel: "signal", | |
| id: "+15550001111", | |
| }); | |
| expect(created.created).toBe(true); | |
| const oauthDir = resolveOAuthDir(process.env, stateDir); | |
| const filePath = path.join(oauthDir, "signal-pairing.json"); | |
| const raw = await fs.readFile(filePath, "utf8"); | |
| const parsed = JSON.parse(raw) as { | |
| requests?: Array<Record<string, unknown>>; | |
| }; | |
| const expiredAt = new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString(); | |
| const requests = (parsed.requests ?? []).map((entry) => ({ | |
| ...entry, | |
| createdAt: expiredAt, | |
| lastSeenAt: expiredAt, | |
| })); | |
| await fs.writeFile( | |
| filePath, | |
| `${JSON.stringify({ version: 1, requests }, null, 2)}\n`, | |
| "utf8", | |
| ); | |
| const list = await listChannelPairingRequests("signal"); | |
| expect(list).toHaveLength(0); | |
| const next = await upsertChannelPairingRequest({ | |
| channel: "signal", | |
| id: "+15550001111", | |
| }); | |
| expect(next.created).toBe(true); | |
| }); | |
| }); | |
| it("regenerates when a generated code collides", async () => { | |
| await withTempStateDir(async () => { | |
| const spy = vi.spyOn(crypto, "randomInt"); | |
| try { | |
| spy.mockReturnValue(0); | |
| const first = await upsertChannelPairingRequest({ | |
| channel: "telegram", | |
| id: "123", | |
| }); | |
| expect(first.code).toBe("AAAAAAAA"); | |
| const sequence = Array(8).fill(0).concat(Array(8).fill(1)); | |
| let idx = 0; | |
| spy.mockImplementation(() => sequence[idx++] ?? 1); | |
| const second = await upsertChannelPairingRequest({ | |
| channel: "telegram", | |
| id: "456", | |
| }); | |
| expect(second.code).toBe("BBBBBBBB"); | |
| } finally { | |
| spy.mockRestore(); | |
| } | |
| }); | |
| }); | |
| it("caps pending requests at the default limit", async () => { | |
| await withTempStateDir(async () => { | |
| const ids = ["+15550000001", "+15550000002", "+15550000003"]; | |
| for (const id of ids) { | |
| const created = await upsertChannelPairingRequest({ | |
| channel: "whatsapp", | |
| id, | |
| }); | |
| expect(created.created).toBe(true); | |
| } | |
| const blocked = await upsertChannelPairingRequest({ | |
| channel: "whatsapp", | |
| id: "+15550000004", | |
| }); | |
| expect(blocked.created).toBe(false); | |
| const list = await listChannelPairingRequests("whatsapp"); | |
| const listIds = list.map((entry) => entry.id); | |
| expect(listIds).toHaveLength(3); | |
| expect(listIds).toContain("+15550000001"); | |
| expect(listIds).toContain("+15550000002"); | |
| expect(listIds).toContain("+15550000003"); | |
| expect(listIds).not.toContain("+15550000004"); | |
| }); | |
| }); | |
| }); | |