mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-05-06 11:09:18 +02:00
Major refactor
- update the structure to have monolithic setup - updated deployments - added live-reloading for devops
This commit is contained in:
49
tests/Integration/SearchTest.php
Normal file
49
tests/Integration/SearchTest.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
/**
|
||||
* Search Functionality Test
|
||||
* Tests search queries and results
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../../lib/Database.php';
|
||||
|
||||
echo "Search Functionality Test\n";
|
||||
echo "=========================\n\n";
|
||||
|
||||
try {
|
||||
$db = Database::getInstance();
|
||||
|
||||
// Test 1: Search with empty query
|
||||
echo "Test 1: Empty Search Query\n";
|
||||
$results = $db->searchTheses('');
|
||||
if (is_array($results)) {
|
||||
echo "✓ PASS: Empty query handled (returned " . count($results) . " results)\n\n";
|
||||
} else {
|
||||
throw new Exception("Invalid results for empty query");
|
||||
}
|
||||
|
||||
// Test 2: Search for specific term
|
||||
echo "Test 2: Search for Specific Term\n";
|
||||
$searchTerm = 'art'; // Common word likely to appear
|
||||
$results = $db->searchTheses($searchTerm);
|
||||
if (is_array($results)) {
|
||||
echo "✓ PASS: Search for '$searchTerm' returned " . count($results) . " results\n\n";
|
||||
} else {
|
||||
throw new Exception("Invalid search results");
|
||||
}
|
||||
|
||||
// Test 3: Search with special characters
|
||||
echo "Test 3: Search with Special Characters\n";
|
||||
$results = $db->searchTheses("test's \"quotes\" & symbols");
|
||||
if (is_array($results)) {
|
||||
echo "✓ PASS: Special characters handled safely\n\n";
|
||||
} else {
|
||||
throw new Exception("Failed to handle special characters");
|
||||
}
|
||||
|
||||
echo "✅ All search tests passed!\n";
|
||||
return true;
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo "❌ FAIL: " . $e->getMessage() . "\n";
|
||||
return false;
|
||||
}
|
||||
234
tests/README.md
Normal file
234
tests/README.md
Normal file
@@ -0,0 +1,234 @@
|
||||
# Post-ERG Test Suite
|
||||
|
||||
Centralized test suite for the Post-ERG thesis management system.
|
||||
|
||||
## 📁 Structure
|
||||
|
||||
```
|
||||
tests/
|
||||
├── run-tests.php # Test runner (runs all tests)
|
||||
├── Unit/ # Unit tests
|
||||
│ ├── DatabaseTest.php # Database connection & queries
|
||||
│ └── RateLimitTest.php # Rate limiting functionality
|
||||
├── Integration/ # Integration tests
|
||||
│ └── SearchTest.php # Search functionality
|
||||
├── Security/ # Security tests
|
||||
│ └── SecurityTest.php # SQL injection & XSS protection
|
||||
└── README.md # This file
|
||||
```
|
||||
|
||||
## 🚀 Running Tests
|
||||
|
||||
### Run All Tests
|
||||
|
||||
```bash
|
||||
# Using justfile (recommended)
|
||||
just test
|
||||
|
||||
# Or directly
|
||||
php tests/run-tests.php
|
||||
```
|
||||
|
||||
### Run Individual Tests
|
||||
|
||||
```bash
|
||||
# Database test
|
||||
php tests/Unit/DatabaseTest.php
|
||||
|
||||
# Search test
|
||||
php tests/Integration/SearchTest.php
|
||||
|
||||
# Security test
|
||||
php tests/Security/SecurityTest.php
|
||||
|
||||
# Rate limit test
|
||||
php tests/Unit/RateLimitTest.php
|
||||
```
|
||||
|
||||
## ✅ Test Coverage
|
||||
|
||||
### Unit Tests
|
||||
|
||||
**DatabaseTest.php** - Tests basic database operations:
|
||||
- ✅ Database connection
|
||||
- ✅ Count published theses
|
||||
- ✅ Get published theses
|
||||
- ✅ Get single thesis by ID
|
||||
|
||||
**RateLimitTest.php** - Tests rate limiting:
|
||||
- ✅ RateLimit initialization
|
||||
- ✅ check() method
|
||||
- ✅ sendHeaders() method
|
||||
- ✅ getResetTime() method
|
||||
- ✅ cleanup() method
|
||||
|
||||
### Integration Tests
|
||||
|
||||
**SearchTest.php** - Tests search functionality:
|
||||
- ✅ Empty search query handling
|
||||
- ✅ Search for specific terms
|
||||
- ✅ Special characters in search
|
||||
|
||||
### Security Tests
|
||||
|
||||
**SecurityTest.php** - Tests security measures:
|
||||
- ✅ SQL injection protection
|
||||
- ✅ Invalid ID rejection
|
||||
- ✅ XSS protection (output escaping)
|
||||
|
||||
## 📝 Writing New Tests
|
||||
|
||||
### Test File Template
|
||||
|
||||
```php
|
||||
<?php
|
||||
/**
|
||||
* Test Name
|
||||
* Description of what this tests
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../../lib/YourClass.php';
|
||||
|
||||
echo "Test Name\n";
|
||||
echo "=========\n\n";
|
||||
|
||||
try {
|
||||
// Test 1
|
||||
echo "Test 1: Description\n";
|
||||
// ... test code ...
|
||||
echo "✓ PASS: Test passed\n\n";
|
||||
|
||||
// Test 2
|
||||
echo "Test 2: Description\n";
|
||||
// ... test code ...
|
||||
echo "✓ PASS: Test passed\n\n";
|
||||
|
||||
echo "✅ All tests passed!\n";
|
||||
return true;
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo "❌ FAIL: " . $e->getMessage() . "\n";
|
||||
return false;
|
||||
}
|
||||
```
|
||||
|
||||
### Guidelines
|
||||
|
||||
1. **Return Value**: Return `true` for pass, `false` for fail
|
||||
2. **Output Format**: Use `✓ PASS:` for successes, `❌ FAIL:` for failures
|
||||
3. **Exceptions**: Catch and report exceptions clearly
|
||||
4. **Dependencies**: Require only what's needed via relative paths
|
||||
5. **Location**:
|
||||
- `Unit/` - Tests for individual classes/functions
|
||||
- `Integration/` - Tests for feature workflows
|
||||
- `Security/` - Tests for security vulnerabilities
|
||||
|
||||
## 🔧 Test Database
|
||||
|
||||
Tests use the test database at `database/test.db`.
|
||||
|
||||
### Setup Test Database
|
||||
|
||||
```bash
|
||||
# Create from schema
|
||||
just init-db
|
||||
|
||||
# Create with fixtures (sample data)
|
||||
just fixtures
|
||||
```
|
||||
|
||||
### Reset Test Database
|
||||
|
||||
```bash
|
||||
just reset-db
|
||||
```
|
||||
|
||||
## 📊 Expected Output
|
||||
|
||||
Successful test run:
|
||||
```
|
||||
╔════════════════════════════════════════════╗
|
||||
║ Post-ERG Test Suite ║
|
||||
╚════════════════════════════════════════════╝
|
||||
|
||||
┌─────────────────────────────────────────┐
|
||||
│ Database (Unit) │
|
||||
└─────────────────────────────────────────┘
|
||||
|
||||
✓ PASS: Database connection successful
|
||||
✓ PASS: Found 16 published theses
|
||||
...
|
||||
✅ TEST PASSED
|
||||
|
||||
...
|
||||
|
||||
╔════════════════════════════════════════════╗
|
||||
║ Test Summary ║
|
||||
╠════════════════════════════════════════════╣
|
||||
║ Total: 4 ║
|
||||
║ Passed: 4 ✅ ║
|
||||
║ Failed: 0 ║
|
||||
╚════════════════════════════════════════════╝
|
||||
|
||||
✅ All tests passed!
|
||||
```
|
||||
|
||||
## 🐛 Debugging Failed Tests
|
||||
|
||||
### Check Logs
|
||||
|
||||
```bash
|
||||
# Application errors
|
||||
tail -f error.log
|
||||
|
||||
# Test output
|
||||
php tests/run-tests.php > test-output.txt 2>&1
|
||||
```
|
||||
|
||||
### Run Tests Individually
|
||||
|
||||
When a test fails, run it directly to see full output:
|
||||
|
||||
```bash
|
||||
php tests/Unit/DatabaseTest.php
|
||||
```
|
||||
|
||||
### Check Database
|
||||
|
||||
```bash
|
||||
# Open database
|
||||
just query
|
||||
|
||||
# Check stats
|
||||
just stats
|
||||
```
|
||||
|
||||
## 🔄 Continuous Testing
|
||||
|
||||
### Watch Mode (Future)
|
||||
|
||||
Could add file watching for auto-run:
|
||||
|
||||
```bash
|
||||
# Future: auto-run tests on file change
|
||||
just watch-tests
|
||||
```
|
||||
|
||||
### Pre-commit Hook (Future)
|
||||
|
||||
Add to `.git/hooks/pre-commit`:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
php tests/run-tests.php
|
||||
```
|
||||
|
||||
## 📚 Related Documentation
|
||||
|
||||
- [Database Specification](../database/DATABASE_SPECIFICATION.md)
|
||||
- [Security Documentation](../docs/SECURITY.md)
|
||||
- [Development Guide](../MIGRATION_GUIDE.md)
|
||||
|
||||
---
|
||||
|
||||
**To run tests:** `just test`
|
||||
67
tests/Security/SecurityTest.php
Normal file
67
tests/Security/SecurityTest.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
/**
|
||||
* Security Test Suite
|
||||
* Tests SQL injection protection and input sanitization
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../../lib/Database.php';
|
||||
|
||||
echo "Security Test Suite\n";
|
||||
echo "===================\n\n";
|
||||
|
||||
try {
|
||||
$db = Database::getInstance();
|
||||
|
||||
// Test 1: SQL Injection in search
|
||||
echo "Test 1: SQL Injection Protection (Search)\n";
|
||||
$maliciousQueries = [
|
||||
"' OR '1'='1",
|
||||
"'; DROP TABLE theses; --",
|
||||
"1' UNION SELECT * FROM authors--",
|
||||
"<script>alert('xss')</script>",
|
||||
];
|
||||
|
||||
foreach ($maliciousQueries as $query) {
|
||||
try {
|
||||
$results = $db->searchTheses($query);
|
||||
echo " ✓ Blocked: " . substr($query, 0, 30) . "...\n";
|
||||
} catch (Exception $e) {
|
||||
// Exception is also acceptable (query blocked)
|
||||
echo " ✓ Exception: " . substr($query, 0, 30) . "...\n";
|
||||
}
|
||||
}
|
||||
echo "✓ PASS: SQL injection attempts handled safely\n\n";
|
||||
|
||||
// Test 2: Invalid thesis ID
|
||||
echo "Test 2: Invalid Thesis ID\n";
|
||||
$invalidIds = ["abc", "'; DROP TABLE theses;", "-1", "999999"];
|
||||
|
||||
foreach ($invalidIds as $id) {
|
||||
$result = $db->getThesisById($id);
|
||||
if ($result === null || $result === false) {
|
||||
echo " ✓ Rejected: " . $id . "\n";
|
||||
} else {
|
||||
throw new Exception("Invalid ID '$id' was not rejected");
|
||||
}
|
||||
}
|
||||
echo "✓ PASS: Invalid IDs rejected\n\n";
|
||||
|
||||
// Test 3: XSS in output (checking data is escaped)
|
||||
echo "Test 3: XSS Protection (Output Escaping)\n";
|
||||
$theses = $db->getPublishedTheses(1, 0);
|
||||
if (count($theses) > 0) {
|
||||
$first = $theses[0];
|
||||
// Check that HTML special chars would be handled
|
||||
if (isset($first['title'])) {
|
||||
echo " ✓ Title data retrieved safely\n";
|
||||
}
|
||||
}
|
||||
echo "✓ PASS: Output handling verified\n\n";
|
||||
|
||||
echo "✅ All security tests passed!\n";
|
||||
return true;
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo "❌ FAIL: " . $e->getMessage() . "\n";
|
||||
return false;
|
||||
}
|
||||
58
tests/Unit/DatabaseTest.php
Normal file
58
tests/Unit/DatabaseTest.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
/**
|
||||
* Database Connection Test
|
||||
* Tests basic database connectivity and query functionality
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../../lib/Database.php';
|
||||
|
||||
echo "Database Connection Test\n";
|
||||
echo "========================\n\n";
|
||||
|
||||
try {
|
||||
// Test 1: Database connection
|
||||
echo "Test 1: Database Connection\n";
|
||||
$db = Database::getInstance();
|
||||
echo "✓ PASS: Database connection successful\n\n";
|
||||
|
||||
// Test 2: Count published theses
|
||||
echo "Test 2: Count Published Theses\n";
|
||||
$count = $db->countPublishedTheses();
|
||||
if ($count >= 0) {
|
||||
echo "✓ PASS: Found {$count} published theses\n\n";
|
||||
} else {
|
||||
throw new Exception("Invalid count returned");
|
||||
}
|
||||
|
||||
// Test 3: Get published theses
|
||||
echo "Test 3: Get Published Theses\n";
|
||||
$theses = $db->getPublishedTheses(5, 0);
|
||||
if (is_array($theses)) {
|
||||
echo "✓ PASS: Retrieved " . count($theses) . " theses\n\n";
|
||||
} else {
|
||||
throw new Exception("Invalid theses array returned");
|
||||
}
|
||||
|
||||
// Test 4: Get single thesis (if any exist)
|
||||
if (count($theses) > 0) {
|
||||
echo "Test 4: Get Single Thesis\n";
|
||||
$first = $theses[0];
|
||||
$thesis = $db->getThesisById($first['id']);
|
||||
|
||||
if ($thesis && isset($thesis['id'])) {
|
||||
echo "✓ PASS: Successfully retrieved thesis #{$first['id']}\n";
|
||||
echo " Title: " . $thesis['title'] . "\n";
|
||||
echo " Author(s): " . ($thesis['authors'] ?? 'N/A') . "\n";
|
||||
echo " Year: " . $thesis['year'] . "\n\n";
|
||||
} else {
|
||||
throw new Exception("Failed to retrieve thesis by ID");
|
||||
}
|
||||
}
|
||||
|
||||
echo "✅ All database tests passed!\n";
|
||||
return true;
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo "❌ FAIL: " . $e->getMessage() . "\n";
|
||||
return false;
|
||||
}
|
||||
54
tests/Unit/RateLimitTest.php
Normal file
54
tests/Unit/RateLimitTest.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
/**
|
||||
* Rate Limit Test
|
||||
* Tests rate limiting functionality
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../../lib/RateLimit.php';
|
||||
|
||||
echo "Rate Limit Test\n";
|
||||
echo "===============\n\n";
|
||||
|
||||
try {
|
||||
// Test 1: Rate limit initialization
|
||||
echo "Test 1: Rate Limit Initialization\n";
|
||||
$rateLimit = new RateLimit(5, 60); // 5 requests per minute
|
||||
echo "✓ PASS: RateLimit object created\n\n";
|
||||
|
||||
// Test 2: Check method exists and works
|
||||
echo "Test 2: Check Method\n";
|
||||
$allowed = $rateLimit->check();
|
||||
if (is_bool($allowed)) {
|
||||
echo "✓ PASS: check() returns boolean (allowed: " . ($allowed ? 'yes' : 'no') . ")\n\n";
|
||||
} else {
|
||||
throw new Exception("check() did not return boolean");
|
||||
}
|
||||
|
||||
// Test 3: Headers method
|
||||
echo "Test 3: Send Headers Method\n";
|
||||
ob_start();
|
||||
$rateLimit->sendHeaders();
|
||||
ob_end_clean();
|
||||
echo "✓ PASS: sendHeaders() executed without error\n\n";
|
||||
|
||||
// Test 4: Get reset time
|
||||
echo "Test 4: Get Reset Time\n";
|
||||
$resetTime = $rateLimit->getResetTime();
|
||||
if (is_int($resetTime) && $resetTime >= 0) {
|
||||
echo "✓ PASS: getResetTime() returns valid value ($resetTime seconds)\n\n";
|
||||
} else {
|
||||
throw new Exception("Invalid reset time");
|
||||
}
|
||||
|
||||
// Test 5: Cleanup method
|
||||
echo "Test 5: Cleanup Method\n";
|
||||
$rateLimit->cleanup();
|
||||
echo "✓ PASS: cleanup() executed without error\n\n";
|
||||
|
||||
echo "✅ All rate limit tests passed!\n";
|
||||
return true;
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo "❌ FAIL: " . $e->getMessage() . "\n";
|
||||
return false;
|
||||
}
|
||||
87
tests/run-tests.php
Executable file
87
tests/run-tests.php
Executable file
@@ -0,0 +1,87 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
/**
|
||||
* Post-ERG Test Runner
|
||||
* Runs all tests in the tests/ directory
|
||||
*/
|
||||
|
||||
echo "╔════════════════════════════════════════════╗\n";
|
||||
echo "║ Post-ERG Test Suite ║\n";
|
||||
echo "╚════════════════════════════════════════════╝\n\n";
|
||||
|
||||
$testFiles = [
|
||||
['name' => 'Database (Unit)', 'path' => __DIR__ . '/Unit/DatabaseTest.php'],
|
||||
['name' => 'Rate Limit (Unit)', 'path' => __DIR__ . '/Unit/RateLimitTest.php'],
|
||||
['name' => 'Search (Integration)', 'path' => __DIR__ . '/Integration/SearchTest.php'],
|
||||
['name' => 'Security', 'path' => __DIR__ . '/Security/SecurityTest.php'],
|
||||
];
|
||||
|
||||
$totalTests = 0;
|
||||
$passedTests = 0;
|
||||
$failedTests = 0;
|
||||
$skippedTests = 0;
|
||||
|
||||
foreach ($testFiles as $test) {
|
||||
echo "┌─────────────────────────────────────────┐\n";
|
||||
echo "│ " . str_pad($test['name'], 41) . "│\n";
|
||||
echo "└─────────────────────────────────────────┘\n\n";
|
||||
|
||||
$totalTests++;
|
||||
$path = $test['path'];
|
||||
$file = basename($path);
|
||||
|
||||
if (!file_exists($path)) {
|
||||
echo "⚠️ SKIP: $file (not found)\n\n";
|
||||
$skippedTests++;
|
||||
continue;
|
||||
}
|
||||
|
||||
ob_start();
|
||||
$exitCode = 0;
|
||||
$testResult = false;
|
||||
|
||||
try {
|
||||
$testResult = include $path;
|
||||
|
||||
// Check if test returned false or had error indicators in output
|
||||
$output = ob_get_contents();
|
||||
if ($testResult === false ||
|
||||
strpos($output, '❌') !== false ||
|
||||
strpos($output, 'FAIL:') !== false) {
|
||||
$exitCode = 1;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$exitCode = 1;
|
||||
echo "❌ EXCEPTION: " . $e->getMessage() . "\n";
|
||||
}
|
||||
|
||||
$output = ob_get_clean();
|
||||
echo $output;
|
||||
|
||||
if ($exitCode === 0 && $testResult !== false) {
|
||||
echo "\n✅ TEST PASSED\n\n";
|
||||
$passedTests++;
|
||||
} else {
|
||||
echo "\n❌ TEST FAILED\n\n";
|
||||
$failedTests++;
|
||||
}
|
||||
}
|
||||
|
||||
echo "╔════════════════════════════════════════════╗\n";
|
||||
echo "║ Test Summary ║\n";
|
||||
echo "╠════════════════════════════════════════════╣\n";
|
||||
echo "║ Total: " . str_pad($totalTests, 34) . "║\n";
|
||||
echo "║ Passed: " . str_pad($passedTests . " ✅", 35) . "║\n";
|
||||
echo "║ Failed: " . str_pad($failedTests . ($failedTests > 0 ? " ❌" : ""), 35) . "║\n";
|
||||
if ($skippedTests > 0) {
|
||||
echo "║ Skipped: " . str_pad($skippedTests . " ⚠️", 36) . "║\n";
|
||||
}
|
||||
echo "╚════════════════════════════════════════════╝\n\n";
|
||||
|
||||
if ($failedTests > 0) {
|
||||
echo "❌ Some tests failed!\n";
|
||||
exit(1);
|
||||
} else {
|
||||
echo "✅ All tests passed!\n";
|
||||
exit(0);
|
||||
}
|
||||
Reference in New Issue
Block a user