packages/sql-catalog/tests/Unit/Core/Catalog/ValueDomainTest.php

1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Core\Catalog;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\UsesClass;
9use PHPUnit\Framework\TestCase;
10use SqlCatalog\Core\Catalog\ValueDomain;
11use SqlCatalog\Core\Evaluation\ArrayTerm;
12use SqlCatalog\Core\Evaluation\Domain;
13use SqlCatalog\Core\Evaluation\LiteralTerm;
14use SqlCatalog\Core\Evaluation\OpaqueTerm;
15use SqlCatalog\Core\Text\Origin;
16use SqlCatalog\Core\Text\TextHole;
17use SqlCatalog\Core\Text\TextPattern;
18use SqlCatalog\Core\Type\TypeShape;
19
20#[CoversClass(ValueDomain::class)]
21#[UsesClass(ArrayTerm::class)]
22#[UsesClass(Domain::class)]
23#[UsesClass(LiteralTerm::class)]
24#[UsesClass(OpaqueTerm::class)]
25#[UsesClass(TextHole::class)]
26#[UsesClass(TextPattern::class)]
27#[UsesClass(TypeShape::class)]
28#[UsesClass(\SqlCatalog\Core\Evaluation\PatternTerm::class)]
29#[UsesClass(\SqlCatalog\Core\Text\LiteralText::class)]
30#[UsesClass(\SqlCatalog\Core\Text\TextGeneralization::class)]
31final class ValueDomainTest extends TestCase
32{
33    public function testFromDomainListsTheResolvedAlternatives(): void
34    {
35        $value = ValueDomain::fromDomain(Domain::literal('active')->union(Domain::literal('banned')));
36        self::assertSame(['active', 'banned'], $value->values);
37        self::assertTrue($value->exhaustive);
38        self::assertSame('string', $value->type);
39    }
40
41    public function testFromDomainReportsOnlyTheTypeWhenAValueDidNotResolve(): void
42    {
43        $value = ValueDomain::fromDomain(Domain::opaque(TypeShape::of(['int', 'null']), Origin::Parameter));
44        self::assertSame([], $value->values);
45        self::assertFalse($value->exhaustive);
46        self::assertSame(['parameter'], $value->origins);
47        self::assertSame('int|null', $value->type);
48    }
49
50    public function testFromDomainIsNotExhaustiveOnceTheAlternativesWereWidened(): void
51    {
52        $widened = Domain::literal('a')->union(Domain::literal('b'))->collapse(Origin::Branch);
53        self::assertFalse(ValueDomain::fromDomain($widened)->exhaustive);
54    }
55
56    public function testFromDomainRecordsAnUnresolvedOriginForANonScalarTerm(): void
57    {
58        self::assertSame(['unresolved'], ValueDomain::fromDomain(Domain::of(new ArrayTerm([])))->origins);
59    }
60
61    public function testIsResolvedNeedsBothExhaustivenessAndValues(): void
62    {
63        self::assertTrue(ValueDomain::fromDomain(Domain::literal('a'))->isResolved());
64        self::assertFalse(ValueDomain::fromDomain(Domain::unknown())->isResolved());
65    }
66
67    public function testAdmitsEverythingWhenNothingWasResolved(): void
68    {
69        self::assertTrue(ValueDomain::fromDomain(Domain::unknown())->admits('anything'));
70    }
71
72    public function testAdmitsOnlyTheResolvedAlternatives(): void
73    {
74        $value = ValueDomain::fromDomain(Domain::literal('active')->union(Domain::literal('banned')));
75        self::assertTrue($value->admits('active'));
76        self::assertFalse($value->admits('deleted'));
77    }
78
79    public function testAdmitsAcrossTheDriverWideningIntegersToStrings(): void
80    {
81        self::assertTrue(ValueDomain::fromDomain(Domain::literal(1))->admits('1'));
82    }
83
84    public function testDisplayWritesTheAlternativesOrTheType(): void
85    {
86        self::assertSame("'a'|1", ValueDomain::fromDomain(Domain::literal('a')->union(Domain::literal(1)))->display());
87        self::assertSame('mixed', ValueDomain::fromDomain(Domain::unknown())->display());
88    }
89}
90