Files
grimoire/src/lib/auth-state-machine.test.ts
2025-12-13 15:49:04 +01:00

327 lines
9.9 KiB
TypeScript

import { describe, it, expect } from "vitest";
import { transitionAuthState, type AuthEvent } from "./auth-state-machine";
import type { AuthStatus } from "@/types/relay-state";
describe("Auth State Machine", () => {
describe("none state transitions", () => {
it("should transition to challenge_received when receiving challenge with ask preference", () => {
const result = transitionAuthState("none", {
type: "CHALLENGE_RECEIVED",
challenge: "test-challenge",
preference: "ask",
});
expect(result.newStatus).toBe("challenge_received");
expect(result.shouldAutoAuth).toBe(false);
expect(result.clearChallenge).toBe(false);
});
it("should transition to authenticating with always preference", () => {
const result = transitionAuthState("none", {
type: "CHALLENGE_RECEIVED",
challenge: "test-challenge",
preference: "always",
});
expect(result.newStatus).toBe("authenticating");
expect(result.shouldAutoAuth).toBe(true);
expect(result.clearChallenge).toBe(false);
});
it("should transition to rejected with never preference", () => {
const result = transitionAuthState("none", {
type: "CHALLENGE_RECEIVED",
challenge: "test-challenge",
preference: "never",
});
expect(result.newStatus).toBe("rejected");
expect(result.shouldAutoAuth).toBe(false);
expect(result.clearChallenge).toBe(true);
});
it("should default to ask when no preference provided", () => {
const result = transitionAuthState("none", {
type: "CHALLENGE_RECEIVED",
challenge: "test-challenge",
});
expect(result.newStatus).toBe("challenge_received");
expect(result.shouldAutoAuth).toBe(false);
});
it("should not transition on other events", () => {
const result = transitionAuthState("none", { type: "AUTH_SUCCESS" });
expect(result.newStatus).toBe("none");
});
});
describe("challenge_received state transitions", () => {
it("should transition to authenticating when user accepts", () => {
const result = transitionAuthState("challenge_received", {
type: "USER_ACCEPTED",
});
expect(result.newStatus).toBe("authenticating");
expect(result.shouldAutoAuth).toBe(false);
expect(result.clearChallenge).toBe(false);
});
it("should transition to rejected when user rejects", () => {
const result = transitionAuthState("challenge_received", {
type: "USER_REJECTED",
});
expect(result.newStatus).toBe("rejected");
expect(result.shouldAutoAuth).toBe(false);
expect(result.clearChallenge).toBe(true);
});
it("should transition to none when disconnected", () => {
const result = transitionAuthState("challenge_received", {
type: "DISCONNECTED",
});
expect(result.newStatus).toBe("none");
expect(result.clearChallenge).toBe(true);
});
});
describe("authenticating state transitions", () => {
it("should transition to authenticated on success", () => {
const result = transitionAuthState("authenticating", {
type: "AUTH_SUCCESS",
});
expect(result.newStatus).toBe("authenticated");
expect(result.shouldAutoAuth).toBe(false);
expect(result.clearChallenge).toBe(true);
});
it("should transition to failed on auth failure", () => {
const result = transitionAuthState("authenticating", {
type: "AUTH_FAILED",
});
expect(result.newStatus).toBe("failed");
expect(result.shouldAutoAuth).toBe(false);
expect(result.clearChallenge).toBe(true);
});
it("should transition to none when disconnected", () => {
const result = transitionAuthState("authenticating", {
type: "DISCONNECTED",
});
expect(result.newStatus).toBe("none");
expect(result.clearChallenge).toBe(true);
});
});
describe("authenticated state transitions", () => {
it("should transition to none when disconnected", () => {
const result = transitionAuthState("authenticated", {
type: "DISCONNECTED",
});
expect(result.newStatus).toBe("none");
expect(result.clearChallenge).toBe(true);
});
it("should handle new challenge with always preference", () => {
const result = transitionAuthState("authenticated", {
type: "CHALLENGE_RECEIVED",
challenge: "new-challenge",
preference: "always",
});
expect(result.newStatus).toBe("authenticating");
expect(result.shouldAutoAuth).toBe(true);
});
it("should transition to challenge_received for new challenge", () => {
const result = transitionAuthState("authenticated", {
type: "CHALLENGE_RECEIVED",
challenge: "new-challenge",
preference: "ask",
});
expect(result.newStatus).toBe("challenge_received");
expect(result.shouldAutoAuth).toBe(false);
});
it("should stay authenticated on other events", () => {
const result = transitionAuthState("authenticated", {
type: "AUTH_SUCCESS",
});
expect(result.newStatus).toBe("authenticated");
});
});
describe("failed state transitions", () => {
it("should transition to challenge_received on new challenge", () => {
const result = transitionAuthState("failed", {
type: "CHALLENGE_RECEIVED",
challenge: "retry-challenge",
preference: "ask",
});
expect(result.newStatus).toBe("challenge_received");
});
it("should auto-auth on new challenge with always preference", () => {
const result = transitionAuthState("failed", {
type: "CHALLENGE_RECEIVED",
challenge: "retry-challenge",
preference: "always",
});
expect(result.newStatus).toBe("authenticating");
expect(result.shouldAutoAuth).toBe(true);
});
it("should transition to rejected with never preference", () => {
const result = transitionAuthState("failed", {
type: "CHALLENGE_RECEIVED",
challenge: "retry-challenge",
preference: "never",
});
expect(result.newStatus).toBe("rejected");
});
it("should transition to none when disconnected", () => {
const result = transitionAuthState("failed", {
type: "DISCONNECTED",
});
expect(result.newStatus).toBe("none");
});
});
describe("rejected state transitions", () => {
it("should handle new challenge after rejection", () => {
const result = transitionAuthState("rejected", {
type: "CHALLENGE_RECEIVED",
challenge: "new-challenge",
preference: "ask",
});
expect(result.newStatus).toBe("challenge_received");
});
it("should transition to none when disconnected", () => {
const result = transitionAuthState("rejected", {
type: "DISCONNECTED",
});
expect(result.newStatus).toBe("none");
});
});
describe("edge cases", () => {
it("should handle missing preference as ask", () => {
const result = transitionAuthState("none", {
type: "CHALLENGE_RECEIVED",
challenge: "test",
});
expect(result.newStatus).toBe("challenge_received");
expect(result.shouldAutoAuth).toBe(false);
});
it("should not transition on invalid events for each state", () => {
const states: AuthStatus[] = [
"none",
"challenge_received",
"authenticating",
"authenticated",
"failed",
"rejected",
];
states.forEach((state) => {
const result = transitionAuthState(state, {
type: "USER_ACCEPTED",
} as AuthEvent);
// Should either stay in same state or have a valid transition
expect(result.newStatus).toBeTruthy();
});
});
});
describe("clearChallenge flag", () => {
it("should clear challenge on auth success", () => {
const result = transitionAuthState("authenticating", {
type: "AUTH_SUCCESS",
});
expect(result.clearChallenge).toBe(true);
});
it("should clear challenge on auth failure", () => {
const result = transitionAuthState("authenticating", {
type: "AUTH_FAILED",
});
expect(result.clearChallenge).toBe(true);
});
it("should clear challenge on rejection", () => {
const result = transitionAuthState("challenge_received", {
type: "USER_REJECTED",
});
expect(result.clearChallenge).toBe(true);
});
it("should clear challenge on disconnect", () => {
const result = transitionAuthState("authenticated", {
type: "DISCONNECTED",
});
expect(result.clearChallenge).toBe(true);
});
it("should not clear challenge when receiving new one", () => {
const result = transitionAuthState("none", {
type: "CHALLENGE_RECEIVED",
challenge: "test",
});
expect(result.clearChallenge).toBe(false);
});
});
describe("shouldAutoAuth flag", () => {
it("should be true only with always preference", () => {
const result = transitionAuthState("none", {
type: "CHALLENGE_RECEIVED",
challenge: "test",
preference: "always",
});
expect(result.shouldAutoAuth).toBe(true);
});
it("should be false with ask preference", () => {
const result = transitionAuthState("none", {
type: "CHALLENGE_RECEIVED",
challenge: "test",
preference: "ask",
});
expect(result.shouldAutoAuth).toBe(false);
});
it("should be false with never preference", () => {
const result = transitionAuthState("none", {
type: "CHALLENGE_RECEIVED",
challenge: "test",
preference: "never",
});
expect(result.shouldAutoAuth).toBe(false);
});
it("should be false on user acceptance (manual auth)", () => {
const result = transitionAuthState("challenge_received", {
type: "USER_ACCEPTED",
});
expect(result.shouldAutoAuth).toBe(false);
});
});
});