Skip to content

Commit a20f505

Browse files
authored
feat: add query credential authentication method (#10)
1 parent 9f82454 commit a20f505

File tree

7 files changed

+170
-9
lines changed

7 files changed

+170
-9
lines changed

composer.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"require": {
1212
"php": "^8.0",
1313
"ext-json": "*",
14-
"dogado/json-api-common": "^2.0",
14+
"dogado/json-api-common": "^3.0",
1515
"psr/http-factory": "^1.0",
1616
"psr/http-client": "^1.0"
1717
},
@@ -39,7 +39,10 @@
3939
"@cs"
4040
],
4141
"unit": "phpunit",
42-
"coverage": "phpunit --log-junit build/logs/unitreport.xml --coverage-html build/coverage",
42+
"coverage": [
43+
"@putenv XDEBUG_MODE=coverage",
44+
"phpunit --log-junit build/logs/unitreport.xml --coverage-html build/coverage"
45+
],
4346
"stan": "phpstan analyse",
4447
"cs": "phpcs --standard=phpcs.xml"
4548
},

src/Exception/ResponseValidationException.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ class ResponseValidationException extends Exception
1616
public const CODE_TYPE_MISMATCH = 103;
1717
public const CODE_RESOURCE_ID_EMPTY = 104;
1818
public const CODE_SCALAR_RESULT_EXPECTED = 105;
19+
public const CODE_RESOURCE_ID_FOUND = 106;
1920

2021
protected ResponseInterface $response;
2122

@@ -78,6 +79,17 @@ public static function resourceIdEmpty(
7879
);
7980
}
8081

82+
public static function resourceIdFound(ResponseInterface $response): self
83+
{
84+
return new self(
85+
sprintf(
86+
'JSON API response contains resources with IDs, while none is expected'
87+
),
88+
self::CODE_RESOURCE_ID_FOUND,
89+
$response
90+
);
91+
}
92+
8193
public static function scalarResultExpected(ResponseInterface $response, int $totalResources): self
8294
{
8395
return new self(

src/Middleware/AuthenticationMiddleware.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@
66

77
use Dogado\JsonApi\Client\Model\BasicCredentials;
88
use Dogado\JsonApi\Client\Model\OAuth2Credentials;
9+
use Dogado\JsonApi\Client\Model\QueryCredentials;
910
use Dogado\JsonApi\Model\Request\RequestInterface;
1011

1112
class AuthenticationMiddleware implements AuthenticationMiddlewareInterface
1213
{
1314
protected ?OAuth2Credentials $oauth2Credentials = null;
1415
protected ?BasicCredentials $basicCredentials = null;
16+
protected ?QueryCredentials $queryCredentials = null;
1517

1618
public function setOAuth2Credentials(OAuth2Credentials $oauth2Credentials): self
1719
{
@@ -25,6 +27,12 @@ public function setBasicCredentials(BasicCredentials $basicCredentials): self
2527
return $this;
2628
}
2729

30+
public function setQueryCredentials(QueryCredentials $queryCredentials): self
31+
{
32+
$this->queryCredentials = $queryCredentials;
33+
return $this;
34+
}
35+
2836
public function authenticateRequest(RequestInterface $request): void
2937
{
3038
if (null !== $this->oauth2Credentials) {
@@ -40,5 +48,9 @@ public function authenticateRequest(RequestInterface $request): void
4048
),
4149
]);
4250
}
51+
52+
if (null !== $this->queryCredentials) {
53+
$request->customQueryParameters()->mergeCollection($this->queryCredentials);
54+
}
4355
}
4456
}

src/Model/QueryCredentials.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Dogado\JsonApi\Client\Model;
6+
7+
use Dogado\JsonApi\Support\Collection\KeyValueCollection;
8+
9+
class QueryCredentials extends KeyValueCollection
10+
{
11+
12+
}

