chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:32:57 +08:00
commit cd420f9332
4811 changed files with 884702 additions and 0 deletions
+25
View File
@@ -0,0 +1,25 @@
{
"name": "@trigger.dev/sso",
"private": true,
"version": "0.0.1",
"main": "./dist/src/index.js",
"types": "./dist/src/index.d.ts",
"dependencies": {
"@trigger.dev/core": "workspace:*",
"@trigger.dev/plugins": "workspace:*",
"neverthrow": "^8.2.0"
},
"devDependencies": {
"@trigger.dev/database": "workspace:*",
"@types/node": "^22.20.0",
"rimraf": "6.0.1"
},
"scripts": {
"clean": "rimraf dist",
"typecheck": "tsc --noEmit",
"build": "pnpm run clean && tsc --noEmit false --outDir dist --declaration",
"dev": "tsc --noEmit false --outDir dist --declaration --watch",
"test": "vitest run",
"test:watch": "vitest"
}
}
+239
View File
@@ -0,0 +1,239 @@
import type {
DirectorySyncEffect,
DirectorySyncStatus,
OrgSsoStatus,
SsoBeginError,
SsoCompleteError,
SsoController,
SsoDecisionError,
SsoFlow,
SsoMutationError,
SsoPortalError,
SsoProfile,
SsoResolutionDecision,
SsoRouteDecision,
SsoValidateError,
SsoWebhookError,
SsoWebhookEvent,
} from "@trigger.dev/plugins";
import { errAsync, okAsync, type ResultAsync } from "neverthrow";
// The default fallback used when no cloud SSO plugin is installed.
// `decideRouteForEmail` returns no_sso so OSS deployments behave
// identically to a deployment with no SSO feature at all. Mutation
// methods return feature_disabled so callers can surface a clear
// "not available" message in UI gated by `isUsingPlugin()`.
//
// The fallback never touches the database. It still accepts the loader's
// Prisma input for signature parity with the real cloud plugin factory
// (so the loader can swap implementations without changing its call),
// but ignores it entirely.
export class SsoFallback {
constructor(_prisma?: unknown) {}
create(): SsoController {
return new SsoFallbackController();
}
}
class SsoFallbackController implements SsoController {
async isUsingPlugin(): Promise<boolean> {
return false;
}
getStatus(_organizationId: string): ResultAsync<OrgSsoStatus, SsoDecisionError> {
return okAsync({
hasIdpOrg: false,
enforced: false,
jitProvisioningEnabled: false,
jitDefaultRoleId: null,
idpOrgId: null,
primaryConnectionId: null,
domains: [],
connections: [],
});
}
generatePortalLink(_params: {
organizationId: string;
userId: string;
intent: "sso" | "domain_verification" | "dsync";
returnUrl: string;
}): ResultAsync<{ url: string }, SsoPortalError> {
return errAsync("idp_org_unavailable" as const);
}
setEnforced(_params: {
organizationId: string;
enforced: boolean;
}): ResultAsync<void, SsoMutationError> {
return errAsync("feature_disabled" as const);
}
setJitProvisioningEnabled(_params: {
organizationId: string;
enabled: boolean;
}): ResultAsync<void, SsoMutationError> {
return errAsync("feature_disabled" as const);
}
setJitDefaultRole(_params: {
organizationId: string;
roleId: string | null;
}): ResultAsync<void, SsoMutationError> {
return errAsync("feature_disabled" as const);
}
updateConfig(_params: {
organizationId: string;
enforced: boolean;
jitProvisioningEnabled: boolean;
jitDefaultRoleId: string | null;
}): ResultAsync<void, SsoMutationError> {
return errAsync("feature_disabled" as const);
}
getDirectorySyncStatus(
_organizationId: string
): ResultAsync<DirectorySyncStatus, SsoDecisionError> {
return okAsync({
hasDirectory: false,
hasActiveDirectory: false,
allowExternalDomainSync: false,
allowManualMembership: true,
directoryDefaultRoleId: null,
userCount: 0,
directories: [],
groups: [],
});
}
setDirectoryGroupRole(_params: {
organizationId: string;
groupId: string;
roleId: string | null;
}): ResultAsync<{ effects: DirectorySyncEffect[] }, SsoMutationError> {
return errAsync("feature_disabled" as const);
}
setDirectoryDefaultRole(_params: {
organizationId: string;
roleId: string | null;
}): ResultAsync<void, SsoMutationError> {
return errAsync("feature_disabled" as const);
}
setAllowExternalDomainSync(_params: {
organizationId: string;
allowed: boolean;
}): ResultAsync<void, SsoMutationError> {
return errAsync("feature_disabled" as const);
}
// OSS has no directory, so manual membership is always allowed.
getMembershipPolicy(
_organizationId: string
): ResultAsync<{ manualMembershipAllowed: boolean }, SsoDecisionError> {
return okAsync({ manualMembershipAllowed: true });
}
setAllowManualMembership(_params: {
organizationId: string;
allowed: boolean;
}): ResultAsync<void, SsoMutationError> {
return errAsync("feature_disabled" as const);
}
recordMembershipRemoval(_params: {
organizationId: string;
userId: string;
reason: "manual_removal" | "self_leave";
}): ResultAsync<void, SsoMutationError> {
// No plugin → no JIT → nothing to guard against. No-op success.
return okAsync(undefined as void);
}
clearMembershipRemoval(_params: {
organizationId: string;
userId: string;
}): ResultAsync<void, SsoMutationError> {
return okAsync(undefined as void);
}
decideRouteForEmail(_email: string): ResultAsync<SsoRouteDecision, SsoDecisionError> {
return okAsync({ kind: "no_sso" as const });
}
beginAuthorization(_params: {
email: string;
redirectTo: string;
flow: SsoFlow;
}): ResultAsync<{ url: string }, SsoBeginError> {
return errAsync("feature_disabled" as const);
}
completeAuthorization(_params: { code: string; state: string }): ResultAsync<
{
profile: SsoProfile;
redirectTo: string;
flow: SsoFlow;
},
SsoCompleteError
> {
return errAsync("connection_unknown" as const);
}
completeIdpInitiatedAuthorization(_params: {
code: string;
}): ResultAsync<{ profile: SsoProfile; redirectTo: string }, SsoCompleteError> {
return errAsync("connection_unknown" as const);
}
// Fail-open: with no plugin there are no SSO sessions to invalidate,
// and the host treats `valid: true` as "leave the session alone".
validateSession(_params: {
userId: string;
idpOrgId: string;
connectionId: string;
}): ResultAsync<{ valid: boolean }, SsoValidateError> {
return okAsync({ valid: true });
}
resolveSsoIdentity(_params: {
profile: SsoProfile;
}): ResultAsync<SsoResolutionDecision, SsoMutationError> {
return errAsync("feature_disabled" as const);
}
attachSsoIdentity(_params: {
userId: string;
profile: SsoProfile;
}): ResultAsync<void, SsoMutationError> {
return errAsync("feature_disabled" as const);
}
evaluateJit(_params: {
userId: string;
idpOrgId: string;
}): ResultAsync<
{ shouldProvision: boolean; organizationId: string; roleId: string | null },
SsoMutationError
> {
return errAsync("feature_disabled" as const);
}
verifyWebhook(_params: {
rawBody: string;
headers: Record<string, string>;
}): ResultAsync<{ event: SsoWebhookEvent }, SsoWebhookError> {
return errAsync("feature_disabled" as const);
}
processWebhookEvent(
_event: SsoWebhookEvent
): ResultAsync<{ effects: DirectorySyncEffect[] }, SsoWebhookError> {
// No plugin: nothing to verify or act on. The host's webhook proxy
// already rejects unverified requests, so a call here just no-ops.
return okAsync({ effects: [] });
}
}
+291
View File
@@ -0,0 +1,291 @@
import type {
DirectorySyncEffect,
DirectorySyncStatus,
OrgSsoStatus,
PluginDatabaseConfig,
SsoBeginError,
SsoCompleteError,
SsoController,
SsoDecisionError,
SsoFlow,
SsoMutationError,
SsoPlugin,
SsoPortalError,
SsoProfile,
SsoResolutionDecision,
SsoRouteDecision,
SsoValidateError,
SsoWebhookError,
SsoWebhookEvent,
} from "@trigger.dev/plugins";
import type { PrismaClient } from "@trigger.dev/database";
import { ResultAsync } from "neverthrow";
import { SsoFallback } from "./fallback.js";
export type { SsoController } from "@trigger.dev/plugins";
export type SsoPrismaInput = PrismaClient | { primary: PrismaClient; replica: PrismaClient };
export type SsoCreateOptions = {
// When true, skip loading the plugin. Useful for tests and for
// contributors who don't have the cloud plugin installed.
forceFallback?: boolean;
// Override the dynamic importer. Lets tests inject a fake plugin
// module or a synthetic ERR_MODULE_NOT_FOUND failure without touching
// the real plugin install on disk.
importer?: (moduleName: string) => Promise<{ default: SsoPlugin }>;
// Writer/reader connection URLs + pool sizes for a plugin that owns its
// own database client, resolved by the host from its env so the plugin
// follows the host's writer/replica topology. The fallback ignores this —
// it queries through the Prisma clients passed as `SsoPrismaInput`.
database?: PluginDatabaseConfig;
};
// Loads the cloud plugin lazily; falls back to the OSS no-op
// implementation if not installed. Synchronous create() avoids
// top-level await (not supported in the webapp's CJS build).
export class LazyController implements SsoController {
private readonly _init: Promise<SsoController>;
constructor(prisma: SsoPrismaInput, options?: SsoCreateOptions) {
this._init = this.load(prisma, options);
}
private async load(prisma: SsoPrismaInput, options?: SsoCreateOptions): Promise<SsoController> {
if (options?.forceFallback) {
return new SsoFallback(prisma).create();
}
const moduleName = "@triggerdotdev/plugins/sso";
const importer =
options?.importer ?? ((m: string) => import(m) as Promise<{ default: SsoPlugin }>);
try {
const module = await importer(moduleName);
const plugin: SsoPlugin = module.default;
console.log("SSO: using plugin implementation");
return plugin.create({ database: options?.database });
} catch (err) {
// Distinguish the two failure modes the dynamic import can hit:
//
// 1. The plugin itself is absent (no install) — expected on OSS
// deployments. Quiet by default; logged when SSO_LOG_FALLBACK=1
// so contributors can opt into a visible signal locally.
// 2. The plugin module loaded but its initialization failed
// (transitive dep missing, syntax error, …). Always logged
// loudly because this indicates a real bug.
//
// Node throws ERR_MODULE_NOT_FOUND for both cases, so we
// disambiguate by checking whether the missing specifier is the
// plugin's own module name.
const code = (err as NodeJS.ErrnoException | undefined)?.code;
const message = err instanceof Error ? err.message : String(err);
const isModuleNotFound = code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND";
const isPluginItselfMissing = isModuleNotFound && message.includes(moduleName);
if (!isPluginItselfMissing) {
console.error(
"SSO: plugin found but failed to load; falling back to default implementation",
err
);
} else if (process.env.SSO_LOG_FALLBACK === "1" || process.env.SSO_LOG_FALLBACK === "true") {
console.log("SSO: no plugin installed (ERR_MODULE_NOT_FOUND); using fallback");
}
return new SsoFallback(prisma).create();
}
}
private c(): Promise<SsoController> {
return this._init;
}
// Bridges a Promise<ResultAsync<T,E>> back into a ResultAsync<T,E>.
// The `load()` method above always resolves (it catches and falls
// back), so `this.c()` is safe to lift via fromSafePromise. Inner
// controller methods are expected to never throw — they return
// errors via the Result instead — so the .andThen flatten is total.
private call<T, E>(factory: (c: SsoController) => ResultAsync<T, E>): ResultAsync<T, E> {
return ResultAsync.fromSafePromise(this.c().then(factory)).andThen((r) => r);
}
async isUsingPlugin(): Promise<boolean> {
return (await this.c()).isUsingPlugin();
}
getStatus(organizationId: string): ResultAsync<OrgSsoStatus, SsoDecisionError> {
return this.call((c) => c.getStatus(organizationId));
}
generatePortalLink(params: {
organizationId: string;
userId: string;
intent: "sso" | "domain_verification" | "dsync";
returnUrl: string;
}): ResultAsync<{ url: string }, SsoPortalError> {
return this.call((c) => c.generatePortalLink(params));
}
setEnforced(params: {
organizationId: string;
enforced: boolean;
}): ResultAsync<void, SsoMutationError> {
return this.call((c) => c.setEnforced(params));
}
setJitProvisioningEnabled(params: {
organizationId: string;
enabled: boolean;
}): ResultAsync<void, SsoMutationError> {
return this.call((c) => c.setJitProvisioningEnabled(params));
}
setJitDefaultRole(params: {
organizationId: string;
roleId: string | null;
}): ResultAsync<void, SsoMutationError> {
return this.call((c) => c.setJitDefaultRole(params));
}
updateConfig(params: {
organizationId: string;
enforced: boolean;
jitProvisioningEnabled: boolean;
jitDefaultRoleId: string | null;
}): ResultAsync<void, SsoMutationError> {
return this.call((c) => c.updateConfig(params));
}
getDirectorySyncStatus(
organizationId: string
): ResultAsync<DirectorySyncStatus, SsoDecisionError> {
return this.call((c) => c.getDirectorySyncStatus(organizationId));
}
setDirectoryGroupRole(params: {
organizationId: string;
groupId: string;
roleId: string | null;
}): ResultAsync<{ effects: DirectorySyncEffect[] }, SsoMutationError> {
return this.call((c) => c.setDirectoryGroupRole(params));
}
setDirectoryDefaultRole(params: {
organizationId: string;
roleId: string | null;
}): ResultAsync<void, SsoMutationError> {
return this.call((c) => c.setDirectoryDefaultRole(params));
}
setAllowExternalDomainSync(params: {
organizationId: string;
allowed: boolean;
}): ResultAsync<void, SsoMutationError> {
return this.call((c) => c.setAllowExternalDomainSync(params));
}
getMembershipPolicy(
organizationId: string
): ResultAsync<{ manualMembershipAllowed: boolean }, SsoDecisionError> {
return this.call((c) => c.getMembershipPolicy(organizationId));
}
setAllowManualMembership(params: {
organizationId: string;
allowed: boolean;
}): ResultAsync<void, SsoMutationError> {
return this.call((c) => c.setAllowManualMembership(params));
}
recordMembershipRemoval(params: {
organizationId: string;
userId: string;
reason: "manual_removal" | "self_leave";
}): ResultAsync<void, SsoMutationError> {
return this.call((c) => c.recordMembershipRemoval(params));
}
clearMembershipRemoval(params: {
organizationId: string;
userId: string;
}): ResultAsync<void, SsoMutationError> {
return this.call((c) => c.clearMembershipRemoval(params));
}
decideRouteForEmail(email: string): ResultAsync<SsoRouteDecision, SsoDecisionError> {
return this.call((c) => c.decideRouteForEmail(email));
}
beginAuthorization(params: {
email: string;
redirectTo: string;
flow: SsoFlow;
}): ResultAsync<{ url: string }, SsoBeginError> {
return this.call((c) => c.beginAuthorization(params));
}
completeAuthorization(params: {
code: string;
state: string;
}): ResultAsync<{ profile: SsoProfile; redirectTo: string; flow: SsoFlow }, SsoCompleteError> {
return this.call((c) => c.completeAuthorization(params));
}
completeIdpInitiatedAuthorization(params: {
code: string;
}): ResultAsync<{ profile: SsoProfile; redirectTo: string }, SsoCompleteError> {
return this.call((c) => c.completeIdpInitiatedAuthorization(params));
}
validateSession(params: {
userId: string;
idpOrgId: string;
connectionId: string;
}): ResultAsync<{ valid: boolean }, SsoValidateError> {
return this.call((c) => c.validateSession(params));
}
resolveSsoIdentity(params: {
profile: SsoProfile;
}): ResultAsync<SsoResolutionDecision, SsoMutationError> {
return this.call((c) => c.resolveSsoIdentity(params));
}
attachSsoIdentity(params: {
userId: string;
profile: SsoProfile;
}): ResultAsync<void, SsoMutationError> {
return this.call((c) => c.attachSsoIdentity(params));
}
evaluateJit(params: {
userId: string;
idpOrgId: string;
}): ResultAsync<
{ shouldProvision: boolean; organizationId: string; roleId: string | null },
SsoMutationError
> {
return this.call((c) => c.evaluateJit(params));
}
verifyWebhook(params: {
rawBody: string;
headers: Record<string, string>;
}): ResultAsync<{ event: SsoWebhookEvent }, SsoWebhookError> {
return this.call((c) => c.verifyWebhook(params));
}
processWebhookEvent(
event: SsoWebhookEvent
): ResultAsync<{ effects: DirectorySyncEffect[] }, SsoWebhookError> {
return this.call((c) => c.processWebhookEvent(event));
}
}
class Sso {
// Synchronous — returns a lazy controller that resolves any installed
// plugin on first call.
create(prisma: SsoPrismaInput, options?: SsoCreateOptions): SsoController {
return new LazyController(prisma, options);
}
}
const loader = new Sso();
export default loader;
+331
View File
@@ -0,0 +1,331 @@
import { describe, expect, it, vi } from "vitest";
import type {
OrgSsoStatus,
SsoController,
SsoFlow,
SsoPlugin,
SsoProfile,
SsoResolutionDecision,
SsoRouteDecision,
} from "@trigger.dev/plugins";
import { errAsync, okAsync, type ResultAsync } from "neverthrow";
import loader, { LazyController } from "./index.js";
// A minimal stub controller used in the "plugin found" test path. Only
// the methods the test cares about return useful values; the rest
// return permissive defaults.
function makeStubController(overrides: Partial<SsoController> = {}): SsoController {
const stub: SsoController = {
async isUsingPlugin() {
return true;
},
getStatus(): ResultAsync<OrgSsoStatus, "internal"> {
return okAsync({
hasIdpOrg: true,
enforced: false,
jitProvisioningEnabled: true,
jitDefaultRoleId: null,
idpOrgId: "idp_stub",
primaryConnectionId: null,
domains: [],
connections: [],
});
},
generatePortalLink() {
return okAsync({ url: "https://stub.example/portal" });
},
getDirectorySyncStatus() {
return okAsync({
hasDirectory: false,
hasActiveDirectory: false,
allowExternalDomainSync: false,
allowManualMembership: true,
directoryDefaultRoleId: null,
userCount: 0,
directories: [],
groups: [],
});
},
setDirectoryGroupRole() {
return okAsync({ effects: [] });
},
setDirectoryDefaultRole() {
return okAsync(undefined as void);
},
setAllowExternalDomainSync() {
return okAsync(undefined as void);
},
getMembershipPolicy() {
return okAsync({ manualMembershipAllowed: true });
},
setAllowManualMembership() {
return okAsync(undefined as void);
},
recordMembershipRemoval() {
return okAsync(undefined as void);
},
clearMembershipRemoval() {
return okAsync(undefined as void);
},
setEnforced() {
return okAsync(undefined as void);
},
setJitProvisioningEnabled() {
return okAsync(undefined as void);
},
setJitDefaultRole() {
return okAsync(undefined as void);
},
updateConfig() {
return okAsync(undefined as void);
},
decideRouteForEmail(): ResultAsync<SsoRouteDecision, "internal"> {
return okAsync<SsoRouteDecision, "internal">({
kind: "sso_required",
idpOrgId: "idp_stub",
});
},
beginAuthorization() {
return okAsync({ url: "https://stub.example/auth" });
},
completeAuthorization() {
return errAsync("connection_unknown" as const);
},
completeIdpInitiatedAuthorization() {
return errAsync("connection_unknown" as const);
},
resolveSsoIdentity(): ResultAsync<SsoResolutionDecision, "feature_disabled"> {
return errAsync("feature_disabled" as const);
},
attachSsoIdentity() {
return errAsync("feature_disabled" as const);
},
evaluateJit() {
return errAsync("feature_disabled" as const);
},
validateSession() {
return okAsync({ valid: true });
},
verifyWebhook() {
return errAsync("invalid_signature" as const);
},
processWebhookEvent() {
return okAsync({ effects: [] });
},
...overrides,
};
return stub;
}
// Minimal Prisma stub. The fallback's only constructor work is to
// record the input; nothing else here touches the database.
const fakePrisma = {} as unknown as Parameters<typeof loader.create>[0];
describe("SSO LazyController", () => {
describe("plugin missing (ERR_MODULE_NOT_FOUND on the plugin's own moduleName)", () => {
it("falls back to the no-op implementation and reports isUsingPlugin=false", async () => {
const importer = vi.fn(async (moduleName: string) => {
const err = Object.assign(new Error(`Cannot find module '${moduleName}'`), {
code: "ERR_MODULE_NOT_FOUND",
});
throw err;
});
const controller = new LazyController(fakePrisma, { importer });
expect(await controller.isUsingPlugin()).toBe(false);
const decision = await controller.decideRouteForEmail("anyone@example.com");
expect(decision.isOk()).toBe(true);
expect(decision._unsafeUnwrap()).toEqual({ kind: "no_sso" });
});
it("does not log to console.log unless SSO_LOG_FALLBACK=1", async () => {
const previous = process.env.SSO_LOG_FALLBACK;
delete process.env.SSO_LOG_FALLBACK;
const importer = vi.fn(async (moduleName: string) => {
const err = Object.assign(new Error(`Cannot find module '${moduleName}'`), {
code: "ERR_MODULE_NOT_FOUND",
});
throw err;
});
const logSpy = vi.spyOn(console, "log").mockImplementation(() => undefined);
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => undefined);
try {
const controller = new LazyController(fakePrisma, { importer });
await controller.isUsingPlugin();
const fallbackLogs = logSpy.mock.calls.filter((args) =>
args.some((a) => typeof a === "string" && a.includes("no plugin installed"))
);
expect(fallbackLogs.length).toBe(0);
expect(errorSpy).not.toHaveBeenCalled();
} finally {
logSpy.mockRestore();
errorSpy.mockRestore();
if (previous === undefined) delete process.env.SSO_LOG_FALLBACK;
else process.env.SSO_LOG_FALLBACK = previous;
}
});
it("logs an info line when SSO_LOG_FALLBACK=1", async () => {
const previous = process.env.SSO_LOG_FALLBACK;
process.env.SSO_LOG_FALLBACK = "1";
const importer = vi.fn(async (moduleName: string) => {
const err = Object.assign(new Error(`Cannot find module '${moduleName}'`), {
code: "ERR_MODULE_NOT_FOUND",
});
throw err;
});
const logSpy = vi.spyOn(console, "log").mockImplementation(() => undefined);
try {
const controller = new LazyController(fakePrisma, { importer });
await controller.isUsingPlugin();
const fallbackLogs = logSpy.mock.calls.filter((args) =>
args.some((a) => typeof a === "string" && a.includes("no plugin installed"))
);
expect(fallbackLogs.length).toBe(1);
} finally {
logSpy.mockRestore();
if (previous === undefined) delete process.env.SSO_LOG_FALLBACK;
else process.env.SSO_LOG_FALLBACK = previous;
}
});
});
describe("plugin broken (transitive dep missing or init error)", () => {
it("logs a console.error and falls back", async () => {
const importer = vi.fn(async () => {
// Module-not-found from a *transitive* dep, not the plugin
// itself — its `message` won't contain the plugin's moduleName.
const err = Object.assign(new Error(`Cannot find module 'some-transitive-dep'`), {
code: "ERR_MODULE_NOT_FOUND",
});
throw err;
});
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => undefined);
try {
const controller = new LazyController(fakePrisma, { importer });
expect(await controller.isUsingPlugin()).toBe(false);
expect(errorSpy).toHaveBeenCalled();
const firstCallArgs = errorSpy.mock.calls[0]!;
expect(
firstCallArgs.some(
(a) => typeof a === "string" && a.includes("plugin found but failed to load")
)
).toBe(true);
} finally {
errorSpy.mockRestore();
}
});
it("logs a console.error for non-module-not-found errors too", async () => {
const importer = vi.fn(async () => {
throw new SyntaxError("Unexpected token in plugin source");
});
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => undefined);
try {
const controller = new LazyController(fakePrisma, { importer });
expect(await controller.isUsingPlugin()).toBe(false);
expect(errorSpy).toHaveBeenCalled();
} finally {
errorSpy.mockRestore();
}
});
});
describe("plugin found", () => {
it("delegates isUsingPlugin to the plugin implementation", async () => {
const stub = makeStubController();
const plugin: SsoPlugin = { create: () => stub };
const importer = vi.fn(async () => ({ default: plugin }));
const controller = new LazyController(fakePrisma, { importer });
expect(await controller.isUsingPlugin()).toBe(true);
});
it("delegates decideRouteForEmail and propagates the result", async () => {
const stub = makeStubController();
const plugin: SsoPlugin = { create: () => stub };
const importer = vi.fn(async () => ({ default: plugin }));
const controller = new LazyController(fakePrisma, { importer });
const decision = await controller.decideRouteForEmail("admin@example.com");
expect(decision.isOk()).toBe(true);
expect(decision._unsafeUnwrap()).toEqual({
kind: "sso_required",
idpOrgId: "idp_stub",
});
});
it("propagates plugin errors through ResultAsync", async () => {
const stub = makeStubController();
const plugin: SsoPlugin = { create: () => stub };
const importer = vi.fn(async () => ({ default: plugin }));
const controller = new LazyController(fakePrisma, { importer });
const profile: SsoProfile = {
email: "user@example.com",
firstName: null,
lastName: null,
idpSubjectId: "sub_x",
idpOrgId: "idp_stub",
idpConnectionId: "conn_x",
};
const result = await controller.resolveSsoIdentity({ profile });
expect(result.isErr()).toBe(true);
expect(result._unsafeUnwrapErr()).toBe("feature_disabled");
});
it("loads the plugin module only once across many calls", async () => {
const stub = makeStubController();
const plugin: SsoPlugin = { create: () => stub };
const importer = vi.fn(async () => ({ default: plugin }));
const controller = new LazyController(fakePrisma, { importer });
await controller.isUsingPlugin();
await controller.decideRouteForEmail("a@example.com");
await controller.decideRouteForEmail("b@example.com");
expect(importer).toHaveBeenCalledTimes(1);
});
});
describe("forceFallback option", () => {
it("skips the importer entirely", async () => {
const importer = vi.fn();
const controller = new LazyController(fakePrisma, {
forceFallback: true,
importer: importer as never,
});
expect(await controller.isUsingPlugin()).toBe(false);
expect(importer).not.toHaveBeenCalled();
});
});
describe("loader.create() factory", () => {
it("returns a working LazyController", async () => {
const controller = loader.create(fakePrisma, { forceFallback: true });
expect(await controller.isUsingPlugin()).toBe(false);
const decision = await controller.decideRouteForEmail("x@example.com");
expect(decision._unsafeUnwrap()).toEqual({ kind: "no_sso" });
});
});
describe("fallback parameter shapes", () => {
it("propagates SsoFlow through beginAuthorization (uses fallback for error path)", async () => {
// Smoke test that `SsoFlow` typing flows through. Plugin not present →
// beginAuthorization returns `feature_disabled` per the fallback.
const controller = loader.create(fakePrisma, { forceFallback: true });
const flow: SsoFlow = "user_initiated";
const result = await controller.beginAuthorization({
email: "x@example.com",
redirectTo: "/",
flow,
});
expect(result.isErr()).toBe(true);
expect(result._unsafeUnwrapErr()).toBe("feature_disabled");
});
});
});
+17
View File
@@ -0,0 +1,17 @@
{
"compilerOptions": {
"target": "ES2019",
"lib": ["ES2019", "DOM"],
"module": "ESNext",
"moduleResolution": "Bundler",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"isolatedModules": true,
"preserveWatchOutput": true,
"skipLibCheck": true,
"noEmit": true,
"strict": true,
"customConditions": ["@triggerdotdev/source"]
},
"exclude": ["node_modules"]
}
+10
View File
@@ -0,0 +1,10 @@
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
include: ["**/*.test.ts"],
globals: true,
isolate: true,
testTimeout: 10_000,
},
});