Passed
Push — master ( a4b7b7...e08bee )
by Dmitry
02:56
created

DomainValidatorTest::validDomainProvider()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 19
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

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