Files
sase.tr/packages/shared/src/index.spec.ts
Fusion 1039812a8f feat(FN-349): add vitest tests for @sase/config and @sase/shared packages
Fusion-Task-Id: FN-349
Fusion-Task-Lineage: 6221f636-1ede-4ebb-b8b2-b3abb3b252c3
2026-05-14 05:21:21 +00:00

720 lines
20 KiB
TypeScript

import { describe, it, expect } from "vitest";
import {
// Schemas
vinSchema,
loginSchema,
registerSchema,
forgotPasswordSchema,
resetPasswordSchema,
changelogChangeTypeEnum,
changelogEntrySchema,
createChangelogEntrySchema,
updateChangelogEntrySchema,
paginationSchema,
// Utils
isValidVin,
validateVinCheckDigit,
extractWmi,
extractModelYear,
formatTRY,
kurusToLira,
liraToKurus,
formatVin,
formatDate,
formatDateTime,
slugify,
generateReferralCode,
// Constants
VIN_REGEX,
EMAIL_REGEX,
OEM_CODE_REGEX,
PLANS,
REFERRAL_REWARDS,
CURRENCY,
FULL_PLAN_BRAND_LIMIT,
ERROR_CODES,
} from "./index";
// --------------- schemas/vin ---------------
describe("vinSchema", () => {
const validVin = "WVWZZZ1JZ3W597935";
it("accepts a valid 17-character VIN", () => {
const result = vinSchema.safeParse(validVin);
expect(result.success).toBe(true);
});
it("transforms VIN to uppercase after validation", () => {
const result = vinSchema.safeParse("WVWZZZ1JZ3W597935");
expect(result.success).toBe(true);
if (result.success) {
expect(result.data).toBe("WVWZZZ1JZ3W597935");
}
});
it("rejects VIN shorter than 17 chars", () => {
const result = vinSchema.safeParse("WVWZZZ1JZ3W59793");
expect(result.success).toBe(false);
});
it("rejects VIN longer than 17 chars", () => {
const result = vinSchema.safeParse("WVWZZZ1JZ3W597935X");
expect(result.success).toBe(false);
});
it("rejects VIN with I character (not allowed)", () => {
const result = vinSchema.safeParse("WVWZZZ1IZ3W597935");
expect(result.success).toBe(false);
});
it("rejects VIN with O character (not allowed)", () => {
const result = vinSchema.safeParse("WVWZZZ1OZ3W597935");
expect(result.success).toBe(false);
});
it("rejects VIN with Q character (not allowed)", () => {
const result = vinSchema.safeParse("WVWZZZ1QZ3W597935");
expect(result.success).toBe(false);
});
it("rejects empty string", () => {
const result = vinSchema.safeParse("");
expect(result.success).toBe(false);
});
it("accepts 17-char uppercase string with valid chars", () => {
const result = vinSchema.safeParse("ABCDEFGHJKLMNPRST");
expect(result.success).toBe(true); // This is 17 valid chars (no I, O, Q)
});
it("rejects string with lowercase chars (regex runs before transform)", () => {
const result = vinSchema.safeParse("abcdefghjklmnprst");
expect(result.success).toBe(false); // VIN_REGEX requires uppercase
});
});
// --------------- schemas/auth ---------------
describe("loginSchema", () => {
it("accepts valid login", () => {
const result = loginSchema.safeParse({
email: "user@example.com",
password: "password123",
});
expect(result.success).toBe(true);
});
it("rejects invalid email", () => {
const result = loginSchema.safeParse({
email: "not-an-email",
password: "password123",
});
expect(result.success).toBe(false);
});
it("rejects short password", () => {
const result = loginSchema.safeParse({
email: "user@example.com",
password: "short",
});
expect(result.success).toBe(false);
});
it("rejects missing password", () => {
const result = loginSchema.safeParse({ email: "user@example.com" });
expect(result.success).toBe(false);
});
it("rejects missing email", () => {
const result = loginSchema.safeParse({ password: "password123" });
expect(result.success).toBe(false);
});
});
describe("registerSchema", () => {
const validRegister = {
name: "John Doe",
email: "john@example.com",
password: "Aa123456",
};
it("accepts valid registration", () => {
const result = registerSchema.safeParse(validRegister);
expect(result.success).toBe(true);
});
it("rejects short name", () => {
const result = registerSchema.safeParse({ ...validRegister, name: "J" });
expect(result.success).toBe(false);
});
it("rejects name exceeding 100 chars", () => {
const result = registerSchema.safeParse({ ...validRegister, name: "A".repeat(101) });
expect(result.success).toBe(false);
});
it("rejects invalid email", () => {
const result = registerSchema.safeParse({ ...validRegister, email: "bad-email" });
expect(result.success).toBe(false);
});
it("rejects password without uppercase", () => {
const result = registerSchema.safeParse({ ...validRegister, password: "aa123456" });
expect(result.success).toBe(false);
});
it("rejects password without lowercase", () => {
const result = registerSchema.safeParse({ ...validRegister, password: "AA123456" });
expect(result.success).toBe(false);
});
it("rejects password without number", () => {
const result = registerSchema.safeParse({ ...validRegister, password: "Aaabcdef" });
expect(result.success).toBe(false);
});
it("rejects password shorter than 8 chars", () => {
const result = registerSchema.safeParse({ ...validRegister, password: "Aa1" });
expect(result.success).toBe(false);
});
it("rejects password longer than 128 chars", () => {
const result = registerSchema.safeParse({
...validRegister,
password: "Aa1" + "x".repeat(126),
});
expect(result.success).toBe(false);
});
});
describe("forgotPasswordSchema", () => {
it("accepts valid email", () => {
const result = forgotPasswordSchema.safeParse({ email: "user@example.com" });
expect(result.success).toBe(true);
});
it("rejects invalid email", () => {
const result = forgotPasswordSchema.safeParse({ email: "not-email" });
expect(result.success).toBe(false);
});
it("rejects empty object", () => {
const result = forgotPasswordSchema.safeParse({});
expect(result.success).toBe(false);
});
});
describe("resetPasswordSchema", () => {
const validReset = {
token: "abc123token",
password: "Aa123456",
};
it("accepts valid reset input", () => {
const result = resetPasswordSchema.safeParse(validReset);
expect(result.success).toBe(true);
});
it("rejects empty token", () => {
const result = resetPasswordSchema.safeParse({ ...validReset, token: "" });
expect(result.success).toBe(false);
});
it("rejects missing token", () => {
const result = resetPasswordSchema.safeParse({ password: "Aa123456" });
expect(result.success).toBe(false);
});
it("rejects weak password", () => {
const result = resetPasswordSchema.safeParse({ token: "abc", password: "weak" });
expect(result.success).toBe(false);
});
});
// --------------- schemas/pagination ---------------
describe("paginationSchema", () => {
it("accepts empty object and applies defaults", () => {
const result = paginationSchema.safeParse({});
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.page).toBe(1);
expect(result.data.limit).toBe(20);
}
});
it("coerces string page to number", () => {
const result = paginationSchema.safeParse({ page: "3", limit: "50" });
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.page).toBe(3);
expect(result.data.limit).toBe(50);
expect(typeof result.data.page).toBe("number");
expect(typeof result.data.limit).toBe("number");
}
});
it("rejects page less than 1", () => {
const result = paginationSchema.safeParse({ page: 0 });
expect(result.success).toBe(false);
});
it("rejects limit greater than 100", () => {
const result = paginationSchema.safeParse({ limit: 101 });
expect(result.success).toBe(false);
});
it("rejects limit less than 1", () => {
const result = paginationSchema.safeParse({ limit: 0 });
expect(result.success).toBe(false);
});
it("rejects non-integer page", () => {
const result = paginationSchema.safeParse({ page: 1.5 });
expect(result.success).toBe(false);
});
});
// --------------- schemas/changelog ---------------
describe("changelogChangeTypeEnum", () => {
it("accepts fix", () => {
expect(changelogChangeTypeEnum.safeParse("fix").success).toBe(true);
});
it("accepts feature", () => {
expect(changelogChangeTypeEnum.safeParse("feature").success).toBe(true);
});
it("accepts improvement", () => {
expect(changelogChangeTypeEnum.safeParse("improvement").success).toBe(true);
});
it("rejects invalid value", () => {
expect(changelogChangeTypeEnum.safeParse("bugfix").success).toBe(false);
});
});
describe("changelogEntrySchema", () => {
const validEntry = {
id: "123e4567-e89b-12d3-a456-426614174000",
changeType: "fix",
title: "Fixed bug",
description: "This is a fix description",
publishedAt: "2026-01-15T10:00:00.000Z",
createdAt: "2026-01-15T10:00:00.000Z",
updatedAt: "2026-01-15T10:00:00.000Z",
};
it("accepts valid changelog entry", () => {
expect(changelogEntrySchema.safeParse(validEntry).success).toBe(true);
});
it("rejects missing id", () => {
const { id, ...rest } = validEntry;
expect(changelogEntrySchema.safeParse(rest).success).toBe(false);
});
it("rejects non-UUID id", () => {
expect(changelogEntrySchema.safeParse({ ...validEntry, id: "not-uuid" }).success).toBe(false);
});
it("rejects empty title", () => {
expect(changelogEntrySchema.safeParse({ ...validEntry, title: "" }).success).toBe(false);
});
it("rejects empty description", () => {
expect(changelogEntrySchema.safeParse({ ...validEntry, description: "" }).success).toBe(false);
});
it("rejects invalid publishedAt datetime", () => {
expect(
changelogEntrySchema.safeParse({ ...validEntry, publishedAt: "not-a-date" }).success,
).toBe(false);
});
it("rejects invalid changeType", () => {
expect(
changelogEntrySchema.safeParse({ ...validEntry, changeType: "bugfix" }).success,
).toBe(false);
});
});
describe("createChangelogEntrySchema", () => {
const validCreate = {
changeType: "feature",
title: "New feature",
description: "Description of the new feature",
publishedAt: "2026-01-15T10:00:00.000Z",
};
it("accepts valid create input", () => {
expect(createChangelogEntrySchema.safeParse(validCreate).success).toBe(true);
});
it("rejects missing title", () => {
const { title, ...rest } = validCreate;
expect(createChangelogEntrySchema.safeParse(rest).success).toBe(false);
});
it("rejects empty description", () => {
expect(createChangelogEntrySchema.safeParse({ ...validCreate, description: "" }).success).toBe(
false,
);
});
it("does not require id field", () => {
// create schema doesn't have id
expect(validCreate).not.toHaveProperty("id");
});
});
describe("updateChangelogEntrySchema", () => {
it("accepts partial update (empty object)", () => {
expect(updateChangelogEntrySchema.safeParse({}).success).toBe(true);
});
it("accepts single field update", () => {
expect(updateChangelogEntrySchema.safeParse({ title: "Updated" }).success).toBe(true);
});
it("accepts full update", () => {
expect(
updateChangelogEntrySchema.safeParse({
changeType: "fix",
title: "Full update",
description: "Updated description",
publishedAt: "2026-01-15T10:00:00.000Z",
}).success,
).toBe(true);
});
it("rejects invalid changeType in partial update", () => {
expect(updateChangelogEntrySchema.safeParse({ changeType: "bugfix" }).success).toBe(false);
});
it("rejects empty title in partial update", () => {
expect(updateChangelogEntrySchema.safeParse({ title: "" }).success).toBe(false);
});
});
// --------------- utils/vin-validator ---------------
describe("isValidVin", () => {
it("returns true for valid VIN", () => {
expect(isValidVin("WVWZZZ1JZ3W597935")).toBe(true);
});
it("returns false for VIN with invalid characters", () => {
expect(isValidVin("WVWZZZ1IZ3W597935")).toBe(false);
});
it("returns false for short VIN", () => {
expect(isValidVin("WVWZZZ1JZ3W59793")).toBe(false);
});
it("returns false for empty string", () => {
expect(isValidVin("")).toBe(false);
});
it("converts lowercase to uppercase for validation", () => {
expect(isValidVin("wvwzzz1jz3w597935")).toBe(true);
});
});
describe("validateVinCheckDigit", () => {
it("returns true for a VIN with valid check digit", () => {
// Known-good VIN with check digit that validates
expect(validateVinCheckDigit("1HGBH41JXMN109186")).toBe(true);
});
it("returns false for invalid VIN", () => {
expect(validateVinCheckDigit("INVALIDVIN1234567")).toBe(false);
});
it("returns false for VIN with wrong check digit", () => {
// Take a valid VIN and change position 8 (the check digit)
expect(validateVinCheckDigit("WVWZZZ1JZ9W597935")).toBe(false);
});
});
describe("extractWmi", () => {
it("extracts first 3 characters as WMI", () => {
expect(extractWmi("WVWZZZ1JZ3W597935")).toBe("WVW");
});
it("uppercases the result", () => {
expect(extractWmi("wvwzzz1jz3w597935")).toBe("WVW");
});
});
describe("extractModelYear", () => {
it("extracts year for 2010 (A)", () => {
expect(extractModelYear("WVWZZZ1JZAW597935")).toBe(2010);
});
it("extracts year for 2025 (S)", () => {
expect(extractModelYear("WVWZZZ1JZSW597935")).toBe(2025);
});
it("returns null for unknown year character", () => {
expect(extractModelYear("WVWZZZ1JZ0W597935")).toBeNull();
});
it("handles lowercase input", () => {
expect(extractModelYear("wvwzzz1jzaw597935")).toBe(2010);
});
});
// --------------- utils/currency ---------------
describe("formatTRY", () => {
it("formats kurus to TRY", () => {
const result = formatTRY(200_00);
expect(result).toContain("200");
});
it("formats zero", () => {
const result = formatTRY(0);
expect(result).toContain("0");
});
});
describe("kurusToLira", () => {
it("converts 100 kurus to 1 lira", () => {
expect(kurusToLira(100)).toBe(1);
});
it("converts 20000 kurus to 200 lira", () => {
expect(kurusToLira(200_00)).toBe(200);
});
it("converts 0", () => {
expect(kurusToLira(0)).toBe(0);
});
});
describe("liraToKurus", () => {
it("converts 1 lira to 100 kurus", () => {
expect(liraToKurus(1)).toBe(100);
});
it("converts 200 lira to 20000 kurus", () => {
expect(liraToKurus(200)).toBe(200_00);
});
it("rounds to nearest kurus", () => {
expect(liraToKurus(0.005)).toBe(1);
});
});
// --------------- utils/formatters ---------------
describe("formatVin", () => {
it("uppercases and strips invalid chars", () => {
expect(formatVin("wvw-zzz 1jz3w597935")).toBe("WVWZZZ1JZ3W597935");
});
it("handles already clean VIN", () => {
expect(formatVin("WVWZZZ1JZ3W597935")).toBe("WVWZZZ1JZ3W597935");
});
});
describe("formatDate", () => {
it("formats a Date object", () => {
const d = new Date(2026, 0, 15);
const result = formatDate(d);
expect(result).toBe("15.01.2026");
});
it("formats an ISO string", () => {
const result = formatDate("2026-01-15");
expect(result).toBe("15.01.2026");
});
});
describe("formatDateTime", () => {
it("returns a string containing date and time", () => {
const d = new Date(2026, 0, 15, 14, 30);
const result = formatDateTime(d);
expect(result).toContain("15.01.2026");
});
});
describe("slugify", () => {
it("lowercases and replaces spaces with dashes", () => {
expect(slugify("Hello World")).toBe("hello-world");
});
it("replaces Turkish characters", () => {
expect(slugify("ğüşıöç")).toBe("gusioc");
});
it("removes leading/trailing dashes", () => {
expect(slugify(" hello ")).toBe("hello");
});
it("handles special characters", () => {
expect(slugify("Merhaba Dünya!")).toBe("merhaba-dunya");
});
});
describe("generateReferralCode", () => {
it("returns 8-character string", () => {
const code = generateReferralCode();
expect(code).toHaveLength(8);
});
it("contains only uppercase letters and numbers", () => {
const code = generateReferralCode();
expect(/^[A-Z0-9]+$/.test(code)).toBe(true);
});
it("does not contain ambiguous characters", () => {
const code = generateReferralCode();
expect(code).not.toMatch(/[IO0]/);
});
it("generates different codes on repeated calls", () => {
const codes = new Set(Array.from({ length: 10 }, () => generateReferralCode()));
// Extremely unlikely all 10 generate the same code
expect(codes.size).toBeGreaterThan(1);
});
});
// --------------- constants/regex ---------------
describe("VIN_REGEX", () => {
it("matches a valid VIN", () => {
expect(VIN_REGEX.test("WVWZZZ1JZ3W597935")).toBe(true);
});
it("does not match VIN with I", () => {
expect(VIN_REGEX.test("WVWZZZ1IZ3W597935")).toBe(false);
});
it("does not match VIN with O", () => {
expect(VIN_REGEX.test("WVWZZZ1OZ3W597935")).toBe(false);
});
});
describe("EMAIL_REGEX", () => {
it("matches valid email", () => {
expect(EMAIL_REGEX.test("user@example.com")).toBe(true);
});
it("does not match missing @", () => {
expect(EMAIL_REGEX.test("userexample.com")).toBe(false);
});
it("does not match missing domain", () => {
expect(EMAIL_REGEX.test("user@")).toBe(false);
});
});
describe("OEM_CODE_REGEX", () => {
it("matches valid OEM code", () => {
expect(OEM_CODE_REGEX.test("06A-109-108-B")).toBe(true);
});
it("matches short OEM code", () => {
expect(OEM_CODE_REGEX.test("ABC")).toBe(true);
});
});
// --------------- constants/plans ---------------
describe("PLANS", () => {
it("has SINGLE, DOUBLE, TRIPLE, and FULL plans", () => {
expect(PLANS.SINGLE).toBeDefined();
expect(PLANS.DOUBLE).toBeDefined();
expect(PLANS.TRIPLE).toBeDefined();
expect(PLANS.FULL).toBeDefined();
});
it("FULL plan has brandCount 0 (unlimited)", () => {
expect(PLANS.FULL.brandCount).toBe(0);
});
it("SINGLE plan has brandCount 1", () => {
expect(PLANS.SINGLE.brandCount).toBe(1);
});
it("all plans have positive price", () => {
for (const plan of Object.values(PLANS)) {
expect(plan.priceMonthly).toBeGreaterThan(0);
expect(plan.priceYearly).toBeGreaterThan(0);
}
});
});
describe("FULL_PLAN_BRAND_LIMIT", () => {
it("equals 999", () => {
expect(FULL_PLAN_BRAND_LIMIT).toBe(999);
});
});
describe("REFERRAL_REWARDS", () => {
it("TIER_1 requires 3 referrals for 7 extension days", () => {
expect(REFERRAL_REWARDS.TIER_1.count).toBe(3);
expect(REFERRAL_REWARDS.TIER_1.extensionDays).toBe(7);
});
it("TIER_2 requires 5 referrals for 30 extension days", () => {
expect(REFERRAL_REWARDS.TIER_2.count).toBe(5);
expect(REFERRAL_REWARDS.TIER_2.extensionDays).toBe(30);
});
});
describe("CURRENCY", () => {
it("is TRY", () => {
expect(CURRENCY).toBe("TRY");
});
});
// --------------- constants/error-codes ---------------
describe("ERROR_CODES", () => {
it("has auth error codes", () => {
expect(ERROR_CODES.INVALID_CREDENTIALS).toBe("AUTH_001");
expect(ERROR_CODES.EMAIL_ALREADY_EXISTS).toBe("AUTH_002");
expect(ERROR_CODES.SESSION_EXPIRED).toBe("AUTH_003");
expect(ERROR_CODES.UNAUTHORIZED).toBe("AUTH_004");
expect(ERROR_CODES.FORBIDDEN).toBe("AUTH_005");
});
it("has VIN error codes", () => {
expect(ERROR_CODES.INVALID_VIN).toBe("VIN_001");
expect(ERROR_CODES.VIN_DECODE_FAILED).toBe("VIN_002");
expect(ERROR_CODES.BRAND_NOT_SUPPORTED).toBe("VIN_003");
});
it("has subscription error codes", () => {
expect(ERROR_CODES.NO_ACTIVE_SUBSCRIPTION).toBe("SUB_001");
expect(ERROR_CODES.BRAND_ACCESS_DENIED).toBe("SUB_002");
expect(ERROR_CODES.INVALID_BRAND_COUNT).toBe("SUB_003");
expect(ERROR_CODES.SUBSCRIPTION_ALREADY_ACTIVE).toBe("SUB_004");
});
it("has payment error codes", () => {
expect(ERROR_CODES.PAYMENT_FAILED).toBe("PAY_001");
expect(ERROR_CODES.EFT_RECEIPT_REQUIRED).toBe("PAY_003");
expect(ERROR_CODES.PAYMENT_ALREADY_PROCESSED).toBe("PAY_004");
});
it("has general error codes", () => {
expect(ERROR_CODES.NOT_FOUND).toBe("GEN_001");
expect(ERROR_CODES.VALIDATION_ERROR).toBe("GEN_002");
expect(ERROR_CODES.INTERNAL_ERROR).toBe("GEN_003");
expect(ERROR_CODES.RATE_LIMITED).toBe("GEN_004");
expect(ERROR_CODES.CONFLICT).toBe("GEN_005");
});
it("has integration error codes", () => {
expect(ERROR_CODES.PL24_ERROR).toBe("INT_001");
expect(ERROR_CODES.EMEX_ERROR).toBe("INT_002");
expect(ERROR_CODES.CORGI_ERROR).toBe("INT_003");
});
});