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 ]); } }