packages/sql-catalog/tests/Unit/Extension/Mysqli/MysqliExtensionTest.php

1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Extension\Mysqli;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\UsesClass;
9use PHPUnit\Framework\TestCase;
10use SqlCatalog\Core\Extension\SinkCallKind;
11use SqlCatalog\Core\Extension\SinkSpec;
12use SqlCatalog\Extension\Mysqli\MysqliExtension;
13
14#[CoversClass(MysqliExtension::class)]
15#[UsesClass(SinkSpec::class)]
16final class MysqliExtensionTest extends TestCase
17{
18    public function testNameIsHowTheCommandLineSelectsIt(): void
19    {
20        self::assertSame('mysqli', (new MysqliExtension())->name());
21    }
22
23    public function testDescriptionMentionsWhatItCovers(): void
24    {
25        self::assertStringContainsString('mysqli', (new MysqliExtension())->description());
26    }
27
28    public function testSinksCoverBothTheObjectAndTheProceduralApi(): void
29    {
30        $kinds = array_map(static fn (SinkSpec $sink): SinkCallKind => $sink->callKind, (new MysqliExtension())->sinks());
31        self::assertContains(SinkCallKind::Method, $kinds);
32        self::assertContains(SinkCallKind::FunctionCall, $kinds);
33    }
34
35    public function testMethodSinksBindVariadicValues(): void
36    {
37        $bind = array_values(array_filter(
38            (new MysqliExtension())->methodSinks(),
39            static fn (SinkSpec $sink): bool => $sink->id === 'mysqli.stmt.bind_param',
40        ));
41        self::assertSame(1, $bind[0]->valuesFrom);
42    }
43
44    public function testFunctionSinksTakeTheStatementAsTheSecondArgument(): void
45    {
46        $positions = array_map(
47            static fn (SinkSpec $sink): ?int => $sink->sqlParameter,
48            (new MysqliExtension())->functionSinks(),
49        );
50        self::assertSame([1], array_values(array_unique($positions)));
51    }
52    public function testEverySinkIsDescribedExactly(): void
53    {
54        $described = array_map(
55            static fn (SinkSpec $sink): string => implode('|', [
56                $sink->id,
57                $sink->callKind->value,
58                $sink->receiverType ?? '-',
59                $sink->name,
60                $sink->role->value,
61                $sink->sqlParameter ?? '-',
62                $sink->valuesParameter ?? '-',
63                $sink->valuesFrom ?? '-',
64                $sink->nameParameter ?? '-',
65                $sink->valueParameter ?? '-',
66                ($sink->kind === null ? '-' : $sink->kind->value) . '/' . ($sink->handleType ?? '-'),
67            ]),
68            (new MysqliExtension())->sinks(),
69        );
70
71        self::assertSame(
72            [
73            'mysqli.query|method|mysqli|query|query|0|-|-|-|-|-/-',
74            'mysqli.real_query|method|mysqli|real_query|query|0|-|-|-|-|-/-',
75            'mysqli.multi_query|method|mysqli|multi_query|query|0|-|-|-|-|-/-',
76            'mysqli.execute_query|method|mysqli|execute_query|query|0|1|-|-|-|-/-',
77            'mysqli.prepare|method|mysqli|prepare|prepare|0|-|-|-|-|-/mysqli_stmt',
78            'mysqli.stmt.bind_param|method|mysqli_stmt|bind_param|bind|-|-|1|-|-|-/-',
79            'mysqli.stmt.execute|method|mysqli_stmt|execute|execute|-|0|-|-|-|-/-',
80            'mysqli.fn.query|function|-|mysqli_query|query|1|-|-|-|-|-/-',
81            'mysqli.fn.real_query|function|-|mysqli_real_query|query|1|-|-|-|-|-/-',
82            'mysqli.fn.multi_query|function|-|mysqli_multi_query|query|1|-|-|-|-|-/-',
83            'mysqli.fn.execute_query|function|-|mysqli_execute_query|query|1|2|-|-|-|-/-',
84            'mysqli.fn.prepare|function|-|mysqli_prepare|prepare|1|-|-|-|-|-/mysqli_stmt',
85            ],
86            $described,
87        );
88    }
89
90    public function testGlobalsAreEmptyBecauseTheHandleIsNotReachedThroughOne(): void
91    {
92        self::assertSame([], (new MysqliExtension())->globals());
93    }
94}
95