DomainValidatorTest::validDomainIdnProvider()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 19
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 1
eloc 15
c 3
b 0
f 0
nc 1
nop 0
dl 0
loc 19
rs 9.7666
1
<?php
2
3
/**
4
 * @noinspection HttpUrlsUsage
5
 */
6
7
namespace kdn\yii2\validators;
8
9
use kdn\yii2\validators\mocks\ModelMock;
10
use stdClass;
11
use Yii;
12
use yii\base\InvalidConfigException;
13
14
/**
15
 * Class DomainValidatorTest.
16
 * @package kdn\yii2\validators
17
 * @covers \kdn\yii2\validators\DomainValidator::init
18
 */
19
class DomainValidatorTest extends TestCase
20
{
21
    const NONEXISTENT_DOMAIN = 'nonexistent-subdomain.example.com';
22
23
    /**
24
     * @var DomainValidator
25
     */
26
    protected $validator;
27
28
    /**
29
     * @before
30
     */
31
    protected function prepare()
32
    {
33
        parent::prepare();
34
        $this->validator = new DomainValidator(['labelNumberMin' => 1]);
35
    }
36
37
    public static function validDomainProvider()
38
    {
39
        return [
40
            'one domain name label' => ['localhost'],
41
            'two domain name labels' => ['example.com'],
42
            'domain name with trailing dot' => ['example.com.'],
43
            '127 levels, 253 characters and trailing dot' => [
44
                'a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.' .
45
                'a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.' .
46
                'a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.' .
47
                'a.a.a.a.',
48
            ],
49
            'domain name labels with 63 characters' => [
50
                'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.' .
51
                'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.' .
52
                'example.com',
53
            ],
54
            'domain name with various symbols' => ['1.a-B.c2'],
55
            'Punycode, mixed domain name' => ['xn--e1afmkfd.test.xn--80akhbyknj4f'],
56
        ];
57
    }
58
59
    public static function validDomainInUrlProvider()
60
    {
61
        return [
62
            'HTTP, one domain name label' => ['http://localhost'],
63
            'HTTP, two domain name labels' => ['http://example.com/index.html'],
64
            'FTP, domain name with trailing dot' => ['ftp://example.com./img/dir/'],
65
            'HTTPS, 127 levels, 253 characters and trailing dot' => [
66
                'https://a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.' .
67
                'a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.' .
68
                'a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.' .
69
                'a.a.a.a./index.html',
70
            ],
71
            'missing scheme, domain name labels with 63 characters' => [
72
                '//aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.' .
73
                'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.' .
74
                'example.com',
75
            ],
76
            'complex URL, domain name with various symbols' => [
77
                'http://username:[email protected]:9090/path?a=b&c=d#anchor',
78
            ],
79
            'Punycode, FTP, mixed domain name' => ['ftp://xn--e1afmkfd.test.xn--80akhbyknj4f/img/dir/'],
80
        ];
81
    }
82
83
    public static function validDomainIdnProvider()
84
    {
85
        return [
86
            'IDN, one domain name label' => ['пример'],
87
            'IDN, two domain name labels' => ['пример.испытание'],
88
            'IDN, domain name with trailing dot' => ['пример.испытание.'],
89
            'IDN, mixed domain name' => ['пример.test.испытание'],
90
            'IDN, 34 levels, 253 characters (ф. == xn--t1a.) and trailing dot' => [
91
                'ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.s.s.s.',
92
            ],
93
            'IDN, domain name labels with 63 characters' => [
94
                'ффффффффффффффффффффффффффффффффффффффффффффффффффффффффф.' .
95
                'ффффффффффффффффффффффффффффффффффффффффффффффффффффффффф.' .
96
                'испытание',
97
            ],
98
            'IDN, domain name with various symbols' => ['1.a-B.cф2'],
99
            'IDN, hot beverage' => ['☕.us'],
100
            'IDN, full-width characters' => ['日本語。JP'],
101
            'IDN, box-drawing character' => ['ex╬ample.com'],
102
        ];
103
    }
104
105
    public static function validDomainIdnInUrlProvider()
106
    {
107
        return [
108
            'IDN, HTTP, one domain name label' => ['http://пример'],
109
            'IDN, HTTP, two domain name labels' => ['http://пример.испытание/index.html'],
110
            'IDN, FTP, domain name with trailing dot' => ['ftp://пример.испытание./img/dir/'],
111
            'IDN, FTP, mixed domain name' => ['ftp://пример.test.испытание/img/dir/'],
112
            'IDN, HTTPS, 34 levels, 253 characters (ф. == xn--t1a.) and trailing dot' => [
113
                'https://ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.s.s.s./index.html',
114
            ],
115
            'IDN, missing scheme, domain name labels with 63 characters' => [
116
                '//ффффффффффффффффффффффффффффффффффффффффффффффффффффффффф.' .
117
                'ффффффффффффффффффффффффффффффффффффффффффффффффффффффффф.' .
118
                'испытание',
119
            ],
120
            'IDN, complex URL, domain name with various symbols' => [
121
                'http://username:[email protected]ф2:9090/path?a=ф&c=d#-пример',
122
            ],
123
            'IDN, HTTP, hot beverage' => ['http://☕.us/index.html'],
124
            'IDN, HTTP, full-width characters' => ['http://日本語。JP/index.html'],
125
            'IDN, HTTP, box-drawing character' => ['http://ex╬ample.com/index.html'],
126
        ];
127
    }
128
129
    public static function validDomainAllWithoutIdnProvider()
130
    {
131
        return array_merge(
132
            static::validDomainProvider(),
133
            static::validDomainInUrlProvider()
134
        );
135
    }
136
137
    public static function validDomainAllOnlyIdnProvider()
138
    {
139
        return array_merge(
140
            static::validDomainIdnProvider(),
141
            static::validDomainIdnInUrlProvider()
142
        );
143
    }
144
145
    public static function validDomainAllProvider()
146
    {
147
        return array_merge(
148
            static::validDomainProvider(),
149
            static::validDomainInUrlProvider(),
150
            static::validDomainIdnProvider(),
151
            static::validDomainIdnInUrlProvider()
152
        );
153
    }
154
155
    /**
156
     * @param string $value
157
     * @covers       \kdn\yii2\validators\DomainValidator::validateValue
158
     * @dataProvider validDomainAllWithoutIdnProvider
159
     * @small
160
     */
161
    public function testValidDomain($value)
162
    {
163
        $this->assertTrue($this->validator->validate($value));
164
    }
165
166
    /**
167
     * @param string $value
168
     * @covers       \kdn\yii2\validators\DomainValidator::getDefaultErrorMessages
169
     * @covers       \kdn\yii2\validators\DomainValidator::getErrorMessage
170
     * @covers       \kdn\yii2\validators\DomainValidator::validateValue
171
     * @dataProvider validDomainAllOnlyIdnProvider
172
     * @small
173
     */
174
    public function testInvalidDomainWithDisabledIdn($value)
175
    {
176
        $this->assertFalse($this->validator->validate($value, $errorMessage));
177
        $this->assertEquals(
178
            'Each label of the input value can consist of only latin letters, numbers and hyphens.',
179
            $errorMessage
180
        );
181
    }
182
183
    /**
184
     * @param string $value
185
     * @covers       \kdn\yii2\validators\DomainValidator::validateValue
186
     * @dataProvider validDomainAllProvider
187
     * @small
188
     */
189
    public function testValidDomainWithEnabledIdn($value)
190
    {
191
        if (!function_exists('idn_to_ascii')) {
192
            $this->markTestSkipped('intl extension required.');
193
194
            /** @noinspection PhpUnreachableStatementInspection */
195
            return;
196
        }
197
198
        $this->validator->enableIDN = true;
199
        $this->testValidDomain($value);
200
    }
201
202
    /**
203
     * @covers \kdn\yii2\validators\DomainValidator::getDefaultErrorMessages
204
     * @covers \kdn\yii2\validators\DomainValidator::getErrorMessage
205
     * @covers \kdn\yii2\validators\DomainValidator::checkDNS
206
     * @covers \kdn\yii2\validators\DomainValidator::validateValue
207
     * @large
208
     */
209
    public function testDns()
210
    {
211
        $validator = $this->validator;
212
        $nonexistentDomain = static::NONEXISTENT_DOMAIN;
213
        $this->assertTrue($validator->validate($nonexistentDomain));
214
        $validator->checkDNS = true;
215
        $this->assertFalse($validator->validate($nonexistentDomain, $errorMessage));
216
        $this->assertEquals('DNS record corresponding to the input value not found.', $errorMessage);
217
218
        $data = [
219
            'google.com',
220
            'http://username:[email protected]:9090/path?a=b&c=d#anchor',
221
        ];
222
        foreach ($data as $value) {
223
            $this->assertTrue(
224
                $validator->validate($value),
225
                "Failed to validate \"$value\" (checkDNS = true)."
226
            );
227
        }
228
    }
229
230
    /**
231
     * @covers \kdn\yii2\validators\DomainValidator::validateValue
232
     * @large
233
     */
234
    public function testDnsCallable()
235
    {
236
        $validator = $this->validator;
237
        $nonexistentDomain = static::NONEXISTENT_DOMAIN;
238
        $this->assertTrue($validator->validate($nonexistentDomain));
239
        $customErrorMessage = 'test';
240
        $validator->checkDNS = function ($value) use ($nonexistentDomain, $customErrorMessage) {
241
            $records = @dns_get_record("$value.", DNS_MX); // @ is just for simplicity of test, avoid using it
242
            if (empty($records)) {
243
                $this->assertEquals($nonexistentDomain, $value);
244
245
                return [$customErrorMessage, []];
246
            }
247
248
            return null;
249
        };
250
        $this->assertFalse($validator->validate($nonexistentDomain, $errorMessage));
251
        $this->assertEquals($customErrorMessage, $errorMessage);
252
253
        $data = [
254
            'gmail.com',
255
            'http://username:[email protected]:9090/path?a=b&c=d#anchor',
256
        ];
257
        foreach ($data as $value) {
258
            $this->assertTrue(
259
                $validator->validate($value),
260
                "Failed to validate \"$value\" (checkDNS is callable)."
261
            );
262
        }
263
    }
264
265
    /**
266
     * @covers \kdn\yii2\validators\DomainValidator::getDefaultErrorMessages
267
     * @covers \kdn\yii2\validators\DomainValidator::getErrorMessage
268
     * @covers \kdn\yii2\validators\DomainValidator::checkDNS
269
     * @covers \kdn\yii2\validators\DomainValidator::validateValue
270
     * @large
271
     */
272
    public function testDnsWithEnabledIdn()
273
    {
274
        if (!function_exists('idn_to_ascii')) {
275
            $this->markTestSkipped('intl extension required.');
276
277
            /** @noinspection PhpUnreachableStatementInspection */
278
            return;
279
        }
280
281
        $validator = $this->validator;
282
        $validator->checkDNS = true;
283
        // enabling of IDN should not affect error message
284
        $validator->enableIDN = true;
285
        $this->assertFalse($validator->validate(static::NONEXISTENT_DOMAIN, $errorMessage));
286
        $this->assertEquals('DNS record corresponding to the input value not found.', $errorMessage);
287
288
        $data = [
289
            'google.com',
290
            'http://username:[email protected]:9090/path?a=b&c=d#anchor',
291
            'яндекс.рф',
292
            'http://username:password@яндекс.рф:9090/path?a=ф&c=d#-пример',
293
        ];
294
        foreach ($data as $value) {
295
            $this->assertTrue(
296
                $validator->validate($value),
297
                "Failed to validate \"$value\" (checkDNS = true, enableIDN = true)."
298
            );
299
        }
300
    }
301
302
    /**
303
     * @covers \kdn\yii2\validators\DomainValidator::getDefaultErrorMessages
304
     * @covers \kdn\yii2\validators\DomainValidator::getErrorMessage
305
     * @covers \kdn\yii2\validators\DomainValidator::validateValue
306
     * @small
307
     */
308
    public function testUnderscore()
309
    {
310
        $validator = $this->validator;
311
        $validator->allowUnderscore = true;
312
313
        $data = [
314
            'ex_ample.com',
315
            'http://username:password@ex_ample.com:9090/path?a=b&c=d#anchor',
316
        ];
317
        foreach ($data as $value) {
318
            $this->assertTrue(
319
                $validator->validate($value),
320
                "Failed to validate \"$value\" (allowUnderscore = true)."
321
            );
322
        }
323
324
        $this->assertFalse($validator->validate('a_@_a', $errorMessage));
325
        if ($validator->enableIDN) {
326
            $expectedErrorMessage =
327
                'Each label of the input value can consist of only letters, numbers, hyphens and underscores.';
328
        } else {
329
            $expectedErrorMessage =
330
                'Each label of the input value can consist of only latin letters, numbers, hyphens and underscores.';
331
        }
332
        $this->assertEquals($expectedErrorMessage, $errorMessage);
333
    }
334
335
    /**
336
     * @covers \kdn\yii2\validators\DomainValidator::getDefaultErrorMessages
337
     * @covers \kdn\yii2\validators\DomainValidator::getErrorMessage
338
     * @covers \kdn\yii2\validators\DomainValidator::validateValue
339
     * @small
340
     */
341
    public function testUnderscoreWithEnabledIdn()
342
    {
343
        if (!function_exists('idn_to_ascii')) {
344
            $this->markTestSkipped('intl extension required.');
345
346
            /** @noinspection PhpUnreachableStatementInspection */
347
            return;
348
        }
349
350
        $this->validator->enableIDN = true;
351
        $this->testUnderscore();
352
    }
353
354
    public static function urlNotAllowedProvider()
355
    {
356
        return array_merge(
357
            static::arrayAddColumn(static::validDomainProvider(), true),
358
            static::arrayAddColumn(static::validDomainInUrlProvider(), false)
359
        );
360
    }
361
362
    /**
363
     * @param string $value
364
     * @param bool $expectedResult
365
     * @covers       \kdn\yii2\validators\DomainValidator::validateValue
366
     * @uses         \kdn\yii2\validators\DomainValidator::getDefaultErrorMessages
367
     * @uses         \kdn\yii2\validators\DomainValidator::getErrorMessage
368
     * @dataProvider urlNotAllowedProvider
369
     * @small
370
     */
371
    public function testUrlNotAllowed($value, $expectedResult)
372
    {
373
        $validator = $this->validator;
374
        $validator->allowURL = false;
375
        $this->assertEquals($expectedResult, $validator->validate($value));
376
    }
377
378
    public static function urlNotAllowedProviderWithEnabledIdn()
379
    {
380
        return array_merge(
381
            static::urlNotAllowedProvider(),
382
            static::arrayAddColumn(static::validDomainIdnProvider(), true),
383
            static::arrayAddColumn(static::validDomainIdnInUrlProvider(), false)
384
        );
385
    }
386
387
    /**
388
     * @param string $value
389
     * @param bool $expectedResult
390
     * @covers       \kdn\yii2\validators\DomainValidator::validateValue
391
     * @uses         \kdn\yii2\validators\DomainValidator::getDefaultErrorMessages
392
     * @uses         \kdn\yii2\validators\DomainValidator::getErrorMessage
393
     * @dataProvider urlNotAllowedProviderWithEnabledIdn
394
     * @small
395
     */
396
    public function testUrlNotAllowedWithEnabledIdn($value, $expectedResult)
397
    {
398
        if (!function_exists('idn_to_ascii')) {
399
            $this->markTestSkipped('intl extension required.');
400
401
            /** @noinspection PhpUnreachableStatementInspection */
402
            return;
403
        }
404
405
        $this->validator->enableIDN = true;
406
        $this->testUrlNotAllowed($value, $expectedResult);
407
    }
408
409
    /**
410
     * @covers \kdn\yii2\validators\DomainValidator::getDefaultErrorMessages
411
     * @covers \kdn\yii2\validators\DomainValidator::getErrorMessage
412
     * @covers \kdn\yii2\validators\DomainValidator::validateValue
413
     * @small
414
     */
415
    public function testLabelNumberMin()
416
    {
417
        $validator = $this->validator;
418
        $validator->labelNumberMin = 2;
419
        $this->assertFalse($validator->validate('localhost', $errorMessage));
420
        $this->assertEquals('the input value should consist of at least 2 labels separated by dot.', $errorMessage);
421
        $this->assertTrue($validator->validate('example.com'));
422
        $this->assertTrue($validator->validate('test.example.com'));
423
        $validator->labelNumberMin = 3;
424
        $this->assertFalse($validator->validate('example.com', $errorMessage));
425
        $this->assertEquals('the input value should consist of at least 3 labels separated by dots.', $errorMessage);
426
        $this->assertTrue($validator->validate('test.example.com'));
427
    }
428
429
    /**
430
     * @covers \kdn\yii2\validators\DomainValidator::getDefaultErrorMessages
431
     * @covers \kdn\yii2\validators\DomainValidator::getErrorMessage
432
     * @covers \kdn\yii2\validators\DomainValidator::validateValue
433
     * @small
434
     */
435
    public function testLabelNumberMinWithEnabledIdn()
436
    {
437
        if (!function_exists('idn_to_ascii')) {
438
            $this->markTestSkipped('intl extension required.');
439
440
            /** @noinspection PhpUnreachableStatementInspection */
441
            return;
442
        }
443
444
        $this->validator->enableIDN = true;
445
        $this->testLabelNumberMin();
446
    }
447
448
    public static function invalidDomainProvider($testName)
449
    {
450
        if ($testName == 'testInvalidDomain') {
451
            $messageInvalidCharacter =
452
                'Each label of the input value can consist of only latin letters, numbers and hyphens.';
453
            $messageLabelStartEnd = 'Each label of the input value should start and end with latin letter or number.' .
454
                ' The rightmost label of the input value should start with latin letter.';
455
            $messageLabelTooLong = 'Each label of the input value should contain at most 63 characters.';
456
            $messageTooLong = 'the input value should contain at most 253 characters.';
457
        } else {
458
            $messageInvalidCharacter =
459
                'Each label of the input value can consist of only letters, numbers and hyphens.';
460
            $messageLabelStartEnd = 'Each label of the input value should start and end with letter or number.' .
461
                ' The rightmost label of the input value should start with letter.';
462
            $messageLabelTooLong = 'Label of the input value is too long.';
463
            $messageTooLong = 'the input value is too long.';
464
        }
465
        $messageLabelTooShort = 'Each label of the input value should contain at least 1 character.';
466
        $messageNotString = 'the input value must be a string.';
467
        $messageTooShort = 'the input value should contain at least 1 character.';
468
469
        return [
470
            'null' => [null, $messageNotString],
471
            'boolean' => [true, $messageNotString],
472
            'integer' => [1, $messageNotString],
473
            'float' => [1.2, $messageNotString],
474
            'array' => [[], $messageNotString],
475
            'object' => [new stdClass(), $messageNotString],
476
477
            'domain name too long' => [str_repeat('a.', 126) . 'aa', $messageTooLong],
478
479
            'domain name too short' => ['', $messageTooShort],
480
481
            'first domain name label starts with hyphen' => ['-example.com', $messageLabelStartEnd],
482
            'first domain name label ends with hyphen' => ['example-.com', $messageLabelStartEnd],
483
            'last domain name label starts with hyphen' => ['example.-com', $messageLabelStartEnd],
484
            'last domain name label ends with hyphen' => ['example.com-', $messageLabelStartEnd],
485
486
            'IDN, first domain name label starts with hyphen' => ['-пример.испытание', $messageInvalidCharacter],
487
            'IDN, first domain name label ends with hyphen' => ['пример-.испытание', $messageInvalidCharacter],
488
            'IDN, last domain name label starts with hyphen' => ['пример.-испытание', $messageInvalidCharacter],
489
            'IDN, last domain name label ends with hyphen' => ['пример.испытание-', $messageInvalidCharacter],
490
491
            'IDN, HTTP, first domain name label starts with hyphen' => [
492
                'http://-пример.испытание/index.html',
493
                $messageInvalidCharacter,
494
            ],
495
            'IDN, HTTP, first domain name label ends with hyphen' => [
496
                'http://пример-.испытание/index.html',
497
                $messageInvalidCharacter,
498
            ],
499
            'IDN, HTTP, last domain name label starts with hyphen' => [
500
                'http://пример.-испытание/index.html',
501
                $messageInvalidCharacter,
502
            ],
503
            'IDN, HTTP, last domain name label ends with hyphen' => [
504
                'http://пример.испытание-/index.html',
505
                $messageInvalidCharacter,
506
            ],
507
508
            'last domain name label starts with number' => ['example.4om', $messageLabelStartEnd],
509
510
            'domain name label too long' => [str_repeat('a', 64), $messageLabelTooLong],
511
512
            'dot' => ['.', $messageLabelTooShort],
513
            'domain name starts with dot' => ['.example.com', $messageLabelTooShort],
514
            'domain name ends with two dots' => ['example.com..', $messageLabelTooShort],
515
            'domain name contains two dots in a row' => ['example..com', $messageLabelTooShort],
516
517
            'domain name contains underscore' => ['ex_ample.com', $messageInvalidCharacter],
518
            'domain name contains space' => ['ex ample.com', $messageInvalidCharacter],
519
            'domain name contains disallowed character' => ['a⒈com', $messageInvalidCharacter],
520
521
            'IDN, domain name too long' => [
522
                'ффффффффффффффффффффффффффффффффффффффффффффффффффффффффф.' .
523
                'ффффффффффффффффффффффффффффффффффффффффффффффффффффффффф.' .
524
                'ффффффффффффффффффффффффффффффффффффффффффффффффффффффффф.' .
525
                'ффффффффффффффффффффффффффффффффффффффффффффффффффффффффф.',
526
                $messageInvalidCharacter,
527
            ],
528
            'IDN, domain name label too long' => [
529
                'фффффффффффффффффффффффффффффффффффффффффффффффффффффффффs',
530
                $messageInvalidCharacter,
531
            ],
532
533
            'invalid url with valid domain name' => ['http//example.com/index.html', $messageInvalidCharacter],
534
            'IDN, invalid url with valid domain name' => ['http//пример.com/index.html', $messageInvalidCharacter],
535
        ];
536
    }
537
538
    /**
539
     * @param string $value
540
     * @param string $expectedErrorMessage
541
     * @covers       \kdn\yii2\validators\DomainValidator::getDefaultErrorMessages
542
     * @covers       \kdn\yii2\validators\DomainValidator::getErrorMessage
543
     * @covers       \kdn\yii2\validators\DomainValidator::validateValue
544
     * @dataProvider invalidDomainProvider
545
     * @small
546
     */
547
    public function testInvalidDomain($value, $expectedErrorMessage)
548
    {
549
        $this->assertFalse($this->validator->validate($value, $errorMessage));
550
        $this->assertEquals($expectedErrorMessage, $errorMessage);
551
    }
552
553
    public static function invalidDomainWithEnabledIdnProvider()
554
    {
555
        $message = 'the input value is invalid.';
556
        $messageLabelStartEnd = 'Each label of the input value should start and end with letter or number.' .
557
            ' The rightmost label of the input value should start with letter.';
558
        $messageLabelTooLong = 'Label of the input value is too long.';
559
        $messageTooLong = 'the input value is too long.';
560
561
        return array_merge(
562
            static::invalidDomainProvider('testInvalidDomainWithEnabledIdn'),
563
            [
564
                'IDN, domain name too long, numerous labels' => [
565
                    'ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.ф.s.s.s.s',
566
                    $messageTooLong,
567
                ],
568
                'IDN, domain name too long, long labels' => [
569
                    'ффффффффффффффффффффффффффффффффффффффффффффффффффффффффф.' .
570
                    'ффффффффффффффффффффффффффффффффффффффффффффффффффффффффф.' .
571
                    'ффффффффффффффффффффффффффффффффффффффффффффффффффффффффф.' .
572
                    'фффффффффффффффффффффффффффффффффффффффффффффффффффффффф.',
573
                    $messageTooLong,
574
                ],
575
576
                'IDN, first domain name label starts with hyphen' => ['-пример.испытание', $messageLabelStartEnd],
577
                'IDN, first domain name label ends with hyphen' => ['пример-.испытание', $messageLabelStartEnd],
578
                'IDN, last domain name label starts with hyphen' => ['пример.-испытание', $messageLabelStartEnd],
579
                'IDN, last domain name label ends with hyphen' => ['пример.испытание-', $messageLabelStartEnd],
580
581
                'IDN, HTTP, first domain name label starts with hyphen' => [
582
                    'http://-пример.испытание/index.html',
583
                    $messageLabelStartEnd,
584
                ],
585
                'IDN, HTTP, first domain name label ends with hyphen' => [
586
                    'http://пример-.испытание/index.html',
587
                    $messageLabelStartEnd,
588
                ],
589
                'IDN, HTTP, last domain name label starts with hyphen' => [
590
                    'http://пример.-испытание/index.html',
591
                    $messageLabelStartEnd,
592
                ],
593
                'IDN, HTTP, last domain name label ends with hyphen' => [
594
                    'http://пример.испытание-/index.html',
595
                    $messageLabelStartEnd,
596
                ],
597
598
                'IDN, domain name too long' => [
599
                    'ффффффффффффффффффффффффффффффффффффффффффффффффффффффффф.' .
600
                    'ффффффффффффффффффффффффффффффффффффффффффффффффффффффффф.' .
601
                    'ффффффффффффффффффффффффффффффффффффффффффффффффффффффффф.' .
602
                    'ффффффффффффффффффффффффффффффффффффффффффффффффффффффффф.',
603
                    $messageTooLong,
604
                ],
605
                'IDN, domain name label too long' => [
606
                    'фффффффффффффффффффффффффффффффффффффффффффффффффффффффффs',
607
                    $messageLabelTooLong,
608
                ],
609
610
                'IDN, IDNA_ERROR_HYPHEN_3_4' => ['aa--a', $message],
611
                'IDN, IDNA_ERROR_LEADING_COMBINING_MARK' => [static::u('\u0308c'), $message],
612
                'IDN, IDNA_ERROR_PUNYCODE' => ['xn--0', $message],
613
                'IDN, IDNA_ERROR_INVALID_ACE_LABEL' => ['xn--a', $message],
614
                'IDN, IDNA_ERROR_BIDI' => [static::u('0A.\u05D0'), $message],
615
            ]
616
        );
617
    }
618
619
    /**
620
     * @param string $value
621
     * @param string $expectedErrorMessage
622
     * @covers       \kdn\yii2\validators\DomainValidator::getDefaultErrorMessages
623
     * @covers       \kdn\yii2\validators\DomainValidator::getErrorMessage
624
     * @covers       \kdn\yii2\validators\DomainValidator::validateValue
625
     * @dataProvider invalidDomainWithEnabledIdnProvider
626
     * @small
627
     */
628
    public function testInvalidDomainWithEnabledIdn($value, $expectedErrorMessage)
629
    {
630
        if (!function_exists('idn_to_ascii')) {
631
            $this->markTestSkipped('intl extension required.');
632
633
            /** @noinspection PhpUnreachableStatementInspection */
634
            return;
635
        }
636
637
        $this->validator->enableIDN = true;
638
        $this->testInvalidDomain($value, $expectedErrorMessage);
639
    }
640
641
    /**
642
     * @covers \kdn\yii2\validators\DomainValidator::getErrorMessage
643
     * @covers \kdn\yii2\validators\DomainValidator::validateValue
644
     * @small
645
     */
646
    public function testCustomErrorMessage()
647
    {
648
        $validator = $this->validator;
649
        $messageNotString = 'test';
650
        $validator->messageNotString = $messageNotString;
651
        $this->assertFalse($validator->validate(null, $errorMessage));
652
        $this->assertEquals($messageNotString, $errorMessage);
653
    }
654
655
    /**
656
     * @covers \kdn\yii2\validators\DomainValidator::getDefaultErrorMessages
657
     * @covers \kdn\yii2\validators\DomainValidator::getErrorMessage
658
     * @covers \kdn\yii2\validators\DomainValidator::validateValue
659
     * @small
660
     */
661
    public function testSimpleErrorMessage()
662
    {
663
        $validator = $this->validator;
664
        $validator->simpleErrorMessage = true;
665
        $this->assertFalse($validator->validate('-', $errorMessage));
666
        $this->assertEquals('the input value is invalid.', $errorMessage);
667
    }
668
669
    /**
670
     * @covers \kdn\yii2\validators\DomainValidator::getDefaultErrorMessages
671
     * @covers \kdn\yii2\validators\DomainValidator::getErrorMessage
672
     * @covers \kdn\yii2\validators\DomainValidator::validateValue
673
     * @small
674
     */
675
    public function testValidateAttribute()
676
    {
677
        $model = new ModelMock(['domain' => 'example']);
678
        $validator = $this->validator;
679
680
        $validator->validateAttribute($model, 'domain');
681
        $this->assertFalse($model->hasErrors('domain'));
682
683
        $validator->labelNumberMin = 2;
684
        $validator->validateAttribute($model, 'domain');
685
        $this->assertTrue($model->hasErrors('domain'));
686
    }
687
688
    /**
689
     * @covers \kdn\yii2\validators\DomainValidator::getDefaultErrorMessages
690
     * @covers \kdn\yii2\validators\DomainValidator::getErrorMessage
691
     * @covers \kdn\yii2\validators\DomainValidator::validateValue
692
     * @small
693
     */
694
    public function testValidateAttributeAndI18n()
695
    {
696
        if (!function_exists('idn_to_ascii')) {
697
            $this->markTestSkipped('intl extension required.');
698
699
            /** @noinspection PhpUnreachableStatementInspection */
700
            return;
701
        }
702
703
        Yii::$app->language = 'ru-RU';
704
        $model = new ModelMock(['domain' => 'example']);
705
        $validator = $this->validator;
706
707
        $validator->validateAttribute($model, 'domain');
708
        $this->assertFalse($model->hasErrors('domain'));
709
710
        $validator->labelNumberMin = 2;
711
        $validator->validateAttribute($model, 'domain');
712
        $this->assertTrue($model->hasErrors('domain'));
713
        $this->assertEquals(
714
            'Значение «Доменное имя» должно состоять минимум из 2 меток, разделённых точкой.',
715
            $model->getFirstError('domain')
716
        );
717
718
        $model->clearErrors('domain');
719
720
        $validator->labelNumberMin = 21;
721
        $validator->validateAttribute($model, 'domain');
722
        $this->assertTrue($model->hasErrors('domain'));
723
        $this->assertEquals(
724
            'Значение «Доменное имя» должно состоять минимум из 21 метки, разделённых точками.',
725
            $model->getFirstError('domain')
726
        );
727
    }
728
729
    /**
730
     * IMPORTANT: this test should be executed after others, because it can remove function "idn_to_ascii".
731
     * @covers \kdn\yii2\validators\DomainValidator::init
732
     * @small
733
     */
734
    public function testInitIdnIntlException()
735
    {
736
        $runkitFunctionName = null;
737
        if (ini_get('runkit.internal_override')) {
738
            if (function_exists('runkit7_function_remove')) {
739
                $runkitFunctionName = 'runkit7_function_remove';
740
            } elseif (function_exists('runkit_function_remove')) {
741
                $runkitFunctionName = 'runkit_function_remove';
742
            }
743
        }
744
745
        if ($runkitFunctionName === null) {
746
            $this->markTestSkipped('runkit extension required. runkit.internal_override should be set to "On".');
747
748
            /** @noinspection PhpUnreachableStatementInspection */
749
            return;
750
        }
751
752
        $runkitFunctionName('idn_to_ascii');
753
754
        $expectedException = new InvalidConfigException(
755
            'In order to use IDN validation intl extension must be installed and enabled.'
756
        );
757
        $actualException = null;
758
        try {
759
            new DomainValidator(['enableIDN' => true]);
760
        } catch (InvalidConfigException $e) {
761
            $actualException = $e;
762
        }
763
        $this->assertEquals($expectedException, $actualException);
764
    }
765
766
    /**
767
     * IMPORTANT: this test should be executed after others, because it can replace function "dns_get_record".
768
     * @covers \kdn\yii2\validators\DomainValidator::checkDNS
769
     * @covers \kdn\yii2\validators\DomainValidator::getDefaultErrorMessages
770
     * @covers \kdn\yii2\validators\DomainValidator::getErrorMessage
771
     * @covers \kdn\yii2\validators\DomainValidator::validateValue
772
     * @small
773
     */
774
    public function testDnsWarning()
775
    {
776
        $runkitFunctionName = null;
777
        if (ini_get('runkit.internal_override')) {
778
            if (function_exists('runkit7_function_redefine')) {
779
                $runkitFunctionName = 'runkit7_function_redefine';
780
            } elseif (function_exists('runkit_function_redefine')) {
781
                $runkitFunctionName = 'runkit_function_redefine';
782
            }
783
        }
784
785
        if ($runkitFunctionName === null) {
786
            $this->markTestSkipped('runkit extension required. runkit.internal_override should be set to "On".');
787
788
            /** @noinspection PhpUnreachableStatementInspection */
789
            return;
790
        }
791
792
        // redefine dns_get_record to emit PHP Warning, which will be converted by Yii to yii\base\ErrorException
793
        if (!$runkitFunctionName('dns_get_record', '', 'trigger_error("Warning", E_USER_WARNING);')) {
794
            $this->markTestSkipped('Cannot redefine function "dns_get_record".');
795
796
            /** @noinspection PhpUnreachableStatementInspection */
797
            return;
798
        }
799
800
        $validator = $this->validator;
801
        $validator->checkDNS = true;
802
        $this->assertFalse($validator->validate('google.com', $errorMessage));
803
        $this->assertEquals('DNS record corresponding to the input value not found.', $errorMessage);
804
    }
805
}
806