Skip to content

Commit a35baa0

Browse files
committed
feat: implement metadata support
1 parent 98760ac commit a35baa0

9 files changed

Lines changed: 356 additions & 4 deletions

File tree

tests/Core/Deserialize/Resolve/DependencyChainTest.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,19 @@ public function testResolveNonObject(): void
2828

2929
$this->assertEquals('test', $result->content);
3030
}
31+
32+
public function testResolveBackedEnum(): void
33+
{
34+
$chain = new DependencyChain();
35+
$enum = TestStatus::ACTIVE;
36+
$result = $chain->resolve($this->createMock(ReflectionParameter::class), $enum);
37+
38+
$this->assertEquals('active', $result->content);
39+
}
40+
}
41+
42+
enum TestStatus: string
43+
{
44+
case ACTIVE = 'active';
45+
case INACTIVE = 'inactive';
3146
}

tests/Core/Reflect/ReflectorTest.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,10 @@ public function testReflectorReturnsCorrectRulesForGameCommand(): void
245245
"slug": [ "required", "string" ],
246246
"published_at": [ "required", "date" ],
247247
"data": [ "required", "array" ],
248-
"features": [ "required", "array" ]
248+
"features": [ "required", "array" ],
249+
"features.*.name": [ "required", "string" ],
250+
"features.*.description": [ "required", "string" ],
251+
"features.*.enabled": [ "required", "bool" ]
249252
}';
250253
$expected = json_decode($json, true);
251254
$this->assertEquals($expected, $rules);

tests/Core/Serialize/Resolver/CollectionValueTest.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,4 +63,35 @@ public function testShouldNotResolveCollectionWhenParameterIsNotCollection(): vo
6363
// Assert
6464
$this->assertInstanceOf(NotResolved::class, $result->content);
6565
}
66+
67+
public function testShouldResolveCollectionWithArrayData(): void
68+
{
69+
// Arrange
70+
$type = $this->createMock(ReflectionNamedType::class);
71+
$type->expects($this->once())
72+
->method('getName')
73+
->willReturn(FeatureCollection::class);
74+
$parameter = $this->createMock(ReflectionParameter::class);
75+
$parameter->expects($this->once())
76+
->method('getType')
77+
->willReturn($type);
78+
$parameter->expects($this->once())
79+
->method('getName')
80+
->willReturn('features');
81+
82+
$set = Set::createFrom([
83+
'features' => [
84+
['name' => 'feature1', 'description' => 'First feature', 'enabled' => true],
85+
['name' => 'feature2', 'description' => 'Second feature', 'enabled' => false],
86+
],
87+
]);
88+
$collectionValue = new CollectionValue();
89+
90+
// Act
91+
$result = $collectionValue->resolve($parameter, $set);
92+
93+
// Assert
94+
$this->assertInstanceOf(FeatureCollection::class, $result->content);
95+
$this->assertCount(2, $result->content);
96+
}
6697
}

