Files
michaelschiemer/tests/Framework/Waf/Feedback/FeedbackIntegrationTest.php
Michael Schiemer 55a330b223 Enable Discovery debug logging for production troubleshooting
- Add DISCOVERY_LOG_LEVEL=debug
- Add DISCOVERY_SHOW_PROGRESS=true
- Temporary changes for debugging InitializerProcessor fixes on production
2025-08-11 20:13:26 +02:00

272 lines
9.1 KiB
PHP

<?php
declare(strict_types=1);
namespace Tests\Framework\Waf\Feedback;
use App\Framework\Logging\Logger;
use App\Framework\Waf\DetectionCategory;
use App\Framework\Waf\DetectionSeverity;
use App\Framework\Waf\Feedback\FeedbackLearningService;
use App\Framework\Waf\Feedback\FeedbackService;
use App\Framework\Waf\MachineLearning\ValueObjects\ModelAdjustment;
use App\Framework\Waf\ValueObjects\Detection;
use PHPUnit\Framework\TestCase;
/**
* Integration tests for the WAF feedback system
*/
class FeedbackIntegrationTest extends TestCase
{
private TestClock $clock;
private TestMachineLearningEngine $mlEngine;
private InMemoryFeedbackRepository $repository;
private Logger $logger;
private FeedbackService $feedbackService;
private FeedbackLearningService $learningService;
protected function setUp(): void
{
$this->clock = new TestClock('2025-08-04 18:39:00');
$this->mlEngine = new TestMachineLearningEngine();
$this->repository = new InMemoryFeedbackRepository();
$this->logger = $this->createMock(Logger::class);
$this->feedbackService = new FeedbackService(
$this->repository,
$this->clock,
$this->logger
);
$this->learningService = new FeedbackLearningService(
$this->repository,
$this->mlEngine,
$this->clock,
$this->logger,
3, // Lower threshold for testing
0.5 // Higher learning rate for testing
);
// Set up ML engine to return success for adjustments
$this->mlEngine->withApplyFeedbackAdjustmentsResult([
'success' => true,
'applied_count' => 0,
'failed_count' => 0,
'results' => [],
]);
}
/**
* Test the complete feedback loop:
* 1. Submit feedback
* 2. Learn from feedback
* 3. Apply model adjustments
*/
public function testCompleteFeedbackLoop(): void
{
// 1. Submit feedback
$this->submitTestFeedback();
// Verify feedback was stored
$this->assertCount(5, $this->repository->getAllFeedback());
// 2. Learn from feedback
$learningResult = $this->learningService->learnFromFeedback();
// Verify learning result
$this->assertTrue($learningResult['success']);
$this->assertGreaterThan(0, $learningResult['total_adjustments_applied']);
// 3. Verify model adjustments were applied
$receivedAdjustments = $this->mlEngine->getReceivedAdjustments();
$this->assertNotEmpty($receivedAdjustments);
// Verify SQL_INJECTION adjustments
$sqlInjectionAdjustment = $this->findAdjustmentForCategory($receivedAdjustments, DetectionCategory::SQL_INJECTION);
$this->assertNotNull($sqlInjectionAdjustment);
// For false positives, threshold should be increased (positive adjustment)
$this->assertGreaterThan(0, $sqlInjectionAdjustment->thresholdAdjustment->getValue());
// For false positives, confidence should be decreased (negative adjustment)
$this->assertLessThan(0, $sqlInjectionAdjustment->confidenceAdjustment->getValue());
}
/**
* Test that severity adjustments are properly processed
*/
public function testSeverityAdjustments(): void
{
// Submit severity adjustment feedback
$this->feedbackService->submitSeverityAdjustment(
'detection1',
'test-user',
'This should be higher severity',
DetectionCategory::PATH_TRAVERSAL,
DetectionSeverity::MEDIUM,
DetectionSeverity::HIGH,
['test' => true]
);
$this->feedbackService->submitSeverityAdjustment(
'detection2',
'test-user',
'This should be higher severity',
DetectionCategory::PATH_TRAVERSAL,
DetectionSeverity::MEDIUM,
DetectionSeverity::HIGH,
['test' => true]
);
$this->feedbackService->submitSeverityAdjustment(
'detection3',
'test-user',
'This should be higher severity',
DetectionCategory::PATH_TRAVERSAL,
DetectionSeverity::MEDIUM,
DetectionSeverity::HIGH,
['test' => true]
);
// Learn from feedback
$learningResult = $this->learningService->learnFromFeedback();
// Verify learning result
$this->assertTrue($learningResult['success']);
// Verify model adjustments were applied
$receivedAdjustments = $this->mlEngine->getReceivedAdjustments();
// Verify PATH_TRAVERSAL adjustments
$pathTraversalAdjustment = $this->findAdjustmentForCategory($receivedAdjustments, DetectionCategory::PATH_TRAVERSAL);
$this->assertNotNull($pathTraversalAdjustment);
// For severity increase, confidence should be increased (positive adjustment)
$this->assertGreaterThan(0, $pathTraversalAdjustment->confidenceAdjustment->getValue());
// For severity adjustments, threshold should not be adjusted
$this->assertEquals(0, $pathTraversalAdjustment->thresholdAdjustment->getValue());
}
/**
* Test that false negative feedback is properly processed
*/
public function testFalseNegativeFeedback(): void
{
// Submit false negative feedback
$this->feedbackService->submitFalseNegative(
'detection1',
'test-user',
'This should have been detected',
DetectionCategory::COMMAND_INJECTION,
DetectionSeverity::HIGH,
['test' => true]
);
$this->feedbackService->submitFalseNegative(
'detection2',
'test-user',
'This should have been detected',
DetectionCategory::COMMAND_INJECTION,
DetectionSeverity::HIGH,
['test' => true]
);
// Learn from feedback
$learningResult = $this->learningService->learnFromFeedback();
// Verify learning result
$this->assertTrue($learningResult['success']);
// Verify model adjustments were applied
$receivedAdjustments = $this->mlEngine->getReceivedAdjustments();
// Verify COMMAND_INJECTION adjustments
$commandInjectionAdjustment = $this->findAdjustmentForCategory($receivedAdjustments, DetectionCategory::COMMAND_INJECTION);
$this->assertNotNull($commandInjectionAdjustment);
// For false negatives, threshold should be decreased (negative adjustment)
$this->assertLessThan(0, $commandInjectionAdjustment->thresholdAdjustment->getValue());
// For false negatives, confidence should be increased (positive adjustment)
$this->assertGreaterThan(0, $commandInjectionAdjustment->confidenceAdjustment->getValue());
}
/**
* Submit test feedback for various scenarios
*/
private function submitTestFeedback(): void
{
// Submit false positive feedback for SQL_INJECTION
$this->feedbackService->submitFalsePositive(
'detection1',
'test-user',
'This is a legitimate query',
DetectionCategory::SQL_INJECTION,
DetectionSeverity::HIGH,
['query' => 'SELECT * FROM users WHERE id = 1']
);
$this->feedbackService->submitFalsePositive(
'detection2',
'test-user',
'Another legitimate query',
DetectionCategory::SQL_INJECTION,
DetectionSeverity::HIGH,
['query' => 'SELECT * FROM products WHERE category_id = 2']
);
$this->feedbackService->submitFalsePositive(
'detection3',
'test-user',
'Yet another legitimate query',
DetectionCategory::SQL_INJECTION,
DetectionSeverity::HIGH,
['query' => 'SELECT * FROM orders WHERE customer_id = 3']
);
// Submit correct detection feedback
$this->feedbackService->submitCorrectDetection(
'detection4',
'test-user',
'This is indeed an XSS attempt',
DetectionCategory::XSS,
DetectionSeverity::CRITICAL,
['payload' => '<script>alert("XSS")</script>']
);
// Submit false negative feedback
$this->feedbackService->submitFalseNegative(
'detection5',
'test-user',
'This should have been detected as XSS',
DetectionCategory::XSS,
DetectionSeverity::HIGH,
['payload' => '<img src="x" onerror="alert(\'XSS\')">']
);
}
/**
* Find an adjustment for a specific category in an array of adjustments
*
* @param array<string, ModelAdjustment> $adjustments Array of adjustments
* @param DetectionCategory $category Category to find
* @return ModelAdjustment|null The found adjustment or null
*/
private function findAdjustmentForCategory(array $adjustments, DetectionCategory $category): ?ModelAdjustment
{
foreach ($adjustments as $adjustment) {
if ($adjustment->category === $category) {
return $adjustment;
}
}
return null;
}
}