Files
HiveGrid-Wireframes/hivegrid-cloud/apps/web/app/utils/services/authService.validation-i18n.test.ts
T

221 lines
9.2 KiB
TypeScript

// @vitest-environment node
import { beforeEach, describe, expect, it, vi } from "vitest";
const mocks = vi.hoisted(() => ({
findInBlacklist: vi.fn(),
getAppConfiguration: vi.fn(),
getTenantBySlug: vi.fn(),
getUserByEmail: vi.fn(),
register: vi.fn(),
createTenant: vi.fn(),
createTenantUser: vi.fn(),
getAllRoles: vi.fn(),
verifyLeadToken: vi.fn(),
recordExistingTenantLeadConversion: vi.fn(),
leadFindUnique: vi.fn(),
}));
vi.mock("~/locale/i18next.server", () => ({
getTranslations: vi.fn(async () => ({ t: (key: string) => key })),
}));
vi.mock("~/utils/session.server", () => ({
getUserInfo: vi.fn(async () => ({ lng: "en" })),
}));
vi.mock("~/utils/db/appConfiguration.db.server", () => ({
getAppConfiguration: (...args: unknown[]) => mocks.getAppConfiguration(...args),
}));
vi.mock("~/utils/app/UserValidationUtils", () => ({
default: {
validateEmail: vi.fn(() => true),
validatePassword: vi.fn(() => true),
},
}));
vi.mock("~/services/auth/password-policy.server", () => ({
validatePassword: vi.fn(async () => ({ valid: true, errors: [] })),
}));
vi.mock("~/utils/db/blacklist.db.server", () => ({
addBlacklistAttempt: vi.fn(),
findInBlacklist: (...args: unknown[]) => mocks.findInBlacklist(...args),
}));
vi.mock("~/utils/db/users.db.server", () => ({
getUserByEmail: (...args: unknown[]) => mocks.getUserByEmail(...args),
register: (...args: unknown[]) => mocks.register(...args),
}));
vi.mock("~/utils/db/tenants.db.server", () => ({
createTenant: (...args: unknown[]) => mocks.createTenant(...args),
createTenantUser: (...args: unknown[]) => mocks.createTenantUser(...args),
getTenantBySlug: (...args: unknown[]) => mocks.getTenantBySlug(...args),
}));
vi.mock("~/utils/db/permissions/roles.db.server", () => ({ getAllRoles: (...args: unknown[]) => mocks.getAllRoles(...args) }));
vi.mock("~/utils/db/registration.db.server", () => ({
createRegistration: vi.fn(),
getRegistrationByEmail: vi.fn(),
updateRegistration: vi.fn(),
}));
vi.mock("~/utils/db.server", () => ({ db: { lead: { findUnique: (...args: unknown[]) => mocks.leadFindUnique(...args) } } }));
vi.mock("~/utils/stripe.server", () => ({ createStripeCustomer: vi.fn(), isStripeConfigured: vi.fn(() => false) }));
vi.mock("~/utils/url.server", () => ({ getAbsoluteUrl: vi.fn(() => "https://example.test") }));
vi.mock("~/modules/events/services/.server/EventsService", () => ({ default: { create: vi.fn() } }));
vi.mock("~/modules/emails/services/TransactionalEmailQueue.server", () => ({ queueTransactionalEmail: vi.fn() }));
vi.mock("~/modules/emails/utils/EmailTemplates", () => ({ default: { WELCOME_EMAIL: { parse: vi.fn(() => ({ subject: "Welcome", body: "Welcome" })) } } }));
vi.mock("~/locale/mail-translator.server", () => ({
getMailTranslator: vi.fn(async () => ({ t: (key: string) => key, locale: "en" })),
getMailTranslatorFromRequest: vi.fn(),
}));
vi.mock("~/modules/ipAddress/services/IpAddressService.server", () => ({ default: { log: vi.fn() } }));
vi.mock("~/utils/api/.server/TenantTypesApi", () => ({ TenantTypesApi: { setTenantTypes: vi.fn() } }));
vi.mock("~/utils/leads/leadToken.server", () => ({ verifyLeadToken: (...args: unknown[]) => mocks.verifyLeadToken(...args) }));
vi.mock("~/utils/leads/convertLead.server", () => ({ recordExistingTenantLeadConversion: (...args: unknown[]) => mocks.recordExistingTenantLeadConversion(...args) }));
import { validateRegistration } from "./authService";
const request = new Request("https://example.test/register");
function appConfiguration(slug: { require: boolean; type: "tenant" | "user" } = { require: false, type: "tenant" }) {
return {
auth: {
requireEmailVerification: false,
requireOrganization: false,
requireName: false,
slug,
},
};
}
describe("validateRegistration i18n errors", () => {
beforeEach(() => {
vi.clearAllMocks();
mocks.getAppConfiguration.mockResolvedValue(appConfiguration());
mocks.findInBlacklist.mockResolvedValue(null);
mocks.getUserByEmail.mockResolvedValue(null);
mocks.getTenantBySlug.mockResolvedValue(null);
mocks.createTenant.mockResolvedValue({ id: "tenant_1", name: "Ada", slug: "ada" });
mocks.register.mockResolvedValue({ id: "user_1", email: "ada@example.test", locale: "en" });
mocks.createTenantUser.mockResolvedValue({});
mocks.getAllRoles.mockResolvedValue([]);
mocks.verifyLeadToken.mockReturnValue(null);
mocks.recordExistingTenantLeadConversion.mockResolvedValue({});
mocks.leadFindUnique.mockResolvedValue(null);
delete process.env.LEAD_CONVERSION_ENABLED;
});
it.each([
["companyMaxLength", { company: "a".repeat(101) }],
["firstNameMaxLength", { firstName: "a".repeat(51) }],
["lastNameMaxLength", { lastName: "a".repeat(51) }],
])("uses a translation key for %s", async (errorKey, registrationData) => {
await expect(
validateRegistration({
request,
registrationData: { email: "ada@example.test", password: "Password123!", ...registrationData },
addToTrialOrFreePlan: false,
})
).rejects.toThrow(`account.register.errors.${errorKey}`);
});
it.each([
["tenant", "slugRequired"],
["user", "usernameRequired"],
] as const)("uses a translation key when the %s slug is required", async (slugType, errorKey) => {
mocks.getAppConfiguration.mockResolvedValueOnce(appConfiguration({ require: true, type: slugType }));
await expect(
validateRegistration({
request,
registrationData: { email: "ada@example.test", password: "Password123!" },
addToTrialOrFreePlan: false,
})
).rejects.toThrow(`account.register.errors.${errorKey}`);
});
it.each([
["tenant", "slugTaken"],
["user", "usernameTaken"],
] as const)("uses a translation key when the %s slug is already taken", async (slugType, errorKey) => {
mocks.getAppConfiguration.mockResolvedValueOnce(appConfiguration({ require: true, type: slugType }));
mocks.getTenantBySlug.mockResolvedValueOnce({ id: "tenant_1" });
await expect(
validateRegistration({
request,
registrationData: { email: "ada@example.test", password: "Password123!", slug: "ada" },
addToTrialOrFreePlan: false,
})
).rejects.toThrow(`account.register.errors.${errorKey}`);
});
it.each([
["reserved", "slugReserved", "admin"],
["too short", "slugTooShort", "x"],
] as const)("rejects a user-provided slug that is %s, independent of auth.slug.require", async (_label, errorKey, slug) => {
await expect(
validateRegistration({
request,
registrationData: { email: "ada@example.test", password: "Password123!", slug },
addToTrialOrFreePlan: false,
})
).rejects.toThrow(`account.register.errors.${errorKey}`);
});
it.each([
["only plan id", { email: "ada@example.test", password: "Password123!", leadPlanId: "plan_pro" }],
["missing token", { email: "ada@example.test", password: "Password123!", leadId: "lead_1" }],
["token for a different lead", { email: "ada@example.test", password: "Password123!", leadId: "lead_1", leadToken: "signed" }],
["converted lead", { email: "ada@example.test", password: "Password123!", leadId: "lead_1", leadToken: "signed" }],
["email mismatch", { email: "ada@example.test", password: "Password123!", leadId: "lead_1", leadToken: "signed" }],
])("rejects invalid lead conversion context: %s", async (name, registrationData) => {
if (name === "token for a different lead") {
mocks.verifyLeadToken.mockReturnValueOnce({ leadId: "other_lead", planId: "plan_token" });
} else if (name === "converted lead") {
mocks.verifyLeadToken.mockReturnValueOnce({ leadId: "lead_1", planId: "plan_token" });
mocks.leadFindUnique.mockResolvedValueOnce({ email: "ada@example.test", convertedToTenantId: "tenant_existing" });
} else if (name === "email mismatch") {
mocks.verifyLeadToken.mockReturnValueOnce({ leadId: "lead_1", planId: "plan_token" });
mocks.leadFindUnique.mockResolvedValueOnce({ email: "grace@example.test", convertedToTenantId: null });
}
await expect(
validateRegistration({
request,
registrationData,
addToTrialOrFreePlan: false,
})
).rejects.toThrow("account.register.errors.invalidLeadConversion");
expect(mocks.recordExistingTenantLeadConversion).not.toHaveBeenCalled();
});
it("records valid self-service lead conversion with token plan precedence", async () => {
mocks.verifyLeadToken.mockReturnValueOnce({ leadId: "lead_1", planId: "plan_from_token" });
mocks.leadFindUnique.mockResolvedValueOnce({ email: "ada@example.test", convertedToTenantId: null });
await expect(
validateRegistration({
request,
registrationData: {
email: "ada@example.test",
password: "Password123!",
leadId: "lead_1",
leadToken: "signed",
leadPlanId: "plan_from_form",
},
addToTrialOrFreePlan: false,
})
).resolves.toMatchObject({ verificationRequired: false, registered: { tenant: { id: "tenant_1" }, user: { id: "user_1" } } });
expect(mocks.recordExistingTenantLeadConversion).toHaveBeenCalledWith({
leadId: "lead_1",
tenantId: "tenant_1",
planId: "plan_from_token",
conversionPath: "self-service",
});
});
});