486 lines
18 KiB
TypeScript
486 lines
18 KiB
TypeScript
/**
|
||
* 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<string, string>
|
||
): Promise<any> {
|
||
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<Session> {
|
||
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<void> {
|
||
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<any[]> {
|
||
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<any> {
|
||
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<any> {
|
||
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<any> {
|
||
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<any[]> {
|
||
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();
|