Skip to content

Commit 81f6814

Browse files
committed
Tests
1 parent 6928368 commit 81f6814

File tree

54 files changed

+2117
-2
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+2117
-2
lines changed

test/Exercise/TheAttributesOfSuccessTest.php

Lines changed: 178 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@
44

55
use PhpSchool\PHP8Appreciate\Exercise\TheAttributesOfSuccess;
66
use PhpSchool\PhpWorkshop\Application;
7+
use PhpSchool\PhpWorkshop\Result\Cli\RequestFailure;
8+
use PhpSchool\PhpWorkshop\Result\Failure;
9+
use PhpSchool\PhpWorkshop\Result\FileComparisonFailure;
10+
use PhpSchool\PhpWorkshop\Result\FunctionRequirementsFailure;
711
use PhpSchool\PhpWorkshop\TestUtils\WorkshopExerciseTest;
8-
use PHPUnit\Framework\TestCase;
912

1013
class TheAttributesOfSuccessTest extends WorkshopExerciseTest
1114
{
@@ -24,25 +27,199 @@ public function testSuccessfulSolution(): void
2427
$this->runExercise('correct-solution/solution.php');
2528

2629
$this->assertVerifyWasSuccessful();
30+
$this->assertOutputWasCorrect();
31+
}
32+
33+
public function testSuccessfulSolutionWithPromotedProperty(): void
34+
{
35+
$this->runExercise('correct-solution-promoted/solution.php');
36+
37+
$this->assertVerifyWasSuccessful();
38+
$this->assertOutputWasCorrect();
2739
}
2840

2941
public function testModifyingExternalCodeFails(): void
3042
{
43+
$this->runExercise('modified-external-code/solution.php');
3144

45+
$this->assertVerifyWasNotSuccessful();
46+
$this->assertOutputWasCorrect();
47+
48+
$this->assertResultsHasFailureAndMatches(
49+
FileComparisonFailure::class,
50+
function (FileComparisonFailure $failure) {
51+
self::assertEquals('deserialize.php', $failure->getFileName());
52+
53+
return true;
54+
}
55+
);
3256
}
3357

3458
public function testNotCallingDeserializeFails(): void
3559
{
60+
$this->runExercise('no-deserialize-call/solution.php');
3661

62+
$this->assertVerifyWasNotSuccessful();
63+
$this->assertOutputWasIncorrect();
64+
65+
$this->assertResultsHasFailureAndMatches(
66+
FunctionRequirementsFailure::class,
67+
function (FunctionRequirementsFailure $failure) {
68+
self::assertSame(['deserialize'], $failure->getMissingFunctions());
69+
return true;
70+
}
71+
);
3772
}
3873

3974
public function testNotDumpingObjectFails(): void
4075
{
76+
$this->runExercise('no-var-dump/solution.php');
77+
78+
$this->assertVerifyWasNotSuccessful();
79+
$this->assertOutputWasIncorrect();
4180

81+
$this->assertResultsHasFailureAndMatches(
82+
FunctionRequirementsFailure::class,
83+
function (FunctionRequirementsFailure $failure) {
84+
self::assertSame(['var_dump'], $failure->getMissingFunctions());
85+
return true;
86+
}
87+
);
4288
}
4389

