service = new RuleExpressionService(); } // ============================================ // SYNTAX TESTS // ============================================ public function testSemicolonSyntaxBasic() { $result = $this->service->compile('if(sex("M"); result_set(0.5); result_set(0.6))'); $this->assertArrayHasKey('conditionExpr', $result); $this->assertArrayHasKey('valueExpr', $result); $this->assertArrayHasKey('then', $result); $this->assertArrayHasKey('else', $result); $this->assertEquals('patient["Sex"] == "M"', $result['conditionExpr']); $this->assertEquals('0.5', $result['then'][0]['valueExpr']); $this->assertEquals('0.6', $result['else'][0]['valueExpr']); } public function testTernarySyntaxFallback() { // Legacy ternary syntax should still work (fallback) $result = $this->service->compile('if(sex("M") ? result_set(0.5) : result_set(0.6))'); $this->assertArrayHasKey('conditionExpr', $result); $this->assertEquals('patient["Sex"] == "M"', $result['conditionExpr']); } // ============================================ // MULTI-ACTION TESTS // ============================================ public function testMultiActionWithColon() { $result = $this->service->compile('if(sex("M"); result_set(0.5):test_insert("HBA1C"); nothing)'); $this->assertCount(2, $result['then']); $this->assertEquals('RESULT_SET', $result['then'][0]['type']); $this->assertEquals(0.5, $result['then'][0]['value']); $this->assertEquals('TEST_INSERT', $result['then'][1]['type']); $this->assertEquals('HBA1C', $result['then'][1]['testCode']); $this->assertEquals('NO_OP', $result['else'][0]['type']); } public function testThreeActions() { $result = $this->service->compile('if(priority("S"); result_set("URGENT"):test_insert("STAT_TEST"):comment_insert("Stat order"); nothing)'); $this->assertCount(3, $result['then']); $this->assertEquals('RESULT_SET', $result['then'][0]['type']); $this->assertEquals('URGENT', $result['then'][0]['value']); $this->assertEquals('TEST_INSERT', $result['then'][1]['type']); $this->assertEquals('STAT_TEST', $result['then'][1]['testCode']); $this->assertEquals('COMMENT_INSERT', $result['then'][2]['type']); $this->assertEquals('Stat order', $result['then'][2]['comment']); } public function testMultiActionInElse() { $result = $this->service->compile('if(sex("M"); result_set(1.0); result_set(0.8):comment_insert("Default"))'); $this->assertCount(1, $result['then']); $this->assertCount(2, $result['else']); $this->assertEquals('RESULT_SET', $result['else'][0]['type']); $this->assertEquals(0.8, $result['else'][0]['value']); $this->assertEquals('COMMENT_INSERT', $result['else'][1]['type']); } // ============================================ // LOGICAL OPERATOR TESTS // ============================================ public function testAndOperator() { $result = $this->service->compile('if(sex("M") && age > 40; result_set(1.2); result_set(1.0))'); $this->assertStringContainsString('and', strtolower($result['conditionExpr'])); $this->assertStringContainsString('patient["Sex"] == "M"', $result['conditionExpr']); $this->assertStringContainsString('age > 40', $result['conditionExpr']); } public function testOrOperator() { $result = $this->service->compile('if(sex("M") || age > 65; result_set(1.0); result_set(0.8))'); $this->assertStringContainsString('or', strtolower($result['conditionExpr'])); $this->assertStringContainsString('patient["Sex"] == "M"', $result['conditionExpr']); $this->assertStringContainsString('age > 65', $result['conditionExpr']); } public function testCombinedAndOr() { $result = $this->service->compile('if((sex("M") && age > 40) || (sex("F") && age > 50); result_set(1.5); result_set(1.0))'); $this->assertStringContainsString('or', strtolower($result['conditionExpr'])); $this->assertStringContainsString('and', strtolower($result['conditionExpr'])); } public function testComplexNestedCondition() { $result = $this->service->compile('if(sex("M") && (age > 40 || priority("S")); result_set(1.2); nothing)'); $this->assertStringContainsString('patient["Sex"] == "M"', $result['conditionExpr']); $this->assertStringContainsString('or', strtolower($result['conditionExpr'])); } // ============================================ // ACTION TESTS // ============================================ public function testNothingAction() { $result = $this->service->compile('if(sex("M"); result_set(0.5); nothing)'); $this->assertEquals('NO_OP', $result['else'][0]['type']); } public function testNothingActionInThen() { $result = $this->service->compile('if(sex("M"); nothing; result_set(0.6))'); $this->assertEquals('NO_OP', $result['then'][0]['type']); } public function testInsertAction() { $result = $this->service->compile('if(requested("GLU"); test_insert("HBA1C"); nothing)'); $this->assertEquals('TEST_INSERT', $result['then'][0]['type']); $this->assertEquals('HBA1C', $result['then'][0]['testCode']); } public function testAddCommentAction() { $result = $this->service->compile('if(sex("M"); comment_insert("Male patient"); nothing)'); $this->assertEquals('COMMENT_INSERT', $result['then'][0]['type']); $this->assertEquals('Male patient', $result['then'][0]['comment']); } // ============================================ // CONDITION TESTS // ============================================ public function testSexCondition() { $result = $this->service->compile('if(sex("F"); result_set(0.7); result_set(1.0))'); $this->assertEquals('patient["Sex"] == "F"', $result['conditionExpr']); } public function testPriorityCondition() { $result = $this->service->compile('if(priority("S"); result_set("URGENT"); result_set("NORMAL"))'); $this->assertEquals('order["Priority"] == "S"', $result['conditionExpr']); } public function testPriorityUrgent() { $result = $this->service->compile('if(priority("U"); result_set("CRITICAL"); nothing)'); $this->assertEquals('order["Priority"] == "U"', $result['conditionExpr']); } public function testAgeCondition() { $result = $this->service->compile('if(age > 18; result_set(1.0); result_set(0.5))'); $this->assertEquals('age > 18', $result['conditionExpr']); } public function testAgeGreaterThanEqual() { $result = $this->service->compile('if(age >= 18; result_set(1.0); nothing)'); $this->assertEquals('age >= 18', $result['conditionExpr']); } public function testAgeLessThan() { $result = $this->service->compile('if(age < 65; result_set(1.0); nothing)'); $this->assertEquals('age < 65', $result['conditionExpr']); } public function testAgeRange() { $result = $this->service->compile('if(age >= 18 && age <= 65; result_set(1.0); nothing)'); $this->assertStringContainsString('age >= 18', $result['conditionExpr']); $this->assertStringContainsString('age <= 65', $result['conditionExpr']); } public function testRequestedCondition() { $result = $this->service->compile('if(requested("GLU"); test_insert("HBA1C"); nothing)'); $this->assertStringContainsString('requested', $result['conditionExpr']); $this->assertStringContainsString('GLU', $result['conditionExpr']); } // ============================================ // MULTI-RULE TESTS // ============================================ public function testParseMultiRule() { $expr = 'if(sex("M"); result_set(0.5); nothing), if(age > 65; result_set(1.0); nothing)'; $rules = $this->service->parseMultiRule($expr); $this->assertCount(2, $rules); $this->assertStringContainsString('sex("M")', $rules[0]); $this->assertStringContainsString('age > 65', $rules[1]); } public function testParseMultiRuleThreeRules() { $expr = 'if(sex("M"); result_set(0.5); nothing), if(age > 65; result_set(1.0); nothing), if(priority("S"); result_set(2.0); nothing)'; $rules = $this->service->parseMultiRule($expr); $this->assertCount(3, $rules); } // ============================================ // ERROR HANDLING TESTS // ============================================ public function testInvalidSyntaxThrowsException() { $this->expectException(\InvalidArgumentException::class); $this->service->compile('invalid syntax here'); } public function testUnknownActionThrowsException() { $this->expectException(\InvalidArgumentException::class); $this->service->compile('if(sex("M"); unknown_action(); nothing)'); } public function testEmptyExpressionReturnsEmptyArray() { $result = $this->service->compile(''); $this->assertEmpty($result); } // ============================================ // DOCUMENTATION EXAMPLES // ============================================ public function testExample1SexBasedResult() { // Example 1 from docs $result = $this->service->compile('if(sex("M"); result_set(0.5); result_set(0.6))'); $this->assertEquals('RESULT_SET', $result['then'][0]['type']); $this->assertEquals(0.5, $result['then'][0]['value']); $this->assertEquals('RESULT_SET', $result['else'][0]['type']); $this->assertEquals(0.6, $result['else'][0]['value']); } public function testExample2ConditionalTestInsertion() { // Example 2 from docs $result = $this->service->compile("if(requested('GLU'); test_insert('HBA1C'):test_insert('INS'); nothing)"); $this->assertCount(2, $result['then']); $this->assertEquals('TEST_INSERT', $result['then'][0]['type']); $this->assertEquals('HBA1C', $result['then'][0]['testCode']); $this->assertEquals('INS', $result['then'][1]['testCode']); } public function testExample3MultipleConditionsAnd() { // Example 3 from docs $result = $this->service->compile("if(sex('M') && age > 40; result_set(1.2); result_set(1.0))"); $this->assertStringContainsString('and', strtolower($result['conditionExpr'])); } public function testExample4OrCondition() { // Example 4 from docs $result = $this->service->compile("if(sex('M') || age > 65; result_set(1.0); result_set(0.8))"); $this->assertStringContainsString('or', strtolower($result['conditionExpr'])); } public function testExample5CombinedAndOr() { // Example 5 from docs $result = $this->service->compile("if((sex('M') && age > 40) || (sex('F') && age > 50); result_set(1.5); result_set(1.0))"); $this->assertStringContainsString('or', strtolower($result['conditionExpr'])); } public function testExample6MultipleActions() { // Example 6 from docs $result = $this->service->compile("if(priority('S'); result_set('URGENT'):test_insert('STAT_TEST'); result_set('NORMAL'))"); $this->assertCount(2, $result['then']); $this->assertEquals('RESULT_SET', $result['then'][0]['type']); $this->assertEquals('TEST_INSERT', $result['then'][1]['type']); } public function testExample7ThreeActions() { // Example 7 from docs $result = $this->service->compile("if(sex('M') && age > 40; result_set(1.5):test_insert('EXTRA_TEST'):comment_insert('Male over 40'); nothing)"); $this->assertCount(3, $result['then']); $this->assertEquals('RESULT_SET', $result['then'][0]['type']); $this->assertEquals('TEST_INSERT', $result['then'][1]['type']); $this->assertEquals('COMMENT_INSERT', $result['then'][2]['type']); } public function testExample8ComplexRule() { // Example 8 from docs $result = $this->service->compile("if(sex('F') && (age >= 18 && age <= 50) && priority('S'); result_set('HIGH_PRIO'):comment_insert('Female stat 18-50'); result_set('NORMAL'))"); $this->assertCount(2, $result['then']); $this->assertStringContainsString('and', strtolower($result['conditionExpr'])); } }