Files
xamxam/apps/public/tests/Integration/SearchTest.php
Théophile Gervreau-Mercier 467aced734 Restructure repository and implement secure search feature
Phase 1: Consolidate shared infrastructure
- Create shared/ directory for common code
- Consolidate Database.php from front-backend and formulaire into unified shared/Database.php
  - Smart path detection for test.db vs posterg.db
  - Secure search with wildcard escaping and input validation
  - Support both singleton and direct instantiation patterns
  - Full CRUD methods for admin functionality
- Move RateLimit.php to shared/ (30 requests/min)
- Update all require paths across apps to use shared/

Phase 2: Reorganize directory structure
- Rename front-backend/ → apps/public/
- Rename formulaire/ → apps/admin/
- Rename db/ → database/
- Update all file paths for new structure
- Create root .gitignore excluding databases, cache, logs

Implement secure search feature
- Add apps/public/search.php with full-text search across theses
- Search filters: query, year, orientation, AP program, keywords
- Security features:
  - SQL injection prevention (prepared statements)
  - Wildcard injection prevention (escape % and _)
  - Input validation (max 200 chars, year range 1900-2100)
  - Rate limiting (30 req/min per IP)
  - Pagination limited to 100 results/page
  - XSS protection (htmlspecialchars on output)

Add comprehensive test suite
- Create apps/public/tests/ with proper structure
  - tests/Integration/SearchTest.php - 12 search scenarios
  - tests/Security/SecurityTest.php - vulnerability testing
  - tests/Unit/RateLimitTest.php - rate limit behavior
- Create database/fixtures/CreateTestDatabase.php
- Add apps/public/run-tests.php test runner
- All tests passing (4/4 suites)

Update deployment configuration
- Rename justfile 'sync' recipe to 'deploy'
- Create deploy group with separate deploy-public and deploy-admin
- Add test-deploy recipe for test database
- Exclude *.db, tests/, cache/, *.md from production deploy
- Deploy shared/ to both public and admin locations

Stats: +4482 insertions, -654 deletions across 72 files
2026-02-02 18:53:58 +01:00

122 lines
4.1 KiB
PHP

<?php
/**
* Test script for search functionality
* Run this to verify that search methods work correctly
*/
require_once __DIR__ . '/../../../../shared/Database.php';
echo "=== Testing Search Feature ===\n\n";
try {
$db = Database::getInstance();
// Test 1: Get all published theses
echo "Test 1: Getting all published theses\n";
$allTheses = $db->searchTheses([], 100, 0);
echo "Found " . count($allTheses) . " published theses\n";
foreach ($allTheses as $thesis) {
echo " - [{$thesis['year']}] {$thesis['title']} by {$thesis['authors']}\n";
}
echo "\n";
// Test 2: Full-text search
echo "Test 2: Full-text search for 'urbain'\n";
$results = $db->searchTheses(['query' => 'urbain']);
echo "Found " . count($results) . " results\n";
foreach ($results as $thesis) {
echo " - {$thesis['title']}\n";
}
echo "\n";
// Test 3: Search by year
echo "Test 3: Search by year (2024)\n";
$results = $db->searchTheses(['year' => 2024]);
echo "Found " . count($results) . " results\n";
foreach ($results as $thesis) {
echo " - [{$thesis['year']}] {$thesis['title']}\n";
}
echo "\n";
// Test 4: Search by orientation
echo "Test 4: Search by orientation (Installation-Performance)\n";
$results = $db->searchTheses(['orientation' => 'Installation-Performance']);
echo "Found " . count($results) . " results\n";
foreach ($results as $thesis) {
echo " - {$thesis['title']} ({$thesis['orientation']})\n";
}
echo "\n";
// Test 5: Search by AP program
echo "Test 5: Search by AP program (Narration Spéculative)\n";
$results = $db->searchTheses(['ap_program' => 'Narration Spéculative']);
echo "Found " . count($results) . " results\n";
foreach ($results as $thesis) {
echo " - {$thesis['title']} ({$thesis['ap_program']})\n";
}
echo "\n";
// Test 6: Search by keyword
echo "Test 6: Search by keyword (performance)\n";
$results = $db->searchTheses(['keyword' => 'performance']);
echo "Found " . count($results) . " results\n";
foreach ($results as $thesis) {
echo " - {$thesis['title']}\n";
echo " Keywords: {$thesis['keywords']}\n";
}
echo "\n";
// Test 7: Combined search
echo "Test 7: Combined search (query='performance' + year=2024)\n";
$results = $db->searchTheses(['query' => 'performance', 'year' => 2024]);
echo "Found " . count($results) . " results\n";
foreach ($results as $thesis) {
echo " - [{$thesis['year']}] {$thesis['title']}\n";
}
echo "\n";
// Test 8: Get available years
echo "Test 8: Getting available years\n";
$years = $db->getAvailableYears();
echo "Available years: " . implode(', ', $years) . "\n\n";
// Test 9: Get orientations
echo "Test 9: Getting orientations\n";
$orientations = $db->getOrientations();
echo "Total orientations: " . count($orientations) . "\n";
echo "Sample: " . $orientations[0]['name'] . ", " . $orientations[1]['name'] . ", ...\n\n";
// Test 10: Get keywords
echo "Test 10: Getting used keywords\n";
$keywords = $db->getUsedKeywords();
echo "Total keywords in use: " . count($keywords) . "\n";
$keywordNames = array_map(function($k) { return $k['keyword']; }, $keywords);
echo "Keywords: " . implode(', ', array_slice($keywordNames, 0, 10)) . "...\n\n";
// Test 11: Count results
echo "Test 11: Count search results\n";
$count = $db->countSearchResults(['year' => 2024]);
echo "Count for year 2024: $count\n\n";
// Test 12: Pagination
echo "Test 12: Testing pagination\n";
$page1 = $db->searchTheses([], 2, 0); // First 2 results
$page2 = $db->searchTheses([], 2, 2); // Next 2 results
echo "Page 1 (first 2):\n";
foreach ($page1 as $thesis) {
echo " - {$thesis['title']}\n";
}
echo "Page 2 (next 2):\n";
foreach ($page2 as $thesis) {
echo " - {$thesis['title']}\n";
}
echo "\n";
echo "✅ All tests completed successfully!\n";
} catch (Exception $e) {
echo "❌ Error: " . $e->getMessage() . "\n";
echo "Stack trace:\n" . $e->getTraceAsString() . "\n";
exit(1);
}