definition(); $result = (new SimulationTaxRuleExpression)->evaluate($definition, ['base' => '1234.56'], ['rate' => '0.16']); self::assertSame('determinate', $result['status']); self::assertSame('19753/100', $result['result']); self::assertCount(4, $result['steps']); } #[Test] public function it_reports_missing_values_as_indeterminate_without_defaults(): void { $result = (new SimulationTaxRuleExpression)->evaluate($this->definition(), [], []); self::assertSame('indeterminate', $result['status']); self::assertNull($result['result']); self::assertSame(['input:base', 'parameter:rate'], $result['missing']); } #[Test] public function it_rejects_cycles_unknown_operations_and_undeclared_fields(): void { $engine = new SimulationTaxRuleExpression; $cycle = ['language' => 'investmoz.tax.v1', 'nodes' => ['a' => ['op' => 'add', 'args' => ['b', 'b']], 'b' => ['op' => 'add', 'args' => ['a', 'a']]], 'result' => 'a']; $unknown = ['language' => 'investmoz.tax.v1', 'nodes' => ['a' => ['op' => 'execute', 'code' => 'x']], 'result' => 'a']; $extra = ['language' => 'investmoz.tax.v1', 'nodes' => ['a' => ['op' => 'literal', 'value' => '1', 'formula' => 'x']], 'result' => 'a']; foreach ([$cycle, $unknown, $extra] as $invalid) { try { $engine->validate($invalid); self::fail('Invalid DSL was accepted.'); } catch (InvalidArgumentException) { self::assertTrue(true); } } } /** @return array */ private function definition(): array { return [ 'language' => 'investmoz.tax.v1', 'nodes' => [ 'base' => ['op' => 'input', 'key' => 'base'], 'rate' => ['op' => 'parameter', 'key' => 'rate'], 'raw' => ['op' => 'multiply', 'args' => ['base', 'rate']], 'tax' => ['op' => 'round', 'args' => ['raw'], 'scale' => 2, 'mode' => 'HALF_UP'], ], 'result' => 'tax', ]; } }