src/Validator/ResponseValidator.php

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,14 +65,60 @@ public function assertResourcesMatchTypeAndContainIds(ResponseInterface $respons
6565
/**
6666
* @throws ResponseValidationException
6767
*/
68-
public function assertScalarResultWithId(ResponseInterface $response, string $type): void
68+
public function assertResourcesMatchType(ResponseInterface $response, string $type): void
6969
{
70-
$this->assertDataNotEmpty($response);
71-
$this->assertResourcesMatchTypeAndContainIds($response, $type);
70+
$this->assertDocument($response);
7271

72+
assert($response->document() instanceof DocumentInterface);
73+
foreach ($response->document()->data()->all() as $key => $resource) {
74+
if ($type !== $resource->type()) {
75+
throw ResponseValidationException::typeMismatch($response, $type, $resource->type(), $key);
76+
}
77+
}
78+
}
79+
80+
/**
81+
* @throws ResponseValidationException
82+
*/
83+
public function assertScalarResult(ResponseInterface $response): void
84+
{
7385
assert($response->document() instanceof DocumentInterface);
7486
if (1 !== $response->document()->data()->count()) {
7587
throw ResponseValidationException::scalarResultExpected($response, $response->document()->data()->count());
7688
}
7789
}
90+
91+
/**
92+
* @throws ResponseValidationException
93+
*/
94+
public function assertScalarResultWithId(ResponseInterface $response, string $type): void
95+
{
96+
$this->assertDataNotEmpty($response);
97+
$this->assertResourcesMatchTypeAndContainIds($response, $type);
98+
$this->assertScalarResult($response);
99+
}
100+
101+
/**
102+
* @throws ResponseValidationException
103+
*/
104+
public function assertScalarResultWithoutId(ResponseInterface $response, string $type): void
105+
{
106+
$this->assertDataNotEmpty($response);
107+
$this->assertResourcesMatchType($response, $type);
108+
$this->assertScalarResult($response);
109+
110+
if (!empty($response->document()->data()->first()->id())) {
111+
throw ResponseValidationException::resourceIdFound($response);
112+
}
113+
}
114+
115+
/**
116+
* @throws ResponseValidationException
117+
*/
118+
public function assertScalarResultWithOptionalId(ResponseInterface $response, string $type): void
119+
{
120+
$this->assertDataNotEmpty($response);
121+
$this->assertResourcesMatchType($response, $type);
122+
$this->assertScalarResult($response);
123+
}
78124
}

tests/Middleware/AuthenticationMiddlewareTest.php

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
55
use Dogado\JsonApi\Client\Middleware\AuthenticationMiddleware;
66
use Dogado\JsonApi\Client\Model\BasicCredentials;
77
use Dogado\JsonApi\Client\Model\OAuth2Credentials;
8+
use Dogado\JsonApi\Client\Model\QueryCredentials;
89
use Dogado\JsonApi\Client\Tests\TestCase;
910
use Dogado\JsonApi\Model\Request\RequestInterface;
1011
use Dogado\JsonApi\Support\Collection\CollectionInterface;
12+
use Dogado\JsonApi\Support\Collection\KeyValueCollection;
1113
use Dogado\JsonApi\Support\Collection\KeyValueCollectionInterface;
1214
use PHPUnit\Framework\MockObject\MockObject;
1315

@@ -25,7 +27,6 @@ protected function setUp(): void
2527
$this->authMiddleware = new AuthenticationMiddleware();
2628
$this->request = $this->createMock(RequestInterface::class);
2729
$this->headers = $this->createMock(KeyValueCollectionInterface::class);
28-
$this->request->expects(self::atLeastOnce())->method('headers')->willReturn($this->headers);
2930
}
3031

3132
public function testOAuth2(): void
@@ -40,7 +41,7 @@ public function testOAuth2(): void
4041
$this->headers->expects(self::once())->method('merge')->with([
4142
'Authorization' => $oauth2Credentials->tokenType . ' ' . $oauth2Credentials->accessToken,
4243
]);
43-
44+
$this->request->expects(self::atLeastOnce())->method('headers')->willReturn($this->headers);
4445
$this->authMiddleware->authenticateRequest($this->request);
4546
}
4647

