feat: DB 연결 오버라이딩 및 대시보드 통계 위젯 추가
- DB 연결: 로컬/Docker 환경 오버라이딩 설정 (.env) - 테넌트 위젯: redirect 버그 수정 (TenantSelectorWidget) - 통계 위젯: 사용자/제품/자재/주문 카드 추가 (StatsOverviewWidget) - 리소스 한국어화: Product, Material 모델 레이블 추가 - 대시보드: 위젯 등록 및 캐시 최적화 🤖 Generated with [Claude Code](https://claude.ai/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
490
tests/Performance/BomResolutionPerformanceTest.php
Normal file
490
tests/Performance/BomResolutionPerformanceTest.php
Normal file
@@ -0,0 +1,490 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Performance;
|
||||
|
||||
use App\Models\BomConditionRule;
|
||||
use App\Models\Model;
|
||||
use App\Models\ModelFormula;
|
||||
use App\Models\ModelParameter;
|
||||
use App\Models\User;
|
||||
use App\Services\ProductFromModelService;
|
||||
use Database\Seeders\ParameterBasedBomTestSeeder;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Laravel\Sanctum\Sanctum;
|
||||
use Tests\TestCase;
|
||||
|
||||
class BomResolutionPerformanceTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
private User $user;
|
||||
private Model $performanceModel;
|
||||
private ProductFromModelService $service;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->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));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user