version('2025-01-01', '2025-12-31', '15', 1); $current = $this->version('2026-01-01', null, '16', 2); $resolver = new SimulationTaxParameterResolver; self::assertSame('15', $resolver->resolve([$old, $current], 'IVA', 'scope:test', '2025-12-31', 'VAT_RATE')['value']); $resolved = $resolver->resolve([$old, $current], 'IVA', 'scope:test', '2026-01-01', 'VAT_RATE'); self::assertSame('resolved', $resolved['status']); self::assertSame('16', $resolved['value']); self::assertSame('LAW-2026', $resolved['source']['diploma']); self::assertSame(2, $resolved['rule_version']); } #[Test] public function it_returns_indeterminate_for_missing_or_overlapping_candidates(): void { $version = $this->version('2026-01-01', null, '16', 1); $resolver = new SimulationTaxParameterResolver; self::assertSame('indeterminate_missing', $resolver->resolve([], 'IVA', 'scope:test', '2026-07-01', 'VAT_RATE')['status']); self::assertSame('indeterminate_missing', $resolver->resolve([$version], 'IVA', 'different-scope', '2026-07-01', 'VAT_RATE')['status']); self::assertSame('indeterminate_missing', $resolver->resolve([$version], 'IVA', 'scope:test', '2025-12-31', 'VAT_RATE')['status']); self::assertSame('indeterminate_overlap', $resolver->resolve([$version, $version], 'IVA', 'scope:test', '2026-07-01', 'VAT_RATE')['status']); } #[Test] public function it_requires_exactly_matching_conditions_and_a_still_validated_source_version(): void { $version = $this->version('2026-01-01', null, '16', 1); $version['parameters']['VAT_RATE']['conditions'] = ['operation' => 'domestic']; $resolver = new SimulationTaxParameterResolver; self::assertSame('indeterminate_missing', $resolver->resolve([$version], 'IVA', 'scope:test', '2026-03-01', 'VAT_RATE', ['operation' => 'import'])['status']); $version['source_version_no'] = 2; self::assertSame('indeterminate_missing', $resolver->resolve([$version], 'IVA', 'scope:test', '2026-03-01', 'VAT_RATE', ['operation' => 'domestic'])['status']); } #[Test] public function it_rejects_negative_values_and_percentages_over_one_hundred(): void { $resolver = new SimulationTaxParameterResolver; foreach (['-0.01' => 'mzn', '100.0001' => 'percent'] as $value => $unit) { try { $resolver->validateParameters(['TEST_VALUE' => ['value' => $value, 'unit' => $unit, 'conditions' => []]]); self::fail('Invalid parameter value was accepted.'); } catch (InvalidArgumentException) { self::assertTrue(true); } } } #[Test] public function it_does_not_select_a_parameter_when_its_validity_changes_inside_the_requested_period(): void { $before = $this->publicVersion('2026-01-01', '2026-06-30', '16', 1); $after = $this->publicVersion('2026-07-01', null, '17', 2); $resolver = new SimulationTaxParameterResolver; $result = $resolver->resolveAvailableParameters([$before, $after], 'IVA', '2026-01-01', '2026-12-31', ['operation' => 'domestic']); self::assertSame('indeterminate_period_change', $result[0]['status']); self::assertNull($result[0]['value']); } #[Test] public function it_resolves_a_single_parameter_only_for_an_exactly_matching_context(): void { $version = $this->publicVersion('2026-01-01', null, '16', 1); $resolver = new SimulationTaxParameterResolver; $result = $resolver->resolveAvailableParameters([$version], 'IVA', '2026-01-01', '2026-12-31', ['operation' => 'domestic']); self::assertSame('resolved', $result[0]['status']); self::assertSame('16', $result[0]['value']); self::assertSame('Synthetic source fixture', $result[0]['source']['title']); } /** @return array */ private function version(string $from, ?string $until, string $value, int $version): array { return [ 'id' => 'rule-'.$version, 'rule_code' => 'IVA.VAT.RATE', 'rule_version' => $version, 'component' => 'IVA', 'scope_key' => 'scope:test', 'status' => 'published', 'valid_from' => $from, 'valid_until' => $until, 'parameters' => ['VAT_RATE' => ['value' => $value, 'unit' => 'percent', 'conditions' => []]], 'source_version_no' => 1, 'source_sha256' => str_repeat('a', 64), 'source' => [ 'validation_status' => 'validated', 'version_no' => 1, 'valid_from' => '2020-01-01', 'valid_until' => null, 'title' => 'Synthetic source fixture', 'diploma' => 'LAW-2026', 'article' => 'Article fixture', 'url' => 'https://example.invalid/source', ], ]; } /** @return array */ private function publicVersion(string $from, ?string $until, string $value, int $version): array { $record = $this->version($from, $until, $value, $version); $record['rule_definition'] = ['language' => 'investmoz.tax.v1']; $record['parameters']['VAT_RATE']['conditions'] = ['operation' => 'domestic']; return $record; } }