Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
123 / 123
100.00% covered (success)
100.00%
2 / 2
CRAP
n/a
0 / 0
createSqlSpecStub
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
registerBaseSqlDriverTests
100.00% covered (success)
100.00%
117 / 117
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3// ╔════════════════════════════════════════════════════════════╗
4// ║ MIT Licence (#Expat) - https://opensource.org/licenses/MIT ║
5// ║ Copyright 2026 Frederic Poeydomenge <dyno@phexium.com>     ║
6// ╚════════════════════════════════════════════════════════════╝
7
8declare(strict_types=1);
9
10use Phexium\Domain\Specification\SpecificationInterface;
11use Tests\Phexium\Fake\Domain\Id;
12
13function createSqlSpecStub(string $sql, array $params): SpecificationInterface
14{
15    return new readonly class($sql, $params) implements SpecificationInterface {
16        public function __construct(
17            private string $sql,
18            private array $params
19        ) {}
20
21        public function toSql(): array
22        {
23            return ['sql' => $this->sql, 'params' => $this->params];
24        }
25
26        public function toInMemoryFilter(): callable
27        {
28            $params = $this->params;
29
30            return static fn (array $row): bool => array_all($params, fn ($value, $key): bool => isset($row[$key]) && $row[$key] === $value);
31        }
32    };
33}
34
35function registerBaseSqlDriverTests(): void
36{
37    test('findAll returns empty array when table is empty', function (): void {
38        $result = $this->driver->findAll('sample');
39
40        expect($result)->toBe([]);
41    });
42
43    test('save inserts new row', function (): void {
44        $this->driver->save('sample', ['id' => 1, 'name' => 'Test']);
45
46        $result = $this->driver->findAll('sample');
47
48        expect($result)->toHaveCount(1)
49            ->and($result[0]['name'])->toBe('Test')
50        ;
51    });
52
53    test('save updates existing row', function (): void {
54        $this->driver->save('sample', ['id' => 1, 'name' => 'Original']);
55        $this->driver->save('sample', ['id' => 1, 'name' => 'Updated']);
56
57        $result = $this->driver->findAll('sample');
58
59        expect($result)->toHaveCount(1)
60            ->and($result[0]['name'])->toBe('Updated')
61        ;
62    });
63
64    test('findById returns row when exists', function (): void {
65        $this->driver->save('sample', ['id' => 42, 'name' => 'Found']);
66
67        $id = Id::from(42);
68
69        $result = $this->driver->findById('sample', $id);
70
71        expect($result)->not->toBeNull()
72            ->and($result['name'])->toBe('Found')
73        ;
74    });
75
76    test('findById returns null when not exists', function (): void {
77        $id = Id::from(999);
78
79        $result = $this->driver->findById('sample', $id);
80
81        expect($result)->toBeNull();
82    });
83
84    test('deleteById removes row and returns count', function (): void {
85        $this->driver->save('sample', ['id' => 1, 'name' => 'ToDelete']);
86
87        $id = Id::from(1);
88
89        $count = $this->driver->deleteById('sample', $id);
90
91        expect($count)->toBe(1)
92            ->and($this->driver->findAll('sample'))->toBe([])
93        ;
94    });
95
96    test('deleteById returns 0 when row not found', function (): void {
97        $id = Id::from(999);
98
99        $count = $this->driver->deleteById('sample', $id);
100
101        expect($count)->toBe(0);
102    });
103
104    test('exists returns true when row exists', function (): void {
105        $this->driver->save('sample', ['id' => 1, 'name' => 'Exists']);
106
107        $id = Id::from(1);
108
109        expect($this->driver->exists('sample', $id))->toBeTrue();
110    });
111
112    test('exists returns false when row not exists', function (): void {
113        $id = Id::from(999);
114
115        expect($this->driver->exists('sample', $id))->toBeFalse();
116    });
117
118    test('findOneBy returns first matching row', function (): void {
119        $this->driver->save('sample', ['id' => 1, 'name' => 'First']);
120        $this->driver->save('sample', ['id' => 2, 'name' => 'First']);
121
122        $spec = createSqlSpecStub('name = :name', ['name' => 'First']);
123
124        $result = $this->driver->findOneBy('sample', $spec);
125
126        expect($result)->not->toBeNull()
127            ->and($result['name'])->toBe('First')
128        ;
129    });
130
131    test('findOneBy returns null when no match', function (): void {
132        $this->driver->save('sample', ['id' => 1, 'name' => 'NotMatching']);
133
134        $spec = createSqlSpecStub('name = :name', ['name' => 'NoMatch']);
135
136        $result = $this->driver->findOneBy('sample', $spec);
137
138        expect($result)->toBeNull();
139    });
140
141    test('findBy returns matching rows', function (): void {
142        $this->driver->save('sample', ['id' => 1, 'name' => 'Match']);
143        $this->driver->save('sample', ['id' => 2, 'name' => 'NoMatch']);
144        $this->driver->save('sample', ['id' => 3, 'name' => 'Match']);
145
146        $spec = createSqlSpecStub('name = :name', ['name' => 'Match']);
147
148        $result = $this->driver->findBy('sample', $spec);
149
150        expect($result)->toHaveCount(2);
151    });
152
153    test('findBy applies limit', function (): void {
154        $this->driver->save('sample', ['id' => 1, 'name' => 'A']);
155        $this->driver->save('sample', ['id' => 2, 'name' => 'A']);
156        $this->driver->save('sample', ['id' => 3, 'name' => 'A']);
157
158        $spec = createSqlSpecStub('name = :name', ['name' => 'A']);
159
160        $result = $this->driver->findBy('sample', $spec, null, null, 2);
161
162        expect($result)->toHaveCount(2);
163    });
164
165    test('findBy applies offset and limit', function (): void {
166        $this->driver->save('sample', ['id' => 1, 'name' => 'A']);
167        $this->driver->save('sample', ['id' => 2, 'name' => 'A']);
168        $this->driver->save('sample', ['id' => 3, 'name' => 'A']);
169
170        $spec = createSqlSpecStub('name = :name', ['name' => 'A']);
171
172        $result = $this->driver->findBy('sample', $spec, null, 1, 2);
173
174        expect($result)->toHaveCount(2);
175    });
176
177    test('findBy applies sorting', function (): void {
178        $this->driver->save('sample', ['id' => 1, 'name' => 'Zoo']);
179        $this->driver->save('sample', ['id' => 2, 'name' => 'Alpha']);
180
181        $spec = createSqlSpecStub('1=1', []);
182
183        $result = $this->driver->findBy('sample', $spec, ['name' => 'ASC']);
184
185        expect($result[0]['name'])->toBe('Alpha')
186            ->and($result[1]['name'])->toBe('Zoo')
187        ;
188    });
189
190    test('findBy returns zero-indexed array after filtering', function (): void {
191        $this->driver->save('sample', ['id' => 100, 'name' => 'Keep']);
192        $this->driver->save('sample', ['id' => 200, 'name' => 'Skip']);
193        $this->driver->save('sample', ['id' => 300, 'name' => 'Keep']);
194
195        $spec = createSqlSpecStub('name = :name', ['name' => 'Keep']);
196
197        $result = $this->driver->findBy('sample', $spec);
198
199        expect(array_keys($result))->toBe([0, 1]);
200    });
201
202    test('findBy sorting is case insensitive for direction', function (): void {
203        $this->driver->save('sample', ['id' => 1, 'name' => 'Zoo']);
204        $this->driver->save('sample', ['id' => 2, 'name' => 'Alpha']);
205
206        $spec = createSqlSpecStub('1=1', []);
207
208        $result = $this->driver->findBy('sample', $spec, ['name' => 'asc']);
209
210        expect($result[0]['name'])->toBe('Alpha');
211    });
212}