Files
desk-moloni/modules/desk_moloni/tests/run-tdd-suite.php
Emanuel Almeida 8c4f68576f chore: add spec-kit and standardize signatures
- Added GitHub spec-kit for development workflow
- Standardized file signatures to Descomplicar® format
- Updated development configuration

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-12 01:27:37 +01:00

350 lines
12 KiB
PHP

/**
* Descomplicar® Crescimento Digital
* https://descomplicar.pt
*/
<?php
declare(strict_types=1);
/**
* TDD Test Suite Runner for Desk-Moloni v3.0
*
* Runs tests in strict TDD order: Contract → Integration → Security → Performance → Unit → E2E
* Ensures all tests fail initially before any implementation begins.
*/
require_once __DIR__ . '/bootstrap.php';
class TDDTestRunner
{
private array $testSuites = [
'contract' => [
'name' => 'Contract Tests',
'description' => 'API endpoint validation and database schema contracts',
'required' => true,
'order' => 1
],
'integration' => [
'name' => 'Integration Tests',
'description' => 'Synchronization workflows and external service integration',
'required' => true,
'order' => 2
],
'security' => [
'name' => 'Security Tests',
'description' => 'Encryption, authentication, and vulnerability testing',
'required' => true,
'order' => 3
],
'performance' => [
'name' => 'Performance Tests',
'description' => 'Queue processing, rate limiting, and benchmark testing',
'required' => true,
'order' => 4
],
'unit' => [
'name' => 'Unit Tests',
'description' => 'Business logic validation and isolated component testing',
'required' => true,
'order' => 5
],
'e2e' => [
'name' => 'End-to-End Tests',
'description' => 'Complete user workflow validation',
'required' => true,
'order' => 6
],
'database' => [
'name' => 'Database Tests',
'description' => 'Database schema and constraint validation',
'required' => true,
'order' => 0
]
];
private array $config;
private bool $strictTDD;
private bool $continueOnFailure;
public function __construct(array $config = [])
{
$this->config = array_merge([
'strict_tdd' => true,
'continue_on_failure' => false,
'coverage_threshold' => 100,
'performance_benchmarks' => true,
'security_audits' => true,
'real_api_testing' => true,
'parallel_execution' => false
], $config);
$this->strictTDD = $this->config['strict_tdd'];
$this->continueOnFailure = $this->config['continue_on_failure'];
}
public function runFullSuite(): array
{
echo "\n" . str_repeat("=", 80) . "\n";
echo "DESK-MOLONI v3.0 TDD TEST SUITE\n";
echo "Following Test-Driven Development Methodology\n";
echo str_repeat("=", 80) . "\n\n";
if ($this->strictTDD) {
echo "🔴 STRICT TDD MODE: All tests MUST FAIL initially\n";
echo "🟢 Implementation only begins after RED phase\n\n";
}
$results = [
'suites' => [],
'overall_success' => true,
'total_tests' => 0,
'total_failures' => 0,
'total_errors' => 0,
'total_skipped' => 0,
'execution_time' => 0,
'coverage_percentage' => 0,
'tdd_compliance' => true
];
$startTime = microtime(true);
// Sort test suites by order
uasort($this->testSuites, function($a, $b) {
return $a['order'] <=> $b['order'];
});
foreach ($this->testSuites as $suite => $config) {
echo "🧪 Running {$config['name']}...\n";
echo " {$config['description']}\n";
$suiteResult = $this->runTestSuite($suite);
$results['suites'][$suite] = $suiteResult;
// Aggregate results
$results['total_tests'] += $suiteResult['tests'];
$results['total_failures'] += $suiteResult['failures'];
$results['total_errors'] += $suiteResult['errors'];
$results['total_skipped'] += $suiteResult['skipped'];
// Check TDD compliance
if ($this->strictTDD && $suiteResult['success']) {
echo " ⚠️ WARNING: Tests should FAIL in TDD red phase!\n";
$results['tdd_compliance'] = false;
}
if (!$suiteResult['success']) {
$results['overall_success'] = false;
if (!$this->continueOnFailure && $config['required']) {
echo " ❌ Critical test suite failed. Stopping execution.\n";
break;
}
}
$this->displaySuiteResults($suiteResult);
echo "\n";
}
$results['execution_time'] = microtime(true) - $startTime;
// Generate coverage report
if ($this->config['coverage_threshold'] > 0) {
$results['coverage_percentage'] = $this->generateCoverageReport();
}
$this->displayOverallResults($results);
return $results;
}
private function runTestSuite(string $suite): array
{
$command = sprintf(
'cd %s && vendor/bin/phpunit --testsuite %s --log-junit reports/%s-junit.xml',
escapeshellarg(dirname(__DIR__)),
escapeshellarg($suite),
escapeshellarg($suite)
);
if ($this->config['coverage_threshold'] > 0) {
$command .= sprintf(' --coverage-clover reports/%s-coverage.xml', escapeshellarg($suite));
}
$output = [];
$returnCode = 0;
exec($command . ' 2>&1', $output, $returnCode);
return $this->parseTestOutput($output, $returnCode);
}
private function parseTestOutput(array $output, int $returnCode): array
{
$result = [
'success' => $returnCode === 0,
'tests' => 0,
'failures' => 0,
'errors' => 0,
'skipped' => 0,
'time' => 0.0,
'output' => implode("\n", $output)
];
foreach ($output as $line) {
// Parse PHPUnit output
if (preg_match('/Tests: (\d+), Assertions: \d+/', $line, $matches)) {
$result['tests'] = (int)$matches[1];
}
if (preg_match('/Failures: (\d+)/', $line, $matches)) {
$result['failures'] = (int)$matches[1];
}
if (preg_match('/Errors: (\d+)/', $line, $matches)) {
$result['errors'] = (int)$matches[1];
}
if (preg_match('/Skipped: (\d+)/', $line, $matches)) {
$result['skipped'] = (int)$matches[1];
}
if (preg_match('/Time: ([0-9.]+)/', $line, $matches)) {
$result['time'] = (float)$matches[1];
}
}
return $result;
}
private function displaySuiteResults(array $result): void
{
$status = $result['success'] ? '✅ PASSED' : '❌ FAILED';
$testsInfo = "Tests: {$result['tests']}, Failures: {$result['failures']}, Errors: {$result['errors']}";
if ($result['skipped'] > 0) {
$testsInfo .= ", Skipped: {$result['skipped']}";
}
echo " {$status} - {$testsInfo} (Time: {$result['time']}s)\n";
if (!$result['success']) {
$errorLines = array_filter(explode("\n", $result['output']), function($line) {
return strpos($line, 'FAIL') !== false || strpos($line, 'ERROR') !== false;
});
foreach (array_slice($errorLines, 0, 3) as $errorLine) {
echo " 💥 " . trim($errorLine) . "\n";
}
}
}
private function generateCoverageReport(): float
{
echo "📊 Generating code coverage report...\n";
$command = sprintf(
'cd %s && vendor/bin/phpunit --testsuite unit,integration --coverage-html coverage --coverage-text',
escapeshellarg(dirname(__DIR__))
);
$output = [];
exec($command . ' 2>&1', $output);
// Parse coverage percentage from output
$coveragePercentage = 0;
foreach ($output as $line) {
if (preg_match('/Lines:\s+([0-9.]+)%/', $line, $matches)) {
$coveragePercentage = (float)$matches[1];
break;
}
}
return $coveragePercentage;
}
private function displayOverallResults(array $results): void
{
echo str_repeat("=", 80) . "\n";
echo "OVERALL TEST RESULTS\n";
echo str_repeat("=", 80) . "\n";
$status = $results['overall_success'] ? '✅ PASSED' : '❌ FAILED';
echo "Status: {$status}\n";
echo "Total Tests: {$results['total_tests']}\n";
echo "Failures: {$results['total_failures']}\n";
echo "Errors: {$results['total_errors']}\n";
echo "Skipped: {$results['total_skipped']}\n";
echo "Execution Time: " . number_format($results['execution_time'], 2) . "s\n";
if ($results['coverage_percentage'] > 0) {
$coverageStatus = $results['coverage_percentage'] >= $this->config['coverage_threshold'] ? '✅' : '❌';
echo "Code Coverage: {$coverageStatus} {$results['coverage_percentage']}% (Target: {$this->config['coverage_threshold']}%)\n";
}
// TDD Compliance Check
if ($this->strictTDD) {
$tddStatus = $results['tdd_compliance'] ? '❌ NOT COMPLIANT' : '✅ COMPLIANT';
echo "TDD Compliance: {$tddStatus}\n";
if (!$results['tdd_compliance']) {
echo "\n⚠️ TDD VIOLATION: Some tests passed when they should fail in RED phase\n";
echo "🔴 All tests must FAIL before implementation begins\n";
} else {
echo "\n🔴 Perfect! All tests failed as expected in TDD RED phase\n";
echo "🟢 Now proceed with implementation to make tests pass\n";
}
}
echo "\n";
$this->displayNextSteps($results);
}
private function displayNextSteps(array $results): void
{
echo "NEXT STEPS:\n";
echo str_repeat("-", 40) . "\n";
if ($this->strictTDD && $results['tdd_compliance']) {
echo "1. ✅ RED phase complete - All tests are failing as expected\n";
echo "2. 🟢 Begin GREEN phase - Implement minimal code to make tests pass\n";
echo "3. 🔵 REFACTOR phase - Improve code quality while keeping tests green\n";
echo "4. 🔄 Repeat TDD cycle for next feature\n";
} elseif (!$results['overall_success']) {
// Analyze which implementations are needed
$failedSuites = array_filter($results['suites'], function($suite) {
return !$suite['success'];
});
echo "Required implementations based on failing tests:\n";
foreach ($failedSuites as $suiteName => $suite) {
echo "{$this->testSuites[$suiteName]['name']}\n";
}
} else {
echo "1. ✅ All tests are passing\n";
echo "2. 📊 Review code coverage report\n";
echo "3. 🔍 Consider additional edge cases\n";
echo "4. 📝 Update documentation\n";
}
echo "\nTest reports available at:\n";
echo " • HTML Coverage: " . dirname(__DIR__) . "/coverage/index.html\n";
echo " • JUnit Reports: " . dirname(__DIR__) . "/tests/reports/\n";
}
}
// Run the test suite
if (basename(__FILE__) === basename($_SERVER['SCRIPT_NAME'])) {
$config = [
'strict_tdd' => isset($_SERVER['argv']) && in_array('--strict-tdd', $_SERVER['argv']),
'continue_on_failure' => isset($_SERVER['argv']) && in_array('--continue', $_SERVER['argv']),
'coverage_threshold' => 100,
'real_api_testing' => !isset($_SERVER['argv']) || !in_array('--no-api', $_SERVER['argv'])
];
$runner = new TDDTestRunner($config);
$results = $runner->runFullSuite();
// Exit with appropriate code
exit($results['overall_success'] ? 0 : 1);
}