Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like MemberTest often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use MemberTest, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
6 | class MemberTest extends FunctionalTest { |
||
7 | protected static $fixture_file = 'MemberTest.yml'; |
||
8 | |||
9 | protected $orig = array(); |
||
10 | protected $local = null; |
||
11 | |||
12 | protected $illegalExtensions = array( |
||
13 | 'Member' => array( |
||
14 | // TODO Coupling with modules, this should be resolved by automatically |
||
15 | // removing all applied extensions before a unit test |
||
16 | 'ForumRole', |
||
17 | 'OpenIDAuthenticatedRole' |
||
18 | ) |
||
19 | ); |
||
20 | |||
21 | public function __construct() { |
||
22 | parent::__construct(); |
||
|
|||
23 | |||
24 | //Setting the locale has to happen in the constructor (using the setUp and tearDown methods doesn't work) |
||
25 | //This is because the test relies on the yaml file being interpreted according to a particular date format |
||
26 | //and this setup occurs before the setUp method is run |
||
27 | $this->local = i18n::default_locale(); |
||
28 | i18n::set_default_locale('en_US'); |
||
29 | } |
||
30 | |||
31 | public function __destruct() { |
||
34 | |||
35 | public function setUp() { |
||
36 | parent::setUp(); |
||
37 | |||
38 | $this->orig['Member_unique_identifier_field'] = Member::config()->unique_identifier_field; |
||
39 | Member::config()->unique_identifier_field = 'Email'; |
||
40 | Member::set_password_validator(null); |
||
41 | } |
||
42 | |||
43 | public function tearDown() { |
||
47 | |||
48 | |||
49 | |||
50 | /** |
||
51 | * @expectedException ValidationException |
||
52 | */ |
||
53 | View Code Duplication | public function testWriteDoesntMergeNewRecordWithExistingMember() { |
|
54 | $m1 = new Member(); |
||
55 | $m1->Email = '[email protected]'; |
||
56 | $m1->write(); |
||
57 | |||
58 | $m2 = new Member(); |
||
59 | $m2->Email = '[email protected]'; |
||
60 | $m2->write(); |
||
61 | } |
||
62 | |||
63 | /** |
||
64 | * @expectedException ValidationException |
||
65 | */ |
||
66 | View Code Duplication | public function testWriteDoesntMergeExistingMemberOnIdentifierChange() { |
|
67 | $m1 = new Member(); |
||
68 | $m1->Email = '[email protected]'; |
||
69 | $m1->write(); |
||
70 | |||
71 | $m2 = new Member(); |
||
72 | $m2->Email = '[email protected]'; |
||
73 | $m2->write(); |
||
74 | |||
75 | $m2->Email = '[email protected]'; |
||
76 | $m2->write(); |
||
77 | } |
||
78 | |||
79 | public function testDefaultPasswordEncryptionOnMember() { |
||
80 | $memberWithPassword = new Member(); |
||
81 | $memberWithPassword->Password = 'mypassword'; |
||
82 | $memberWithPassword->write(); |
||
83 | $this->assertEquals( |
||
84 | $memberWithPassword->PasswordEncryption, |
||
85 | Security::config()->password_encryption_algorithm, |
||
86 | 'Password encryption is set for new member records on first write (with setting "Password")' |
||
87 | ); |
||
88 | |||
89 | $memberNoPassword = new Member(); |
||
90 | $memberNoPassword->write(); |
||
91 | $this->assertNull( |
||
92 | $memberNoPassword->PasswordEncryption, |
||
93 | 'Password encryption is not set for new member records on first write, when not setting a "Password")' |
||
94 | ); |
||
95 | } |
||
96 | |||
97 | public function testDefaultPasswordEncryptionDoesntChangeExistingMembers() { |
||
98 | $member = new Member(); |
||
99 | $member->Password = 'mypassword'; |
||
100 | $member->PasswordEncryption = 'sha1_v2.4'; |
||
101 | $member->write(); |
||
102 | |||
103 | $origAlgo = Security::config()->password_encryption_algorithm; |
||
104 | Security::config()->password_encryption_algorithm = 'none'; |
||
105 | |||
106 | $member->Password = 'mynewpassword'; |
||
107 | $member->write(); |
||
108 | |||
109 | $this->assertEquals( |
||
110 | $member->PasswordEncryption, |
||
111 | 'sha1_v2.4' |
||
112 | ); |
||
113 | $result = $member->checkPassword('mynewpassword'); |
||
114 | $this->assertTrue($result->valid()); |
||
115 | |||
116 | Security::config()->password_encryption_algorithm = $origAlgo; |
||
117 | } |
||
118 | |||
119 | public function testKeepsEncryptionOnEmptyPasswords() { |
||
120 | $member = new Member(); |
||
121 | $member->Password = 'mypassword'; |
||
122 | $member->PasswordEncryption = 'sha1_v2.4'; |
||
123 | $member->write(); |
||
124 | |||
125 | $member->Password = ''; |
||
126 | $member->write(); |
||
127 | |||
128 | $this->assertEquals( |
||
129 | $member->PasswordEncryption, |
||
130 | 'sha1_v2.4' |
||
131 | ); |
||
132 | $result = $member->checkPassword(''); |
||
133 | $this->assertTrue($result->valid()); |
||
134 | } |
||
135 | |||
136 | public function testSetPassword() { |
||
137 | $member = $this->objFromFixture('Member', 'test'); |
||
138 | $member->Password = "test1"; |
||
139 | $member->write(); |
||
140 | $result = $member->checkPassword('test1'); |
||
141 | $this->assertTrue($result->valid()); |
||
142 | } |
||
143 | |||
144 | /** |
||
145 | * Test that password changes are logged properly |
||
146 | */ |
||
147 | public function testPasswordChangeLogging() { |
||
148 | $member = $this->objFromFixture('Member', 'test'); |
||
149 | $this->assertNotNull($member); |
||
150 | $member->Password = "test1"; |
||
151 | $member->write(); |
||
152 | |||
153 | $member->Password = "test2"; |
||
154 | $member->write(); |
||
155 | |||
156 | $member->Password = "test3"; |
||
157 | $member->write(); |
||
158 | |||
159 | $passwords = DataObject::get("MemberPassword", "\"MemberID\" = $member->ID", "\"Created\" DESC, \"ID\" DESC") |
||
160 | ->getIterator(); |
||
161 | $this->assertNotNull($passwords); |
||
162 | $passwords->rewind(); |
||
163 | $this->assertTrue($passwords->current()->checkPassword('test3'), "Password test3 not found in MemberRecord"); |
||
164 | |||
165 | $passwords->next(); |
||
166 | $this->assertTrue($passwords->current()->checkPassword('test2'), "Password test2 not found in MemberRecord"); |
||
167 | |||
168 | $passwords->next(); |
||
169 | $this->assertTrue($passwords->current()->checkPassword('test1'), "Password test1 not found in MemberRecord"); |
||
170 | |||
171 | $passwords->next(); |
||
172 | $this->assertInstanceOf('DataObject', $passwords->current()); |
||
173 | $this->assertTrue($passwords->current()->checkPassword('1nitialPassword'), |
||
174 | "Password 1nitialPassword not found in MemberRecord"); |
||
175 | |||
176 | //check we don't retain orphaned records when a member is deleted |
||
177 | $member->delete(); |
||
178 | |||
179 | $passwords = MemberPassword::get()->filter('MemberID', $member->OldID); |
||
180 | |||
181 | $this->assertCount(0, $passwords); |
||
182 | } |
||
183 | |||
184 | /** |
||
185 | * Test that changed passwords will send an email |
||
186 | */ |
||
187 | public function testChangedPasswordEmaling() { |
||
188 | $this->clearEmails(); |
||
189 | |||
190 | $member = $this->objFromFixture('Member', 'test'); |
||
191 | $this->assertNotNull($member); |
||
192 | $valid = $member->changePassword('32asDF##$$%%'); |
||
193 | $this->assertTrue($valid->valid()); |
||
194 | /* |
||
195 | $this->assertEmailSent("[email protected]", null, "/changed password/", |
||
196 | '/sam@silverstripe\.com.*32asDF##\$\$%%/'); |
||
197 | */ |
||
198 | } |
||
199 | |||
200 | /** |
||
201 | * Test that passwords validate against NZ e-government guidelines |
||
202 | * - don't allow the use of the last 6 passwords |
||
203 | * - require at least 3 of lowercase, uppercase, digits and punctuation |
||
204 | * - at least 7 characters long |
||
205 | */ |
||
206 | public function testValidatePassword() { |
||
207 | $member = $this->objFromFixture('Member', 'test'); |
||
208 | $this->assertNotNull($member); |
||
209 | |||
210 | Member::set_password_validator(new MemberTest_PasswordValidator()); |
||
211 | |||
212 | // BAD PASSWORDS |
||
213 | |||
214 | $valid = $member->changePassword('shorty'); |
||
215 | $this->assertFalse($valid->valid()); |
||
216 | $this->assertContains("TOO_SHORT", $valid->codeList()); |
||
217 | |||
218 | $valid = $member->changePassword('longone'); |
||
219 | $this->assertNotContains("TOO_SHORT", $valid->codeList()); |
||
220 | $this->assertContains("LOW_CHARACTER_STRENGTH", $valid->codeList()); |
||
221 | $this->assertFalse($valid->valid()); |
||
222 | |||
223 | $valid = $member->changePassword('w1thNumb3rs'); |
||
224 | $this->assertNotContains("LOW_CHARACTER_STRENGTH", $valid->codeList()); |
||
225 | $this->assertTrue($valid->valid()); |
||
226 | |||
227 | // Clear out the MemberPassword table to ensure that the system functions properly in that situation |
||
228 | DB::query("DELETE FROM \"MemberPassword\""); |
||
229 | |||
230 | // GOOD PASSWORDS |
||
231 | |||
232 | $valid = $member->changePassword('withSym###Ls'); |
||
233 | $this->assertNotContains("LOW_CHARACTER_STRENGTH", $valid->codeList()); |
||
234 | $this->assertTrue($valid->valid()); |
||
235 | |||
236 | $valid = $member->changePassword('withSym###Ls2'); |
||
237 | $this->assertTrue($valid->valid()); |
||
238 | |||
239 | $valid = $member->changePassword('withSym###Ls3'); |
||
240 | $this->assertTrue($valid->valid()); |
||
241 | |||
242 | $valid = $member->changePassword('withSym###Ls4'); |
||
243 | $this->assertTrue($valid->valid()); |
||
244 | |||
245 | $valid = $member->changePassword('withSym###Ls5'); |
||
246 | $this->assertTrue($valid->valid()); |
||
247 | |||
248 | $valid = $member->changePassword('withSym###Ls6'); |
||
249 | $this->assertTrue($valid->valid()); |
||
250 | |||
251 | $valid = $member->changePassword('withSym###Ls7'); |
||
252 | $this->assertTrue($valid->valid()); |
||
253 | |||
254 | // CAN'T USE PASSWORDS 2-7, but I can use pasword 1 |
||
255 | |||
256 | $valid = $member->changePassword('withSym###Ls2'); |
||
257 | $this->assertFalse($valid->valid()); |
||
258 | $this->assertContains("PREVIOUS_PASSWORD", $valid->codeList()); |
||
259 | |||
260 | $valid = $member->changePassword('withSym###Ls5'); |
||
261 | $this->assertFalse($valid->valid()); |
||
262 | $this->assertContains("PREVIOUS_PASSWORD", $valid->codeList()); |
||
263 | |||
264 | $valid = $member->changePassword('withSym###Ls7'); |
||
265 | $this->assertFalse($valid->valid()); |
||
266 | $this->assertContains("PREVIOUS_PASSWORD", $valid->codeList()); |
||
267 | |||
268 | $valid = $member->changePassword('withSym###Ls'); |
||
269 | $this->assertTrue($valid->valid()); |
||
270 | |||
271 | // HAVING DONE THAT, PASSWORD 2 is now available from the list |
||
272 | |||
273 | $valid = $member->changePassword('withSym###Ls2'); |
||
274 | $this->assertTrue($valid->valid()); |
||
275 | |||
276 | $valid = $member->changePassword('withSym###Ls3'); |
||
277 | $this->assertTrue($valid->valid()); |
||
278 | |||
279 | $valid = $member->changePassword('withSym###Ls4'); |
||
280 | $this->assertTrue($valid->valid()); |
||
281 | |||
282 | Member::set_password_validator(null); |
||
283 | } |
||
284 | |||
285 | /** |
||
286 | * Test that the PasswordExpiry date is set when passwords are changed |
||
287 | */ |
||
288 | public function testPasswordExpirySetting() { |
||
289 | Member::config()->password_expiry_days = 90; |
||
290 | |||
291 | $member = $this->objFromFixture('Member', 'test'); |
||
292 | $this->assertNotNull($member); |
||
293 | $valid = $member->changePassword("Xx?1234234"); |
||
294 | $this->assertTrue($valid->valid()); |
||
295 | |||
296 | $expiryDate = date('Y-m-d', time() + 90*86400); |
||
297 | $this->assertEquals($expiryDate, $member->PasswordExpiry); |
||
298 | |||
299 | Member::config()->password_expiry_days = null; |
||
300 | $valid = $member->changePassword("Xx?1234235"); |
||
301 | $this->assertTrue($valid->valid()); |
||
302 | |||
303 | $this->assertNull($member->PasswordExpiry); |
||
304 | } |
||
305 | |||
306 | public function testIsPasswordExpired() { |
||
328 | |||
329 | public function testMemberWithNoDateFormatFallsbackToGlobalLocaleDefaultFormat() { |
||
330 | Config::inst()->update('i18n', 'date_format', 'yyyy-MM-dd'); |
||
331 | Config::inst()->update('i18n', 'time_format', 'H:mm'); |
||
332 | $member = $this->objFromFixture('Member', 'noformatmember'); |
||
333 | $this->assertEquals('yyyy-MM-dd', $member->DateFormat); |
||
334 | $this->assertEquals('H:mm', $member->TimeFormat); |
||
335 | } |
||
336 | |||
337 | public function testInGroups() { |
||
338 | $staffmember = $this->objFromFixture('Member', 'staffmember'); |
||
339 | $managementmember = $this->objFromFixture('Member', 'managementmember'); |
||
340 | $accountingmember = $this->objFromFixture('Member', 'accountingmember'); |
||
341 | $ceomember = $this->objFromFixture('Member', 'ceomember'); |
||
342 | |||
343 | $staffgroup = $this->objFromFixture('Group', 'staffgroup'); |
||
344 | $managementgroup = $this->objFromFixture('Group', 'managementgroup'); |
||
345 | $accountinggroup = $this->objFromFixture('Group', 'accountinggroup'); |
||
346 | $ceogroup = $this->objFromFixture('Group', 'ceogroup'); |
||
347 | |||
348 | $this->assertTrue( |
||
349 | $staffmember->inGroups(array($staffgroup, $managementgroup)), |
||
350 | 'inGroups() succeeds if a membership is detected on one of many passed groups' |
||
351 | ); |
||
352 | $this->assertFalse( |
||
353 | $staffmember->inGroups(array($ceogroup, $managementgroup)), |
||
354 | 'inGroups() fails if a membership is detected on none of the passed groups' |
||
355 | ); |
||
356 | $this->assertFalse( |
||
357 | $ceomember->inGroups(array($staffgroup, $managementgroup), true), |
||
358 | 'inGroups() fails if no direct membership is detected on any of the passed groups (in strict mode)' |
||
359 | ); |
||
360 | } |
||
361 | |||
362 | public function testAddToGroupByCode() { |
||
363 | $grouplessMember = $this->objFromFixture('Member', 'grouplessmember'); |
||
364 | $memberlessGroup = $this->objFromFixture('Group','memberlessgroup'); |
||
365 | |||
366 | $this->assertFalse($grouplessMember->Groups()->exists()); |
||
367 | $this->assertFalse($memberlessGroup->Members()->exists()); |
||
368 | |||
369 | $grouplessMember->addToGroupByCode('memberless'); |
||
370 | |||
371 | $this->assertEquals($memberlessGroup->Members()->Count(), 1); |
||
372 | $this->assertEquals($grouplessMember->Groups()->Count(), 1); |
||
373 | |||
374 | $grouplessMember->addToGroupByCode('somegroupthatwouldneverexist', 'New Group'); |
||
375 | $this->assertEquals($grouplessMember->Groups()->Count(), 2); |
||
376 | |||
377 | $group = DataObject::get_one('Group', array( |
||
378 | '"Group"."Code"' => 'somegroupthatwouldneverexist' |
||
379 | )); |
||
380 | $this->assertNotNull($group); |
||
381 | $this->assertEquals($group->Code, 'somegroupthatwouldneverexist'); |
||
382 | $this->assertEquals($group->Title, 'New Group'); |
||
383 | |||
384 | } |
||
385 | |||
386 | public function testRemoveFromGroupByCode() { |
||
387 | $grouplessMember = $this->objFromFixture('Member', 'grouplessmember'); |
||
388 | $memberlessGroup = $this->objFromFixture('Group','memberlessgroup'); |
||
389 | |||
390 | $this->assertFalse($grouplessMember->Groups()->exists()); |
||
391 | $this->assertFalse($memberlessGroup->Members()->exists()); |
||
392 | |||
393 | $grouplessMember->addToGroupByCode('memberless'); |
||
394 | |||
395 | $this->assertEquals($memberlessGroup->Members()->Count(), 1); |
||
396 | $this->assertEquals($grouplessMember->Groups()->Count(), 1); |
||
397 | |||
398 | $grouplessMember->addToGroupByCode('somegroupthatwouldneverexist', 'New Group'); |
||
399 | $this->assertEquals($grouplessMember->Groups()->Count(), 2); |
||
400 | |||
401 | $group = DataObject::get_one('Group', "\"Code\" = 'somegroupthatwouldneverexist'"); |
||
402 | $this->assertNotNull($group); |
||
403 | $this->assertEquals($group->Code, 'somegroupthatwouldneverexist'); |
||
404 | $this->assertEquals($group->Title, 'New Group'); |
||
405 | |||
406 | $grouplessMember->removeFromGroupByCode('memberless'); |
||
407 | $this->assertEquals($memberlessGroup->Members()->Count(), 0); |
||
408 | $this->assertEquals($grouplessMember->Groups()->Count(), 1); |
||
409 | |||
410 | $grouplessMember->removeFromGroupByCode('somegroupthatwouldneverexist'); |
||
411 | $this->assertEquals($grouplessMember->Groups()->Count(), 0); |
||
412 | } |
||
413 | |||
414 | public function testInGroup() { |
||
415 | $staffmember = $this->objFromFixture('Member', 'staffmember'); |
||
416 | $managementmember = $this->objFromFixture('Member', 'managementmember'); |
||
417 | $accountingmember = $this->objFromFixture('Member', 'accountingmember'); |
||
418 | $ceomember = $this->objFromFixture('Member', 'ceomember'); |
||
419 | |||
420 | $staffgroup = $this->objFromFixture('Group', 'staffgroup'); |
||
421 | $managementgroup = $this->objFromFixture('Group', 'managementgroup'); |
||
422 | $accountinggroup = $this->objFromFixture('Group', 'accountinggroup'); |
||
423 | $ceogroup = $this->objFromFixture('Group', 'ceogroup'); |
||
424 | |||
425 | $this->assertTrue( |
||
426 | $staffmember->inGroup($staffgroup), |
||
427 | 'Direct group membership is detected' |
||
428 | ); |
||
429 | $this->assertTrue( |
||
430 | $managementmember->inGroup($staffgroup), |
||
431 | 'Users of child group are members of a direct parent group (if not in strict mode)' |
||
432 | ); |
||
433 | $this->assertTrue( |
||
434 | $accountingmember->inGroup($staffgroup), |
||
435 | 'Users of child group are members of a direct parent group (if not in strict mode)' |
||
436 | ); |
||
437 | $this->assertTrue( |
||
438 | $ceomember->inGroup($staffgroup), |
||
439 | 'Users of indirect grandchild group are members of a parent group (if not in strict mode)' |
||
440 | ); |
||
441 | $this->assertTrue( |
||
442 | $ceomember->inGroup($ceogroup, true), |
||
443 | 'Direct group membership is dected (if in strict mode)' |
||
444 | ); |
||
445 | $this->assertFalse( |
||
446 | $ceomember->inGroup($staffgroup, true), |
||
447 | 'Users of child group are not members of a direct parent group (if in strict mode)' |
||
448 | ); |
||
449 | $this->assertFalse( |
||
450 | $staffmember->inGroup($managementgroup), |
||
451 | 'Users of parent group are not members of a direct child group' |
||
452 | ); |
||
453 | $this->assertFalse( |
||
454 | $staffmember->inGroup($ceogroup), |
||
455 | 'Users of parent group are not members of an indirect grandchild group' |
||
456 | ); |
||
457 | $this->assertFalse( |
||
458 | $accountingmember->inGroup($managementgroup), |
||
459 | 'Users of group are not members of any siblings' |
||
460 | ); |
||
461 | $this->assertFalse( |
||
462 | $staffmember->inGroup('does-not-exist'), |
||
463 | 'Non-existant group returns false' |
||
464 | ); |
||
465 | } |
||
466 | |||
467 | /** |
||
468 | * Tests that the user is able to view their own record, and in turn, they can |
||
469 | * edit and delete their own record too. |
||
470 | */ |
||
471 | public function testCanManipulateOwnRecord() { |
||
472 | $extensions = $this->removeExtensions(Object::get_extensions('Member')); |
||
473 | $member = $this->objFromFixture('Member', 'test'); |
||
474 | $member2 = $this->objFromFixture('Member', 'staffmember'); |
||
475 | |||
476 | $this->session()->inst_set('loggedInAs', null); |
||
477 | |||
478 | /* Not logged in, you can't view, delete or edit the record */ |
||
479 | $this->assertFalse($member->canView()); |
||
480 | $this->assertFalse($member->canDelete()); |
||
481 | $this->assertFalse($member->canEdit()); |
||
482 | |||
483 | /* Logged in users can edit their own record */ |
||
484 | $this->session()->inst_set('loggedInAs', $member->ID); |
||
485 | $this->assertTrue($member->canView()); |
||
486 | $this->assertFalse($member->canDelete()); |
||
487 | $this->assertTrue($member->canEdit()); |
||
488 | |||
489 | /* Other uses cannot view, delete or edit others records */ |
||
490 | $this->session()->inst_set('loggedInAs', $member2->ID); |
||
491 | $this->assertFalse($member->canView()); |
||
492 | $this->assertFalse($member->canDelete()); |
||
493 | $this->assertFalse($member->canEdit()); |
||
494 | |||
495 | $this->addExtensions($extensions); |
||
496 | $this->session()->inst_set('loggedInAs', null); |
||
497 | } |
||
498 | |||
499 | public function testAuthorisedMembersCanManipulateOthersRecords() { |
||
500 | $extensions = $this->removeExtensions(Object::get_extensions('Member')); |
||
501 | $member = $this->objFromFixture('Member', 'test'); |
||
502 | $member2 = $this->objFromFixture('Member', 'staffmember'); |
||
503 | |||
504 | /* Group members with SecurityAdmin permissions can manipulate other records */ |
||
505 | $this->session()->inst_set('loggedInAs', $member->ID); |
||
506 | $this->assertTrue($member2->canView()); |
||
507 | $this->assertTrue($member2->canDelete()); |
||
508 | $this->assertTrue($member2->canEdit()); |
||
509 | |||
510 | $this->addExtensions($extensions); |
||
511 | $this->session()->inst_set('loggedInAs', null); |
||
512 | } |
||
513 | |||
514 | public function testExtendedCan() { |
||
515 | $extensions = $this->removeExtensions(Object::get_extensions('Member')); |
||
516 | $member = $this->objFromFixture('Member', 'test'); |
||
517 | |||
518 | /* Normal behaviour is that you can't view a member unless canView() on an extension returns true */ |
||
519 | $this->assertFalse($member->canView()); |
||
520 | $this->assertFalse($member->canDelete()); |
||
521 | $this->assertFalse($member->canEdit()); |
||
522 | |||
523 | /* Apply a extension that allows viewing in any case (most likely the case for member profiles) */ |
||
524 | Member::add_extension('MemberTest_ViewingAllowedExtension'); |
||
525 | $member2 = $this->objFromFixture('Member', 'staffmember'); |
||
526 | |||
527 | $this->assertTrue($member2->canView()); |
||
528 | $this->assertFalse($member2->canDelete()); |
||
529 | $this->assertFalse($member2->canEdit()); |
||
530 | |||
531 | /* Apply a extension that denies viewing of the Member */ |
||
532 | Member::remove_extension('MemberTest_ViewingAllowedExtension'); |
||
533 | Member::add_extension('MemberTest_ViewingDeniedExtension'); |
||
534 | $member3 = $this->objFromFixture('Member', 'managementmember'); |
||
535 | |||
536 | $this->assertFalse($member3->canView()); |
||
537 | $this->assertFalse($member3->canDelete()); |
||
538 | $this->assertFalse($member3->canEdit()); |
||
539 | |||
540 | /* Apply a extension that allows viewing and editing but denies deletion */ |
||
541 | Member::remove_extension('MemberTest_ViewingDeniedExtension'); |
||
542 | Member::add_extension('MemberTest_EditingAllowedDeletingDeniedExtension'); |
||
543 | $member4 = $this->objFromFixture('Member', 'accountingmember'); |
||
544 | |||
545 | $this->assertTrue($member4->canView()); |
||
546 | $this->assertFalse($member4->canDelete()); |
||
547 | $this->assertTrue($member4->canEdit()); |
||
548 | |||
549 | Member::remove_extension('MemberTest_EditingAllowedDeletingDeniedExtension'); |
||
550 | $this->addExtensions($extensions); |
||
551 | } |
||
552 | |||
553 | /** |
||
554 | * Tests for {@link Member::getName()} and {@link Member::setName()} |
||
555 | */ |
||
556 | public function testName() { |
||
557 | $member = $this->objFromFixture('Member', 'test'); |
||
558 | $member->setName('Test Some User'); |
||
559 | $this->assertEquals('Test Some User', $member->getName()); |
||
560 | $member->setName('Test'); |
||
561 | $this->assertEquals('Test', $member->getName()); |
||
562 | $member->FirstName = 'Test'; |
||
563 | $member->Surname = ''; |
||
564 | $this->assertEquals('Test', $member->getName()); |
||
565 | } |
||
566 | |||
567 | public function testMembersWithSecurityAdminAccessCantEditAdminsUnlessTheyreAdminsThemselves() { |
||
568 | $adminMember = $this->objFromFixture('Member', 'admin'); |
||
569 | $otherAdminMember = $this->objFromFixture('Member', 'other-admin'); |
||
570 | $securityAdminMember = $this->objFromFixture('Member', 'test'); |
||
571 | $ceoMember = $this->objFromFixture('Member', 'ceomember'); |
||
572 | |||
573 | // Careful: Don't read as english language. |
||
574 | // More precisely this should read canBeEditedBy() |
||
575 | |||
576 | $this->assertTrue($adminMember->canEdit($adminMember), 'Admins can edit themselves'); |
||
577 | $this->assertTrue($otherAdminMember->canEdit($adminMember), 'Admins can edit other admins'); |
||
578 | $this->assertTrue($securityAdminMember->canEdit($adminMember), 'Admins can edit other members'); |
||
579 | |||
580 | $this->assertTrue($securityAdminMember->canEdit($securityAdminMember), 'Security-Admins can edit themselves'); |
||
581 | $this->assertFalse($adminMember->canEdit($securityAdminMember), 'Security-Admins can not edit other admins'); |
||
582 | $this->assertTrue($ceoMember->canEdit($securityAdminMember), 'Security-Admins can edit other members'); |
||
583 | } |
||
584 | |||
585 | public function testOnChangeGroups() { |
||
586 | $staffGroup = $this->objFromFixture('Group', 'staffgroup'); |
||
587 | $staffMember = $this->objFromFixture('Member', 'staffmember'); |
||
588 | $adminMember = $this->objFromFixture('Member', 'admin'); |
||
589 | $newAdminGroup = new Group(array('Title' => 'newadmin')); |
||
590 | $newAdminGroup->write(); |
||
591 | Permission::grant($newAdminGroup->ID, 'ADMIN'); |
||
592 | $newOtherGroup = new Group(array('Title' => 'othergroup')); |
||
593 | $newOtherGroup->write(); |
||
594 | |||
595 | $this->assertTrue( |
||
596 | $staffMember->onChangeGroups(array($staffGroup->ID)), |
||
597 | 'Adding existing non-admin group relation is allowed for non-admin members' |
||
598 | ); |
||
599 | $this->assertTrue( |
||
600 | $staffMember->onChangeGroups(array($newOtherGroup->ID)), |
||
601 | 'Adding new non-admin group relation is allowed for non-admin members' |
||
602 | ); |
||
603 | $this->assertFalse( |
||
604 | $staffMember->onChangeGroups(array($newAdminGroup->ID)), |
||
605 | 'Adding new admin group relation is not allowed for non-admin members' |
||
606 | ); |
||
607 | |||
608 | $this->session()->inst_set('loggedInAs', $adminMember->ID); |
||
609 | $this->assertTrue( |
||
610 | $staffMember->onChangeGroups(array($newAdminGroup->ID)), |
||
611 | 'Adding new admin group relation is allowed for normal users, when granter is logged in as admin' |
||
612 | ); |
||
613 | $this->session()->inst_set('loggedInAs', null); |
||
614 | |||
615 | $this->assertTrue( |
||
616 | $adminMember->onChangeGroups(array($newAdminGroup->ID)), |
||
617 | 'Adding new admin group relation is allowed for admin members' |
||
618 | ); |
||
619 | } |
||
620 | |||
621 | /** |
||
622 | * Test Member_GroupSet::add |
||
623 | */ |
||
624 | public function testOnChangeGroupsByAdd() { |
||
625 | $staffMember = $this->objFromFixture('Member', 'staffmember'); |
||
626 | $adminMember = $this->objFromFixture('Member', 'admin'); |
||
627 | |||
628 | // Setup new admin group |
||
629 | $newAdminGroup = new Group(array('Title' => 'newadmin')); |
||
630 | $newAdminGroup->write(); |
||
631 | Permission::grant($newAdminGroup->ID, 'ADMIN'); |
||
632 | |||
633 | // Setup non-admin group |
||
634 | $newOtherGroup = new Group(array('Title' => 'othergroup')); |
||
635 | $newOtherGroup->write(); |
||
636 | |||
637 | // Test staff can be added to other group |
||
638 | $this->assertFalse($staffMember->inGroup($newOtherGroup)); |
||
639 | $staffMember->Groups()->add($newOtherGroup); |
||
640 | $this->assertTrue( |
||
641 | $staffMember->inGroup($newOtherGroup), |
||
642 | 'Adding new non-admin group relation is allowed for non-admin members' |
||
643 | ); |
||
644 | |||
645 | // Test staff member can't be added to admin groups |
||
646 | $this->assertFalse($staffMember->inGroup($newAdminGroup)); |
||
647 | $staffMember->Groups()->add($newAdminGroup); |
||
648 | $this->assertFalse( |
||
649 | $staffMember->inGroup($newAdminGroup), |
||
650 | 'Adding new admin group relation is not allowed for non-admin members' |
||
651 | ); |
||
652 | |||
653 | // Test staff member can be added to admin group by admins |
||
654 | $this->logInAs($adminMember); |
||
655 | $staffMember->Groups()->add($newAdminGroup); |
||
656 | $this->assertTrue( |
||
657 | $staffMember->inGroup($newAdminGroup), |
||
658 | 'Adding new admin group relation is allowed for normal users, when granter is logged in as admin' |
||
659 | ); |
||
660 | |||
661 | // Test staff member can be added if they are already admin |
||
662 | $this->session()->inst_set('loggedInAs', null); |
||
663 | $this->assertFalse($adminMember->inGroup($newAdminGroup)); |
||
664 | $adminMember->Groups()->add($newAdminGroup); |
||
665 | $this->assertTrue( |
||
666 | $adminMember->inGroup($newAdminGroup), |
||
667 | 'Adding new admin group relation is allowed for admin members' |
||
668 | ); |
||
669 | } |
||
670 | |||
671 | /** |
||
672 | * Test Member_GroupSet::add |
||
673 | */ |
||
674 | public function testOnChangeGroupsBySetIDList() { |
||
675 | $staffMember = $this->objFromFixture('Member', 'staffmember'); |
||
676 | |||
677 | // Setup new admin group |
||
678 | $newAdminGroup = new Group(array('Title' => 'newadmin')); |
||
679 | $newAdminGroup->write(); |
||
680 | Permission::grant($newAdminGroup->ID, 'ADMIN'); |
||
681 | |||
682 | // Test staff member can't be added to admin groups |
||
683 | $this->assertFalse($staffMember->inGroup($newAdminGroup)); |
||
684 | $staffMember->Groups()->setByIDList(array($newAdminGroup->ID)); |
||
685 | $this->assertFalse( |
||
686 | $staffMember->inGroup($newAdminGroup), |
||
687 | 'Adding new admin group relation is not allowed for non-admin members' |
||
688 | ); |
||
689 | } |
||
690 | |||
691 | /** |
||
692 | * Test that extensions using updateCMSFields() are applied correctly |
||
693 | */ |
||
694 | public function testUpdateCMSFields() { |
||
695 | Member::add_extension('MemberTest_FieldsExtension'); |
||
696 | |||
697 | $member = singleton('Member'); |
||
698 | $fields = $member->getCMSFields(); |
||
699 | |||
700 | $this->assertNotNull($fields->dataFieldByName('Email'), 'Scaffolded fields are retained'); |
||
701 | $this->assertNull($fields->dataFieldByName('Salt'), 'Field modifications run correctly'); |
||
702 | $this->assertNotNull($fields->dataFieldByName('TestMemberField'), 'Extension is applied correctly'); |
||
703 | |||
704 | Member::remove_extension('MemberTest_FieldsExtension'); |
||
705 | } |
||
706 | |||
707 | /** |
||
708 | * Test that all members are returned |
||
709 | */ |
||
710 | public function testMap_in_groupsReturnsAll() { |
||
714 | |||
715 | /** |
||
716 | * Test that only admin members are returned |
||
717 | */ |
||
718 | public function testMap_in_groupsReturnsAdmins() { |
||
719 | $adminID = $this->objFromFixture('Group', 'admingroup')->ID; |
||
720 | $members = Member::map_in_groups($adminID); |
||
721 | |||
722 | $admin = $this->objFromFixture('Member', 'admin'); |
||
723 | $otherAdmin = $this->objFromFixture('Member', 'other-admin'); |
||
724 | |||
725 | $this->assertTrue(in_array($admin->getTitle(), $members), |
||
726 | $admin->getTitle().' should be in the returned list.'); |
||
727 | $this->assertTrue(in_array($otherAdmin->getTitle(), $members), |
||
728 | $otherAdmin->getTitle().' should be in the returned list.'); |
||
729 | $this->assertEquals(2, count($members), 'There should be 2 members from the admin group'); |
||
730 | } |
||
731 | |||
732 | /** |
||
733 | * Add the given array of member extensions as class names. |
||
734 | * This is useful for re-adding extensions after being removed |
||
735 | * in a test case to produce an unbiased test. |
||
736 | * |
||
737 | * @param array $extensions |
||
738 | * @return array The added extensions |
||
739 | */ |
||
740 | protected function addExtensions($extensions) { |
||
741 | if($extensions) foreach($extensions as $extension) { |
||
746 | |||
747 | /** |
||
748 | * Remove given extensions from Member. This is useful for |
||
749 | * removing extensions that could produce a biased |
||
750 | * test result, as some extensions applied by project |
||
751 | * code or modules can do this. |
||
752 | * |
||
753 | * @param array $extensions |
||
754 | * @return array The removed extensions |
||
755 | */ |
||
756 | protected function removeExtensions($extensions) { |
||
762 | |||
763 | public function testGenerateAutologinTokenAndStoreHash() { |
||
774 | |||
775 | public function testValidateAutoLoginToken() { |
||
791 | |||
792 | public function testCanDelete() { |
||
819 | |||
820 | public function testFailedLoginCount() { |
||
844 | |||
845 | public function testMemberValidator() |
||
910 | |||
911 | public function testMemberValidatorWithExtensions() |
||
968 | |||
969 | public function testCustomMemberValidator() |
||
1017 | |||
1018 | } |
||
1019 | |||
1141 |
Let’s take a look at an example:
In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.
Available Fixes
Change the type-hint for the parameter:
Add an additional type-check:
Add the method to the parent class: