88 lines
2.9 KiB
TypeScript
88 lines
2.9 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
import { getRlsExecutionContext } from "@hivegrid/db/rls-context.server";
|
|
|
|
const mocks = vi.hoisted(() => ({
|
|
getUserInfo: vi.fn(),
|
|
findMembership: vi.fn(),
|
|
}));
|
|
|
|
vi.mock("~/utils/session.server", () => ({ getUserInfo: mocks.getUserInfo }));
|
|
vi.mock("~/utils/db.server", () => ({
|
|
db: { tenantUser: { findFirst: mocks.findMembership } },
|
|
}));
|
|
|
|
import { ProviderOAuthCallbackAccessError, runAuthenticatedProviderOAuthCallback } from "./providerOAuthCallbackAccess.server";
|
|
|
|
async function captureRejection(promise: Promise<unknown>): Promise<unknown> {
|
|
try {
|
|
await promise;
|
|
} catch (error) {
|
|
return error;
|
|
}
|
|
throw new Error("Expected promise to reject");
|
|
}
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
mocks.getUserInfo.mockResolvedValue({ userId: "user_1" });
|
|
mocks.findMembership.mockResolvedValue({ id: "membership_1" });
|
|
});
|
|
|
|
describe("provider OAuth callback access", () => {
|
|
it("rejects a stale or foreign web session before membership or provider work", async () => {
|
|
mocks.getUserInfo.mockResolvedValue({ userId: "user_foreign" });
|
|
const callback = vi.fn();
|
|
|
|
const error = await captureRejection(
|
|
runAuthenticatedProviderOAuthCallback({ request: new Request("https://app.example.test/callback"), tenantId: "tenant_1", userId: "user_1", callback })
|
|
);
|
|
|
|
expect(error).toBeInstanceOf(ProviderOAuthCallbackAccessError);
|
|
expect(error).toMatchObject({
|
|
name: "ProviderOAuthCallbackAccessError",
|
|
failureCode: "session_mismatch",
|
|
});
|
|
|
|
expect(mocks.findMembership).not.toHaveBeenCalled();
|
|
expect(callback).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("rejects a removed membership before provider work", async () => {
|
|
mocks.findMembership.mockResolvedValue(null);
|
|
const callback = vi.fn();
|
|
|
|
const error = await captureRejection(
|
|
runAuthenticatedProviderOAuthCallback({ request: new Request("https://app.example.test/callback"), tenantId: "tenant_1", userId: "user_1", callback })
|
|
);
|
|
|
|
expect(error).toBeInstanceOf(ProviderOAuthCallbackAccessError);
|
|
expect(error).toMatchObject({
|
|
name: "ProviderOAuthCallbackAccessError",
|
|
failureCode: "membership_inactive",
|
|
});
|
|
|
|
expect(callback).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("checks the active exact membership and runs provider work in tenant/user context", async () => {
|
|
const callback = vi.fn(async () => getRlsExecutionContext());
|
|
|
|
await expect(
|
|
runAuthenticatedProviderOAuthCallback({ request: new Request("https://app.example.test/callback"), tenantId: "tenant_1", userId: "user_1", callback })
|
|
).resolves.toMatchObject({ tenantId: "tenant_1", userId: "user_1" });
|
|
|
|
expect(mocks.findMembership).toHaveBeenCalledWith({
|
|
where: {
|
|
tenantId: "tenant_1",
|
|
userId: "user_1",
|
|
status: 2,
|
|
tenant: { active: true },
|
|
user: { active: true },
|
|
},
|
|
select: { id: true },
|
|
});
|
|
expect(callback).toHaveBeenCalledOnce();
|
|
});
|
|
});
|