4490
public function testWhenOutputIsIncorrectComparisonFails(): void
4591
{
92+
$this->runExercise('incorrect-output/solution.php');
93+
94+
$this->assertVerifyWasNotSuccessful();
95+
$this->assertOutputWasIncorrect();
96+
97+
$output = $this->getOutputResult();
98+
99+
self::assertCount(1, $output->getResults());
100+
self::assertInstanceOf(RequestFailure::class, $output->getResults()[0]);
101+
}
102+
103+
public function testWhenNoClassNamedReviewDefined(): void
104+
{
105+
$this->runExercise('no-review-class/solution.php');
106+
107+
$this->assertVerifyWasNotSuccessful();
108+
109+
$this->assertResultsHasFailure(Failure::class, 'A class named Review was not found');
110+
}
111+
112+
public function testWhenNoMethodNamedObfuscateReviewerDefined(): void
113+
{
114+
$this->runExercise('no-obfuscate-method/solution.php');
115+
116+
$this->assertVerifyWasNotSuccessful();
117+
118+
$this->assertResultsHasFailure(Failure::class, 'A method named obfuscateReviewer was not found');
119+
}
120+
121+
public function testWhenNoAttributeDefinedOnObfuscateReviewerMethod(): void
122+
{
123+
$this->runExercise('no-attributes/solution.php');
124+
125+
$this->assertVerifyWasNotSuccessful();
126+
127+
$this->assertResultsHasFailure(Failure::class, 'No attributes found on method obfuscateReviewer');
128+
}
129+
130+
public function testWhenNoAttributedNamedObfuscateUsedOnMethod(): void
131+
{
132+
$this->runExercise('no-attribute-named-obfuscate/solution.php');
133+
134+
$this->assertVerifyWasNotSuccessful();
135+
136+
$this->assertResultsHasFailure(
137+
Failure::class,
138+
'No attribute named Obfuscate found on method obfuscateReviewer'
139+
);
140+
}
141+
142+
public function testWhenNoArgumentsPassedToObfuscateAttribute(): void
143+
{
144+
$this->runExercise('no-arguments-obfuscate-attribute/solution.php');
145+
146+
$this->assertVerifyWasNotSuccessful();
147+
148+
$this->assertResultsHasFailure(
149+
Failure::class,
150+
'No property name argument was passed to the Obfuscate attribute'
151+
);
152+
}
153+
154+
public function testWhenIncorrectPropertyPassedToObfuscateAttribute(): void
155+
{
156+
$this->runExercise('invalid-arg-obfuscate-attribute/solution.php');
157+
158+
$this->assertVerifyWasNotSuccessful();
159+
160+
$this->assertResultsHasFailure(
161+
Failure::class,
162+
'The Obfuscate attribute was not passed the correct data property'
163+
);
164+
}
165+
166+
public function testWhenObfuscateAttributeNotDefined(): void
167+
{
168+
$this->runExercise('no-obfuscate-class/solution.php');
169+
170+
$this->assertVerifyWasNotSuccessful();
171+
172+
$this->assertResultsHasFailure(Failure::class, 'A class named Obfuscate was not found');
173+
}
174+
175+
public function testWhenObfuscateHasNoAttributes(): void
176+
{
177+
$this->runExercise('obfuscate-no-attributes/solution.php');
178+
179+
$this->assertVerifyWasNotSuccessful();
180+
181+
$this->assertResultsHasFailure(Failure::class, 'No attributes found on class Obfuscate');
182+
}
183+
184+
public function testWhenObfuscateAttributeIncorrectlyDefined(): void
185+
{
186+
$this->runExercise('obfuscate-attribute-incorrect/solution.php');
187+
188+
$this->assertVerifyWasNotSuccessful();
189+
190+
$this->assertResultsHasFailure(Failure::class, 'The Obfuscate class was not defined as an Attribute');
191+
}
192+
193+
public function testWhenObfuscateAttributeHasNoFlags(): void
194+
{
195+
$this->runExercise('obfuscate-attribute-no-flags/solution.php');
196+
197+
$this->assertVerifyWasNotSuccessful();
198+
199+
$this->assertResultsHasFailure(Failure::class, 'No flags were passed to Obfuscate Attribute definition');
200+
}
201+
202+
public function testWhenObfuscateAttributeConfigurationIsWrong(): void
203+
{
204+
$this->runExercise('obfuscate-attribute-wrong-target/solution.php');
205+
206+
$this->assertVerifyWasNotSuccessful();
207+
208+
$this->assertResultsHasFailure(
209+
Failure::class,
210+
'The Obfuscate Attribute was not configured as Attribute::TARGET_METHOD'
211+
);
212+
}
213+
214+
public function testWhenObfuscateAttributeHasNoPublicPropertyNamedKey(): void
215+
{
216+
$this->runExercise('no-public-property-named-key/solution.php');
217+
218+
$this->assertVerifyWasNotSuccessful();
46219

220+
$this->assertResultsHasFailure(
221+
Failure::class,
222+
'The Obfuscate Attribute has no public property named "key"'
223+
);
47224
}
48225
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
#[Attribute(Attribute::TARGET_CLASS)]
4+
class Deserialize {
5+
6+
}
7+
8+
#[Attribute(Attribute::TARGET_PROPERTY)]
9+
class Map {
10+
public function __construct(public string $mapFrom)
11+
{
12+
}
13+
}
14+
15+
#[Attribute(Attribute::TARGET_PROPERTY)]
16+
class Skip {
17+
18+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
3+
function camelCaseToSnakeCase(string $string): string
4+
{
5+
return strtolower(preg_replace(['/([a-z\d])([A-Z])/', '/([^_])([A-Z][a-z])/'], '$1_$2', $string));
6+
}
7+
8+
function deserialize(string $data, string $className): object
9+
{
10+
$reflectionClass = new \ReflectionClass($className);
11+
$attrs = $reflectionClass->getAttributes(Deserialize::class);
12+
13+
if (empty($attrs)) {
14+
throw new \RuntimeException('Class cannot be deserialized');
15+
}
16+
17+
$attrs[0]->newInstance();
18+
19+
$object = new $className();
20+
21+
$data = json_decode($data, true);
22+
23+
$obfuscators = array_filter(
24+
$reflectionClass->getMethods(),
25+
fn (ReflectionMethod $m) => count($m->getAttributes(Obfuscate::class)) > 0
26+
);
27+
28+
$obfuscators = array_combine(
29+
array_map(
30+
fn(ReflectionMethod $m) => $m->getAttributes(Obfuscate::class)[0]->newInstance()->key,
31+
$obfuscators
32+
),
33+
$obfuscators
34+
);
35+
36+
foreach ($data as $key => $value) {
37+
if (isset($obfuscators[$key])) {
38+
$data[$key] = $object->{$obfuscators[$key]->getName()}($value);
39+
}
40+
}
41+
42+
foreach ($reflectionClass->getProperties() as $property) {
43+
if ($map = $property->getAttributes(Map::class)) {
44+
$key = $map[0]->newInstance()->mapFrom;
45+
46+
if (isset($data[$key])) {
47+
$object->{$property->getName()} = $data[$key];
48+
}
49+
50+
} elseif ($skip = $property->getAttributes(Skip::class)) {
51+
continue;
52+
} elseif (isset($data[camelCaseToSnakeCase($property->getName())])) {
53+
$object->{$property->getName()} = $data[camelCaseToSnakeCase($property->getName())];
54+
}
55+
}
56+
57+
return $object;
58+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
require_once __DIR__ . '/deserialize.php';
4+
require_once __DIR__ . '/attributes.php';
5+
6+
7+
#[Attribute(Attribute::TARGET_METHOD)]
8+
class Obfuscate {
9+
public function __construct(public string $key)
10+
{
11+
}
12+
}
13+
14+
#[Deserialize]
15+
class Review {
16+
public string $comment;
17+
18+
#[Map('rating')]
19+
public string $starRating;
20+
21+
public string $date;
22+
23+
#[Skip()]
24+
public string $id;
25+
26+
public ?string $reviewer = null;
27+
28+
#[Obfuscate('reviewer')]
29+
public function obfuscateReviewer(string $reviewer): string
30+
{
31+
return md5($reviewer);
32+
}
33+
}
34+
35+
$object = deserialize($argv[1], Review::class);
36+
37+
var_dump($object);

test/solutions/the-attributes-of-success/correct-solution/deserialize.php

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,29 @@ function deserialize(string $data, string $className): object
1616

1717
$attrs[0]->newInstance();
1818

19-
$object = new $className;
19+
$object = new $className();
2020

2121
$data = json_decode($data, true);
2222

23+
$obfuscators = array_filter(
24+
$reflectionClass->getMethods(),
25+
fn (ReflectionMethod $m) => count($m->getAttributes(Obfuscate::class)) > 0
26+
);
27+
28+
$obfuscators = array_combine(
29+
array_map(
30+
fn(ReflectionMethod $m) => $m->getAttributes(Obfuscate::class)[0]->newInstance()->key,
31+
$obfuscators
32+
),
33+
$obfuscators
34+
);
35+
36+
foreach ($data as $key => $value) {
37+
if (isset($obfuscators[$key])) {
38+
$data[$key] = $object->{$obfuscators[$key]->getName()}($value);
39+
}
40+
}
41+
2342
foreach ($reflectionClass->getProperties() as $property) {
2443
if ($map = $property->getAttributes(Map::class)) {
2544
$key = $map[0]->newInstance()->mapFrom;

test/solutions/the-attributes-of-success/correct-solution/solution.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,16 @@
33
require_once __DIR__ . '/deserialize.php';
44
require_once __DIR__ . '/attributes.php';
55

6+
7+
#[Attribute(Attribute::TARGET_METHOD)]
8+
class Obfuscate {
9+
public string $key;
10+
public function __construct(string $key)
11+
{
12+
$this->key = $key;
13+
}
14+
}
15+
616
#[Deserialize]
717
class Review {
818
public string $comment;
@@ -13,7 +23,15 @@ class Review {
1323
public string $date;
1424

1525
#[Skip()]
26+
public string $id;
27+
1628
public ?string $reviewer = null;
29+
30+
#[Obfuscate('reviewer')]
31+
public function obfuscateReviewer(string $reviewer): string
32+
{
33+
return md5($reviewer);
34+
}
1735
}
1836

1937
$object = deserialize($argv[1], Review::class);

0 commit comments

Comments
 (0)