Skip to content

Commit a63f81a

Browse files
committed
improved tests
1 parent da613e2 commit a63f81a

7 files changed

Lines changed: 855 additions & 7 deletions
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Nette\Schema\Expect;
6+
use Nette\Schema\Processor;
7+
use Tester\Assert;
8+
9+
10+
require __DIR__ . '/../bootstrap.php';
11+
12+
13+
test('discriminated union with valid variant one', function () {
14+
$schema = Expect::anyOf(
15+
Expect::structure([
16+
'type' => Expect::anyOf('session')->required(),
17+
'expiration' => Expect::string(),
18+
]),
19+
Expect::structure([
20+
'type' => Expect::anyOf('cookie')->required(),
21+
'name' => Expect::string()->required(),
22+
'domain' => Expect::string(),
23+
]),
24+
);
25+
26+
Assert::equal(
27+
(object) ['type' => 'session', 'expiration' => '20 minutes'],
28+
(new Processor)->process($schema, ['type' => 'session', 'expiration' => '20 minutes']),
29+
);
30+
});
31+
32+
33+
test('discriminated union with valid variant two', function () {
34+
$schema = Expect::anyOf(
35+
Expect::structure([
36+
'type' => Expect::anyOf('session')->required(),
37+
'expiration' => Expect::string(),
38+
]),
39+
Expect::structure([
40+
'type' => Expect::anyOf('cookie')->required(),
41+
'name' => Expect::string()->required(),
42+
'domain' => Expect::string(),
43+
]),
44+
);
45+
46+
Assert::equal(
47+
(object) ['type' => 'cookie', 'name' => 'auth', 'domain' => null],
48+
(new Processor)->process($schema, ['type' => 'cookie', 'name' => 'auth']),
49+
);
50+
});
51+
52+
53+
test('discriminated union with invalid discriminator', function () {
54+
$schema = Expect::anyOf(
55+
Expect::structure([
56+
'type' => Expect::anyOf('session')->required(),
57+
'expiration' => Expect::string(),
58+
]),
59+
Expect::structure([
60+
'type' => Expect::anyOf('cookie')->required(),
61+
'name' => Expect::string()->required(),
62+
]),
63+
);
64+
65+
checkValidationErrors(function () use ($schema) {
66+
(new Processor)->process($schema, ['type' => 'redis']);
67+
}, [
68+
"The item 'type' expects to be 'session', 'redis' given.",
69+
"The item 'type' expects to be 'cookie', 'redis' given.",
70+
"The mandatory item 'name' is missing.",
71+
]);
72+
});
73+
74+
75+
test('discriminated union with missing required field', function () {
76+
$schema = Expect::anyOf(
77+
Expect::structure([
78+
'type' => Expect::anyOf('session')->required(),
79+
'expiration' => Expect::string(),
80+
]),
81+
Expect::structure([
82+
'type' => Expect::anyOf('cookie')->required(),
83+
'name' => Expect::string()->required(),
84+
]),
85+
);
86+
87+
checkValidationErrors(function () use ($schema) {
88+
(new Processor)->process($schema, ['type' => 'cookie']);
89+
}, [
90+
"The item 'type' expects to be 'session', 'cookie' given.",
91+
"The mandatory item 'name' is missing.",
92+
]);
93+
});
94+
95+
96+
test('discriminated union with three variants', function () {
97+
$schema = Expect::anyOf(
98+
Expect::structure([
99+
'driver' => Expect::anyOf('mysql')->required(),
100+
'host' => Expect::string()->default('localhost'),
101+
'port' => Expect::int()->default(3306),
102+
]),
103+
Expect::structure([
104+
'driver' => Expect::anyOf('sqlite')->required(),
105+
'path' => Expect::string()->required(),
106+
]),
107+
Expect::structure([
108+
'driver' => Expect::anyOf('pgsql')->required(),
109+
'host' => Expect::string()->default('localhost'),
110+
'port' => Expect::int()->default(5432),
111+
]),
112+
);
113+
114+
Assert::equal(
115+
(object) ['driver' => 'sqlite', 'path' => '/tmp/db.sqlite'],
116+
(new Processor)->process($schema, ['driver' => 'sqlite', 'path' => '/tmp/db.sqlite']),
117+
);
118+
119+
Assert::equal(
120+
(object) ['driver' => 'mysql', 'host' => 'localhost', 'port' => 3306],
121+
(new Processor)->process($schema, ['driver' => 'mysql']),
122+
);
123+
124+
Assert::equal(
125+
(object) ['driver' => 'pgsql', 'host' => 'db.example.com', 'port' => 5432],
126+
(new Processor)->process($schema, ['driver' => 'pgsql', 'host' => 'db.example.com']),
127+
);
128+
});
129+
130+
131+
test('nested discriminated unions', function () {
132+
$schema = Expect::structure([
133+
'cache' => Expect::anyOf(
134+
Expect::structure([
135+
'driver' => Expect::anyOf('file')->required(),
136+
'dir' => Expect::string()->required(),
137+
]),
138+
Expect::structure([
139+
'driver' => Expect::anyOf('redis')->required(),
140+
'host' => Expect::string()->default('localhost'),
141+
'port' => Expect::int()->default(6379),
142+
]),
143+
false,
144+
),
145+
]);
146+
147+
Assert::equal(
148+
(object) ['cache' => (object) ['driver' => 'file', 'dir' => '/tmp/cache']],
149+
(new Processor)->process($schema, ['cache' => ['driver' => 'file', 'dir' => '/tmp/cache']]),
150+
);
151+
152+
Assert::equal(
153+
(object) ['cache' => false],
154+
(new Processor)->process($schema, ['cache' => false]),
155+
);
156+
});
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Nette\Schema\Context;
6+
use Nette\Schema\Expect;
7+
use Nette\Schema\Processor;
8+
use Tester\Assert;
9+
10+
11+
require __DIR__ . '/../bootstrap.php';
12+
13+
14+
test('chaining before, assert, and transform', function () {
15+
$schema = Expect::string()
16+
->before(fn($v) => strtolower($v))
17+
->assert(fn($s) => ctype_alpha($s), 'Must contain only letters')
18+
->transform(fn($v) => ucfirst($v));
19+
20+
Assert::same('Hello', (new Processor)->process($schema, 'HELLO'));
21+
Assert::same('World', (new Processor)->process($schema, 'world'));
22+
23+
checkValidationErrors(function () use ($schema) {
24+
(new Processor)->process($schema, 'HELLO123');
25+
}, ["Failed assertion 'Must contain only letters' for item with value 'hello123'."]);
26+
});
27+
28+
29+
test('multiple assertions in sequence', function () {
30+
$schema = Expect::string()
31+
->assert(ctype_digit(...), 'Must be numeric')
32+
->assert(fn($s) => strlen($s) >= 3, 'Too short')
33+
->assert(fn($s) => strlen($s) <= 10, 'Too long');
34+
35+
Assert::same('12345', (new Processor)->process($schema, '12345'));
36+
37+
checkValidationErrors(function () use ($schema) {
38+
(new Processor)->process($schema, 'abc');
39+
}, ["Failed assertion 'Must be numeric' for item with value 'abc'."]);
40+
41+
checkValidationErrors(function () use ($schema) {
42+
(new Processor)->process($schema, '12');
43+
}, ["Failed assertion 'Too short' for item with value '12'."]);
44+
45+
checkValidationErrors(function () use ($schema) {
46+
(new Processor)->process($schema, '12345678901');
47+
}, ["Failed assertion 'Too long' for item with value '12345678901'."]);
48+
});
49+
50+
51+
test('pattern() combined with min/max', function () {
52+
$schema = Expect::string()
53+
->pattern('[a-z]+')
54+
->min(3)
55+
->max(10);
56+
57+
Assert::same('hello', (new Processor)->process($schema, 'hello'));
58+
59+
checkValidationErrors(function () use ($schema) {
60+
(new Processor)->process($schema, 'Hello');
61+
}, ["The item expects to match pattern '[a-z]+', 'Hello' given."]);
62+
63+
checkValidationErrors(function () use ($schema) {
64+
(new Processor)->process($schema, 'ab');
65+
}, ['The length of item expects to be in range 3..10, 2 bytes given.']);
66+
67+
checkValidationErrors(function () use ($schema) {
68+
(new Processor)->process($schema, 'abcdefghijk');
69+
}, ['The length of item expects to be in range 3..10, 11 bytes given.']);
70+
});
71+
72+
73+
test('transform with validation using context', function () {
74+
$schema = Expect::string()
75+
->transform(function (string $s, Context $context) {
76+
if (!ctype_lower($s)) {
77+
$context->addError('All characters must be lowercase', 'validation.lowercase');
78+
return null;
79+
}
80+
return strtoupper($s);
81+
});
82+
83+
Assert::same('HELLO', (new Processor)->process($schema, 'hello'));
84+
85+
checkValidationErrors(function () use ($schema) {
86+
(new Processor)->process($schema, 'Hello');
87+
}, ['All characters must be lowercase']);
88+
});
89+
90+
91+
test('multiple transforms in sequence', function () {
92+
$schema = Expect::string()
93+
->transform(fn($s) => trim($s))
94+
->transform(fn($s) => strtolower($s))
95+
->transform(fn($s) => ucwords($s));
96+
97+
Assert::same('Hello World', (new Processor)->process($schema, ' HELLO WORLD '));
98+
});
99+
100+
101+
test('assert() and transform interleaved', function () {
102+
$schema = Expect::int()
103+
->assert(fn($v) => $v > 0, 'Must be positive')
104+
->transform(fn($v) => $v * 2)
105+
->assert(fn($v) => $v < 100, 'Result too large')
106+
->transform(fn($v) => (string) $v);
107+
108+
Assert::same('20', (new Processor)->process($schema, 10));
109+
110+
checkValidationErrors(function () use ($schema) {
111+
(new Processor)->process($schema, -5);
112+
}, ["Failed assertion 'Must be positive' for item with value -5."]);
113+
114+
checkValidationErrors(function () use ($schema) {
115+
(new Processor)->process($schema, 60);
116+
}, ["Failed assertion 'Result too large' for item with value 120."]);
117+
});
118+
119+
120+
test('before() normalization with type conversion', function () {
121+
$schema = Expect::int()
122+
->before(fn($v) => is_string($v) ? (int) $v : $v)
123+
->min(1)
124+
->max(100);
125+
126+
Assert::same(42, (new Processor)->process($schema, '42'));
127+
Assert::same(42, (new Processor)->process($schema, 42));
128+
129+
checkValidationErrors(function () use ($schema) {
130+
(new Processor)->process($schema, '0');
131+
}, ['The item expects to be in range 1..100, 0 given.']);
132+
});
133+
134+
135+
test('castTo() combined with transformations', function () {
136+
$schema = Expect::string()
137+
->pattern('\d{4}-\d{2}-\d{2}')
138+
->castTo(DateTime::class);
139+
140+
$result = (new Processor)->process($schema, '2024-01-15');
141+
Assert::type(DateTime::class, $result);
142+
Assert::same('2024-01-15', $result->format('Y-m-d'));
143+
144+
checkValidationErrors(function () use ($schema) {
145+
(new Processor)->process($schema, 'invalid-date');
146+
}, ["The item expects to match pattern '\\d{4}-\\d{2}-\\d{2}', 'invalid-date' given."]);
147+
});
148+
149+
150+
test('nullable with all validators', function () {
151+
$schema = Expect::string()
152+
->nullable()
153+
->pattern('[a-z]+')
154+
->min(3)
155+
->transform(fn($v) => $v ? strtoupper($v) : null);
156+
157+
Assert::same('HELLO', (new Processor)->process($schema, 'hello'));
158+
Assert::same(null, (new Processor)->process($schema, null));
159+
160+
checkValidationErrors(function () use ($schema) {
161+
(new Processor)->process($schema, 'ab');
162+
}, ['The length of item expects to be in range 3.., 2 bytes given.']);
163+
});
164+
165+
166+
test('deprecated combined with other validators', function () {
167+
$schema = Expect::string()
168+
->deprecated('Use newField instead')
169+
->pattern('\w+');
170+
171+
$processor = new Processor;
172+
Assert::same('test', $processor->process($schema, 'test'));
173+
Assert::same(['Use newField instead'], $processor->getWarnings());
174+
});
175+
176+
177+
test('deprecated does not warn when value not provided', function () {
178+
$schema = Expect::structure([
179+
'oldField' => Expect::string()->deprecated('Use newField instead'),
180+
'newField' => Expect::string(),
181+
]);
182+
183+
$processor = new Processor;
184+
Assert::equal(
185+
(object) ['oldField' => null, 'newField' => 'value'],
186+
$processor->process($schema, ['newField' => 'value']),
187+
);
188+
Assert::same([], $processor->getWarnings());
189+
});
190+
191+
192+
test('complex validation chain on array items', function () {
193+
$schema = Expect::arrayOf(
194+
Expect::string()
195+
->pattern('[a-z]+')
196+
->min(2)
197+
->transform(fn($s) => ucfirst($s)),
198+
);
199+
200+
Assert::same(['Hello', 'World'], (new Processor)->process($schema, ['hello', 'world']));
201+
202+
checkValidationErrors(function () use ($schema) {
203+
(new Processor)->process($schema, ['hello', 'X']);
204+
}, ["The length of item '1' expects to be in range 2.., 1 bytes given."]);
205+
206+
checkValidationErrors(function () use ($schema) {
207+
(new Processor)->process($schema, ['hello', 'World']);
208+
}, ["The item '1' expects to match pattern '[a-z]+', 'World' given."]);
209+
});

tests/Schema/Expect.enum.phpt

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Nette\Schema\Expect;
6+
use Nette\Schema\Processor;
7+
use Tester\Assert;
8+
9+
require __DIR__ . '/../bootstrap.php';
10+
11+
12+
enum Suit
13+
{
14+
case Clubs;
15+
case Diamonds;
16+
case Hearts;
17+
case Spades;
18+
}
19+
20+
test('unit enum as standalone type', function () {
21+
$schema = Expect::type(Suit::class);
22+
23+
Assert::same(Suit::Clubs, (new Processor)->process($schema, Suit::Clubs));
24+
Assert::same(Suit::Hearts, (new Processor)->process($schema, Suit::Hearts));
25+
26+
checkValidationErrors(function () use ($schema) {
27+
(new Processor)->process($schema, 'Clubs');
28+
}, ['The item expects to be Suit, \'Clubs\' given.']);
29+
});

0 commit comments

Comments
 (0)