Initial commit: Fahrrad Verschleißteile Tracker
- Next.js SPA mit Bun Runtime - Prisma mit SQLite Datenbank - Vollständige CRUD-Operationen für Fahrräder, Verschleißteile und Wartungshistorie - Warnsystem für bevorstehende Wartungen - Statistik-Features (Gesamtkosten, durchschnittliche Lebensdauer) - Zod-Validierung für alle API-Requests - Umfassende Test-Suite (41 Tests)
This commit is contained in:
330
__tests__/lib/utils.test.ts
Normal file
330
__tests__/lib/utils.test.ts
Normal file
@@ -0,0 +1,330 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import {
|
||||
calculateServiceStatus,
|
||||
calculateTotalCosts,
|
||||
calculateAverageLifespan,
|
||||
formatDate,
|
||||
formatCurrency,
|
||||
} from '@/lib/utils'
|
||||
import { WearPart, MaintenanceHistory } from '@prisma/client'
|
||||
|
||||
describe('Utility Functions', () => {
|
||||
describe('calculateServiceStatus', () => {
|
||||
it('should return OK status when part is new', () => {
|
||||
const part = {
|
||||
id: '1',
|
||||
bikeId: 'bike1',
|
||||
type: 'CHAIN',
|
||||
installDate: new Date(),
|
||||
installMileage: 0,
|
||||
serviceInterval: 1000,
|
||||
status: 'ACTIVE',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
brand: null,
|
||||
model: null,
|
||||
cost: null,
|
||||
notes: null,
|
||||
maintenanceHistory: [],
|
||||
}
|
||||
|
||||
const status = calculateServiceStatus(part)
|
||||
expect(status.status).toBe('OK')
|
||||
expect(status.remainingKm).toBe(1000)
|
||||
expect(status.percentageUsed).toBe(0)
|
||||
})
|
||||
|
||||
it('should return WARNING status when 75% used', () => {
|
||||
const part = {
|
||||
id: '1',
|
||||
bikeId: 'bike1',
|
||||
type: 'CHAIN',
|
||||
installDate: new Date(),
|
||||
installMileage: 0,
|
||||
serviceInterval: 1000,
|
||||
status: 'ACTIVE',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
brand: null,
|
||||
model: null,
|
||||
cost: null,
|
||||
notes: null,
|
||||
maintenanceHistory: [
|
||||
{
|
||||
id: '1',
|
||||
wearPartId: '1',
|
||||
date: new Date(),
|
||||
mileage: 750,
|
||||
action: 'SERVICE',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
notes: null,
|
||||
cost: null,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const status = calculateServiceStatus(part)
|
||||
expect(status.status).toBe('WARNING')
|
||||
expect(status.percentageUsed).toBeGreaterThanOrEqual(75)
|
||||
})
|
||||
|
||||
it('should return CRITICAL status when 90% used', () => {
|
||||
const part = {
|
||||
id: '1',
|
||||
bikeId: 'bike1',
|
||||
type: 'CHAIN',
|
||||
installDate: new Date(),
|
||||
installMileage: 0,
|
||||
serviceInterval: 1000,
|
||||
status: 'ACTIVE',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
brand: null,
|
||||
model: null,
|
||||
cost: null,
|
||||
notes: null,
|
||||
maintenanceHistory: [
|
||||
{
|
||||
id: '1',
|
||||
wearPartId: '1',
|
||||
date: new Date(),
|
||||
mileage: 950,
|
||||
action: 'SERVICE',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
notes: null,
|
||||
cost: null,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const status = calculateServiceStatus(part)
|
||||
expect(status.status).toBe('CRITICAL')
|
||||
expect(status.percentageUsed).toBeGreaterThanOrEqual(90)
|
||||
})
|
||||
})
|
||||
|
||||
describe('calculateTotalCosts', () => {
|
||||
it('should calculate total costs correctly', () => {
|
||||
const parts = [
|
||||
{
|
||||
id: '1',
|
||||
bikeId: 'bike1',
|
||||
type: 'CHAIN',
|
||||
installDate: new Date(),
|
||||
installMileage: 0,
|
||||
serviceInterval: 1000,
|
||||
status: 'ACTIVE',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
brand: null,
|
||||
model: null,
|
||||
cost: 50.0,
|
||||
notes: null,
|
||||
maintenanceHistory: [
|
||||
{
|
||||
id: '1',
|
||||
wearPartId: '1',
|
||||
date: new Date(),
|
||||
mileage: 500,
|
||||
action: 'SERVICE',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
notes: null,
|
||||
cost: 20.0,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
bikeId: 'bike1',
|
||||
type: 'BRAKE_PADS',
|
||||
installDate: new Date(),
|
||||
installMileage: 0,
|
||||
serviceInterval: 500,
|
||||
status: 'ACTIVE',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
brand: null,
|
||||
model: null,
|
||||
cost: 30.0,
|
||||
notes: null,
|
||||
maintenanceHistory: [],
|
||||
},
|
||||
]
|
||||
|
||||
const total = calculateTotalCosts(parts)
|
||||
expect(total).toBe(100.0) // 50 + 20 + 30
|
||||
})
|
||||
|
||||
it('should return 0 for parts with no costs', () => {
|
||||
const parts = [
|
||||
{
|
||||
id: '1',
|
||||
bikeId: 'bike1',
|
||||
type: 'CHAIN',
|
||||
installDate: new Date(),
|
||||
installMileage: 0,
|
||||
serviceInterval: 1000,
|
||||
status: 'ACTIVE',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
brand: null,
|
||||
model: null,
|
||||
cost: null,
|
||||
notes: null,
|
||||
maintenanceHistory: [],
|
||||
},
|
||||
]
|
||||
|
||||
const total = calculateTotalCosts(parts)
|
||||
expect(total).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('calculateAverageLifespan', () => {
|
||||
it('should calculate average lifespan for replaced parts', () => {
|
||||
const parts = [
|
||||
{
|
||||
id: '1',
|
||||
bikeId: 'bike1',
|
||||
type: 'CHAIN',
|
||||
installDate: new Date(),
|
||||
installMileage: 0,
|
||||
serviceInterval: 1000,
|
||||
status: 'REPLACED',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
brand: null,
|
||||
model: null,
|
||||
cost: null,
|
||||
notes: null,
|
||||
maintenanceHistory: [
|
||||
{
|
||||
id: '1',
|
||||
wearPartId: '1',
|
||||
date: new Date('2024-01-01'),
|
||||
mileage: 0,
|
||||
action: 'INSTALL',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
notes: null,
|
||||
cost: null,
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
wearPartId: '1',
|
||||
date: new Date('2024-06-01'),
|
||||
mileage: 2000,
|
||||
action: 'REPLACE',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
notes: null,
|
||||
cost: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
bikeId: 'bike1',
|
||||
type: 'BRAKE_PADS',
|
||||
installDate: new Date(),
|
||||
installMileage: 0,
|
||||
serviceInterval: 500,
|
||||
status: 'REPLACED',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
brand: null,
|
||||
model: null,
|
||||
cost: null,
|
||||
notes: null,
|
||||
maintenanceHistory: [
|
||||
{
|
||||
id: '3',
|
||||
wearPartId: '2',
|
||||
date: new Date('2024-01-01'),
|
||||
mileage: 0,
|
||||
action: 'INSTALL',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
notes: null,
|
||||
cost: null,
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
wearPartId: '2',
|
||||
date: new Date('2024-03-01'),
|
||||
mileage: 1000,
|
||||
action: 'REPLACE',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
notes: null,
|
||||
cost: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const avg = calculateAverageLifespan(parts)
|
||||
expect(avg).toBe(1500) // (2000 + 1000) / 2
|
||||
})
|
||||
|
||||
it('should return null when no replaced parts exist', () => {
|
||||
const parts = [
|
||||
{
|
||||
id: '1',
|
||||
bikeId: 'bike1',
|
||||
type: 'CHAIN',
|
||||
installDate: new Date(),
|
||||
installMileage: 0,
|
||||
serviceInterval: 1000,
|
||||
status: 'ACTIVE',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
brand: null,
|
||||
model: null,
|
||||
cost: null,
|
||||
notes: null,
|
||||
maintenanceHistory: [],
|
||||
},
|
||||
]
|
||||
|
||||
const avg = calculateAverageLifespan(parts)
|
||||
expect(avg).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('formatDate', () => {
|
||||
it('should format date correctly', () => {
|
||||
const date = new Date('2024-01-15')
|
||||
const formatted = formatDate(date)
|
||||
expect(formatted).toMatch(/\d{2}\.\d{2}\.\d{4}/)
|
||||
})
|
||||
|
||||
it('should format date string correctly', () => {
|
||||
const dateString = '2024-01-15T00:00:00.000Z'
|
||||
const formatted = formatDate(dateString)
|
||||
expect(formatted).toMatch(/\d{2}\.\d{2}\.\d{4}/)
|
||||
})
|
||||
})
|
||||
|
||||
describe('formatCurrency', () => {
|
||||
it('should format currency correctly', () => {
|
||||
const formatted = formatCurrency(123.45)
|
||||
expect(formatted).toContain('123,45')
|
||||
expect(formatted).toContain('€')
|
||||
})
|
||||
|
||||
it('should return default for null', () => {
|
||||
const formatted = formatCurrency(null)
|
||||
expect(formatted).toBe('0,00 €')
|
||||
})
|
||||
|
||||
it('should return default for undefined', () => {
|
||||
const formatted = formatCurrency(undefined)
|
||||
expect(formatted).toBe('0,00 €')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user