@@ -57,7 +58,23 @@ public function testBasic(): void
5758
$basicCredentials->username . ':' . $basicCredentials->password
5859
),
5960
]);
61+
$this->request->expects(self::atLeastOnce())->method('headers')->willReturn($this->headers);
62+
$this->authMiddleware->authenticateRequest($this->request);
63+
}
64+
65+
public function testQuery(): void
66+
{
67+
$queryCredentials = new QueryCredentials();
68+
$queryKey = $this->faker()->word();
69+
$queryValue = $this->faker()->word();
70+
$queryCredentials->set($queryKey, $queryValue);
71+
72+
$this->authMiddleware->setQueryCredentials($queryCredentials);
73+
74+
$requestQuery = $this->createMock(KeyValueCollection::class);
75+
$requestQuery->expects(self::once())->method('mergeCollection')->with($queryCredentials);
76+
$this->request->expects(self::once())->method('customQueryParameters')->willReturnReference($requestQuery);
6077

6178
$this->authMiddleware->authenticateRequest($this->request);
6279
}
63-
}
80+
}

tests/Validator/ResponseValidatorTest.php

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,11 +126,70 @@ public function testAssertScalarResultWithIdThrowsException(): void
126126
$this->responseValidator->assertScalarResultWithId($response, $type);
127127
}
128128

129+
public function testAssertResourcesMatchType(): void
130+
{
131+
$type = $this->faker()->slug();
132+
$response = $this->createResponse(new Document([new Resource(
133+
$type
134+
)]));
135+
$this->responseValidator->assertResourcesMatchType($response, $type);
136+
$this->assertTrue(true);
137+
}
138+
139+
public function testAssertResourcesMatchTypeAndContainIdsThrowsException(): void
140+
{
141+
$expectedType = $this->faker()->slug();
142+
$actualType = $this->faker()->slug();
143+
$response = $this->createResponse(new Document([new Resource(
144+
$actualType,
145+
(string) $this->faker()->numberBetween()
146+
)]));
147+
$this->expectExceptionObject(
148+
ResponseValidationException::typeMismatch($response, $expectedType, $actualType, 0)
149+
);
150+
$this->responseValidator->assertResourcesMatchType($response, $expectedType);
151+
}
152+
153+
public function testAssertScalarResultWithoutId(): void
154+
{
155+
$type = $this->faker()->slug();
156+
$response = $this->createResponse(new Document([new Resource($type)]));
157+
$this->responseValidator->assertScalarResultWithoutId($response, $type);
158+
$this->assertTrue(true);
159+
}
160+
161+
public function testAssertScalarResultWithoutIdThrowsException(): void
162+
{
163+
$type = $this->faker()->slug();
164+
$response = $this->createResponse(new Document([new Resource(
165+
$type,
166+
(string) $this->faker()->numberBetween()
167+
)]));
168+
$this->expectExceptionObject(
169+
ResponseValidationException::resourceIdFound($response)
170+
);
171+
$this->responseValidator->assertScalarResultWithoutId($response, $type);
172+
}
173+
174+
public function testAssertScalarResultWithOptionalId(): void
175+
{
176+
$type = $this->faker()->slug();
177+
$resources = [
178+
new Resource($type, (string) $this->faker()->numberBetween()),
179+
new Resource($type),
180+
];
181+
foreach ($resources as $resource) {
182+
$response = $this->createResponse(new Document([$resource]));
183+
$this->responseValidator->assertScalarResultWithOptionalId($response, $type);
184+
$this->assertTrue(true);
185+
}
186+
}
187+
129188
private function createResponse(?DocumentInterface $document = null): ResponseInterface
130189
{
131190
$response = $this->createMock(\Psr\Http\Message\ResponseInterface::class);
132191
$response->method('getStatusCode')->willReturn(200);
133192
$response->method('getHeaders')->willReturn([]);
134193
return new Response($response, $document);
135194
}
136-
}
195+
}

0 commit comments

Comments
 (0)