user = User::factory()->create(['tenant_id' => 1]); Sanctum::actingAs($this->user); $this->service = new ProductFromModelService(); $this->service->setTenantId(1)->setApiUserId(1); // Set headers $this->withHeaders([ 'X-API-KEY' => config('app.api_key', 'test-api-key'), 'Accept' => 'application/json', ]); // Seed test data $this->seed(ParameterBasedBomTestSeeder::class); // Create performance test model with large dataset $this->createPerformanceTestModel(); } private function createPerformanceTestModel(): void { $this->performanceModel = Model::factory()->create([ 'code' => 'PERF-TEST', 'name' => 'Performance Test Model', 'product_family' => 'SCREEN', 'tenant_id' => 1, ]); // Create many parameters (50) for ($i = 1; $i <= 50; $i++) { ModelParameter::factory()->create([ 'model_id' => $this->performanceModel->id, 'name' => "param_{$i}", 'label' => "Parameter {$i}", 'type' => 'NUMBER', 'default_value' => '100', 'sort_order' => $i, 'tenant_id' => 1, ]); } // Create many formulas (30) for ($i = 1; $i <= 30; $i++) { ModelFormula::factory()->create([ 'model_id' => $this->performanceModel->id, 'name' => "formula_{$i}", 'expression' => "param_1 + param_2 + {$i}", 'description' => "Formula {$i}", 'return_type' => 'NUMBER', 'sort_order' => $i, 'tenant_id' => 1, ]); } // Create many condition rules (100) for ($i = 1; $i <= 100; $i++) { BomConditionRule::factory()->create([ 'model_id' => $this->performanceModel->id, 'name' => "rule_{$i}", 'condition_expression' => "formula_1 > {$i}", 'component_code' => "COMPONENT_{$i}", 'quantity_expression' => '1', 'priority' => $i, 'tenant_id' => 1, ]); } } /** @test */ public function it_resolves_simple_bom_within_performance_threshold() { // Use KSS01 model for simple test $kss01 = Model::where('code', 'KSS01')->first(); $inputParams = [ 'W0' => 1000, 'H0' => 800, 'screen_type' => 'SCREEN', 'install_type' => 'WALL', 'power_source' => 'AC', ]; $startTime = microtime(true); $response = $this->postJson("/api/v1/design/models/{$kss01->id}/bom/resolve", [ 'parameters' => $inputParams ]); $executionTime = microtime(true) - $startTime; // Should complete within 500ms for simple model $this->assertLessThan(0.5, $executionTime, 'Simple BOM resolution took too long'); $response->assertOk(); } /** @test */ public function it_handles_complex_bom_resolution_efficiently() { // Create parameters for all 50 parameters $inputParams = []; for ($i = 1; $i <= 50; $i++) { $inputParams["param_{$i}"] = 100 + $i; } $startTime = microtime(true); $response = $this->postJson("/api/v1/design/models/{$this->performanceModel->id}/bom/resolve", [ 'parameters' => $inputParams ]); $executionTime = microtime(true) - $startTime; // Should complete within 2 seconds even for complex model $this->assertLessThan(2.0, $executionTime, 'Complex BOM resolution took too long'); $response->assertOk(); } /** @test */ public function it_handles_concurrent_bom_resolutions() { $kss01 = Model::where('code', 'KSS01')->first(); $inputParams = [ 'W0' => 1000, 'H0' => 800, 'screen_type' => 'SCREEN', 'install_type' => 'WALL', 'power_source' => 'AC', ]; $startTime = microtime(true); $responses = []; // Simulate 10 concurrent requests for ($i = 0; $i < 10; $i++) { $responses[] = $this->postJson("/api/v1/design/models/{$kss01->id}/bom/resolve", [ 'parameters' => $inputParams ]); } $totalTime = microtime(true) - $startTime; // All requests should complete successfully foreach ($responses as $response) { $response->assertOk(); } // Total time for 10 concurrent requests should be reasonable $this->assertLessThan(5.0, $totalTime, 'Concurrent BOM resolutions took too long'); } /** @test */ public function it_optimizes_formula_evaluation_with_caching() { $kss01 = Model::where('code', 'KSS01')->first(); $inputParams = [ 'W0' => 1000, 'H0' => 800, 'screen_type' => 'SCREEN', 'install_type' => 'WALL', 'power_source' => 'AC', ]; // First request (cold cache) $startTime1 = microtime(true); $response1 = $this->postJson("/api/v1/design/models/{$kss01->id}/bom/resolve", [ 'parameters' => $inputParams ]); $time1 = microtime(true) - $startTime1; // Second request with same parameters (warm cache) $startTime2 = microtime(true); $response2 = $this->postJson("/api/v1/design/models/{$kss01->id}/bom/resolve", [ 'parameters' => $inputParams ]); $time2 = microtime(true) - $startTime2; // Both should succeed $response1->assertOk(); $response2->assertOk(); // Results should be identical $this->assertEquals($response1->json('data'), $response2->json('data')); // Second request should be faster (with caching) $this->assertLessThan($time1, $time2 * 1.5, 'Caching is not improving performance'); } /** @test */ public function it_measures_memory_usage_during_bom_resolution() { $kss01 = Model::where('code', 'KSS01')->first(); $inputParams = [ 'W0' => 1000, 'H0' => 800, 'screen_type' => 'SCREEN', 'install_type' => 'WALL', 'power_source' => 'AC', ]; $memoryBefore = memory_get_usage(true); $response = $this->postJson("/api/v1/design/models/{$kss01->id}/bom/resolve", [ 'parameters' => $inputParams ]); $memoryAfter = memory_get_usage(true); $memoryUsed = $memoryAfter - $memoryBefore; $response->assertOk(); // Memory usage should be reasonable (less than 50MB for simple BOM) $this->assertLessThan(50 * 1024 * 1024, $memoryUsed, 'Excessive memory usage detected'); } /** @test */ public function it_handles_large_datasets_efficiently() { // Test with the performance model (50 params, 30 formulas, 100 rules) $inputParams = []; for ($i = 1; $i <= 50; $i++) { $inputParams["param_{$i}"] = rand(50, 200); } $memoryBefore = memory_get_usage(true); $startTime = microtime(true); $response = $this->postJson("/api/v1/design/models/{$this->performanceModel->id}/bom/resolve", [ 'parameters' => $inputParams ]); $executionTime = microtime(true) - $startTime; $memoryAfter = memory_get_usage(true); $memoryUsed = $memoryAfter - $memoryBefore; $response->assertOk(); // Performance thresholds for large datasets $this->assertLessThan(5.0, $executionTime, 'Large dataset processing took too long'); $this->assertLessThan(100 * 1024 * 1024, $memoryUsed, 'Excessive memory usage for large dataset'); // Should return reasonable amount of data $data = $response->json('data'); $this->assertArrayHasKey('calculated_formulas', $data); $this->assertArrayHasKey('bom_items', $data); $this->assertGreaterThan(0, count($data['bom_items'])); } /** @test */ public function it_benchmarks_formula_evaluation_complexity() { // Test various formula complexities $complexityTests = [ 'simple' => 'param_1 + param_2', 'medium' => 'param_1 * param_2 + param_3 / param_4', 'complex' => 'CEIL(param_1 / 600) + FLOOR(param_2 * 1.5) + IF(param_3 > 100, param_4, param_5)', ]; $benchmarks = []; foreach ($complexityTests as $complexity => $expression) { // Create test formula $formula = ModelFormula::factory()->create([ 'model_id' => $this->performanceModel->id, 'name' => "benchmark_{$complexity}", 'expression' => $expression, 'sort_order' => 999, 'tenant_id' => 1, ]); $inputParams = [ 'param_1' => 1000, 'param_2' => 800, 'param_3' => 150, 'param_4' => 200, 'param_5' => 50, ]; $startTime = microtime(true); // Evaluate formula multiple times to get average for ($i = 0; $i < 100; $i++) { try { $this->service->evaluateFormula($formula, $inputParams); } catch (\Exception $e) { // Some complex formulas might fail, that's okay for benchmarking } } $avgTime = (microtime(true) - $startTime) / 100; $benchmarks[$complexity] = $avgTime; // Cleanup $formula->delete(); } // Complex formulas should still execute reasonably fast $this->assertLessThan(0.01, $benchmarks['simple'], 'Simple formula evaluation too slow'); $this->assertLessThan(0.02, $benchmarks['medium'], 'Medium formula evaluation too slow'); $this->assertLessThan(0.05, $benchmarks['complex'], 'Complex formula evaluation too slow'); } /** @test */ public function it_scales_with_increasing_rule_count() { // Test BOM resolution with different rule counts $scalingTests = [10, 50, 100]; $scalingResults = []; foreach ($scalingTests as $ruleCount) { // Create test model with specific rule count $testModel = Model::factory()->create([ 'code' => "SCALE_TEST_{$ruleCount}", 'tenant_id' => 1, ]); // Create basic parameters ModelParameter::factory()->create([ 'model_id' => $testModel->id, 'name' => 'test_param', 'type' => 'NUMBER', 'tenant_id' => 1, ]); // Create test formula ModelFormula::factory()->create([ 'model_id' => $testModel->id, 'name' => 'test_formula', 'expression' => 'test_param * 2', 'tenant_id' => 1, ]); // Create specified number of rules for ($i = 1; $i <= $ruleCount; $i++) { BomConditionRule::factory()->create([ 'model_id' => $testModel->id, 'condition_expression' => "test_formula > {$i}", 'component_code' => "COMP_{$i}", 'priority' => $i, 'tenant_id' => 1, ]); } $startTime = microtime(true); $response = $this->postJson("/api/v1/design/models/{$testModel->id}/bom/resolve", [ 'parameters' => ['test_param' => 100] ]); $executionTime = microtime(true) - $startTime; $scalingResults[$ruleCount] = $executionTime; $response->assertOk(); // Cleanup $testModel->delete(); } // Execution time should scale reasonably (not exponentially) $ratio50to10 = $scalingResults[50] / $scalingResults[10]; $ratio100to50 = $scalingResults[100] / $scalingResults[50]; // Should not scale worse than linearly $this->assertLessThan(10, $ratio50to10, 'Poor scaling from 10 to 50 rules'); $this->assertLessThan(5, $ratio100to50, 'Poor scaling from 50 to 100 rules'); } /** @test */ public function it_handles_stress_test_scenarios() { $kss01 = Model::where('code', 'KSS01')->first(); // Stress test with many rapid requests $stressTestCount = 50; $successCount = 0; $errors = []; $totalTime = 0; for ($i = 0; $i < $stressTestCount; $i++) { $inputParams = [ 'W0' => rand(500, 3000), 'H0' => rand(400, 2000), 'screen_type' => rand(0, 1) ? 'SCREEN' : 'SLAT', 'install_type' => ['WALL', 'SIDE', 'MIXED'][rand(0, 2)], 'power_source' => ['AC', 'DC', 'MANUAL'][rand(0, 2)], ]; $startTime = microtime(true); try { $response = $this->postJson("/api/v1/design/models/{$kss01->id}/bom/resolve", [ 'parameters' => $inputParams ]); $executionTime = microtime(true) - $startTime; $totalTime += $executionTime; if ($response->isSuccessful()) { $successCount++; } else { $errors[] = $response->status(); } } catch (\Exception $e) { $errors[] = $e->getMessage(); } } $avgTime = $totalTime / $stressTestCount; $successRate = ($successCount / $stressTestCount) * 100; // Stress test requirements $this->assertGreaterThanOrEqual(95, $successRate, 'Success rate too low under stress'); $this->assertLessThan(1.0, $avgTime, 'Average response time too high under stress'); if (count($errors) > 0) { $this->addToAssertionCount(1); // Just to show we're tracking errors // Log errors for analysis error_log('Stress test errors: ' . json_encode(array_unique($errors))); } } /** @test */ public function it_monitors_database_query_performance() { $kss01 = Model::where('code', 'KSS01')->first(); $inputParams = [ 'W0' => 1000, 'H0' => 800, 'screen_type' => 'SCREEN', 'install_type' => 'WALL', 'power_source' => 'AC', ]; // Enable query logging \DB::enableQueryLog(); $response = $this->postJson("/api/v1/design/models/{$kss01->id}/bom/resolve", [ 'parameters' => $inputParams ]); $queries = \DB::getQueryLog(); \DB::disableQueryLog(); $response->assertOk(); // Analyze query performance $queryCount = count($queries); $totalQueryTime = array_sum(array_column($queries, 'time')); // Should not have excessive queries (N+1 problem) $this->assertLessThan(50, $queryCount, 'Too many database queries'); // Total query time should be reasonable $this->assertLessThan(500, $totalQueryTime, 'Database queries taking too long'); // Check for slow queries $slowQueries = array_filter($queries, fn($query) => $query['time'] > 100); $this->assertEmpty($slowQueries, 'Slow queries detected: ' . json_encode($slowQueries)); } }