Skip to content

Commit 76d0861

Browse files
committed
AC-13535: Added unit test case
1 parent cd4561b commit 76d0861

File tree

1 file changed

+351
-0
lines changed

1 file changed

+351
-0
lines changed
Lines changed: 351 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,351 @@
1+
<?php
2+
/**
3+
* Copyright 2025 Adobe
4+
* All Rights Reserved.
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
namespace Magento\Customer\Test\Unit\Plugin;
10+
11+
use Magento\Customer\Api\CustomerRepositoryInterface;
12+
use Magento\Customer\Api\Data\CustomerInterface;
13+
use Magento\Customer\Plugin\ValidateDobOnSave;
14+
use Magento\Eav\Model\Config as EavConfig;
15+
use Magento\Eav\Model\Entity\Attribute\AbstractAttribute;
16+
use Magento\Framework\Exception\InputException;
17+
use Magento\Framework\Serialize\Serializer\Json as JsonSerializer;
18+
use PHPUnit\Framework\MockObject\MockObject;
19+
use PHPUnit\Framework\TestCase;
20+
21+
class ValidateDobOnSaveTest extends TestCase
22+
{
23+
/** @var EavConfig&MockObject */
24+
private $eavConfig;
25+
26+
/** @var JsonSerializer&MockObject */
27+
private $json;
28+
29+
/** @var CustomerRepositoryInterface&MockObject */
30+
private $repo;
31+
32+
/** @var ValidateDobOnSave */
33+
private $plugin;
34+
35+
protected function setUp(): void
36+
{
37+
$this->eavConfig = $this->createMock(EavConfig::class);
38+
$this->json = $this->createMock(JsonSerializer::class);
39+
$this->repo = $this->createMock(CustomerRepositoryInterface::class);
40+
41+
$this->plugin = new ValidateDobOnSave(
42+
$this->eavConfig,
43+
$this->json
44+
);
45+
}
46+
47+
public function testInvalidDobStringThrows(): void
48+
{
49+
$customer = $this->createCustomerMock('not-a-date');
50+
$this->mockAttributeRulesArray(['date_range_min' => '1980-01-01', 'date_range_max' => '2000-12-31']);
51+
52+
$called = false;
53+
$proceed = function (CustomerInterface $c, $hash = null) use (&$called) {
54+
$called = true;
55+
return $c;
56+
};
57+
58+
$this->expectException(InputException::class);
59+
$this->expectExceptionMessage('Date of Birth is invalid.');
60+
$this->plugin->aroundSave($this->repo, $proceed, $customer, null);
61+
62+
$this->assertFalse($called);
63+
}
64+
65+
public function testDobBeforeMinThrows(): void
66+
{
67+
$customer = $this->createCustomerMock('1979-12-31');
68+
$this->mockAttributeRulesArray(['date_range_min' => '1980-01-01', 'date_range_max' => '2000-12-31']);
69+
70+
$called = false;
71+
$proceed = function (CustomerInterface $c, $hash = null) use (&$called) {
72+
$called = true;
73+
return $c;
74+
};
75+
76+
$this->expectException(InputException::class);
77+
$this->expectExceptionMessage('on or after 1980-01-01');
78+
$this->plugin->aroundSave($this->repo, $proceed, $customer, null);
79+
80+
$this->assertFalse($called);
81+
}
82+
83+
public function testDobAfterMaxThrows(): void
84+
{
85+
$customer = $this->createCustomerMock('2001-01-01');
86+
$this->mockAttributeRulesArray(['date_range_min' => '1980-01-01', 'date_range_max' => '2000-12-31']);
87+
88+
$called = false;
89+
$proceed = function (CustomerInterface $c, $hash = null) use (&$called) {
90+
$called = true;
91+
return $c;
92+
};
93+
94+
$this->expectException(InputException::class);
95+
$this->expectExceptionMessage('on or before 2000-12-31');
96+
$this->plugin->aroundSave($this->repo, $proceed, $customer, null);
97+
98+
$this->assertFalse($called);
99+
}
100+
101+
public function testDobWithinRangeCallsProceedAndReturnsResult(): void
102+
{
103+
$customer = $this->createCustomerMock('1990-06-15');
104+
$this->mockAttributeRulesArray(['date_range_min' => '1980-01-01', 'date_range_max' => '2000-12-31']);
105+
106+
$result = $this->createMock(CustomerInterface::class);
107+
$called = false;
108+
$proceed = function (CustomerInterface $c, $hash = null) use (&$called, $result) {
109+
$called = true;
110+
return $result;
111+
};
112+
113+
$actual = $this->plugin->aroundSave($this->repo, $proceed, $customer, null);
114+
115+
$this->assertTrue($called);
116+
$this->assertSame($result, $actual);
117+
}
118+
119+
public function testEmptyDobSkipsValidationAndProceeds(): void
120+
{
121+
$customer = $this->createCustomerMock(null);
122+
$this->mockAttributeRulesArray(['date_range_min' => '1980-01-01', 'date_range_max' => '2000-12-31']);
123+
124+
$called = false;
125+
$proceed = function (CustomerInterface $c, $hash = null) use (&$called) {
126+
$called = true;
127+
return $c;
128+
};
129+
130+
$actual = $this->plugin->aroundSave($this->repo, $proceed, $customer, null);
131+
132+
$this->assertTrue($called);
133+
$this->assertSame($customer, $actual);
134+
}
135+
136+
public function testRulesAsJsonStringAreUnserialized(): void
137+
{
138+
$customer = $this->createCustomerMock('1979-12-31');
139+
$rules = ['date_range_min' => '1980-01-01', 'date_range_max' => '2000-12-31'];
140+
$json = json_encode($rules);
141+
142+
$attribute = $this->createAttributeMockForGetData('validate_rules', $json);
143+
$this->eavConfig->method('getAttribute')->with('customer', 'dob')->willReturn($attribute);
144+
$this->json->expects($this->once())->method('unserialize')->with($json)->willReturn($rules);
145+
146+
$called = false;
147+
$proceed = function (CustomerInterface $c, $hash = null) use (&$called) {
148+
$called = true;
149+
return $c;
150+
};
151+
152+
$this->expectException(InputException::class);
153+
$this->expectExceptionMessage('on or after 1980-01-01');
154+
$this->plugin->aroundSave($this->repo, $proceed, $customer, null);
155+
156+
$this->assertFalse($called);
157+
}
158+
159+
public function testMillisecondRulesBeforeMinThrows(): void
160+
{
161+
$customer = $this->createCustomerMock('1979-12-31');
162+
$minMs = (new \DateTimeImmutable('1980-01-01', new \DateTimeZone('UTC')))->getTimestamp() * 1000;
163+
$maxMs = (new \DateTimeImmutable('2000-12-31', new \DateTimeZone('UTC')))->getTimestamp() * 1000;
164+
165+
$this->mockAttributeRulesArray(['date_range_min' => $minMs, 'date_range_max' => $maxMs]);
166+
167+
$called = false;
168+
$proceed = function (CustomerInterface $c, $hash = null) use (&$called) {
169+
$called = true;
170+
return $c;
171+
};
172+
173+
$this->expectException(InputException::class);
174+
$this->expectExceptionMessage('on or after 1980-01-01');
175+
$this->plugin->aroundSave($this->repo, $proceed, $customer, null);
176+
177+
$this->assertFalse($called);
178+
}
179+
180+
public function testDobAsMillisecondTimestampThrowsAgainstStringRule(): void
181+
{
182+
$dobMs = (new \DateTimeImmutable('1979-12-31', new \DateTimeZone('UTC')))->getTimestamp() * 1000;
183+
$customer = $this->createCustomerMock($dobMs);
184+
$this->mockAttributeRulesArray(['date_range_min' => '1980-01-01', 'date_range_max' => '2000-12-31']);
185+
186+
$called = false;
187+
$proceed = function (CustomerInterface $c, $hash = null) use (&$called) {
188+
$called = true;
189+
return $c;
190+
};
191+
192+
$this->expectException(InputException::class);
193+
$this->expectExceptionMessage('on or after 1980-01-01');
194+
$this->plugin->aroundSave($this->repo, $proceed, $customer, null);
195+
196+
$this->assertFalse($called);
197+
}
198+
199+
/**
200+
* Return an attribute mock that provides array rules via getData('validate_rules').
201+
*
202+
* @param array $rules
203+
*/
204+
private function mockAttributeRulesArray(array $rules): void
205+
{
206+
$attribute = $this->createAttributeMockForGetData('validate_rules', $rules);
207+
$this->eavConfig->method('getAttribute')->with('customer', 'dob')->willReturn($attribute);
208+
$this->json->expects($this->never())->method('unserialize');
209+
}
210+
211+
/**
212+
* Create an attribute mock stubbing only getData($key).
213+
*
214+
* @param string $key
215+
* @param mixed $value
216+
* @return AbstractAttribute&MockObject
217+
*/
218+
private function createAttributeMockForGetData(string $key, $value)
219+
{
220+
$attribute = $this->getMockBuilder(AbstractAttribute::class)
221+
->disableOriginalConstructor()
222+
->onlyMethods(['getData'])
223+
->getMockForAbstractClass();
224+
225+
$attribute->method('getData')->with($key)->willReturn($value);
226+
return $attribute;
227+
}
228+
229+
/**
230+
* @return void
231+
* @throws InputException
232+
* @throws \Magento\Framework\Exception\LocalizedException
233+
* @throws \PHPUnit\Framework\MockObject\Exception
234+
*/
235+
public function testInvalidJsonRulesCaughtAndIgnored(): void
236+
{
237+
$customer = $this->createCustomerMock('1990-06-15');
238+
239+
$badJson = '{invalid json}';
240+
$attribute = $this->createAttributeMockForGetData('validate_rules', $badJson);
241+
$this->eavConfig->method('getAttribute')->with('customer', 'dob')->willReturn($attribute);
242+
243+
$this->json
244+
->expects($this->once())
245+
->method('unserialize')
246+
->with($badJson)
247+
->willThrowException(new \InvalidArgumentException('bad json'));
248+
249+
$result = $this->createMock(CustomerInterface::class);
250+
$called = false;
251+
$proceed = function (CustomerInterface $c, $hash = null) use (&$called, $result) {
252+
$called = true;
253+
return $result;
254+
};
255+
256+
$actual = $this->plugin->aroundSave($this->repo, $proceed, $customer, null);
257+
258+
$this->assertTrue($called, 'Proceed should be called when rules JSON is invalid (caught and ignored).');
259+
$this->assertSame($result, $actual);
260+
}
261+
262+
/**
263+
* @return void
264+
* @throws InputException
265+
* @throws \Magento\Framework\Exception\LocalizedException
266+
*/
267+
public function testFallbackToGetValidateRulesArrayIsUsed(): void
268+
{
269+
$customer = $this->createCustomerMock('1979-12-31');
270+
271+
$this->mockAttributeRulesViaGetValidateRules([
272+
'date_range_min' => '1980-01-01',
273+
'date_range_max' => '2000-12-31',
274+
]);
275+
276+
$called = false;
277+
$proceed = function (CustomerInterface $c, $hash = null) use (&$called) {
278+
$called = true;
279+
return $c;
280+
};
281+
282+
$this->expectException(InputException::class);
283+
$this->expectExceptionMessage('on or after 1980-01-01');
284+
$this->plugin->aroundSave($this->repo, $proceed, $customer, null);
285+
286+
$this->assertFalse($called);
287+
}
288+
289+
/**
290+
* Mock attribute so validate_rules is not an array/string, forcing fallback to getValidateRules().
291+
*
292+
* @param array $rules
293+
*/
294+
private function mockAttributeRulesViaGetValidateRules(array $rules): void
295+
{
296+
$attribute = $this->getMockBuilder(\Magento\Eav\Model\Entity\Attribute\AbstractAttribute::class)
297+
->disableOriginalConstructor()
298+
->onlyMethods(['getData'])
299+
->addMethods(['getValidateRules'])
300+
->getMockForAbstractClass();
301+
302+
// Force non-array rules to trigger the fallback
303+
$attribute->method('getData')->with('validate_rules')->willReturn(null);
304+
$attribute->method('getValidateRules')->willReturn($rules);
305+
306+
$this->eavConfig->method('getAttribute')->with('customer', 'dob')->willReturn($attribute);
307+
$this->json->expects($this->never())->method('unserialize');
308+
}
309+
310+
/**
311+
* @return void
312+
* @throws InputException
313+
* @throws \Magento\Framework\Exception\LocalizedException
314+
*/
315+
public function testDobZeroEpochInvalidThrows(): void
316+
{
317+
// Covers: return null; in parseAnyDate() for <= 0 timestamps
318+
$customer = $this->createCustomerMock(0);
319+
320+
// Any rules; they won't be reached because DOB is invalid first
321+
$this->mockAttributeRulesArray([
322+
'date_range_min' => '1900-01-01',
323+
'date_range_max' => '2100-01-01',
324+
]);
325+
326+
$called = false;
327+
$proceed = function (CustomerInterface $c, $hash = null) use (&$called) {
328+
$called = true;
329+
return $c;
330+
};
331+
332+
$this->expectException(InputException::class);
333+
$this->expectExceptionMessage('Date of Birth is invalid.');
334+
$this->plugin->aroundSave($this->repo, $proceed, $customer, null);
335+
336+
$this->assertFalse($called);
337+
}
338+
339+
/**
340+
* Create a customer mock with a specific DOB value.
341+
*
342+
* @param mixed $dob
343+
* @return CustomerInterface&MockObject
344+
*/
345+
private function createCustomerMock($dob)
346+
{
347+
$customer = $this->createMock(CustomerInterface::class);
348+
$customer->method('getDob')->willReturn($dob);
349+
return $customer;
350+
}
351+
}

0 commit comments

Comments
 (0)