mirror of
https://github.com/earendil-works/pi.git
synced 2026-06-18 15:54:04 +08:00
130 lines
4.3 KiB
TypeScript
130 lines
4.3 KiB
TypeScript
import { Buffer } from "node:buffer";
|
|
import { describe, expect, it } from "vitest";
|
|
import {
|
|
getSessionSyncWatermark,
|
|
refreshSessionSyncAccessToken,
|
|
SessionSyncApiError,
|
|
startSessionSyncDeviceFlow,
|
|
uploadSessionAnalytics,
|
|
} from "../src/core/session-sync-api.ts";
|
|
|
|
function jsonResponse(body: unknown, status = 200): Response {
|
|
return new Response(JSON.stringify(body), { status, headers: { "Content-Type": "application/json" } });
|
|
}
|
|
|
|
describe("session sync api", () => {
|
|
it("starts the OAuth device flow with the session sync scope", async () => {
|
|
let request: Request | undefined;
|
|
const fetchMock: typeof fetch = async (input, init) => {
|
|
request = new Request(input, init);
|
|
return jsonResponse({
|
|
device_code: "pigd_1",
|
|
user_code: "ABCD-EFGH",
|
|
verification_uri: "https://pi.dev/pair",
|
|
verification_uri_complete: "https://pi.dev/pair?code=ABCD-EFGH",
|
|
expires_in: 300,
|
|
interval: 2,
|
|
});
|
|
};
|
|
|
|
const response = await startSessionSyncDeviceFlow("00000000-0000-4000-8000-000000000000", {
|
|
baseUrl: "https://example.test/",
|
|
fetch: fetchMock,
|
|
});
|
|
|
|
expect(response.device_code).toBe("pigd_1");
|
|
expect(request?.url).toBe("https://example.test/api/oauth/device");
|
|
expect(request?.method).toBe("POST");
|
|
const body = new URLSearchParams(await request?.text());
|
|
expect(body.get("client_id")).toBe("pi-coding-agent");
|
|
expect(body.get("scope")).toBe("session_sync offline_access");
|
|
expect(body.get("device_id")).toBe("00000000-0000-4000-8000-000000000000");
|
|
});
|
|
|
|
it("refreshes tokens and reads watermarks", async () => {
|
|
const urls: string[] = [];
|
|
const fetchMock: typeof fetch = async (input, init) => {
|
|
const request = new Request(input, init);
|
|
urls.push(request.url);
|
|
if (request.url.endsWith("/api/oauth/token")) {
|
|
return jsonResponse({
|
|
token_type: "Bearer",
|
|
access_token: "access-2",
|
|
refresh_token: "refresh-2",
|
|
expires_in: 86400,
|
|
scope: "session_sync offline_access",
|
|
});
|
|
}
|
|
expect(request.headers.get("Authorization")).toBe("Bearer access-2");
|
|
return jsonResponse({ ok: true, watermark: "2026-01-01T00:00:00.000Z" });
|
|
};
|
|
|
|
const token = await refreshSessionSyncAccessToken("refresh-1", {
|
|
baseUrl: "https://example.test",
|
|
fetch: fetchMock,
|
|
});
|
|
const watermark = await getSessionSyncWatermark(token.access_token, "device-1", {
|
|
baseUrl: "https://example.test",
|
|
fetch: fetchMock,
|
|
});
|
|
|
|
expect(token.refresh_token).toBe("refresh-2");
|
|
expect(watermark.watermark).toBe("2026-01-01T00:00:00.000Z");
|
|
expect(urls).toEqual([
|
|
"https://example.test/api/oauth/token",
|
|
"https://example.test/analytics/sessions/device-1",
|
|
]);
|
|
});
|
|
|
|
it("uploads compressed NDJSON with sync headers and surfaces API errors", async () => {
|
|
let request: Request | undefined;
|
|
const fetchMock: typeof fetch = async (input, init) => {
|
|
request = new Request(input, init);
|
|
return jsonResponse(
|
|
{
|
|
ok: true,
|
|
records_received: 1,
|
|
first_record_timestamp: "2026-01-01T00:00:00.000Z",
|
|
last_record_timestamp: "2026-01-01T00:00:00.000Z",
|
|
received_bytes: 10,
|
|
watermark: "2026-01-02T00:00:00.000Z",
|
|
},
|
|
201,
|
|
);
|
|
};
|
|
|
|
const response = await uploadSessionAnalytics({
|
|
baseUrl: "https://example.test",
|
|
fetch: fetchMock,
|
|
accessToken: "access-1",
|
|
deviceId: "device-1",
|
|
watermark: "2026-01-02T00:00:00.000Z",
|
|
idempotencyKey: "retry-key",
|
|
body: Buffer.from("payload"),
|
|
contentEncoding: "zstd",
|
|
});
|
|
|
|
expect(response.records_received).toBe(1);
|
|
expect(request?.headers.get("Authorization")).toBe("Bearer access-1");
|
|
expect(request?.headers.get("Content-Type")).toBe("application/x-ndjson");
|
|
expect(request?.headers.get("Content-Encoding")).toBe("zstd");
|
|
expect(request?.headers.get("Pi-Sync-Watermark")).toBe("2026-01-02T00:00:00.000Z");
|
|
expect(request?.headers.get("Idempotency-Key")).toBe("retry-key");
|
|
|
|
const failingFetch: typeof fetch = async () =>
|
|
jsonResponse({ ok: false, error: "invalid_payload", description: "bad line" }, 400);
|
|
await expect(
|
|
uploadSessionAnalytics({
|
|
baseUrl: "https://example.test",
|
|
fetch: failingFetch,
|
|
accessToken: "access-1",
|
|
deviceId: "device-1",
|
|
watermark: "2026-01-02T00:00:00.000Z",
|
|
idempotencyKey: "retry-key",
|
|
body: Buffer.from("payload"),
|
|
contentEncoding: "zstd",
|
|
}),
|
|
).rejects.toMatchObject(new SessionSyncApiError(400, "invalid_payload", "bad line"));
|
|
});
|
|
});
|