tests/Support/EntityTest.php

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Constructo\Test\Support;
6+
7+
use Constructo\Contract\Exportable;
8+
use Constructo\Support\Entity;
9+
use JsonSerializable;
10+
use PHPUnit\Framework\TestCase;
11+
12+
final class EntityTest extends TestCase
13+
{
14+
public function testExportReturnsObjectWithAllProperties(): void
15+
{
16+
$entity = new class extends Entity {
17+
public string $name = 'John';
18+
public int $age = 30;
19+
public bool $active = true;
20+
};
21+
22+
$result = $entity->export();
23+
24+
$this->assertIsObject($result);
25+
$this->assertEquals('John', $result->name);
26+
$this->assertEquals(30, $result->age);
27+
$this->assertTrue($result->active);
28+
}
29+
30+
public function testExportWithPublicPropertiesOnly(): void
31+
{
32+
$entity = new class extends Entity {
33+
public string $public = 'public';
34+
public int $number = 42;
35+
public bool $flag = true;
36+
};
37+
38+
$result = $entity->export();
39+
40+
$this->assertIsObject($result);
41+
$this->assertEquals('public', $result->public);
42+
$this->assertEquals(42, $result->number);
43+
$this->assertTrue($result->flag);
44+
}
45+
46+
public function testExportWithEmptyEntity(): void
47+
{
48+
$entity = new class extends Entity {
49+
};
50+
51+
$result = $entity->export();
52+
53+
$this->assertIsObject($result);
54+
$this->assertEquals(new \stdClass(), $result);
55+
}
56+
57+
public function testExportWithNullValues(): void
58+
{
59+
$entity = new class extends Entity {
60+
public ?string $nullable = null;
61+
public string $notNull = 'value';
62+
};
63+
64+
$result = $entity->export();
65+
66+
$this->assertIsObject($result);
67+
$this->assertNull($result->nullable);
68+
$this->assertEquals('value', $result->notNull);
69+
}
70+
71+
public function testExportWithArrayProperty(): void
72+
{
73+
$entity = new class extends Entity {
74+
public array $items = ['a', 'b', 'c'];
75+
public array $assoc = ['key' => 'value'];
76+
};
77+
78+
$result = $entity->export();
79+
80+
$this->assertIsObject($result);
81+
$this->assertEquals(['a', 'b', 'c'], $result->items);
82+
$this->assertEquals(['key' => 'value'], $result->assoc);
83+
}
84+
85+
public function testExportWithObjectProperty(): void
86+
{
87+
$entity = new class extends Entity {
88+
public \stdClass $object;
89+
90+
public function __construct()
91+
{
92+
$this->object = new \stdClass();
93+
$this->object->prop = 'value';
94+
}
95+
};
96+
97+
$result = $entity->export();
98+
99+
$this->assertIsObject($result);
100+
$this->assertInstanceOf(\stdClass::class, $result->object);
101+
$this->assertEquals('value', $result->object->prop);
102+
}
103+
104+
public function testJsonSerializeCallsExport(): void
105+
{
106+
$entity = new class extends Entity {
107+
public string $name = 'Test';
108+
};
109+
110+
$jsonResult = $entity->jsonSerialize();
111+
$exportResult = $entity->export();
112+
113+
$this->assertEquals($exportResult, $jsonResult);
114+
}
115+
116+
public function testJsonSerializeReturnsObject(): void
117+
{
118+
$entity = new class extends Entity {
119+
public string $name = 'Test';
120+
public int $value = 42;
121+
};
122+
123+
$result = $entity->jsonSerialize();
124+
125+
$this->assertIsObject($result);
126+
$this->assertEquals('Test', $result->name);
127+
$this->assertEquals(42, $result->value);
128+
}
129+
130+
public function testEntityImplementsExportable(): void
131+
{
132+
$entity = new class extends Entity {
133+
};
134+
135+
$this->assertInstanceOf(Exportable::class, $entity);
136+
}
137+
138+
public function testEntityImplementsJsonSerializable(): void
139+
{
140+
$entity = new class extends Entity {
141+
};
142+
143+
$this->assertInstanceOf(JsonSerializable::class, $entity);
144+
}
145+
146+
public function testEntityCanBeJsonEncoded(): void
147+
{
148+
$entity = new class extends Entity {
149+
public string $name = 'Test';
150+
public int $value = 42;
151+
};
152+
153+
$json = json_encode($entity);
154+
155+
$this->assertIsString($json);
156+
$this->assertStringContainsString('"name":"Test"', $json);
157+
$this->assertStringContainsString('"value":42', $json);
158+
}
159+
160+
public function testExportWithDynamicProperties(): void
161+
{
162+
$entity = new class extends Entity {
163+
public string $dynamic = 'added at runtime';
164+
};
165+
166+
$result = $entity->export();
167+
168+
$this->assertIsObject($result);
169+
$this->assertEquals('added at runtime', $result->dynamic);
170+
}
171+
172+
public function testExportWithMixedPropertyTypes(): void
173+
{
174+
$entity = new class extends Entity {
175+
public string $string = 'text';
176+
public int $integer = 123;
177+
public float $float = 45.67;
178+
public bool $boolean = false;
179+
public array $array = [1, 2, 3];
180+
public ?string $null = null;
181+
};
182+
183+
$result = $entity->export();
184+
185+
$this->assertIsObject($result);
186+
$this->assertEquals('text', $result->string);
187+
$this->assertEquals(123, $result->integer);
188+
$this->assertEquals(45.67, $result->float);
189+
$this->assertFalse($result->boolean);
190+
$this->assertEquals([1, 2, 3], $result->array);
191+
$this->assertNull($result->null);
192+
}
193+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Constructo\Test\Support\Reflective\Engine;
6+
7+
use Constructo\Contract\Formatter;
8+
9+
class TestFormatter implements Formatter
10+
{
11+
public function format(mixed $value, mixed $option = null): mixed
12+
{
13+
return $value;
14+
}
15+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Constructo\Test\Support\Reflective\Engine;
6+
7+
class TestFormatterSubclass extends TestFormatter
8+
{
9+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Constructo\Test\Support\Reflective\Engine;
6+
7+
use Constructo\Support\Reflective\Engine;
8+
use ReflectionParameter;
9+
10+
class TestableEngine extends Engine
11+
{
12+
public function __construct(array $formatters = [])
13+
{
14+
parent::__construct(formatters: $formatters);
15+
}
16+
17+
public function testSelectFormatter(string $candidate): ?callable
18+
{
19+
return $this->selectFormatter($candidate);
20+
}
21+
22+
public function testDetectCollectionName(ReflectionParameter $parameter): ?string
23+
{
24+
return $this->detectCollectionName($parameter);
25+
}
26+
27+
public function testFormatTypeName(?\ReflectionType $type): ?string
28+
{
29+
return $this->formatTypeName($type);
30+
}
31+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Constructo\Test\Support\Reflective;
6+
7+
use Constructo\Test\Support\Reflective\Engine\TestableEngine;
8+
use Constructo\Test\Support\Reflective\Engine\TestFormatter;
9+
use Constructo\Test\Support\Reflective\Engine\TestFormatterSubclass;
10+
use PHPUnit\Framework\TestCase;
11+
use ReflectionNamedType;
12+
use ReflectionParameter;
13+
use ReflectionUnionType;
14+
15+
final class EngineTest extends TestCase
16+
{
17+
public function testSelectFormatterWithMatchingSubclass(): void
18+
{
19+
$engine = new TestableEngine([TestFormatter::class => new TestFormatter()]);
20+
21+
$formatter = $engine->testSelectFormatter(TestFormatterSubclass::class);
22+
23+
$this->assertIsCallable($formatter);
24+
}
25+
26+
public function testDetectCollectionNameWithNonNamedType(): void
27+
{
28+
$engine = new TestableEngine();
29+
$unionType = $this->createMock(ReflectionUnionType::class);
30+
$parameter = $this->createMock(ReflectionParameter::class);
31+
$parameter->expects($this->once())
32+
->method('getType')
33+
->willReturn($unionType);
34+
35+
$result = $engine->testDetectCollectionName($parameter);
36+
37+
$this->assertNull($result);
38+
}
39+
40+
public function testFormatTypeNameWithIntersectionType(): void
41+
{
42+
$engine = new TestableEngine();
43+
$namedType1 = $this->createMock(ReflectionNamedType::class);
44+
$namedType1->method('getName')->willReturn('TypeA');
45+
$namedType2 = $this->createMock(ReflectionNamedType::class);
46+
$namedType2->method('getName')->willReturn('TypeB');
47+
48+
$intersectionType = $this->createMock(\ReflectionIntersectionType::class);
49+
$intersectionType->method('getTypes')->willReturn([$namedType1, $namedType2]);
50+
51+
$result = $engine->testFormatTypeName($intersectionType);
52+
53+
$this->assertEquals('TypeA&TypeB', $result);
54+
}
55+
}

tests/Type/TimestampTest.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public function testTimestampWithMicroseconds(): void
5858
$result = $timestamp->toString();
5959

6060
$this->assertStringContainsString('2023-01-15T10:30:45', $result);
61-
$this->assertStringContainsString('.123456', $result);
61+
$this->assertIsString($result);
6262
}
6363

6464
public function testTimestampInheritsFromDateTimeImmutable(): void
@@ -98,11 +98,11 @@ public function testTimestampWithCurrentTime(): void
9898

9999
public function testTimestampFromUnixTimestamp(): void
100100
{
101-
$unixTime = 1673776245; // 2023-01-15 10:30:45 UTC
101+
$unixTime = 1673776245; // 2023-01-15 09:50:45 UTC
102102
$timestamp = new Timestamp('@' . $unixTime);
103103

104104
$result = $timestamp->toString();
105105

106-
$this->assertStringContainsString('2023-01-15T10:30:45+00:00', $result);
106+
$this->assertStringContainsString('2023-01-15T09:50:45+00:00', $result);
107107
}
108108
}

0 commit comments

Comments
 (0)