4 * Copyright © 2003-2024 The Galette Team
6 * This file is part of Galette (https://galette.eu).
8 * Galette is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation, either version 3 of the License, or
11 * (at your option) any later version.
13 * Galette is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with Galette. If not, see <http://www.gnu.org/licenses/>.
22 namespace Galette\Entity\test\units
;
24 use Galette\GaletteTestCase
;
27 * Adherent tests class
29 * @author Johan Cwiklinski <johan@x-tnd.be>
31 class Adherent
extends GaletteTestCase
33 protected int $seed = 95842354;
34 private array $default_deps;
41 public function tearDown(): void
44 $this->zdb
= new \Galette\Core\
Db();
46 $this->cleanContributions();
48 $delete = $this->zdb
->delete(\Galette\Entity\Adherent
::TABLE
);
49 $delete->where(['fingerprint' => 'FAKER' . $this->seed
]);
50 $delete->where('parent_id IS NOT NULL');
51 $this->zdb
->execute($delete);
53 $delete = $this->zdb
->delete(\Galette\Entity\Adherent
::TABLE
);
54 $delete->where(['fingerprint' => 'FAKER' . $this->seed
]);
55 $this->zdb
->execute($delete);
63 public static function tearDownAfterClass(): void
65 $self = new self(__METHOD__
);
75 public function setUp(): void
79 $this->default_deps
= [
89 $this->adh
= new \Galette\Entity\
Adherent($this->zdb
);
90 $this->adh
->setDependencies(
92 $this->members_fields
,
102 public function testEmpty()
105 $this->assertFalse($adh->isAdmin());
106 $this->assertFalse($adh->admin
);
107 $this->assertFalse($adh->isStaff());
108 $this->assertFalse($adh->staff
);
109 $this->assertFalse($adh->isDueFree());
110 $this->assertFalse($adh->due_free
);
111 $this->assertFalse($adh->isGroupMember('any'));
112 $this->assertFalse($adh->isGroupManager('any'));
113 $this->assertFalse($adh->isCompany());
114 $this->assertFalse($adh->isMan());
115 $this->assertFalse($adh->isWoman());
116 $this->assertTrue($adh->isActive());
117 $this->assertTrue($adh->active
);
118 $this->assertFalse($adh->isUp2Date());
119 $this->assertFalse($adh->appearsInMembersList());
120 $this->assertFalse($adh->appears_in_list
);
122 $this->assertNull($adh->fake_prop
);
124 $this->assertSame($this->default_deps
, $adh->deps
);
128 * Test member load dependencies
132 public function testDependencies()
135 $this->assertSame($this->default_deps
, $adh->deps
);
137 $adh = clone $this->adh
;
138 $adh->disableAllDeps();
148 $this->assertSame($expected, $adh->deps
);
161 ->enableDep('dynamics')
162 ->enableDep('children');
163 $this->assertSame($expected, $adh->deps
);
174 $adh->disableDep('children');
175 $this->assertSame($expected, $adh->deps
);
177 $adh->disableDep('none')->enableDep('anothernone');
178 $this->assertSame($expected, $adh->deps
);
189 $adh->enableAllDeps('children');
190 $this->assertSame($expected, $adh->deps
);
198 public function testGetterWException()
202 $this->expectException('RuntimeException');
207 * Set dependencies from constructor
211 public function testDepsAtConstuct()
222 $adh = new \Galette\Entity\
Adherent(
228 $this->assertSame($deps, $adh->deps
);
232 * Test simple member creation
236 public function testSimpleMember()
238 $this->getMemberOne();
239 $this->checkMemberOneExpected();
241 //load member from db
242 $adh = new \Galette\Entity\
Adherent($this->zdb
, $this->adh
->id
);
243 $this->checkMemberOneExpected($adh);
247 * Test load form login and email
251 public function testLoadForLogin()
253 $this->getMemberOne();
255 $login = $this->adh
->login
;
256 $email = $this->adh
->email
;
258 $this->assertSame($this->adh
->getEmail(), $this->adh
->email
);
260 $adh = new \Galette\Entity\
Adherent($this->zdb
, $login);
261 $this->checkMemberOneExpected($adh);
263 $adh = new \Galette\Entity\
Adherent($this->zdb
, $email);
264 $this->checkMemberOneExpected($adh);
268 * Test password updating
272 public function testUpdatePassword()
274 $this->getMemberOne();
276 $this->checkMemberOneExpected();
279 \Galette\Entity\Adherent
::updatePassword($this->zdb
, $this->adh
->id
, $newpass);
280 $adh = new \Galette\Entity\
Adherent($this->zdb
, $this->adh
->id
);
281 $pw_checked = password_verify($newpass, $adh->password
);
282 $this->assertTrue($pw_checked);
284 //reset original password
285 \Galette\Entity\Adherent
::updatePassword($this->zdb
, $this->adh
->id
, 'J^B-()f');
293 public function testCheckErrors()
297 $data = ['ddn_adh' => 'not a date'];
298 $expected = ['- Wrong date format (Y-m-d) for Birth date!'];
299 $check = $adh->check($data, [], []);
300 $this->assertSame($expected, $check);
304 'date_crea_adh' => 'not a date'
306 $expected = ['- Wrong date format (Y-m-d) for Creation date!'];
307 $check = $adh->check($data, [], []);
308 $this->assertSame($expected, $check);
310 //reste creation date to its default value
311 $data = ['date_crea_adh' => date('Y-m-d')];
312 $check = $adh->check($data, [], []);
313 $this->assertTrue($check);
315 $data = ['email_adh' => 'not an email'];
316 $expected = ['- Non-valid E-Mail address! (E-Mail)'];
317 $check = $adh->check($data, [], []);
318 $this->assertSame($expected, $check);
320 $data = ['login_adh' => 'a'];
321 $expected = ['- The username must be composed of at least 2 characters!'];
322 $check = $adh->check($data, [], []);
323 $this->assertSame($expected, $check);
325 $data = ['login_adh' => 'login@galette'];
326 $expected = ['- The username cannot contain the @ character'];
327 $check = $adh->check($data, [], []);
328 $this->assertSame($expected, $check);
332 'mdp_adh' => 'short',
333 'mdp_adh2' => 'short'
335 $expected = ['Too short (6 characters minimum, 5 found)'];
336 $check = $adh->check($data, [], []);
337 $this->assertSame($expected, $check);
339 $data = ['mdp_adh' => 'mypassword'];
340 $expected = ['- The passwords don\'t match!'];
341 $check = $adh->check($data, [], []);
342 $this->assertSame($expected, $check);
345 'mdp_adh' => 'mypassword',
346 'mdp_adh2' => 'mypasswor'
348 $expected = ['- The passwords don\'t match!'];
349 $check = $adh->check($data, [], []);
350 $this->assertSame($expected, $check);
352 $data = ['id_statut' => 256];
353 $expected = ['Status #256 does not exists in database.'];
354 $check = $adh->check($data, [], []);
355 $this->assertSame($expected, $check);
357 //tests for group managers
358 //test insert failing
359 $g1 = $this->getMockBuilder(\Galette\Entity\Group
::class)
360 ->onlyMethods(array('getId'))
362 $g1->method('getId')->willReturn(1);
364 $g2 = $this->getMockBuilder(\Galette\Entity\Group
::class)
365 ->onlyMethods(array('getId'))
367 $g2->method('getId')->willReturn(2);
369 //groups managers must specify a group they manage
371 $login = $this->getMockBuilder(\Galette\Core\Login
::class)
372 ->setConstructorArgs(array($this->zdb
, $this->i18n
))
373 ->onlyMethods(array('isGroupManager'))
375 $login->method('isGroupManager')->willReturnCallback(
376 function ($gid) use ($g1) {
377 return $gid === null ||
$gid == $g1->getId();
381 $data = ['id_statut' => \Galette\Entity\Status
::DEFAULT_STATUS
];
382 $check = $adh->check($data, [], []);
383 $expected = ['You have to select a group you own!'];
384 $this->assertSame($expected, $check);
386 $data = ['groups_adh' => [$g2->getId()]];
387 $check = $adh->check($data, [], []);
388 $expected = ['You have to select a group you own!'];
389 $this->assertSame($expected, $check);
391 $data = ['groups_adh' => [$g1->getId()]];
392 $check = $adh->check($data, [], []);
393 $this->assertTrue($check);
401 public function testPhoto()
403 $this->getMemberOne();
405 $fakedata = new \Galette\Util\
FakeData($this->zdb
, $this->i18n
);
406 $this->assertTrue($fakedata->addPhoto($this->adh
));
408 $this->assertTrue($this->adh
->hasPicture());
411 $this->assertTrue($this->adh
->picture
->delete());
419 public function testCanEdit()
421 $adh = new \Galette\Entity\
Adherent($this->zdb
);
424 $login = $this->getMockBuilder(\Galette\Core\Login
::class)
425 ->setConstructorArgs(array($this->zdb
, $this->i18n
))
426 ->onlyMethods(array('isGroupManager'))
428 $this->assertFalse($adh->canEdit($login));
430 //admin => authorized
431 $login = $this->getMockBuilder(\Galette\Core\Login
::class)
432 ->setConstructorArgs(array($this->zdb
, $this->i18n
))
433 ->onlyMethods(array('isAdmin'))
435 $login->method('isAdmin')->willReturn(true);
436 $this->assertTrue($adh->canEdit($login));
438 //staff => authorized
439 $login = $this->getMockBuilder(\Galette\Core\Login
::class)
440 ->setConstructorArgs(array($this->zdb
, $this->i18n
))
441 ->onlyMethods(array('isStaff'))
443 $login->method('isStaff')->willReturn(true);
444 $this->assertTrue($adh->canEdit($login));
447 $adh = $this->getMockBuilder(\Galette\Entity\Adherent
::class)
448 ->setConstructorArgs(array($this->zdb
))
449 ->onlyMethods(array('getGroups'))
452 $g1 = $this->getMockBuilder(\Galette\Entity\Group
::class)
453 ->onlyMethods(array('getId'))
455 $g1->method('getId')->willReturn(1);
457 $g2 = $this->getMockBuilder(\Galette\Entity\Group
::class)
458 ->onlyMethods(array('getId'))
460 $g2->method('getId')->willReturn(2);
462 $adh->method('getGroups')->willReturn([$g1, $g2]);
464 $login = $this->getMockBuilder(\Galette\Core\Login
::class)
465 ->setConstructorArgs(array($this->zdb
, $this->i18n
))
466 ->onlyMethods(array('isGroupManager'))
469 $this->assertFalse($adh->canEdit($login));
471 $login->method('isGroupManager')->willReturnCallback(
472 function ($gid) use ($g1) {
473 return $gid === null ||
$gid == $g1->getId();
476 $this->assertFalse($adh->canEdit($login));
478 $this->preferences
->pref_bool_groupsmanagers_edit_member
= true;
479 $canEdit = $adh->canEdit($login);
480 $this->preferences
->pref_bool_groupsmanagers_edit_member
= false; //reset
481 $this->assertTrue($canEdit);
483 //groups managers cannot edit members of the groups they do not own
484 $adh->method('getGroups')->willReturn([$g2]);
485 $this->assertFalse($adh->canEdit($login));
489 * Test member duplication
493 public function testDuplicate()
495 $this->getMemberOne();
497 $this->checkMemberOneExpected();
499 //load member from db
500 $adh = new \Galette\Entity\
Adherent($this->zdb
, $this->adh
->id
);
501 $this->checkMemberOneExpected($adh);
503 $adh->setDuplicate();
505 $this->assertStringContainsString('Duplicated from', $adh->others_infos_admin
);
506 $this->assertNull($adh->email
);
507 $this->assertNull($adh->id
);
508 $this->assertNull($adh->login
);
509 $this->assertNull($adh->birthdate
);
510 $this->assertNull($adh->surname
);
518 public function testParents()
520 $this->getMemberOne();
522 $this->checkMemberOneExpected();
524 //load member from db
525 $parent = new \Galette\Entity\
Adherent($this->zdb
, $this->adh
->id
);
526 $this->checkMemberOneExpected($parent);
528 $this->logSuperAdmin();
532 'prenom_adh' => 'Johny',
533 'parent_id' => $parent->id
,
535 'fingerprint' => 'FAKER' . $this->seed
537 $child = $this->createMember($child_data);
539 $this->assertSame($child_data['nom_adh'], $child->name
);
540 $this->assertInstanceOf('\Galette\Entity\Adherent', $child->parent
);
541 $this->assertSame($parent->id
, $child->parent
->id
);
543 $check = $child->check(['detach_parent' => true], [], []);
544 if (is_array($check)) {
547 $this->assertTrue($check);
548 $this->assertTrue($child->store());
549 $this->assertNull($child->parent
);
553 * Test XSS/SQL injection
557 public function testInjection()
561 'prenom_adh' => 'Johny <script>console.log("anything");</script>',
562 'email_adh' => 'jdoe@doe.com',
563 'login_adh' => 'jdoe',
564 'info_public_adh' => 'Any <script>console.log("useful");</script> information',
565 'fingerprint' => 'FAKER' . $this->seed
566 ] +
$this->dataAdherentOne();
567 $member = $this->createMember($data);
569 $this->assertSame('DOE Johny Console.log("anything");', $member->sfullname
);
570 $this->assertSame('Any console.log("useful"); information', $member->others_infos
);
578 public function testCan()
580 $this->getMemberOne();
581 //load member from db
582 $member = new \Galette\Entity\
Adherent($this->zdb
, $this->adh
->id
);
584 $this->assertFalse($member->canShow($this->login
));
585 $this->assertFalse($member->canCreate($this->login
));
586 $this->assertFalse($member->canEdit($this->login
));
588 //Superadmin can fully change members
589 $this->logSuperAdmin();
591 $this->assertTrue($member->canShow($this->login
));
592 $this->assertTrue($member->canCreate($this->login
));
593 $this->assertTrue($member->canEdit($this->login
));
596 $this->login
->logOut();
597 $this->assertFalse($this->login
->isLogged());
599 //Member can fully change its own information
600 $mdata = $this->dataAdherentOne();
601 $this->assertTrue($this->login
->login($mdata['login_adh'], $mdata['mdp_adh']));
602 $this->assertTrue($this->login
->isLogged());
603 $this->assertFalse($this->login
->isAdmin());
604 $this->assertFalse($this->login
->isStaff());
606 $this->assertTrue($member->canShow($this->login
));
607 $this->assertTrue($member->canCreate($this->login
));
608 $this->assertTrue($member->canEdit($this->login
));
611 $this->login
->logOut();
612 $this->assertFalse($this->login
->isLogged());
614 //Another member has no access
615 $this->getMemberTwo();
616 $mdata = $this->dataAdherentTwo();
617 $this->assertTrue($this->login
->login($mdata['login_adh'], $mdata['mdp_adh']));
618 $this->assertTrue($this->login
->isLogged());
619 $this->assertFalse($this->login
->isAdmin());
620 $this->assertFalse($this->login
->isStaff());
622 $this->assertFalse($member->canShow($this->login
));
623 $this->assertFalse($member->canCreate($this->login
));
624 $this->assertFalse($member->canEdit($this->login
));
626 //parents can fully change children information
627 $this->getMemberOne();
628 $mdata = $this->dataAdherentOne();
630 $login = $this->login
;
631 $this->logSuperAdmin();
635 'prenom_adh' => 'Johny',
636 'parent_id' => $member->id
,
638 'login_adh' => 'child.johny.doe',
639 'fingerprint' => 'FAKER' . $this->seed
641 $child = $this->createMember($child_data);
643 $this->login
->logOut();
646 $child = new \Galette\Entity\
Adherent($this->zdb
);
647 $child->enableDep('parent');
648 $this->assertTrue($child->load($cid));
650 $this->assertSame($child_data['nom_adh'], $child->name
);
651 $this->assertInstanceOf('\Galette\Entity\Adherent', $child->parent
);
652 $this->assertSame($member->id
, $child->parent
->id
);
653 $this->assertTrue($this->login
->login($mdata['login_adh'], $mdata['mdp_adh']));
655 $mdata = $this->dataAdherentOne();
656 $this->assertTrue($this->login
->login($mdata['login_adh'], $mdata['mdp_adh']));
657 $this->assertTrue($this->login
->isLogged());
658 $this->assertFalse($this->login
->isAdmin());
659 $this->assertFalse($this->login
->isStaff());
661 $this->assertTrue($child->canShow($this->login
));
662 $this->assertFalse($child->canCreate($this->login
));
663 $this->assertTrue($child->canEdit($this->login
));
666 $this->login
->logOut();
667 $this->assertFalse($this->login
->isLogged());
669 //tests for group managers
670 $adh = $this->getMockBuilder('\Galette\Entity\Adherent')
671 ->setConstructorArgs([$this->zdb
])
672 ->onlyMethods(['getGroups'])
675 $g1 = $this->getMockBuilder('\Galette\Entity\Group')
676 ->onlyMethods(['getId'])
678 $g1->method('getId')->willReturn(1);
680 $g2 = $this->getMockBuilder('\Galette\Entity\Group')
681 ->onlyMethods(['getId'])
683 $g2->method('getId')->willReturn(2);
685 //groups managers can show members of the groups they own
686 $adh->method('getGroups')->willReturn([$g1, $g2]);
688 $login = $this->getMockBuilder('\Galette\Core\Login')
689 ->setConstructorArgs([$this->zdb
, $this->i18n
])
690 ->onlyMethods(['isGroupManager'])
692 $this->assertFalse($adh->canShow($login));
694 $login->method('isGroupManager')->willReturnCallback(function ($gid) use ($g1) {
695 return $gid === null ||
$gid == $g1->getId();
697 $this->assertTrue($adh->canShow($login));
699 //groups managers cannot show members of the groups they do not own
700 $adh = $this->getMockBuilder('\Galette\Entity\Adherent')
701 ->setConstructorArgs([$this->zdb
])
702 ->onlyMethods(['getGroups'])
704 $adh->method('getGroups')->willReturn([$g2]);
705 $this->assertFalse($adh->canShow($login));
713 public static function nameCaseProvider(): array
722 'expected' => 'DOE John'
730 'expected' => 'DOÉÈ John'
735 'title' => new \Galette\Entity\
Title(\Galette\Entity\Title
::MR
),
738 'expected' => 'Mr. DOE John'
746 'expected' => 'DOE John (foo)'
754 'expected' => 'DOE John (42)'
759 'title' => new \Galette\Entity\
Title(\Galette\Entity\Title
::MR
),
762 'expected' => 'Mr. DOE John (foo, 42)'
768 * Test getNameWithCase
770 * @dataProvider nameCaseProvider
772 * @param string $name Name
773 * @param string $surname Surname
774 * @param \Galette\Entity\Title|false $title Title
775 * @param string|false $id ID
776 * @param string|false $nick Nick
777 * @param string $expected Expected result
781 public function testsGetNameWithCase(string $name, string $surname, $title, $id, $nick, string $expected)
785 \Galette\Entity\Adherent
::getNameWithCase(
796 * Change member active status
798 * @param bool $active Activation status
802 private function changeMemberActivation(bool $active): void
804 $check = $this->adh
->check(['activite_adh' => $active], [], []);
805 if (is_array($check)) {
808 $this->assertTrue($check);
809 $this->assertTrue($this->adh
->store());
810 $this->assertTrue($this->adh
->load($this->adh
->id
));
818 public function testGetDueStatus()
820 $now = new \
DateTime();
821 $member = new \Galette\Entity\
Adherent($this->zdb
);
822 $this->assertSame(\Galette\Entity\Contribution
::STATUS_UNKNOWN
, $member->getDueStatus());
824 $this->getMemberOne();
826 $this->assertTrue($this->adh
->isActive());
827 $this->assertSame(\Galette\Entity\Contribution
::STATUS_NEVER
, $this->adh
->getDueStatus());
829 //non-active members always have OLD due status
830 $this->changeMemberActivation(false);
831 $this->assertSame(\Galette\Entity\Contribution
::STATUS_OLD
, $this->adh
->getDueStatus());
832 $this->changeMemberActivation(true);
834 //create a close to be expired contribution
835 $due_date = clone $now;
836 $due_date->add(new \
DateInterval('P30D'));
837 $begin_date = clone $due_date;
838 $begin_date->add(new \
DateInterval('P1D'));
839 $begin_date->sub(new \
DateInterval('P1Y'));
841 $this->cleanContributions();
842 $this->createContrib([
843 'id_adh' => $this->adh
->id
,
844 'id_type_cotis' => 3,
845 'montant_cotis' => '111',
846 'type_paiement_cotis' => '6',
847 'info_cotis' => 'FAKER' . $this->seed
,
848 'date_fin_cotis' => $due_date->format('Y-m-d'),
849 'date_enreg' => $begin_date->format('Y-m-d'),
850 'date_debut_cotis' => $begin_date->format('Y-m-d')
853 //member is up-to-date, close to be expired
854 $this->assertTrue($this->adh
->load($this->adh
->id
));
855 $this->assertTrue($this->adh
->isActive());
856 $this->assertTrue($this->adh
->isUp2Date());
857 $this->assertSame(\Galette\Entity\Contribution
::STATUS_IMPENDING
, $this->adh
->getDueStatus());
859 //non-active members always have OLD due status
860 $this->changeMemberActivation(false);
861 $this->assertSame(\Galette\Entity\Contribution
::STATUS_OLD
, $this->adh
->getDueStatus());
862 $this->changeMemberActivation(true);
864 //create an expired contribution, 29 days ago
865 $due_date = clone $now;
866 $due_date->sub(new \
DateInterval('P29D'));
867 $begin_date = clone $due_date;
868 $begin_date->add(new \
DateInterval('P1D'));
869 $begin_date->sub(new \
DateInterval('P1Y'));
871 $this->cleanContributions();
872 $this->createContrib([
873 'id_adh' => $this->adh
->id
,
874 'id_type_cotis' => 3,
875 'montant_cotis' => '111',
876 'type_paiement_cotis' => '6',
877 'info_cotis' => 'FAKER' . $this->seed
,
878 'date_fin_cotis' => $due_date->format('Y-m-d'),
879 'date_enreg' => $begin_date->format('Y-m-d'),
880 'date_debut_cotis' => $begin_date->format('Y-m-d')
883 //member is late, but for less than 30 days, no reminder to send
884 $this->assertTrue($this->adh
->load($this->adh
->id
));
885 $this->assertTrue($this->adh
->isActive());
886 $this->assertFalse($this->adh
->isUp2Date());
887 $this->assertSame(\Galette\Entity\Contribution
::STATUS_LATE
, $this->adh
->getDueStatus());
889 //non-active members always have OLD due status
890 $this->changeMemberActivation(false);
891 $this->assertSame(\Galette\Entity\Contribution
::STATUS_OLD
, $this->adh
->getDueStatus());
892 $this->changeMemberActivation(true);
896 * Clean created contributions
900 private function cleanContributions(): void
902 $delete = $this->zdb
->delete(\Galette\Entity\Contribution
::TABLE
);
903 $delete->where(['info_cotis' => 'FAKER' . $this->seed
]);
904 $this->zdb
->execute($delete);