Files
edgeever/apps/web/src/lib/api.test.mjs
T

435 lines
14 KiB
JavaScript

import { afterAll, describe, expect, test } from "bun:test";
const storage = new Map();
const calls = [];
const events = [];
let completeSave;
let secureSessionToken = "";
let failSecureSessionWrite = false;
const originalWindow = Object.getOwnPropertyDescriptor(globalThis, "window");
// Must be installed before importing modules that read `window` at load time.
globalThis.window = {
location: { hostname: "notes.example.com", origin: "https://notes.example.com" },
edgeeverDesktop: {
isAvailable: true,
apiBaseUrl: "",
setApiBaseUrl: async (value) => {
calls.push(["bridge:start", value]);
if (!value) {
calls.push(["bridge:complete", value]);
return value;
}
await new Promise((resolve) => {
completeSave = resolve;
});
calls.push(["bridge:complete", value]);
return value;
},
getSessionToken: () => secureSessionToken,
setSessionToken: async (value) => {
if (failSecureSessionWrite) throw new Error("secure storage unavailable");
secureSessionToken = value;
return { stored: Boolean(value) };
},
clearSessionToken: async () => {
secureSessionToken = "";
return { stored: false };
},
},
localStorage: {
getItem: (key) => storage.get(key) ?? null,
setItem: (key, value) => {
calls.push(["storage", value]);
storage.set(key, value);
},
removeItem: (key) => {
storage.delete(key);
},
},
dispatchEvent: (event) => {
events.push(event.type);
return true;
},
};
afterAll(() => {
if (originalWindow) {
Object.defineProperty(globalThis, "window", originalWindow);
} else {
delete globalThis.window;
}
});
const {
ApiRequestError,
DESKTOP_API_BASE_URL_STORAGE_KEY,
api,
cacheDesktopSession,
clearCachedDesktopSession,
getConfiguredDesktopApiBaseUrl,
getCachedDesktopSession,
saveDesktopApiBaseUrl,
} = await import("./api.ts");
describe("desktop instance setup", () => {
test("can retry with a valid URL after invalid input", async () => {
await expect(saveDesktopApiBaseUrl("ftp://example.com")).rejects.toThrow();
const saving = saveDesktopApiBaseUrl(" https://notes.example.com/ ");
await Promise.resolve();
expect(calls).toEqual([["bridge:start", "https://notes.example.com"]]);
expect(storage.has(DESKTOP_API_BASE_URL_STORAGE_KEY)).toBe(false);
completeSave();
await expect(saving).resolves.toBe("https://notes.example.com");
expect(calls).toEqual([
["bridge:start", "https://notes.example.com"],
["bridge:complete", "https://notes.example.com"],
["storage", "https://notes.example.com"],
]);
expect(storage.get(DESKTOP_API_BASE_URL_STORAGE_KEY)).toBe("https://notes.example.com");
});
test("maps the App Review demo alias to the public instance", async () => {
calls.length = 0;
const saving = saveDesktopApiBaseUrl("demo");
await Promise.resolve();
expect(calls).toEqual([["bridge:start", "https://demo.edgeever.org"]]);
completeSave();
await expect(saving).resolves.toBe("https://demo.edgeever.org");
});
test("prefixes https when the instance host has no protocol", async () => {
calls.length = 0;
const saving = saveDesktopApiBaseUrl(" example.workers.dev/ ");
await Promise.resolve();
expect(calls).toEqual([["bridge:start", "https://example.workers.dev"]]);
completeSave();
await expect(saving).resolves.toBe("https://example.workers.dev");
expect(storage.get(DESKTOP_API_BASE_URL_STORAGE_KEY)).toBe("https://example.workers.dev");
});
test("keeps an explicit http instance URL", async () => {
calls.length = 0;
const saving = saveDesktopApiBaseUrl("http://127.0.0.1:8787/");
await Promise.resolve();
expect(calls).toEqual([["bridge:start", "http://127.0.0.1:8787"]]);
completeSave();
await expect(saving).resolves.toBe("http://127.0.0.1:8787");
});
test("clears the cached session when the login form changes instances", async () => {
calls.length = 0;
window.edgeeverDesktop.apiBaseUrl = "https://notes.example.com";
storage.set(DESKTOP_API_BASE_URL_STORAGE_KEY, "https://notes.example.com");
await cacheDesktopSession({
authRequired: true,
authenticated: true,
demoMode: false,
sessionToken: "old-instance-session",
user: { id: "user-1", username: "admin", displayName: null, role: "owner" },
});
calls.length = 0;
const saving = saveDesktopApiBaseUrl("https://other.example.com");
await Promise.resolve();
expect(getCachedDesktopSession()).toBeNull();
expect(calls).toEqual([
["bridge:start", "https://other.example.com"],
]);
completeSave();
await expect(saving).resolves.toBe("https://other.example.com");
expect(storage.get(DESKTOP_API_BASE_URL_STORAGE_KEY)).toBe("https://other.example.com");
expect(getConfiguredDesktopApiBaseUrl()).toBe("https://other.example.com");
window.edgeeverDesktop.apiBaseUrl = "";
});
test("preserves the desktop token when refreshing the same authenticated session", async () => {
await cacheDesktopSession({
authRequired: true,
authenticated: true,
demoMode: false,
sessionToken: "desktop-session-token",
user: { id: "user-1", username: "admin", displayName: null, role: "owner" },
});
await cacheDesktopSession({
authRequired: true,
authenticated: true,
demoMode: false,
user: { id: "user-1", username: "admin", displayName: "Owner", role: "owner" },
});
expect(getCachedDesktopSession()).toEqual({
authRequired: true,
authenticated: true,
demoMode: false,
user: { id: "user-1", username: "admin", displayName: "Owner", role: "owner" },
});
});
test("does not carry a desktop token into a different account session", async () => {
await cacheDesktopSession({
authRequired: true,
authenticated: true,
demoMode: false,
sessionToken: "user-1-session-token",
user: { id: "user-1", username: "admin", displayName: null, role: "owner" },
});
await cacheDesktopSession({
authRequired: true,
authenticated: true,
demoMode: false,
user: { id: "user-2", username: "member", displayName: null, role: "member" },
});
expect(getCachedDesktopSession()?.sessionToken).toBeUndefined();
});
test("migrates a legacy localStorage token into desktop secure storage", async () => {
clearCachedDesktopSession();
await Promise.resolve();
storage.set("edgeever.desktop.session", JSON.stringify({
authRequired: true,
authenticated: true,
demoMode: false,
sessionToken: "legacy-session-token",
user: { id: "user-1", username: "admin", displayName: null, role: "owner" },
}));
const requests = [];
globalThis.fetch = async (_url, init) => {
requests.push(new Headers(init?.headers).get("Authorization"));
return Response.json({
authRequired: true,
authenticated: true,
demoMode: false,
user: { id: "user-1", username: "admin", displayName: null, role: "owner" },
});
};
const session = await api.getSession();
await cacheDesktopSession(session);
expect(requests).toEqual(["Bearer legacy-session-token"]);
expect(secureSessionToken).toBe("legacy-session-token");
expect(getCachedDesktopSession()?.sessionToken).toBeUndefined();
});
test("keeps a localStorage fallback when secure token persistence fails", async () => {
failSecureSessionWrite = true;
await cacheDesktopSession({
authRequired: true,
authenticated: true,
demoMode: false,
sessionToken: "fallback-session-token",
user: { id: "user-1", username: "admin", displayName: null, role: "owner" },
});
failSecureSessionWrite = false;
expect(getCachedDesktopSession()?.sessionToken).toBe("fallback-session-token");
});
test("keeps secure credentials out of unauthenticated session snapshots", async () => {
await cacheDesktopSession({
authRequired: true,
authenticated: true,
demoMode: false,
sessionToken: "desktop-session-token",
user: { id: "user-1", username: "admin", displayName: null, role: "owner" },
});
await Promise.resolve();
await cacheDesktopSession({
authRequired: true,
authenticated: false,
demoMode: false,
user: null,
});
expect(secureSessionToken).toBe("desktop-session-token");
expect(getCachedDesktopSession()).toEqual({
authRequired: true,
authenticated: false,
demoMode: false,
user: null,
});
});
test("clears a secure token only after the server rejects that exact credential", async () => {
events.length = 0;
await cacheDesktopSession({
authRequired: true,
authenticated: true,
demoMode: false,
sessionToken: "rejected-session-token",
user: { id: "user-1", username: "admin", displayName: null, role: "owner" },
});
await Promise.resolve();
globalThis.fetch = async () => Response.json({
authRequired: true,
authenticated: false,
demoMode: false,
user: null,
});
await expect(api.getSession()).resolves.toMatchObject({ authenticated: false });
await Promise.resolve();
expect(secureSessionToken).toBe("");
expect(events).toEqual(["edgeever:unauthorized"]);
globalThis.fetch = async () => Response.json({
authRequired: true,
authenticated: true,
demoMode: false,
sessionToken: "replacement-session-token",
user: { id: "user-1", username: "admin", displayName: null, role: "owner" },
});
await cacheDesktopSession(await api.login({ username: "admin", password: "secret" }));
});
test("uses the desktop session token and stops network retries after a 401", async () => {
calls.length = 0;
events.length = 0;
storage.set(DESKTOP_API_BASE_URL_STORAGE_KEY, "https://notes.example.com");
await cacheDesktopSession({
authRequired: true,
authenticated: true,
demoMode: false,
sessionToken: "desktop-session-token",
user: { id: "user-1", username: "admin", displayName: null, role: "owner" },
});
const requests = [];
globalThis.fetch = async (url, init) => {
requests.push({ url, authorization: new Headers(init?.headers).get("Authorization") });
return new Response(JSON.stringify({ error: { code: "unauthorized", message: "Authentication required" } }), {
status: 401,
headers: { "Content-Type": "application/json" },
});
};
await expect(api.syncBootstrap({ limit: 200 })).rejects.toMatchObject({ status: 401 });
await expect(api.syncBootstrap({ limit: 200 })).rejects.toMatchObject({ status: 401 });
expect(requests).toEqual([{
url: "https://notes.example.com/api/v1/sync/bootstrap?limit=200",
authorization: "Bearer desktop-session-token",
}]);
expect(events).toEqual(["edgeever:unauthorized"]);
expect(getCachedDesktopSession()).toBeNull();
globalThis.fetch = async (url, init) => {
requests.push({ url, authorization: new Headers(init?.headers).get("Authorization") });
return Response.json({
authRequired: true,
authenticated: true,
demoMode: false,
sessionToken: "replacement-session-token",
user: { id: "user-1", username: "admin", displayName: null, role: "owner" },
});
};
const replacement = await api.login({ username: "admin", password: "secret" });
await cacheDesktopSession(replacement);
await api.syncBootstrap({ limit: 200 });
expect(requests.at(-1)).toEqual({
url: "https://notes.example.com/api/v1/sync/bootstrap?limit=200",
authorization: "Bearer replacement-session-token",
});
});
test("does not clear a replacement session when an older desktop token is rejected late", async () => {
events.length = 0;
storage.set(DESKTOP_API_BASE_URL_STORAGE_KEY, "https://notes.example.com");
await cacheDesktopSession({
authRequired: true,
authenticated: true,
demoMode: false,
sessionToken: "stale-session-token",
user: { id: "user-1", username: "admin", displayName: null, role: "owner" },
});
let releaseRequest;
globalThis.fetch = async () => {
await new Promise((resolve) => {
releaseRequest = resolve;
});
return new Response(JSON.stringify({ error: { code: "unauthorized", message: "Authentication required" } }), {
status: 401,
headers: { "Content-Type": "application/json" },
});
};
const staleRequest = api.syncBootstrap({ limit: 200 });
await Promise.resolve();
await cacheDesktopSession({
authRequired: true,
authenticated: true,
demoMode: false,
sessionToken: "current-session-token",
user: { id: "user-1", username: "admin", displayName: "Owner", role: "owner" },
});
releaseRequest();
await expect(staleRequest).rejects.toMatchObject({ status: 401 });
await Promise.resolve();
expect(secureSessionToken).toBe("current-session-token");
expect(getCachedDesktopSession()).toMatchObject({ authenticated: true, user: { id: "user-1" } });
expect(events).toEqual([]);
});
test("preserves Cloudflare response diagnostics for login failures", async () => {
globalThis.fetch = async () => new Response("<html>challenge</html>", {
status: 403,
headers: {
"CF-Mitigated": "challenge",
"CF-Ray": "abc123-SJC",
"Content-Type": "text/html",
},
});
try {
await api.login({ username: "admin", password: "secret" });
throw new Error("Expected login to fail");
} catch (error) {
expect(error).toBeInstanceOf(ApiRequestError);
expect(error).toMatchObject({
status: 403,
responseDiagnostics: {
cloudflareMitigated: true,
isEdgeEverApiError: false,
rayId: "abc123-SJC",
},
});
}
});
test("does not opt the web proxy into AI streaming", async () => {
const requestBodies = [];
globalThis.fetch = async (url, init) => {
if (String(url).includes("/api/v1/ai/direct-target")) {
return new Response("{}", { status: 404 });
}
requestBodies.push(JSON.parse(String(init?.body)));
return new Response('data: {"type":"finish"}\n\n', {
headers: { "Content-Type": "text/event-stream" },
});
};
await api.streamAiGeneration(
{ action: "summarize", title: "Note", contentMarkdown: "Body" },
{ onEvent: () => {} },
);
expect(requestBodies).toHaveLength(1);
expect(requestBodies[0].stream).toBeUndefined();
});
});