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.
191 lines
5.5 KiB
TypeScript
191 lines
5.5 KiB
TypeScript
import { describe, it, expect, beforeAll, afterAll, beforeEach } from "vitest";
|
|
import { testPrisma, setupTestDatabase, cleanupTestDatabase, createTestUser, createTestCompany, createTestCustomer } from "./setup";
|
|
import bcrypt from "bcryptjs";
|
|
|
|
/**
|
|
* Integration Tests for API Routes
|
|
*
|
|
* These tests require a test database.
|
|
* They will skip gracefully if the database is not available.
|
|
*/
|
|
|
|
describe("API Integration Tests (Database Required)", () => {
|
|
let testUser: any;
|
|
let testCompany: any;
|
|
let testCustomer: any;
|
|
let dbAvailable = false;
|
|
|
|
// Setup before all tests
|
|
beforeAll(async () => {
|
|
dbAvailable = await setupTestDatabase();
|
|
|
|
if (!dbAvailable) {
|
|
console.warn("Skipping database integration tests - no test database available");
|
|
return;
|
|
}
|
|
|
|
// Create test user
|
|
testUser = await createTestUser("test@example.com", "testuser");
|
|
|
|
// Create test company
|
|
testCompany = await createTestCompany(testUser.id, "Integration Test GmbH");
|
|
|
|
// Create test customer
|
|
testCustomer = await createTestCustomer(testCompany.id, "Integration Test Customer");
|
|
});
|
|
|
|
// Cleanup after all tests
|
|
afterAll(async () => {
|
|
if (!dbAvailable) return;
|
|
await cleanupTestDatabase();
|
|
await testPrisma.$disconnect();
|
|
});
|
|
|
|
// Clean data between tests (but keep user/company/customer)
|
|
beforeEach(async () => {
|
|
if (!dbAvailable) return;
|
|
|
|
// Delete invoices and related items
|
|
await testPrisma.invoiceItem.deleteMany({
|
|
where: { invoice: { companyId: testCompany.id } },
|
|
});
|
|
await testPrisma.invoice.deleteMany({
|
|
where: { companyId: testCompany.id },
|
|
});
|
|
});
|
|
|
|
// Helper to skip tests if no database
|
|
const dbTest = (name: string, fn: () => Promise<void>) => {
|
|
it(name, async () => {
|
|
if (!dbAvailable) {
|
|
console.warn(`Skipping "${name}" - no test database`);
|
|
return;
|
|
}
|
|
await fn();
|
|
});
|
|
};
|
|
|
|
describe("Companies API", () => {
|
|
dbTest("should list companies for a user", async () => {
|
|
const companies = await testPrisma.company.findMany({
|
|
where: { userId: testUser.id },
|
|
});
|
|
|
|
expect(companies).toBeDefined();
|
|
expect(companies.length).toBeGreaterThan(0);
|
|
expect(companies[0].name).toBe("Integration Test GmbH");
|
|
});
|
|
|
|
dbTest("should create a new company", async () => {
|
|
const newCompanyData = {
|
|
name: "New Test Company",
|
|
address: "New Street 1",
|
|
zip: "99999",
|
|
city: "Munich",
|
|
country: "DE",
|
|
};
|
|
|
|
const company = await testPrisma.company.create({
|
|
data: {
|
|
...newCompanyData,
|
|
userId: testUser.id,
|
|
},
|
|
});
|
|
|
|
expect(company).toBeDefined();
|
|
expect(company.name).toBe("New Test Company");
|
|
expect(company.userId).toBe(testUser.id);
|
|
});
|
|
});
|
|
|
|
describe("Invoices API", () => {
|
|
dbTest("should create a new invoice", async () => {
|
|
const { calcItemAmounts, calcInvoiceTotals } = await import("@/lib/tax");
|
|
|
|
const invoiceData = {
|
|
companyId: testCompany.id,
|
|
customerId: testCustomer.id,
|
|
issueDate: new Date(),
|
|
dueDate: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000),
|
|
kleinunternehmer: false,
|
|
items: [
|
|
{
|
|
position: 1,
|
|
description: "Test Service",
|
|
quantity: 10,
|
|
unit: "Stunden",
|
|
unitPrice: 100,
|
|
taxRate: 19,
|
|
netAmount: 1000,
|
|
taxAmount: 190,
|
|
grossAmount: 1190,
|
|
},
|
|
],
|
|
};
|
|
|
|
// Recalculate server-side (as the API does)
|
|
const recalculatedItems = invoiceData.items.map(item => ({
|
|
...item,
|
|
...calcItemAmounts(item.quantity, item.unitPrice, item.taxRate),
|
|
}));
|
|
const totals = calcInvoiceTotals(recalculatedItems);
|
|
|
|
const invoice = await testPrisma.invoice.create({
|
|
data: {
|
|
companyId: invoiceData.companyId,
|
|
customerId: invoiceData.customerId,
|
|
issueDate: invoiceData.issueDate,
|
|
dueDate: invoiceData.dueDate,
|
|
status: "DRAFT",
|
|
kleinunternehmer: invoiceData.kleinunternehmer,
|
|
netTotal: totals.netTotal,
|
|
taxTotal: totals.taxTotal,
|
|
grossTotal: totals.grossTotal,
|
|
items: {
|
|
create: recalculatedItems.map((item, idx) => ({
|
|
position: idx + 1,
|
|
description: item.description,
|
|
quantity: item.quantity,
|
|
unit: item.unit,
|
|
unitPrice: item.unitPrice,
|
|
taxRate: item.taxRate,
|
|
netAmount: item.netAmount,
|
|
taxAmount: item.taxAmount,
|
|
grossAmount: item.grossAmount,
|
|
})),
|
|
},
|
|
},
|
|
include: { items: true, customer: true },
|
|
});
|
|
|
|
expect(invoice).toBeDefined();
|
|
expect(invoice.status).toBe("DRAFT");
|
|
expect(invoice.items).toHaveLength(1);
|
|
expect(invoice.grossTotal).toBe(1190);
|
|
});
|
|
});
|
|
|
|
describe("Buchungen API", () => {
|
|
dbTest("should create a new Buchung", async () => {
|
|
const buchungData = {
|
|
companyId: testCompany.id,
|
|
date: new Date(),
|
|
account: "KASSE" as const,
|
|
type: "EINLAGE" as const,
|
|
amount: 1000,
|
|
description: "Initial investment",
|
|
isBusinessRecord: false,
|
|
};
|
|
|
|
const buchung = await testPrisma.buchung.create({
|
|
data: buchungData,
|
|
});
|
|
|
|
expect(buchung).toBeDefined();
|
|
expect(buchung.account).toBe("KASSE");
|
|
expect(buchung.type).toBe("EINLAGE");
|
|
expect(buchung.amount.toNumber()).toBe(1000);
|
|
});
|
|
});
|
|
});
|