Files
Investidor-V2/tests/Unit/SimulationTaxParameterResolverTest.php
T

153 lines
7.4 KiB
PHP

<?php
declare(strict_types=1);
namespace Tests\Unit;
use App\Services\SimulationTaxParameterResolver;
use InvalidArgumentException;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
final class SimulationTaxParameterResolverTest extends TestCase
{
#[Test]
public function synthetic_qa_versions_keep_historical_values_and_expose_a_period_transition_without_activating_rules(): void
{
$before = $this->publicVersion('2026-01-01', '2026-06-30', '7.5', 1);
$after = $this->publicVersion('2026-07-01', null, '9.25', 2);
$before['rule_code'] = $after['rule_code'] = 'QA.SYNTHETIC.NOT.LEGISLATION';
$before['source']['title'] = $after['source']['title'] = 'SINTETICO QA — nao e legislacao';
$before['source']['diploma'] = $after['source']['diploma'] = 'QA-FIXTURE-ONLY';
$resolver = new SimulationTaxParameterResolver;
$historical = $resolver->resolveAvailableParameters([$before], 'IVA', '2026-06-01', '2026-06-30', ['operation' => 'domestic']);
$frozen = json_encode($historical, JSON_THROW_ON_ERROR);
$current = $resolver->resolveAvailableParameters([$before, $after], 'IVA', '2026-07-01', '2026-07-31', ['operation' => 'domestic']);
self::assertSame('7.5', $historical[0]['value']);
self::assertSame('9.25', $current[0]['value']);
self::assertSame(2, $current[0]['rule_version']);
self::assertSame('indeterminate_period_change', $resolver->resolveAvailableParameters([$before, $after], 'IVA', '2026-01-01', '2026-12-31', ['operation' => 'domestic'])[0]['status']);
self::assertSame($frozen, json_encode($historical, JSON_THROW_ON_ERROR));
self::assertSame($historical, $resolver->resolveAvailableParameters([$before, $after], 'IVA', '2026-06-01', '2026-06-30', ['operation' => 'domestic']));
$after['source']['version_no'] = 2;
self::assertSame([], $resolver->resolveAvailableParameters([$after], 'IVA', '2026-07-01', '2026-07-31', ['operation' => 'domestic']));
}
#[Test]
public function it_selects_the_published_version_effective_on_the_requested_date_and_returns_its_source(): void
{
$old = $this->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<string,mixed> */
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<string,mixed> */
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;
}
}