@@ -0,0 +1,130 @@
|
||||
<?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 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user