93 lines
3.1 KiB
TypeScript
93 lines
3.1 KiB
TypeScript
import { describe, expect, it, vi } from "vitest";
|
|
|
|
import {
|
|
createAnonymousSessionAdapter,
|
|
createDemoSessionAdapter,
|
|
createExternalAuthSessionAdapter,
|
|
} from "../../src/adapters/auth/external-session-adapter.ts";
|
|
|
|
describe("external AuthSessionPort adapter", () => {
|
|
it("attaches opaque credentials without exposing a token-shaped session", async () => {
|
|
const adapter = createExternalAuthSessionAdapter({
|
|
readState: () => "authenticated",
|
|
subscribe: () => () => {},
|
|
beginSignIn: async () => {},
|
|
signOut: async () => {},
|
|
attachCredential: async () => ({
|
|
headers: { Authorization: "Bearer opaque" },
|
|
}),
|
|
recoverSession: async () => "restored",
|
|
notifyUnauthenticated: vi.fn(),
|
|
});
|
|
|
|
const patch = await adapter.credentialPatch({
|
|
origin: "https://api.test",
|
|
method: "GET",
|
|
operationId: "GET_RESOURCE",
|
|
});
|
|
expect(patch.headers.authorization).toBe("Bearer opaque");
|
|
expect(adapter.getState()).toBe("authenticated");
|
|
expect(adapter).not.toHaveProperty("accessToken");
|
|
expect(adapter).not.toHaveProperty("refreshToken");
|
|
});
|
|
|
|
it("fails invalid recovery states closed", async () => {
|
|
const adapter = createExternalAuthSessionAdapter({
|
|
readState: () => "authenticated",
|
|
subscribe: () => () => {},
|
|
beginSignIn: async () => {},
|
|
signOut: async () => {},
|
|
attachCredential: async () => ({ headers: {} }),
|
|
// @ts-expect-error Deliberately violates the external-owner contract.
|
|
recoverSession: async () => "unexpected",
|
|
notifyUnauthenticated: vi.fn(),
|
|
});
|
|
|
|
await expect(adapter.recover()).rejects.toThrow("invalid recovery state");
|
|
});
|
|
|
|
it("rejects credential patches that can alter transport-owned headers", async () => {
|
|
const adapter = createExternalAuthSessionAdapter({
|
|
readState: () => "authenticated",
|
|
subscribe: () => () => {},
|
|
beginSignIn: async () => {},
|
|
signOut: async () => {},
|
|
attachCredential: async () => ({
|
|
headers: { Host: "attacker.test" },
|
|
}),
|
|
recoverSession: async () => "restored",
|
|
notifyUnauthenticated: vi.fn(),
|
|
});
|
|
|
|
await expect(
|
|
adapter.credentialPatch({
|
|
origin: "https://api.test",
|
|
method: "GET",
|
|
operationId: "GET_RESOURCE",
|
|
}),
|
|
).rejects.toThrow("forbidden credential patch");
|
|
});
|
|
|
|
it("provides a safe anonymous adapter", async () => {
|
|
const adapter = createAnonymousSessionAdapter();
|
|
expect(adapter.getState()).toBe("unauthenticated");
|
|
await expect(adapter.recover()).resolves.toBe("no-session");
|
|
});
|
|
|
|
it("provides a reactive credential-free demo seam", async () => {
|
|
const adapter = createDemoSessionAdapter();
|
|
let notifications = 0;
|
|
const unsubscribe = adapter.subscribe(() => {
|
|
notifications += 1;
|
|
});
|
|
|
|
expect(adapter.getState()).toBe("unauthenticated");
|
|
await adapter.beginSignIn();
|
|
expect(adapter.getState()).toBe("authenticated");
|
|
await adapter.signOut();
|
|
expect(adapter.getState()).toBe("unauthenticated");
|
|
expect(notifications).toBe(2);
|
|
unsubscribe();
|
|
});
|
|
});
|