testGetAllToQueueBalanceMessageNegative()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace ApplicationTest\Repository;
6
7
use Application\Model\User;
8
use Application\Repository\UserRepository;
9
use ApplicationTest\Traits\LimitedAccessSubQuery;
10
11
class UserRepositoryTest extends AbstractRepositoryTest
12
{
13
    use LimitedAccessSubQuery;
14
15
    private UserRepository $repository;
16
17
    protected function setUp(): void
18
    {
19
        parent::setUp();
20
        $this->repository = $this->getEntityManager()->getRepository(User::class);
21
    }
22
23
    public function providerGetAccessibleSubQuery(): iterable
24
    {
25
        $all = range(1000, 1015);
26
        yield ['anonymous', []];
27
        yield ['accounting_verificator', []];
28
        yield ['bookingonly', $all];
29
        yield ['individual', $all];
30
        yield ['member', $all];
31
        yield ['trainer', $all];
32
        yield ['formationresponsible', $all];
33
        yield ['responsible', $all];
34
        yield ['administrator', $all];
35
    }
36
37
    public function testGetOneByLoginPassword(): void
38
    {
39
        self::assertNull($this->repository->getOneByLoginPassword('foo', 'bar'), 'wrong user');
40
        self::assertNull($this->repository->getOneByLoginPassword('administrator', 'bar'), 'wrong password');
41
42
        $user = $this->repository->getOneByLoginPassword('administrator', 'administrator');
43
        self::assertNotNull($user);
44
        self::assertSame(1000, $user->getId());
45
46
        $hash = $this->getEntityManager()->getConnection()->executeQuery('SELECT password FROM `user` WHERE id = 1000')->fetchOne();
47
        self::assertStringStartsWith('$', $hash, 'password should have been re-hashed automatically');
48
        self::assertNotSame(md5('administrator'), $hash, 'password should have been re-hashed automatically');
49
    }
50
51
    public function testGetOneByLogin(): void
52
    {
53
        self::assertNull($this->repository->getOneById(1), 'wrong user');
54
55
        $user = $this->repository->getOneById(1000);
56
        self::assertNotNull($user);
57
        self::assertSame(1000, $user->getId());
58
    }
59
60
    public function testGetAllAdministratorsToNotify(): void
61
    {
62
        $actual = $this->repository->getAllAdministratorsToNotify();
63
        self::assertCount(1, $actual);
64
    }
65
66
    public function testGetAllToQueueBalanceMessage(): void
67
    {
68
        $actual = $this->repository->getAllToQueueBalanceMessage();
69
        self::assertCount(2, $actual);
70
71
        $actualBookings = [];
72
        foreach ($actual[0]->getBookings() as $booking) {
73
            $actualBookings[] = $booking->getId();
74
        }
75
76
        $expected = [4004, 4005, 4015];
77
        self::assertSame($expected, $actualBookings, 'must have pre-loaded only the bookings that we are interested in');
78
    }
79
80
    public function testGetAllToQueueBalanceMessageNegative(): void
81
    {
82
        $actual = $this->repository->getAllToQueueBalanceMessage(true);
83
        self::assertCount(0, $actual);
84
    }
85
86
    /**
87
     * @dataProvider providerGetOneByLoginOrEmail
88
     */
89
    public function testGetOneByLoginOrEmail(?string $loginOrEmail, ?int $expected): void
90
    {
91
        $actual = $this->repository->getOneByLoginOrEmail($loginOrEmail);
92
        self::assertSame($expected, $actual ? $actual->getId() : $actual);
93
    }
94
95
    public static function providerGetOneByLoginOrEmail(): iterable
96
    {
97
        yield [null, null];
98
        yield ['', null];
99
        yield ['non-existing', null];
100
        yield ['member', 1002];
101
        yield ['[email protected]', 1002];
102
        yield ['son', 1008];
103
    }
104
105
    public function testRecordLogin(): void
106
    {
107
        $this->setCurrentUser('administrator');
108
109
        /** @var User $user */
110
        $user = $this->getEntityManager()->getReference(User::class, 1002);
111
112
        self::assertNull($user->getFirstLogin());
113
        self::assertNull($user->getLastLogin());
114
        $this->assertNoStamp($user);
115
116
        $user->recordLogin();
117
        _em()->flush();
118
119
        $firstLogin = $user->getFirstLogin();
120
        $lastLogin = $user->getLastLogin();
121
        self::assertNotNull($firstLogin);
122
        self::assertNotNull($lastLogin);
123
        $this->assertNoStamp($user);
124
125
        $user->recordLogin();
126
        _em()->flush();
127
128
        $firstLogin2 = $user->getFirstLogin();
129
        $lastLogin2 = $user->getLastLogin();
130
        self::assertSame($firstLogin, $firstLogin2);
131
        self::assertNotSame($lastLogin, $lastLogin2);
132
        self::assertNotNull($lastLogin2);
133
        $this->assertNoStamp($user);
134
    }
135
136
    private function assertNoStamp(User $user): void
137
    {
138
        $count = $this->getEntityManager()->getConnection()->fetchOne('SELECT COUNT(*) FROM user WHERE id = ' . $user->getId() . ' AND creation_date IS NULL AND creator_id IS NULL AND update_date IS NULL AND updater_id IS NULL');
139
        self::assertSame(1, $count);
140
    }
141
142
    public function testAllUserRelationsHaveTestCases(): void
143
    {
144
        $actual = self::providerDeletingUserMightDeleteCascadeRelation();
145
        $connection = $this->getEntityManager()->getConnection();
146
        $missingRelations = '';
147
        foreach ($connection->createSchemaManager()->listTables() as $table) {
148
            foreach ($table->getForeignKeys() as $foreignKey) {
149
                if ('user' === $foreignKey->getForeignTableName()) {
150
                    foreach ($foreignKey->getLocalColumns() as $column) {
151
                        $relation = [$table->getName(), $column, false];
152
                        if (!$this->exist($relation, $actual)) {
153
                            $missingRelations .= preg_replace('~\s+~', ' ', str_replace(["\n", ',]'], ['', ']'], ve($relation, true) . ',')) . PHP_EOL;
154
                        }
155
                    }
156
                }
157
            }
158
        }
159
160
        self::assertTrue('' === $missingRelations, 'some user relations are missing test cases, copy-paste the code bellow into providerDeletingUserMightDeleteCascadeRelation()' . PHP_EOL . PHP_EOL . $missingRelations);
161
    }
162
163
    private function exist(array $needle, iterable $stck): bool
164
    {
165
        foreach ($stck as $candidate) {
166
            if ($candidate[0] === $needle[0] && $candidate[1] === $needle[1]) {
167
                return true;
168
            }
169
        }
170
171
        return false;
172
    }
173
174
    /**
175
     * @dataProvider providerDeletingUserMightDeleteCascadeRelation
176
     */
177
    public function testDeletingUserMightDeleteCascadeRelation(string $table, string $field, bool $expectDeleted): void
178
    {
179
        $connection = $this->getEntityManager()->getConnection();
180
181
        if ($table === 'log') {
182
            $connection->insert('log', []);
183
        }
184
185
        $countBefore = $connection->fetchOne("SELECT COUNT(*) FROM `$table`");
186
        $expectCount = $countBefore - ($expectDeleted || $table === 'user' ? 1 : 0);
187
188
        // 1035 is a user that has no relation at all in tests fixture, so we can be sure that
189
        // we won't trigger unique constraints when updating, and we won't delete things via another
190
        // pre-existing field when deleting a specific field
191
        $connection->executeStatement("UPDATE `$table` SET `$field` = 1015 LIMIT 1");
192
        self::assertSame(1, $connection->fetchOne("SELECT COUNT(*) FROM `$table` WHERE `$field` = 1015"), "should have exactly 1 $table.$field with the user ");
193
        $connection->executeStatement('DELETE FROM user WHERE id = 1015');
194
195
        $countAfter = $connection->fetchOne("SELECT COUNT(*) FROM `$table`");
196
197
        self::assertSame($expectCount, $countAfter, $expectDeleted ? 'should cascade delete' : 'should not cascade delete');
198
    }
199
200
    public static function providerDeletingUserMightDeleteCascadeRelation(): iterable
201
    {
202
        return [
203
            ['transaction_line', 'creator_id', false],
204
            ['transaction_line', 'owner_id', false],
205
            ['transaction_line', 'updater_id', false],
206
            ['bookable', 'owner_id', false],
207
            ['bookable', 'creator_id', false],
208
            ['bookable', 'updater_id', false],
209
            ['account', 'owner_id', false],
210
            ['account', 'creator_id', false],
211
            ['account', 'updater_id', false],
212
            ['transaction_tag', 'owner_id', false],
213
            ['transaction_tag', 'updater_id', false],
214
            ['transaction_tag', 'creator_id', false],
215
            ['user_tag', 'owner_id', false],
216
            ['user_tag', 'updater_id', false],
217
            ['user_tag', 'creator_id', false],
218
            ['bookable_metadata', 'updater_id', false],
219
            ['bookable_metadata', 'creator_id', false],
220
            ['bookable_metadata', 'owner_id', false],
221
            ['license', 'owner_id', false],
222
            ['license', 'updater_id', false],
223
            ['license', 'creator_id', false],
224
            ['booking', 'owner_id', false],
225
            ['booking', 'updater_id', false],
226
            ['booking', 'creator_id', false],
227
            ['image', 'creator_id', false],
228
            ['image', 'owner_id', false],
229
            ['image', 'updater_id', false],
230
            ['accounting_document', 'creator_id', false],
231
            ['accounting_document', 'updater_id', false],
232
            ['accounting_document', 'owner_id', false],
233
            ['user', 'owner_id', false],
234
            ['user', 'updater_id', false],
235
            ['user', 'creator_id', false],
236
            ['log', 'creator_id', true], // delete all my logs, which should not be a huge quantity of data, but still worth it
237
            ['log', 'owner_id', true], // idem
238
            ['log', 'updater_id', false],
239
            ['user_tag_user', 'user_id', true], // non-existing user cannot be tagged
240
            ['expense_claim', 'reviewer_id', false],
241
            ['expense_claim', 'owner_id', false],
242
            ['expense_claim', 'creator_id', false],
243
            ['expense_claim', 'updater_id', false],
244
            ['country', 'creator_id', false],
245
            ['country', 'owner_id', false],
246
            ['country', 'updater_id', false],
247
            ['configuration', 'creator_id', false],
248
            ['configuration', 'owner_id', false],
249
            ['configuration', 'updater_id', false],
250
            ['license_user', 'user_id', true], // non-existing user cannot have a license
251
            ['bookable_tag', 'owner_id', false],
252
            ['bookable_tag', 'updater_id', false],
253
            ['bookable_tag', 'creator_id', false],
254
            ['transaction', 'owner_id', false],
255
            ['transaction', 'creator_id', false],
256
            ['transaction', 'updater_id', false],
257
            ['message', 'creator_id', false],
258
            ['message', 'recipient_id', true], // a message that lost its recipient cannot have any purpose, so delete it
259
            ['message', 'owner_id', true], // owner is semantically exactly the same as recipient
260
            ['message', 'updater_id', false],
261
        ];
262
    }
263
}
264