db953b1e28
- Implement tests for client validation functions including tax ID, VAT ID, IBAN, BIC, website, and company form validation. - Create tests for revenue and expense categories ensuring all expected categories and labels are present. - Add tests for invoice number generation with various scenarios including prefix handling and sequence padding. - Introduce tests for default categories and their integration, ensuring no overlaps and consistent naming conventions. - Implement Zod schema validation tests for currency, tax rates, IBAN, tax ID, VAT ID, invoices, companies, and customers. - Add utility tests for tax calculations, including item amounts and invoice totals, ensuring correct handling of tax rates and formatting. - Set up Vitest configuration and global test setup for consistent testing environment.
244 lines
7.7 KiB
TypeScript
244 lines
7.7 KiB
TypeScript
import {
|
|
currencySchema,
|
|
taxRateSchema,
|
|
ibanSchema,
|
|
taxIdSchema,
|
|
vatIdSchema,
|
|
invoiceSchema,
|
|
companySchema,
|
|
customerSchema,
|
|
} from "@/lib/schemas";
|
|
|
|
describe("schemas.ts - Zod Validation", () => {
|
|
describe("currencySchema", () => {
|
|
it("should accept valid currency values", () => {
|
|
expect(currencySchema.parse(0)).toBe(0);
|
|
expect(currencySchema.parse(100)).toBe(100);
|
|
expect(currencySchema.parse(99.99)).toBe(99.99);
|
|
expect(currencySchema.parse(1000.5)).toBe(1000.5);
|
|
});
|
|
|
|
it("should reject negative values", () => {
|
|
expect(() => currencySchema.parse(-1)).toThrow("Geldbeträge dürfen nicht negativ sein");
|
|
});
|
|
|
|
it("should reject more than 2 decimal places", () => {
|
|
expect(() => currencySchema.parse(1.999)).toThrow(
|
|
"Geldbeträge dürfen maximal 2 Dezimalstellen haben"
|
|
);
|
|
});
|
|
|
|
it("should accept exactly 2 decimal places", () => {
|
|
expect(currencySchema.parse(1.99)).toBe(1.99);
|
|
});
|
|
});
|
|
|
|
describe("taxRateSchema", () => {
|
|
it("should accept valid German tax rates", () => {
|
|
expect(taxRateSchema.parse(0)).toBe(0);
|
|
expect(taxRateSchema.parse(7)).toBe(7);
|
|
expect(taxRateSchema.parse(19)).toBe(19);
|
|
});
|
|
|
|
it("should reject invalid tax rates", () => {
|
|
expect(() => taxRateSchema.parse(5)).toThrow(
|
|
"Steuersatz muss 0 (steuerfrei), 7 (ermäßigt) oder 19 (Standard) sein"
|
|
);
|
|
expect(() => taxRateSchema.parse(20)).toThrow();
|
|
expect(() => taxRateSchema.parse(15)).toThrow();
|
|
});
|
|
|
|
it("should reject non-integer values", () => {
|
|
expect(() => taxRateSchema.parse(7.5)).toThrow("Steuersatz muss eine ganze Zahl sein");
|
|
});
|
|
});
|
|
|
|
describe("ibanSchema", () => {
|
|
it("should accept valid IBANs", () => {
|
|
expect(ibanSchema.parse("DE89370400440532013000")).toBe("DE89370400440532013000");
|
|
expect(ibanSchema.parse("AT611904300234573201")).toBe("AT611904300234573201");
|
|
expect(ibanSchema.parse("CH9300762011623852957")).toBe("CH9300762011623852957");
|
|
});
|
|
|
|
it("should accept empty string", () => {
|
|
expect(ibanSchema.parse("")).toBe("");
|
|
});
|
|
|
|
it("should reject invalid IBAN format", () => {
|
|
// No country code - starts with numbers
|
|
expect(() => ibanSchema.parse("1234567890")).toThrow();
|
|
// Too short - only 4 chars (need at least 5: 2 letters + 2 digits + 1)
|
|
expect(() => ibanSchema.parse("DE1")).toThrow();
|
|
});
|
|
});
|
|
|
|
describe("taxIdSchema", () => {
|
|
it("should accept valid 10-digit tax IDs", () => {
|
|
expect(taxIdSchema.parse("1234567890")).toBe("1234567890");
|
|
});
|
|
|
|
it("should accept empty string", () => {
|
|
expect(taxIdSchema.parse("")).toBe("");
|
|
});
|
|
|
|
it("should reject invalid tax IDs", () => {
|
|
expect(() => taxIdSchema.parse("123")).toThrow("Steuernummer muss 10 Ziffern haben");
|
|
expect(() => taxIdSchema.parse("12345678901")).toThrow();
|
|
expect(() => taxIdSchema.parse("abcdefghij")).toThrow();
|
|
});
|
|
});
|
|
|
|
describe("vatIdSchema", () => {
|
|
it("should accept valid DE VAT IDs", () => {
|
|
expect(vatIdSchema.parse("DE123456789")).toBe("DE123456789");
|
|
});
|
|
|
|
it("should accept empty string", () => {
|
|
expect(vatIdSchema.parse("")).toBe("");
|
|
});
|
|
|
|
it("should reject invalid VAT IDs", () => {
|
|
expect(() => vatIdSchema.parse("DE123")).toThrow(
|
|
"USt-IdNr. muss im Format DE + 9 Ziffern sein"
|
|
);
|
|
expect(() => vatIdSchema.parse("1234567890")).toThrow();
|
|
expect(() => vatIdSchema.parse("DE12345678")).toThrow();
|
|
});
|
|
});
|
|
|
|
describe("invoiceSchema", () => {
|
|
const validInvoice = {
|
|
companyId: "cm123abc",
|
|
customerId: "cm456def",
|
|
issueDate: "2026-05-08",
|
|
dueDate: "2026-06-08",
|
|
kleinunternehmer: false,
|
|
items: [
|
|
{
|
|
position: 1,
|
|
description: "Web Development",
|
|
quantity: 10,
|
|
unit: "Stunden",
|
|
unitPrice: 100,
|
|
taxRate: 19,
|
|
netAmount: 1000,
|
|
taxAmount: 190,
|
|
grossAmount: 1190,
|
|
},
|
|
],
|
|
netTotal: 1000,
|
|
taxTotal: 190,
|
|
grossTotal: 1190,
|
|
};
|
|
|
|
it("should accept valid invoice", () => {
|
|
const result = invoiceSchema.parse(validInvoice);
|
|
expect(result.companyId).toBe("cm123abc");
|
|
expect(result.items).toHaveLength(1);
|
|
});
|
|
|
|
it("should require at least one item", () => {
|
|
const invalid = { ...validInvoice, items: [] };
|
|
expect(() => invoiceSchema.parse(invalid)).toThrow(
|
|
"Mindestens ein Rechnungsposition erforderlich"
|
|
);
|
|
});
|
|
|
|
it("should accept optional deliveryDate", () => {
|
|
const withDelivery = { ...validInvoice, deliveryDate: "2026-05-07" };
|
|
expect(() => invoiceSchema.parse(withDelivery)).not.toThrow();
|
|
});
|
|
|
|
it("should reject invalid dates", () => {
|
|
const invalid = { ...validInvoice, issueDate: "not-a-date" };
|
|
expect(() => invoiceSchema.parse(invalid)).toThrow("Ungültiges Datum");
|
|
});
|
|
|
|
it("should accept optional notes with max length", () => {
|
|
const withNotes = { ...validInvoice, notes: "Test notes" };
|
|
expect(() => invoiceSchema.parse(withNotes)).not.toThrow();
|
|
});
|
|
});
|
|
|
|
describe("companySchema", () => {
|
|
const validCompany = {
|
|
name: "Test GmbH",
|
|
taxId: null,
|
|
vatId: null,
|
|
address: "Hauptstraße 1",
|
|
zip: "12345",
|
|
city: "Berlin",
|
|
country: "DE",
|
|
invoicePrefix: "RE",
|
|
kleinunternehmer: false,
|
|
bankIban: null,
|
|
// bankBic is optional, can be omitted or empty string
|
|
};
|
|
|
|
it("should accept valid company", () => {
|
|
const result = companySchema.parse(validCompany);
|
|
expect(result.name).toBe("Test GmbH");
|
|
});
|
|
|
|
it("should require name and address", () => {
|
|
const invalid = { ...validCompany, name: "" };
|
|
expect(() => companySchema.parse(invalid)).toThrow("Firmenname erforderlich");
|
|
});
|
|
|
|
it("should validate email format", () => {
|
|
const withEmail = { ...validCompany, email: "test@example.com" };
|
|
expect(() => companySchema.parse(withEmail)).not.toThrow();
|
|
});
|
|
|
|
it("should reject invalid email", () => {
|
|
const invalid = { ...validCompany, email: "not-an-email" };
|
|
expect(() => companySchema.parse(invalid)).toThrow("Ungültige E-Mail-Adresse");
|
|
});
|
|
|
|
it("should validate website starts with http/https", () => {
|
|
const withWebsite = { ...validCompany, website: "https://example.com" };
|
|
expect(() => companySchema.parse(withWebsite)).not.toThrow();
|
|
});
|
|
|
|
it("should reject website without protocol", () => {
|
|
const invalid = { ...validCompany, website: "example.com" };
|
|
expect(() => companySchema.parse(invalid)).toThrow(
|
|
"Website muss mit http:// oder https:// beginnen"
|
|
);
|
|
});
|
|
|
|
it("should accept valid IBAN", () => {
|
|
const withIban = { ...validCompany, bankIban: "DE89370400440532013000" };
|
|
expect(() => companySchema.parse(withIban)).not.toThrow();
|
|
});
|
|
});
|
|
|
|
describe("customerSchema", () => {
|
|
const validCustomer = {
|
|
companyId: "cm123abc",
|
|
name: "Max Mustermann",
|
|
address: "Musterstraße 1",
|
|
zip: "12345",
|
|
city: "Berlin",
|
|
country: "DE",
|
|
};
|
|
|
|
it("should accept valid customer", () => {
|
|
const result = customerSchema.parse(validCustomer);
|
|
expect(result.name).toBe("Max Mustermann");
|
|
});
|
|
|
|
it("should require companyId", () => {
|
|
const invalid = { ...validCustomer, companyId: "" };
|
|
expect(() => customerSchema.parse(invalid)).toThrow("Mandant erforderlich");
|
|
});
|
|
|
|
it("should validate zip code format", () => {
|
|
const invalid = { ...validCustomer, zip: "abc" };
|
|
expect(() => customerSchema.parse(invalid)).toThrow(
|
|
"PLZ darf nur Zahlen, Bindestriche und Leerzeichen enthalten"
|
|
);
|
|
});
|
|
});
|
|
});
|