/** * Comprehensive Trigger Test Script for LibreBooking * * Tests: * 1. Authentication * 2. Get All Reservations * 3. Create New Reservation (triggers "New" mode) * 4. Update Reservation (triggers "Updated" mode) * 5. Delete Reservation * 6. Date Range calculations * 7. Time Filter logic */ import * as https from 'https'; const BASE_URL = 'https://librebooking.zell-cloud.de'; const USERNAME = 'sebastian.zell@zell-aufmass.de'; const PASSWORD = 'wanUQ4uVqU6lfP'; // Test date: 7.2.2026 const TEST_DATE = '2026-02-07'; interface TestResult { name: string; success: boolean; message: string; data?: any; } interface Session { sessionToken: string; userId: number; } const results: TestResult[] = []; function log(message: string) { console.log(`[${new Date().toISOString()}] ${message}`); } function logSuccess(name: string, message: string, data?: any) { log(`✅ ${name}: ${message}`); results.push({ name, success: true, message, data }); } function logError(name: string, message: string, data?: any) { log(`❌ ${name}: ${message}`); results.push({ name, success: false, message, data }); } async function makeRequest( method: string, path: string, body?: any, headers?: Record ): Promise { return new Promise((resolve, reject) => { const url = new URL(`${BASE_URL}${path}`); const options: https.RequestOptions = { hostname: url.hostname, port: 443, path: url.pathname + url.search, method, headers: { 'Content-Type': 'application/json', ...headers, }, }; const req = https.request(options, (res) => { let data = ''; res.on('data', (chunk) => (data += chunk)); res.on('end', () => { try { const json = JSON.parse(data); resolve(json); } catch (e) { resolve(data); } }); }); req.on('error', reject); if (body) { req.write(JSON.stringify(body)); } req.end(); }); } // ===== AUTHENTICATION ===== async function authenticate(): Promise { const response = await makeRequest( 'POST', '/Web/Services/index.php/Authentication/Authenticate', { username: USERNAME, password: PASSWORD } ); if (!response.isAuthenticated) { throw new Error('Authentication failed'); } return { sessionToken: response.sessionToken, userId: response.userId, }; } async function signOut(session: Session): Promise { await makeRequest( 'POST', '/Web/Services/index.php/Authentication/SignOut', { userId: session.userId, sessionToken: session.sessionToken } ); } // ===== API REQUESTS ===== async function getReservations( session: Session, startDate: string, endDate: string ): Promise { const response = await makeRequest( 'GET', `/Web/Services/index.php/Reservations/?startDateTime=${startDate}T00:00:00&endDateTime=${endDate}T23:59:59`, undefined, { 'X-Booked-SessionToken': session.sessionToken, 'X-Booked-UserId': session.userId.toString(), } ); return response.reservations || []; } async function createReservation( session: Session, data: { title: string; description: string; resourceId: number; startDateTime: string; endDateTime: string; } ): Promise { const response = await makeRequest( 'POST', '/Web/Services/index.php/Reservations/', { ...data, userId: session.userId, termsAccepted: true, allowParticipation: false, }, { 'X-Booked-SessionToken': session.sessionToken, 'X-Booked-UserId': session.userId.toString(), } ); return response; } async function updateReservation( session: Session, referenceNumber: string, data: { title: string; description: string; resourceId: number; startDateTime: string; endDateTime: string; } ): Promise { const response = await makeRequest( 'POST', `/Web/Services/index.php/Reservations/${referenceNumber}`, { ...data, userId: session.userId, termsAccepted: true, allowParticipation: false, }, { 'X-Booked-SessionToken': session.sessionToken, 'X-Booked-UserId': session.userId.toString(), } ); return response; } async function deleteReservation(session: Session, referenceNumber: string): Promise { const response = await makeRequest( 'DELETE', `/Web/Services/index.php/Reservations/${referenceNumber}`, undefined, { 'X-Booked-SessionToken': session.sessionToken, 'X-Booked-UserId': session.userId.toString(), } ); return response; } async function getResources(session: Session): Promise { const response = await makeRequest( 'GET', '/Web/Services/index.php/Resources/', undefined, { 'X-Booked-SessionToken': session.sessionToken, 'X-Booked-UserId': session.userId.toString(), } ); return response.resources || []; } // ===== DATE RANGE TESTS ===== function testDateRange() { log('\n📅 Testing Date Range Calculations...\n'); const now = new Date('2026-01-25'); // Simulate current date // Test thisWeek const dayOfWeek = now.getDay(); const diffToMonday = dayOfWeek === 0 ? -6 : 1 - dayOfWeek; const monday = new Date(now); monday.setDate(now.getDate() + diffToMonday); monday.setHours(0, 0, 0, 0); const sunday = new Date(monday); sunday.setDate(monday.getDate() + 6); logSuccess( 'thisWeek', `Monday: ${monday.toISOString().split('T')[0]}, Sunday: ${sunday.toISOString().split('T')[0]}` ); // Test next2Weeks const next2Weeks = new Date(now); next2Weeks.setDate(now.getDate() + 14); logSuccess( 'next2Weeks', `From: ${now.toISOString().split('T')[0]}, To: ${next2Weeks.toISOString().split('T')[0]}` ); // Test thisMonth const monthStart = new Date(now.getFullYear(), now.getMonth(), 1); const monthEnd = new Date(now.getFullYear(), now.getMonth() + 1, 0); logSuccess( 'thisMonth', `From: ${monthStart.toISOString().split('T')[0]}, To: ${monthEnd.toISOString().split('T')[0]}` ); // Test next2Months const next2Months = new Date(now); next2Months.setMonth(now.getMonth() + 2); logSuccess( 'next2Months', `From: ${now.toISOString().split('T')[0]}, To: ${next2Months.toISOString().split('T')[0]}` ); // Test thisYear const yearStart = new Date(now.getFullYear(), 0, 1); const yearEnd = new Date(now.getFullYear(), 11, 31); logSuccess( 'thisYear', `From: ${yearStart.toISOString().split('T')[0]}, To: ${yearEnd.toISOString().split('T')[0]}` ); } // ===== TIME FILTER TESTS ===== function testTimeFilter() { log('\n⏰ Testing Time Filter Logic...\n'); const today = new Date('2026-02-07'); today.setHours(0, 0, 0, 0); const reservations = [ { title: 'Today', startDateTime: '2026-02-07T10:00:00' }, { title: 'Tomorrow', startDateTime: '2026-02-08T10:00:00' }, { title: 'In 3 days', startDateTime: '2026-02-10T10:00:00' }, { title: 'In 7 days', startDateTime: '2026-02-14T10:00:00' }, { title: 'In 10 days', startDateTime: '2026-02-17T10:00:00' }, ]; // Filter: today const todayOnly = reservations.filter((r) => { const startDate = new Date(r.startDateTime); startDate.setHours(0, 0, 0, 0); return startDate.getTime() === today.getTime(); }); logSuccess('timeFilter=today', `Found ${todayOnly.length} reservation(s): ${todayOnly.map(r => r.title).join(', ')}`); // Filter: next3Days const threeDays = new Date(today); threeDays.setDate(today.getDate() + 3); const next3Days = reservations.filter((r) => { const startDate = new Date(r.startDateTime); startDate.setHours(0, 0, 0, 0); return startDate >= today && startDate <= threeDays; }); logSuccess('timeFilter=next3Days', `Found ${next3Days.length} reservation(s): ${next3Days.map(r => r.title).join(', ')}`); // Filter: next7Days const sevenDays = new Date(today); sevenDays.setDate(today.getDate() + 7); const next7Days = reservations.filter((r) => { const startDate = new Date(r.startDateTime); startDate.setHours(0, 0, 0, 0); return startDate >= today && startDate <= sevenDays; }); logSuccess('timeFilter=next7Days', `Found ${next7Days.length} reservation(s): ${next7Days.map(r => r.title).join(', ')}`); } // ===== MAIN TEST ===== async function runTests() { console.log('🧪 LibreBooking Trigger Test Suite\n'); console.log('═'.repeat(60) + '\n'); // Test date calculations first (no API needed) testDateRange(); testTimeFilter(); log('\n🔌 Testing API Operations...\n'); let session: Session | null = null; let createdRefNumber: string | null = null; try { // 1. Authentication log('1️⃣ Authenticating...'); session = await authenticate(); logSuccess('Authentication', `Session: ${session.sessionToken.substring(0, 20)}...`); // 2. Get Resources (to find available resource) log('\n2️⃣ Getting Resources...'); const resources = await getResources(session); if (resources.length === 0) { logError('GetResources', 'No resources found'); return; } logSuccess('GetResources', `Found ${resources.length} resources`); const testResourceId = resources[0].resourceId; log(` Using resource: ${resources[0].name} (ID: ${testResourceId})`); // 3. Get Initial Reservations log('\n3️⃣ Getting initial reservations...'); const initialReservations = await getReservations(session, TEST_DATE, '2026-02-14'); logSuccess('GetReservations', `Found ${initialReservations.length} reservations`); // Store initial IDs (simulating first poll) const seenIds = initialReservations.map((r: any) => r.referenceNumber); log(` Stored ${seenIds.length} IDs (simulating first poll)`); // 4. Create New Reservation log('\n4️⃣ Creating new reservation...'); const createResult = await createReservation(session, { title: 'TEST: Neue Reservierung für Trigger-Test', description: 'Diese Reservierung wird erstellt, um den "Neue Objekte" Trigger zu testen', resourceId: testResourceId, startDateTime: `${TEST_DATE}T14:00:00`, endDateTime: `${TEST_DATE}T15:00:00`, }); if (createResult.referenceNumber) { createdRefNumber = createResult.referenceNumber; logSuccess('CreateReservation', `Created: ${createdRefNumber}`); } else { logError('CreateReservation', `Failed: ${JSON.stringify(createResult)}`); } // 5. Get Reservations Again (simulate second poll) log('\n5️⃣ Simulating second poll...'); await new Promise((resolve) => setTimeout(resolve, 2000)); // Wait 2 seconds const afterCreateReservations = await getReservations(session, TEST_DATE, '2026-02-14'); const currentIds = afterCreateReservations.map((r: any) => r.referenceNumber); const newIds = currentIds.filter((id: string) => !seenIds.includes(id)); logSuccess( 'NewReservationDetection', `Found ${newIds.length} new reservation(s): ${newIds.join(', ')}` ); // 6. Update Reservation if (createdRefNumber) { log('\n6️⃣ Updating reservation...'); // Store hash before update const reservationBefore = afterCreateReservations.find( (r: any) => r.referenceNumber === createdRefNumber ); const hashBefore = JSON.stringify({ title: reservationBefore?.title, description: reservationBefore?.description, }); // Update - must include all required fields const updateResult = await updateReservation(session, createdRefNumber, { title: 'TEST: GEÄNDERTE Reservierung', description: 'Diese Reservierung wurde geändert - Update-Trigger sollte feuern', resourceId: testResourceId, startDateTime: `${TEST_DATE}T14:00:00`, endDateTime: `${TEST_DATE}T15:00:00`, }); if (updateResult.referenceNumber) { logSuccess('UpdateReservation', `Updated: ${createdRefNumber}`); } else { logError('UpdateReservation', `Failed: ${JSON.stringify(updateResult)}`); } // 7. Get Reservations Again (check for changes) log('\n7️⃣ Checking for changes...'); await new Promise((resolve) => setTimeout(resolve, 2000)); const afterUpdateReservations = await getReservations(session, TEST_DATE, '2026-02-14'); const reservationAfter = afterUpdateReservations.find( (r: any) => r.referenceNumber === createdRefNumber ); const hashAfter = JSON.stringify({ title: reservationAfter?.title, description: reservationAfter?.description, }); if (hashBefore !== hashAfter) { logSuccess( 'ChangeDetection', `Change detected! Title: "${reservationAfter?.title}"` ); } else { logError('ChangeDetection', 'No change detected (hash unchanged)'); } // 8. Delete Reservation log('\n8️⃣ Deleting reservation...'); await deleteReservation(session, createdRefNumber); logSuccess('DeleteReservation', `Deleted: ${createdRefNumber}`); // Verify deletion await new Promise((resolve) => setTimeout(resolve, 1000)); const afterDeleteReservations = await getReservations(session, TEST_DATE, '2026-02-14'); const stillExists = afterDeleteReservations.some( (r: any) => r.referenceNumber === createdRefNumber ); if (!stillExists) { logSuccess('DeletionVerified', 'Reservation successfully deleted'); } else { logError('DeletionVerified', 'Reservation still exists!'); } } // 9. Sign Out log('\n9️⃣ Signing out...'); await signOut(session); logSuccess('SignOut', 'Successfully signed out'); } catch (error: any) { logError('TestSuite', `Error: ${error.message}`); } // Summary console.log('\n' + '═'.repeat(60)); console.log('📊 TEST SUMMARY\n'); const passed = results.filter((r) => r.success).length; const failed = results.filter((r) => !r.success).length; console.log(` ✅ Passed: ${passed}`); console.log(` ❌ Failed: ${failed}`); console.log(` 📝 Total: ${results.length}`); if (failed > 0) { console.log('\n Failed Tests:'); results .filter((r) => !r.success) .forEach((r) => console.log(` - ${r.name}: ${r.message}`)); } console.log('\n' + '═'.repeat(60)); // Exit with appropriate code process.exit(failed > 0 ? 1 : 0); } runTests();