service = new ModelFormulaService(); $this->service->setTenantId(1)->setApiUserId(1); $this->model = Model::factory()->screen()->create(); } /** @test */ public function it_can_get_all_formulas_for_model() { // Arrange ModelFormula::factory() ->count(3) ->create(['model_id' => $this->model->id]); // Act $result = $this->service->getFormulasByModel($this->model->id); // Assert $this->assertCount(3, $result); $this->assertEquals($this->model->id, $result->first()->model_id); } /** @test */ public function it_filters_inactive_formulas() { // Arrange ModelFormula::factory() ->create(['model_id' => $this->model->id, 'is_active' => true]); ModelFormula::factory() ->create(['model_id' => $this->model->id, 'is_active' => false]); // Act $result = $this->service->getFormulasByModel($this->model->id); // Assert $this->assertCount(1, $result); $this->assertTrue($result->first()->is_active); } /** @test */ public function it_orders_formulas_by_sort_order() { // Arrange ModelFormula::factory() ->create(['model_id' => $this->model->id, 'sort_order' => 3, 'name' => 'third']); ModelFormula::factory() ->create(['model_id' => $this->model->id, 'sort_order' => 1, 'name' => 'first']); ModelFormula::factory() ->create(['model_id' => $this->model->id, 'sort_order' => 2, 'name' => 'second']); // Act $result = $this->service->getFormulasByModel($this->model->id); // Assert $this->assertEquals('first', $result->get(0)->name); $this->assertEquals('second', $result->get(1)->name); $this->assertEquals('third', $result->get(2)->name); } /** @test */ public function it_can_create_formula() { // Arrange $data = [ 'name' => 'test_formula', 'expression' => 'W0 * H0', 'description' => 'Test calculation', 'return_type' => 'NUMBER', 'sort_order' => 1, ]; // Act $result = $this->service->createFormula($this->model->id, $data); // Assert $this->assertInstanceOf(ModelFormula::class, $result); $this->assertEquals('test_formula', $result->name); $this->assertEquals('W0 * H0', $result->expression); $this->assertEquals($this->model->id, $result->model_id); $this->assertEquals(1, $result->tenant_id); $this->assertEquals(1, $result->created_by); } /** @test */ public function it_can_update_formula() { // Arrange $formula = ModelFormula::factory() ->create(['model_id' => $this->model->id, 'name' => 'old_name']); $updateData = [ 'name' => 'new_name', 'expression' => 'W0 + H0', 'description' => 'Updated description', ]; // Act $result = $this->service->updateFormula($formula->id, $updateData); // Assert $this->assertEquals('new_name', $result->name); $this->assertEquals('W0 + H0', $result->expression); $this->assertEquals('Updated description', $result->description); $this->assertEquals(1, $result->updated_by); } /** @test */ public function it_can_delete_formula() { // Arrange $formula = ModelFormula::factory() ->create(['model_id' => $this->model->id]); // Act $result = $this->service->deleteFormula($formula->id); // Assert $this->assertTrue($result); $this->assertSoftDeleted('model_formulas', ['id' => $formula->id]); } /** @test */ public function it_evaluates_simple_arithmetic_expressions() { // Arrange $formula = ModelFormula::factory() ->create([ 'model_id' => $this->model->id, 'expression' => 'W0 * H0', 'return_type' => 'NUMBER', ]); $parameters = ['W0' => 1000, 'H0' => 800]; // Act $result = $this->service->evaluateFormula($formula, $parameters); // Assert $this->assertEquals(800000, $result); } /** @test */ public function it_evaluates_complex_expressions_with_functions() { // Arrange $formula = ModelFormula::factory() ->create([ 'model_id' => $this->model->id, 'expression' => 'CEIL(W0 / 600)', 'return_type' => 'NUMBER', ]); $parameters = ['W0' => 1400]; // Act $result = $this->service->evaluateFormula($formula, $parameters); // Assert $this->assertEquals(3, $result); // ceil(1400/600) = ceil(2.33) = 3 } /** @test */ public function it_evaluates_conditional_expressions() { // Arrange $formula = ModelFormula::factory() ->create([ 'model_id' => $this->model->id, 'expression' => 'IF(area <= 3, "SMALL", IF(area <= 6, "MEDIUM", "LARGE"))', 'return_type' => 'STRING', ]); // Test cases $testCases = [ ['area' => 2, 'expected' => 'SMALL'], ['area' => 5, 'expected' => 'MEDIUM'], ['area' => 8, 'expected' => 'LARGE'], ]; foreach ($testCases as $testCase) { // Act $result = $this->service->evaluateFormula($formula, $testCase); // Assert $this->assertEquals($testCase['expected'], $result); } } /** @test */ public function it_handles_formula_dependencies() { // Arrange - Create formulas with dependencies $w1Formula = ModelFormula::factory() ->create([ 'model_id' => $this->model->id, 'name' => 'W1', 'expression' => 'W0 + 120', 'sort_order' => 1, ]); $h1Formula = ModelFormula::factory() ->create([ 'model_id' => $this->model->id, 'name' => 'H1', 'expression' => 'H0 + 100', 'sort_order' => 2, ]); $areaFormula = ModelFormula::factory() ->create([ 'model_id' => $this->model->id, 'name' => 'area', 'expression' => 'W1 * H1 / 1000000', 'sort_order' => 3, ]); $parameters = ['W0' => 1000, 'H0' => 800]; // Act $results = $this->service->evaluateAllFormulas($this->model->id, $parameters); // Assert $this->assertEquals(1120, $results['W1']); // 1000 + 120 $this->assertEquals(900, $results['H1']); // 800 + 100 $this->assertEquals(1.008, $results['area']); // 1120 * 900 / 1000000 } /** @test */ public function it_validates_formula_syntax() { // Test valid expressions $validExpressions = [ 'W0 + H0', 'W0 * H0 / 1000', 'CEIL(W0 / 600)', 'IF(area > 5, "LARGE", "SMALL")', 'SIN(angle * PI / 180)', ]; foreach ($validExpressions as $expression) { $isValid = $this->service->validateExpressionSyntax($expression); $this->assertTrue($isValid, "Expression should be valid: {$expression}"); } // Test invalid expressions $invalidExpressions = [ 'W0 + + H0', // Double operator 'W0 * )', // Unmatched parenthesis 'UNKNOWN_FUNC(W0)', // Unknown function '', // Empty expression 'W0 AND', // Incomplete expression ]; foreach ($invalidExpressions as $expression) { $isValid = $this->service->validateExpressionSyntax($expression); $this->assertFalse($isValid, "Expression should be invalid: {$expression}"); } } /** @test */ public function it_detects_circular_dependencies() { // Arrange - Create circular dependency ModelFormula::factory() ->create([ 'model_id' => $this->model->id, 'name' => 'A', 'expression' => 'B + 10', ]); ModelFormula::factory() ->create([ 'model_id' => $this->model->id, 'name' => 'B', 'expression' => 'C * 2', ]); ModelFormula::factory() ->create([ 'model_id' => $this->model->id, 'name' => 'C', 'expression' => 'A / 3', // Circular dependency ]); // Act & Assert $this->expectException(\InvalidArgumentException::class); $this->expectExceptionMessage('Circular dependency detected'); $this->service->evaluateAllFormulas($this->model->id, []); } /** @test */ public function it_respects_tenant_isolation() { // Arrange $otherTenantModel = Model::factory()->create(['tenant_id' => 2]); ModelFormula::factory() ->create(['model_id' => $otherTenantModel->id, 'tenant_id' => 2]); // Act $result = $this->service->getFormulasByModel($otherTenantModel->id); // Assert $this->assertCount(0, $result); } /** @test */ public function it_handles_missing_parameters_gracefully() { // Arrange $formula = ModelFormula::factory() ->create([ 'model_id' => $this->model->id, 'expression' => 'W0 * H0', ]); $incompleteParameters = ['W0' => 1000]; // Missing H0 // Act & Assert $this->expectException(\InvalidArgumentException::class); $this->expectExceptionMessage('Missing parameter: H0'); $this->service->evaluateFormula($formula, $incompleteParameters); } /** @test */ public function it_can_bulk_update_formulas() { // Arrange $formulas = ModelFormula::factory() ->count(3) ->create(['model_id' => $this->model->id]); $updateData = [ [ 'id' => $formulas[0]->id, 'name' => 'updated_formula_1', 'expression' => 'W0 + 100', ], [ 'id' => $formulas[1]->id, 'name' => 'updated_formula_2', 'expression' => 'H0 + 50', ], [ 'id' => $formulas[2]->id, 'is_active' => false, ], ]; // Act $result = $this->service->bulkUpdateFormulas($this->model->id, $updateData); // Assert $this->assertTrue($result); $updated = ModelFormula::whereIn('id', $formulas->pluck('id'))->get(); $this->assertEquals('updated_formula_1', $updated->where('id', $formulas[0]->id)->first()->name); $this->assertEquals('W0 + 100', $updated->where('id', $formulas[0]->id)->first()->expression); $this->assertFalse($updated->where('id', $formulas[2]->id)->first()->is_active); } }