- DB 연결: 로컬/Docker 환경 오버라이딩 설정 (.env) - 테넌트 위젯: redirect 버그 수정 (TenantSelectorWidget) - 통계 위젯: 사용자/제품/자재/주문 카드 추가 (StatsOverviewWidget) - 리소스 한국어화: Product, Material 모델 레이블 추가 - 대시보드: 위젯 등록 및 캐시 최적화 🤖 Generated with [Claude Code](https://claude.ai/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
437 lines
14 KiB
PHP
437 lines
14 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature\Design;
|
|
|
|
use Tests\TestCase;
|
|
use App\Models\Design\DesignModel;
|
|
use App\Models\Design\ModelParameter;
|
|
use App\Models\Design\ModelFormula;
|
|
use App\Models\User;
|
|
use App\Models\Tenant;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use Laravel\Sanctum\Sanctum;
|
|
|
|
class ModelFormulaTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
private User $user;
|
|
private Tenant $tenant;
|
|
private DesignModel $model;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
|
|
// Create test tenant and user
|
|
$this->tenant = Tenant::factory()->create();
|
|
$this->user = User::factory()->create();
|
|
|
|
// Associate user with tenant
|
|
$this->user->tenants()->attach($this->tenant->id, ['is_active' => true, 'is_default' => true]);
|
|
|
|
// Create test design model
|
|
$this->model = DesignModel::factory()->create([
|
|
'tenant_id' => $this->tenant->id,
|
|
'code' => 'TEST01',
|
|
'name' => 'Test Model',
|
|
'is_active' => true
|
|
]);
|
|
|
|
// Create test parameters
|
|
ModelParameter::factory()->create([
|
|
'tenant_id' => $this->tenant->id,
|
|
'model_id' => $this->model->id,
|
|
'parameter_name' => 'W0',
|
|
'parameter_type' => 'NUMBER'
|
|
]);
|
|
|
|
ModelParameter::factory()->create([
|
|
'tenant_id' => $this->tenant->id,
|
|
'model_id' => $this->model->id,
|
|
'parameter_name' => 'H0',
|
|
'parameter_type' => 'NUMBER'
|
|
]);
|
|
|
|
// Authenticate user
|
|
Sanctum::actingAs($this->user, ['*']);
|
|
Auth::login($this->user);
|
|
}
|
|
|
|
/** @test */
|
|
public function can_create_model_formula()
|
|
{
|
|
$formulaData = [
|
|
'model_id' => $this->model->id,
|
|
'formula_name' => 'W1',
|
|
'expression' => 'W0 + 100',
|
|
'description' => 'Outer width calculation',
|
|
'sort_order' => 1
|
|
];
|
|
|
|
$response = $this->postJson('/api/v1/design/model-formulas', $formulaData);
|
|
|
|
$response->assertStatus(201)
|
|
->assertJson([
|
|
'success' => true,
|
|
'message' => 'message.created'
|
|
]);
|
|
|
|
$this->assertDatabaseHas('model_formulas', [
|
|
'tenant_id' => $this->tenant->id,
|
|
'model_id' => $this->model->id,
|
|
'formula_name' => 'W1',
|
|
'expression' => 'W0 + 100'
|
|
]);
|
|
}
|
|
|
|
/** @test */
|
|
public function can_list_model_formulas()
|
|
{
|
|
// Create test formulas
|
|
ModelFormula::factory()->count(3)->create([
|
|
'tenant_id' => $this->tenant->id,
|
|
'model_id' => $this->model->id
|
|
]);
|
|
|
|
$response = $this->getJson('/api/v1/design/model-formulas?model_id=' . $this->model->id);
|
|
|
|
$response->assertStatus(200)
|
|
->assertJson([
|
|
'success' => true,
|
|
'message' => 'message.fetched'
|
|
])
|
|
->assertJsonStructure([
|
|
'data' => [
|
|
'data' => [
|
|
'*' => [
|
|
'id',
|
|
'formula_name',
|
|
'expression',
|
|
'description',
|
|
'dependencies',
|
|
'sort_order'
|
|
]
|
|
]
|
|
]
|
|
]);
|
|
}
|
|
|
|
/** @test */
|
|
public function can_update_model_formula()
|
|
{
|
|
$formula = ModelFormula::factory()->create([
|
|
'tenant_id' => $this->tenant->id,
|
|
'model_id' => $this->model->id,
|
|
'formula_name' => 'area',
|
|
'expression' => 'W0 * H0'
|
|
]);
|
|
|
|
$updateData = [
|
|
'formula_name' => 'area_updated',
|
|
'expression' => '(W0 * H0) / 1000000',
|
|
'description' => 'Area in square meters'
|
|
];
|
|
|
|
$response = $this->putJson('/api/v1/design/model-formulas/' . $formula->id, $updateData);
|
|
|
|
$response->assertStatus(200)
|
|
->assertJson([
|
|
'success' => true,
|
|
'message' => 'message.updated'
|
|
]);
|
|
|
|
$this->assertDatabaseHas('model_formulas', [
|
|
'id' => $formula->id,
|
|
'formula_name' => 'area_updated',
|
|
'expression' => '(W0 * H0) / 1000000'
|
|
]);
|
|
}
|
|
|
|
/** @test */
|
|
public function can_delete_model_formula()
|
|
{
|
|
$formula = ModelFormula::factory()->create([
|
|
'tenant_id' => $this->tenant->id,
|
|
'model_id' => $this->model->id
|
|
]);
|
|
|
|
$response = $this->deleteJson('/api/v1/design/model-formulas/' . $formula->id);
|
|
|
|
$response->assertStatus(200)
|
|
->assertJson([
|
|
'success' => true,
|
|
'message' => 'message.deleted'
|
|
]);
|
|
|
|
$this->assertSoftDeleted('model_formulas', [
|
|
'id' => $formula->id
|
|
]);
|
|
}
|
|
|
|
/** @test */
|
|
public function validates_formula_expression_syntax()
|
|
{
|
|
// Test invalid expression
|
|
$invalidData = [
|
|
'model_id' => $this->model->id,
|
|
'formula_name' => 'invalid_formula',
|
|
'expression' => 'W0 +++ H0' // Invalid syntax
|
|
];
|
|
|
|
$response = $this->postJson('/api/v1/design/model-formulas', $invalidData);
|
|
$response->assertStatus(422);
|
|
|
|
// Test valid expression
|
|
$validData = [
|
|
'model_id' => $this->model->id,
|
|
'formula_name' => 'valid_formula',
|
|
'expression' => 'sqrt(W0^2 + H0^2)'
|
|
];
|
|
|
|
$response = $this->postJson('/api/v1/design/model-formulas', $validData);
|
|
$response->assertStatus(201);
|
|
}
|
|
|
|
/** @test */
|
|
public function can_calculate_formulas_with_dependencies()
|
|
{
|
|
// Create formulas with dependencies
|
|
ModelFormula::factory()->create([
|
|
'tenant_id' => $this->tenant->id,
|
|
'model_id' => $this->model->id,
|
|
'formula_name' => 'W1',
|
|
'expression' => 'W0 + 100',
|
|
'sort_order' => 1
|
|
]);
|
|
|
|
ModelFormula::factory()->create([
|
|
'tenant_id' => $this->tenant->id,
|
|
'model_id' => $this->model->id,
|
|
'formula_name' => 'H1',
|
|
'expression' => 'H0 + 100',
|
|
'sort_order' => 2
|
|
]);
|
|
|
|
ModelFormula::factory()->create([
|
|
'tenant_id' => $this->tenant->id,
|
|
'model_id' => $this->model->id,
|
|
'formula_name' => 'area',
|
|
'expression' => 'W1 * H1',
|
|
'sort_order' => 3
|
|
]);
|
|
|
|
$inputParameters = [
|
|
'W0' => 800,
|
|
'H0' => 600
|
|
];
|
|
|
|
$response = $this->postJson('/api/v1/design/model-formulas/calculate', [
|
|
'model_id' => $this->model->id,
|
|
'parameters' => $inputParameters
|
|
]);
|
|
|
|
$response->assertStatus(200)
|
|
->assertJson([
|
|
'success' => true,
|
|
'data' => [
|
|
'W1' => 900, // 800 + 100
|
|
'H1' => 700, // 600 + 100
|
|
'area' => 630000 // 900 * 700
|
|
]
|
|
]);
|
|
}
|
|
|
|
/** @test */
|
|
public function can_detect_circular_dependencies()
|
|
{
|
|
// Create circular dependency: A depends on B, B depends on A
|
|
ModelFormula::factory()->create([
|
|
'tenant_id' => $this->tenant->id,
|
|
'model_id' => $this->model->id,
|
|
'formula_name' => 'A',
|
|
'expression' => 'B + 10'
|
|
]);
|
|
|
|
ModelFormula::factory()->create([
|
|
'tenant_id' => $this->tenant->id,
|
|
'model_id' => $this->model->id,
|
|
'formula_name' => 'B',
|
|
'expression' => 'A + 20'
|
|
]);
|
|
|
|
$response = $this->postJson('/api/v1/design/model-formulas/calculate', [
|
|
'model_id' => $this->model->id,
|
|
'parameters' => ['W0' => 100]
|
|
]);
|
|
|
|
$response->assertStatus(422) // Validation error for circular dependency
|
|
->assertJsonFragment([
|
|
'error' => 'Circular dependency detected'
|
|
]);
|
|
}
|
|
|
|
/** @test */
|
|
public function can_handle_complex_mathematical_expressions()
|
|
{
|
|
// Test various mathematical functions
|
|
$complexFormulas = [
|
|
['name' => 'sqrt_test', 'expression' => 'sqrt(W0^2 + H0^2)'],
|
|
['name' => 'trig_test', 'expression' => 'sin(W0 * pi() / 180)'],
|
|
['name' => 'conditional_test', 'expression' => 'if(W0 > 1000, W0 * 1.2, W0 * 1.1)'],
|
|
['name' => 'round_test', 'expression' => 'round(W0 / 100) * 100'],
|
|
['name' => 'max_test', 'expression' => 'max(W0, H0)']
|
|
];
|
|
|
|
foreach ($complexFormulas as $formulaData) {
|
|
ModelFormula::factory()->create([
|
|
'tenant_id' => $this->tenant->id,
|
|
'model_id' => $this->model->id,
|
|
'formula_name' => $formulaData['name'],
|
|
'expression' => $formulaData['expression']
|
|
]);
|
|
}
|
|
|
|
$response = $this->postJson('/api/v1/design/model-formulas/calculate', [
|
|
'model_id' => $this->model->id,
|
|
'parameters' => ['W0' => 1200, 'H0' => 800]
|
|
]);
|
|
|
|
$response->assertStatus(200)
|
|
->assertJsonStructure([
|
|
'success',
|
|
'data' => [
|
|
'sqrt_test',
|
|
'trig_test',
|
|
'conditional_test',
|
|
'round_test',
|
|
'max_test'
|
|
]
|
|
]);
|
|
}
|
|
|
|
/** @test */
|
|
public function can_validate_formula_dependencies()
|
|
{
|
|
// Create formula that references non-existent parameter
|
|
$invalidFormulaData = [
|
|
'model_id' => $this->model->id,
|
|
'formula_name' => 'invalid_ref',
|
|
'expression' => 'NONEXISTENT_PARAM + 100'
|
|
];
|
|
|
|
$response = $this->postJson('/api/v1/design/model-formulas', $invalidFormulaData);
|
|
$response->assertStatus(422);
|
|
|
|
// Create valid formula that references existing parameter
|
|
$validFormulaData = [
|
|
'model_id' => $this->model->id,
|
|
'formula_name' => 'valid_ref',
|
|
'expression' => 'W0 + 100'
|
|
];
|
|
|
|
$response = $this->postJson('/api/v1/design/model-formulas', $validFormulaData);
|
|
$response->assertStatus(201);
|
|
}
|
|
|
|
/** @test */
|
|
public function enforces_tenant_isolation()
|
|
{
|
|
// Create formula for different tenant
|
|
$otherTenant = Tenant::factory()->create();
|
|
$otherFormula = ModelFormula::factory()->create([
|
|
'tenant_id' => $otherTenant->id
|
|
]);
|
|
|
|
// Should not be able to access other tenant's formula
|
|
$response = $this->getJson('/api/v1/design/model-formulas/' . $otherFormula->id);
|
|
$response->assertStatus(404);
|
|
}
|
|
|
|
/** @test */
|
|
public function can_export_and_import_formulas()
|
|
{
|
|
// Create test formulas
|
|
ModelFormula::factory()->count(3)->create([
|
|
'tenant_id' => $this->tenant->id,
|
|
'model_id' => $this->model->id
|
|
]);
|
|
|
|
// Export formulas
|
|
$response = $this->getJson('/api/v1/design/model-formulas/export?model_id=' . $this->model->id);
|
|
$response->assertStatus(200)
|
|
->assertJsonStructure([
|
|
'success',
|
|
'data' => [
|
|
'formulas' => [
|
|
'*' => [
|
|
'formula_name',
|
|
'expression',
|
|
'description',
|
|
'sort_order'
|
|
]
|
|
]
|
|
]
|
|
]);
|
|
|
|
$exportData = $response->json('data.formulas');
|
|
|
|
// Import formulas to new model
|
|
$newModel = DesignModel::factory()->create([
|
|
'tenant_id' => $this->tenant->id,
|
|
'code' => 'TEST02'
|
|
]);
|
|
|
|
$response = $this->postJson('/api/v1/design/model-formulas/import', [
|
|
'model_id' => $newModel->id,
|
|
'formulas' => $exportData
|
|
]);
|
|
|
|
$response->assertStatus(200)
|
|
->assertJson([
|
|
'success' => true,
|
|
'message' => 'message.bulk_import'
|
|
]);
|
|
|
|
$this->assertDatabaseCount('model_formulas', 6); // 3 original + 3 imported
|
|
}
|
|
|
|
/** @test */
|
|
public function can_reorder_formulas_for_calculation_sequence()
|
|
{
|
|
$formula1 = ModelFormula::factory()->create([
|
|
'tenant_id' => $this->tenant->id,
|
|
'model_id' => $this->model->id,
|
|
'formula_name' => 'area',
|
|
'expression' => 'W1 * H1',
|
|
'sort_order' => 1
|
|
]);
|
|
|
|
$formula2 = ModelFormula::factory()->create([
|
|
'tenant_id' => $this->tenant->id,
|
|
'model_id' => $this->model->id,
|
|
'formula_name' => 'W1',
|
|
'expression' => 'W0 + 100',
|
|
'sort_order' => 2
|
|
]);
|
|
|
|
// Reorder so W1 is calculated before area
|
|
$reorderData = [
|
|
['id' => $formula2->id, 'sort_order' => 1],
|
|
['id' => $formula1->id, 'sort_order' => 2]
|
|
];
|
|
|
|
$response = $this->postJson('/api/v1/design/model-formulas/reorder', [
|
|
'items' => $reorderData
|
|
]);
|
|
|
|
$response->assertStatus(200);
|
|
|
|
$this->assertDatabaseHas('model_formulas', [
|
|
'id' => $formula2->id,
|
|
'sort_order' => 1
|
|
]);
|
|
}
|
|
}
|