Add comprehensive test suite and update configuration

- Add Jest testing framework configuration
- Add test files for services, types, and utilities
- Update package.json with Jest dependencies and test scripts
- Enhance pre-commit checks to include testing
- Add proper environment validation and error handling in mailgun service
This commit is contained in:
William Valentin
2025-09-07 18:18:25 -07:00
parent bfebb34b7a
commit 16d025e747
8 changed files with 1170 additions and 35 deletions

View File

@@ -0,0 +1,308 @@
import { Frequency, UserRole, DoseStatus } from '../../types';
import type {
Medication,
Dose,
CustomReminder,
ReminderInstance,
User,
CouchDBDocument,
} from '../../types';
describe('Type Definitions and Enums', () => {
describe('Frequency Enum', () => {
test('should have correct string values', () => {
expect(Frequency.Daily).toBe('Daily');
expect(Frequency.TwiceADay).toBe('Twice a day');
expect(Frequency.ThreeTimesADay).toBe('Three times a day');
expect(Frequency.EveryXHours).toBe('Every X hours');
});
test('should be usable as object keys', () => {
const frequencyMap = {
[Frequency.Daily]: 1,
[Frequency.TwiceADay]: 2,
[Frequency.ThreeTimesADay]: 3,
[Frequency.EveryXHours]: 'variable',
};
expect(frequencyMap[Frequency.Daily]).toBe(1);
expect(frequencyMap[Frequency.TwiceADay]).toBe(2);
expect(frequencyMap[Frequency.ThreeTimesADay]).toBe(3);
expect(frequencyMap[Frequency.EveryXHours]).toBe('variable');
});
test('should have all expected frequency options', () => {
const frequencyValues = Object.values(Frequency);
expect(frequencyValues).toHaveLength(4);
expect(frequencyValues).toContain('Daily');
expect(frequencyValues).toContain('Twice a day');
expect(frequencyValues).toContain('Three times a day');
expect(frequencyValues).toContain('Every X hours');
});
});
describe('UserRole Enum', () => {
test('should have correct values', () => {
expect(UserRole.USER).toBe('USER');
expect(UserRole.ADMIN).toBe('ADMIN');
});
});
describe('DoseStatus Enum', () => {
test('should have correct values', () => {
expect(DoseStatus.UPCOMING).toBe('UPCOMING');
expect(DoseStatus.TAKEN).toBe('TAKEN');
expect(DoseStatus.MISSED).toBe('MISSED');
expect(DoseStatus.SNOOZED).toBe('SNOOZED');
});
});
describe('CouchDBDocument Type', () => {
test('should require _id and _rev fields', () => {
const doc: CouchDBDocument = {
_id: 'test-id',
_rev: 'test-rev',
};
expect(doc._id).toBe('test-id');
expect(doc._rev).toBe('test-rev');
});
});
describe('Medication Type', () => {
test('should accept valid medication object', () => {
const medication: Medication = {
_id: 'med-123',
_rev: 'rev-456',
name: 'Aspirin',
dosage: '100mg',
frequency: Frequency.Daily,
startTime: '08:00',
notes: 'Take with food',
};
expect(medication._id).toBe('med-123');
expect(medication.name).toBe('Aspirin');
expect(medication.frequency).toBe(Frequency.Daily);
expect(medication.startTime).toBe('08:00');
});
test('should accept medication with hoursBetween for EveryXHours frequency', () => {
const medication: Medication = {
_id: 'med-456',
_rev: 'rev-789',
name: 'Pain Reliever',
dosage: '200mg',
frequency: Frequency.EveryXHours,
startTime: '06:00',
hoursBetween: 6,
notes: '',
};
expect(medication.frequency).toBe(Frequency.EveryXHours);
expect(medication.hoursBetween).toBe(6);
});
test('should accept medication with optional fields undefined', () => {
const medication: Medication = {
_id: 'med-789',
_rev: 'rev-012',
name: 'Vitamin D',
dosage: '1000 IU',
frequency: Frequency.Daily,
startTime: '07:00',
hoursBetween: undefined,
notes: '',
};
expect(medication.hoursBetween).toBeUndefined();
expect(medication.notes).toBe('');
});
});
describe('Dose Type', () => {
test('should accept valid dose object', () => {
const scheduledTime = new Date('2024-01-15T08:00:00.000Z');
const dose: Dose = {
id: 'dose-123',
medicationId: 'med-456',
scheduledTime,
};
expect(dose.id).toBe('dose-123');
expect(dose.medicationId).toBe('med-456');
expect(dose.scheduledTime).toBe(scheduledTime);
expect(dose.scheduledTime).toBeInstanceOf(Date);
});
test('should validate unique dose ID format', () => {
const dose: Dose = {
id: 'med-123-2024-01-15-080000',
medicationId: 'med-123',
scheduledTime: new Date('2024-01-15T08:00:00.000Z'),
};
expect(dose.id).toContain('med-123');
expect(dose.id).toContain('2024-01-15');
});
});
describe('CustomReminder Type', () => {
test('should accept valid custom reminder object', () => {
const reminder: CustomReminder = {
_id: 'reminder-123',
_rev: 'rev-456',
title: 'Check Blood Pressure',
icon: '🩺',
startTime: '09:00',
endTime: '17:00',
frequencyMinutes: 60,
};
expect(reminder._id).toBe('reminder-123');
expect(reminder.title).toBe('Check Blood Pressure');
expect(reminder.icon).toBe('🩺');
expect(reminder.startTime).toBe('09:00');
expect(reminder.endTime).toBe('17:00');
expect(reminder.frequencyMinutes).toBe(60);
});
test('should validate frequency minutes is a number', () => {
const reminder: CustomReminder = {
_id: 'reminder-789',
_rev: 'rev-012',
title: 'Hydrate',
icon: '💧',
startTime: '08:00',
endTime: '20:00',
frequencyMinutes: 30,
};
expect(typeof reminder.frequencyMinutes).toBe('number');
expect(reminder.frequencyMinutes).toBeGreaterThan(0);
});
});
describe('ReminderInstance Type', () => {
test('should accept valid reminder instance object', () => {
const scheduledTime = new Date('2024-01-15T10:00:00.000Z');
const instance: ReminderInstance = {
id: 'instance-123',
reminderId: 'reminder-456',
title: 'Take Vitamins',
icon: '💊',
scheduledTime,
};
expect(instance.id).toBe('instance-123');
expect(instance.reminderId).toBe('reminder-456');
expect(instance.title).toBe('Take Vitamins');
expect(instance.icon).toBe('💊');
expect(instance.scheduledTime).toBe(scheduledTime);
expect(instance.scheduledTime).toBeInstanceOf(Date);
});
});
describe('User Type', () => {
test('should accept valid user object with required fields', () => {
const user: User = {
_id: 'user-123',
_rev: 'rev-456',
username: 'testuser',
};
expect(user._id).toBe('user-123');
expect(user._rev).toBe('rev-456');
expect(user.username).toBe('testuser');
});
test('should accept user with all optional fields', () => {
const user: User = {
_id: 'user-456',
_rev: 'rev-789',
username: 'fulluser',
email: 'full@example.com',
avatar: 'base64-encoded-avatar',
password: 'hashed-password',
emailVerified: true,
role: UserRole.USER,
createdAt: new Date('2024-01-01T00:00:00.000Z'),
lastLoginAt: new Date('2024-01-15T08:00:00.000Z'),
};
expect(user.email).toBe('full@example.com');
expect(user.avatar).toBe('base64-encoded-avatar');
expect(user.password).toBe('hashed-password');
expect(user.emailVerified).toBe(true);
expect(user.role).toBe(UserRole.USER);
expect(user.createdAt).toBeInstanceOf(Date);
expect(user.lastLoginAt).toBeInstanceOf(Date);
});
});
describe('Type Compatibility', () => {
test('should handle Date objects consistently', () => {
const now = new Date();
const dose: Dose = {
id: 'date-test',
medicationId: 'med-test',
scheduledTime: now,
};
expect(dose.scheduledTime).toBe(now);
expect(dose.scheduledTime).toBeInstanceOf(Date);
});
test('should handle unicode characters in string fields', () => {
const reminder: CustomReminder = {
_id: 'unicode-test',
_rev: 'rev-unicode',
title: 'Medicine with émojis 💊🏥',
icon: '🎯',
startTime: '08:00',
endTime: '20:00',
frequencyMinutes: 30,
};
expect(reminder.title).toContain('émojis');
expect(reminder.title).toContain('💊');
expect(reminder.icon).toBe('🎯');
});
test('should handle empty string values where appropriate', () => {
const medication: Medication = {
_id: 'empty-test',
_rev: 'rev-empty',
name: 'Test Med',
dosage: '10mg',
frequency: Frequency.Daily,
startTime: '08:00',
notes: '',
};
expect(medication.notes).toBe('');
expect(typeof medication.notes).toBe('string');
});
test('should maintain type safety with functions', () => {
const createDose = (medication: Medication): Dose => ({
id: `${medication._id}-${Date.now()}`,
medicationId: medication._id,
scheduledTime: new Date(),
});
const medication: Medication = {
_id: 'function-test',
_rev: 'rev-function',
name: 'Function Med',
dosage: '25mg',
frequency: Frequency.TwiceADay,
startTime: '12:00',
notes: '',
};
const dose = createDose(medication);
expect(dose.medicationId).toBe(medication._id);
});
});
});