212 lines
7.5 KiB
TypeScript
212 lines
7.5 KiB
TypeScript
import { afterEach, describe, expect, test } from "bun:test";
|
|
|
|
import { exchangeHandoffAndSignIn } from "../src/app/lib/den-handoff";
|
|
import {
|
|
hasActiveDesktopSignInIntent,
|
|
markDesktopSignInInitiated,
|
|
markOrgSelectionPending,
|
|
readOrgSelectionPending,
|
|
} from "../src/app/lib/den-sign-in-intent";
|
|
|
|
const originalWindow = globalThis.window;
|
|
const originalFetch = globalThis.fetch;
|
|
|
|
function memoryStorage(): Storage {
|
|
const map = new Map<string, string>();
|
|
return {
|
|
get length() {
|
|
return map.size;
|
|
},
|
|
clear() {
|
|
map.clear();
|
|
},
|
|
getItem(key: string) {
|
|
return map.get(key) ?? null;
|
|
},
|
|
key(index: number) {
|
|
return Array.from(map.keys())[index] ?? null;
|
|
},
|
|
removeItem(key: string) {
|
|
map.delete(key);
|
|
},
|
|
setItem(key: string, value: string) {
|
|
map.set(key, value);
|
|
},
|
|
};
|
|
}
|
|
|
|
function stubWindow() {
|
|
Object.defineProperty(globalThis, "window", {
|
|
configurable: true,
|
|
value: {
|
|
localStorage: memoryStorage(),
|
|
sessionStorage: memoryStorage(),
|
|
dispatchEvent: () => true,
|
|
},
|
|
});
|
|
}
|
|
|
|
function stubExchangeResponse(payload: Record<string, unknown>) {
|
|
Object.defineProperty(globalThis, "fetch", {
|
|
configurable: true,
|
|
value: (async () => new Response(JSON.stringify(payload), {
|
|
status: 200,
|
|
headers: { "Content-Type": "application/json" },
|
|
})) satisfies typeof fetch,
|
|
});
|
|
}
|
|
|
|
const exchangeUser = { id: "user_invited", email: "invited@example.com", name: "Invited Member" };
|
|
|
|
afterEach(() => {
|
|
Object.defineProperty(globalThis, "window", {
|
|
configurable: true,
|
|
value: originalWindow,
|
|
});
|
|
Object.defineProperty(globalThis, "fetch", {
|
|
configurable: true,
|
|
value: originalFetch,
|
|
});
|
|
});
|
|
|
|
describe("exchangeHandoffAndSignIn", () => {
|
|
test("persists the organization resolved by the handoff exchange", async () => {
|
|
stubWindow();
|
|
stubExchangeResponse({
|
|
token: "tok_handoff",
|
|
user: exchangeUser,
|
|
organization: { id: "org_invited", slug: "invited-org", name: "Invited Org" },
|
|
connectEnabled: false,
|
|
});
|
|
|
|
const result = await exchangeHandoffAndSignIn("grant_test", { baseUrl: "https://den.test" });
|
|
|
|
expect(result.ok).toBe(true);
|
|
if (!result.ok) throw new Error(result.error);
|
|
expect(window.localStorage.getItem("openwork.den.activeOrgId")).toBe("org_invited");
|
|
expect(window.localStorage.getItem("openwork.den.activeOrgSlug")).toBe("invited-org");
|
|
expect(window.localStorage.getItem("openwork.den.activeOrgName")).toBe("Invited Org");
|
|
expect(result.exchange.connectEnabled).toBe(false);
|
|
expect(window.localStorage.getItem("openwork.den.desktopConfig:https://den.test::org_invited"))
|
|
.toBe(JSON.stringify({ connectEnabled: false }));
|
|
});
|
|
|
|
test("prefers the caller-provided organization over the exchange payload", async () => {
|
|
stubWindow();
|
|
stubExchangeResponse({
|
|
token: "tok_handoff",
|
|
user: exchangeUser,
|
|
organization: { id: "org_exchange", slug: "exchange-org", name: "Exchange Org" },
|
|
});
|
|
|
|
const result = await exchangeHandoffAndSignIn("grant_test", {
|
|
baseUrl: "https://den.test",
|
|
activeOrg: { id: "org_bootstrap", slug: "bootstrap-org", name: "Bootstrap Org" },
|
|
});
|
|
|
|
expect(result.ok).toBe(true);
|
|
expect(window.localStorage.getItem("openwork.den.activeOrgId")).toBe("org_bootstrap");
|
|
expect(window.localStorage.getItem("openwork.den.desktopConfig:https://den.test::org_bootstrap")).toBeNull();
|
|
});
|
|
|
|
test("preserves the stored organization when the exchange has none", async () => {
|
|
stubWindow();
|
|
window.localStorage.setItem("openwork.den.activeOrgId", "org_stored");
|
|
window.localStorage.setItem("openwork.den.activeOrgSlug", "stored-org");
|
|
window.localStorage.setItem("openwork.den.activeOrgName", "Stored Org");
|
|
stubExchangeResponse({ token: "tok_handoff", user: exchangeUser });
|
|
|
|
const result = await exchangeHandoffAndSignIn("grant_test", { baseUrl: "https://den.test" });
|
|
|
|
expect(result.ok).toBe(true);
|
|
if (!result.ok) throw new Error(result.error);
|
|
expect(window.localStorage.getItem("openwork.den.activeOrgId")).toBe("org_stored");
|
|
expect(window.localStorage.getItem("openwork.den.activeOrgSlug")).toBe("stored-org");
|
|
expect(window.localStorage.getItem("openwork.den.activeOrgName")).toBe("Stored Org");
|
|
expect(result.exchange.connectEnabled).toBeNull();
|
|
expect(window.localStorage.getItem("openwork.den.desktopConfig:https://den.test::org_stored"))
|
|
.toBeNull();
|
|
});
|
|
|
|
test("a desktop-initiated sign-in defers the org choice to the chooser", async () => {
|
|
stubWindow();
|
|
stubExchangeResponse({
|
|
token: "tok_handoff",
|
|
user: exchangeUser,
|
|
organization: { id: "org_default", slug: "default-org", name: "Default Org" },
|
|
});
|
|
|
|
const result = await exchangeHandoffAndSignIn("grant_test", {
|
|
baseUrl: "https://den.test",
|
|
desktopInitiated: true,
|
|
});
|
|
|
|
expect(result.ok).toBe(true);
|
|
// Token persists, but no organization is committed…
|
|
expect(window.localStorage.getItem("openwork.den.authToken")).toBe("tok_handoff");
|
|
expect(window.localStorage.getItem("openwork.den.activeOrgId")).toBeNull();
|
|
// …and the chooser sees the pending state with the exchange org suggested.
|
|
expect(readOrgSelectionPending()).toEqual({
|
|
pending: true,
|
|
suggestion: { id: "org_default", slug: "default-org", name: "Default Org" },
|
|
});
|
|
});
|
|
|
|
test("the desktop sign-in intent marker classifies an unlabeled handoff", async () => {
|
|
stubWindow();
|
|
markDesktopSignInInitiated();
|
|
stubExchangeResponse({
|
|
token: "tok_handoff",
|
|
user: exchangeUser,
|
|
organization: { id: "org_default", slug: "default-org", name: "Default Org" },
|
|
});
|
|
|
|
const result = await exchangeHandoffAndSignIn("grant_test", { baseUrl: "https://den.test" });
|
|
|
|
expect(result.ok).toBe(true);
|
|
expect(window.localStorage.getItem("openwork.den.activeOrgId")).toBeNull();
|
|
expect(readOrgSelectionPending().pending).toBe(true);
|
|
// The marker is consumed: a later remote handoff is not reclassified.
|
|
expect(hasActiveDesktopSignInIntent()).toBe(false);
|
|
});
|
|
|
|
test("an explicitly scoped org connects straight through even when desktop-initiated", async () => {
|
|
stubWindow();
|
|
markDesktopSignInInitiated();
|
|
stubExchangeResponse({
|
|
token: "tok_handoff",
|
|
user: exchangeUser,
|
|
organization: { id: "org_exchange", slug: "exchange-org", name: "Exchange Org" },
|
|
});
|
|
|
|
const result = await exchangeHandoffAndSignIn("grant_test", {
|
|
baseUrl: "https://den.test",
|
|
activeOrg: { id: "org_invite", slug: "invite-org", name: "Invite Org" },
|
|
desktopInitiated: true,
|
|
});
|
|
|
|
expect(result.ok).toBe(true);
|
|
expect(window.localStorage.getItem("openwork.den.activeOrgId")).toBe("org_invite");
|
|
expect(readOrgSelectionPending().pending).toBe(false);
|
|
});
|
|
|
|
test("a remote handoff clears stale pending state and commits its organization", async () => {
|
|
stubWindow();
|
|
markOrgSelectionPending({ id: "org_stale", slug: null, name: null });
|
|
stubExchangeResponse({
|
|
token: "tok_handoff",
|
|
user: exchangeUser,
|
|
organization: { id: "org_remote", slug: "remote-org", name: "Remote Org" },
|
|
});
|
|
|
|
const result = await exchangeHandoffAndSignIn("grant_test", {
|
|
baseUrl: "https://den.test",
|
|
desktopInitiated: false,
|
|
});
|
|
|
|
expect(result.ok).toBe(true);
|
|
expect(window.localStorage.getItem("openwork.den.activeOrgId")).toBe("org_remote");
|
|
expect(readOrgSelectionPending()).toEqual({ pending: false, suggestion: null });
|
|
});
|
|
});
|