mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
refactor: extract external auth resolver from OIDC for SAML reuse
Move user resolution logic (match by externalId, auto-link by email, auto-create with user limit check) into a shared module that both OIDC and SAML callbacks can use. Includes sanitizeUsername and findUniqueUsername helpers. Preserves all existing OIDC behavior and audit events.
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
// ── Pure function tests (no DB required) ─────────────────────────
|
||||
|
||||
describe("external auth resolver", () => {
|
||||
it("module exports resolveExternalUser", async () => {
|
||||
const mod = await import("../../../apps/api/src/lib/external-auth-resolver.js");
|
||||
expect(typeof mod.resolveExternalUser).toBe("function");
|
||||
});
|
||||
|
||||
it("module exports sanitizeUsername", async () => {
|
||||
const mod = await import("../../../apps/api/src/lib/external-auth-resolver.js");
|
||||
expect(typeof mod.sanitizeUsername).toBe("function");
|
||||
});
|
||||
|
||||
it("module exports findUniqueUsername", async () => {
|
||||
const mod = await import("../../../apps/api/src/lib/external-auth-resolver.js");
|
||||
expect(typeof mod.findUniqueUsername).toBe("function");
|
||||
});
|
||||
});
|
||||
|
||||
describe("sanitizeUsername", () => {
|
||||
let sanitizeUsername: (raw: string) => string;
|
||||
|
||||
beforeAll(async () => {
|
||||
const mod = await import("../../../apps/api/src/lib/external-auth-resolver.js");
|
||||
sanitizeUsername = mod.sanitizeUsername;
|
||||
});
|
||||
|
||||
it("lowercases input", () => {
|
||||
expect(sanitizeUsername("JohnDoe")).toBe("johndoe");
|
||||
});
|
||||
|
||||
it("replaces non-alphanumeric characters with underscores", () => {
|
||||
expect(sanitizeUsername("john doe!")).toBe("john_doe");
|
||||
});
|
||||
|
||||
it("collapses multiple underscores", () => {
|
||||
expect(sanitizeUsername("john___doe")).toBe("john_doe");
|
||||
});
|
||||
|
||||
it("strips leading and trailing separators", () => {
|
||||
expect(sanitizeUsername("_john_")).toBe("john");
|
||||
expect(sanitizeUsername(".john.")).toBe("john");
|
||||
expect(sanitizeUsername("-john-")).toBe("john");
|
||||
});
|
||||
|
||||
it("truncates to 46 characters", () => {
|
||||
const long = "a".repeat(60);
|
||||
expect(sanitizeUsername(long).length).toBe(46);
|
||||
});
|
||||
|
||||
it("pads short usernames to 3 characters", () => {
|
||||
expect(sanitizeUsername("ab").length).toBe(3);
|
||||
expect(sanitizeUsername("ab")).toBe("ab_");
|
||||
});
|
||||
|
||||
it("preserves dots and hyphens", () => {
|
||||
expect(sanitizeUsername("john.doe")).toBe("john.doe");
|
||||
expect(sanitizeUsername("john-doe")).toBe("john-doe");
|
||||
});
|
||||
|
||||
it("handles email addresses as input", () => {
|
||||
expect(sanitizeUsername("user@example.com")).toBe("user_example.com");
|
||||
});
|
||||
|
||||
it("handles empty-after-strip edge case", () => {
|
||||
// All characters stripped, then padded
|
||||
const result = sanitizeUsername("___");
|
||||
expect(result.length).toBeGreaterThanOrEqual(3);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user