Skip to content

Commit d90f604

Browse files
authored
Merge pull request #204 from internalsystemerror/hotfix/incorrect-release-api-operand
Check the response code is `>= 200 && <= 299` on release
2 parents d3f708a + 926b6e0 commit d90f604

File tree

2 files changed

+187
-1
lines changed

2 files changed

+187
-1
lines changed

src/Github/Api/V3/CreateReleaseThroughApiCall.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ public function __invoke(
6060

6161
$response = $this->client->sendRequest($request);
6262

63-
Psl\invariant($response->getStatusCode() >= 200 || $response->getStatusCode() <= 299, 'Failed to create release through GitHub API.');
63+
Psl\invariant($response->getStatusCode() >= 200 && $response->getStatusCode() <= 299, 'Failed to create release through GitHub API.');
6464

6565
$responseBody = $response
6666
->getBody()
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Laminas\AutomaticReleases\Test\Unit\Github\Api\V3;
6+
7+
use Laminas\AutomaticReleases\Git\Value\SemVerVersion;
8+
use Laminas\AutomaticReleases\Github\Api\V3\CreateReleaseThroughApiCall;
9+
use Laminas\AutomaticReleases\Github\Value\RepositoryName;
10+
use Laminas\Diactoros\Request;
11+
use Laminas\Diactoros\Response;
12+
use PHPUnit\Framework\MockObject\MockObject;
13+
use PHPUnit\Framework\TestCase;
14+
use Psl\Exception\InvariantViolationException;
15+
use Psl\Json\Exception\DecodeException;
16+
use Psl\SecureRandom;
17+
use Psr\Http\Client\ClientInterface;
18+
use Psr\Http\Message\RequestFactoryInterface;
19+
use Psr\Http\Message\RequestInterface;
20+
21+
/** @covers \Laminas\AutomaticReleases\Github\Api\V3\CreateReleaseThroughApiCall */
22+
final class CreateReleaseThroughApiCallTest extends TestCase
23+
{
24+
/** @var ClientInterface&MockObject */
25+
private ClientInterface $httpClient;
26+
/** @var RequestFactoryInterface&MockObject */
27+
private RequestFactoryInterface $messageFactory;
28+
/** @psalm-var non-empty-string */
29+
private string $apiToken;
30+
private CreateReleaseThroughApiCall $createRelease;
31+
32+
protected function setUp(): void
33+
{
34+
parent::setUp();
35+
36+
$this->httpClient = $this->createMock(ClientInterface::class);
37+
$this->messageFactory = $this->createMock(RequestFactoryInterface::class);
38+
$this->apiToken = 'apiToken' . SecureRandom\string(8);
39+
$this->createRelease = new CreateReleaseThroughApiCall(
40+
$this->messageFactory,
41+
$this->httpClient,
42+
$this->apiToken
43+
);
44+
}
45+
46+
/**
47+
* @psalm-param positive-int $responseCode
48+
*
49+
* @dataProvider exampleValidResponseCodes
50+
*/
51+
public function testSuccessfulRequest(int $responseCode): void
52+
{
53+
$this->messageFactory
54+
->method('createRequest')
55+
->with('POST', 'https://api.github.com/repos/foo/bar/releases')
56+
->willReturn(new Request('https://the-domain.com/the-path'));
57+
58+
$validResponse = (new Response())
59+
->withStatus($responseCode);
60+
61+
$validResponse->getBody()
62+
->write('{"html_url": "http://the-domain.com/release"}');
63+
64+
$this->httpClient
65+
->expects(self::once())
66+
->method('sendRequest')
67+
->with(self::callback(function (RequestInterface $request): bool {
68+
self::assertSame(
69+
[
70+
'Host' => ['the-domain.com'],
71+
'Content-Type' => ['application/json'],
72+
'User-Agent' => ['Ocramius\'s minimal API V3 client'],
73+
'Authorization' => ['token ' . $this->apiToken],
74+
],
75+
$request->getHeaders()
76+
);
77+
78+
self::assertJsonStringEqualsJsonString(
79+
<<<'JSON'
80+
{
81+
"body": "A description for my awesome release",
82+
"name": "1.2.3",
83+
"tag_name": "1.2.3"
84+
}
85+
JSON
86+
,
87+
$request->getBody()
88+
->__toString()
89+
);
90+
91+
return true;
92+
}))
93+
->willReturn($validResponse);
94+
95+
self::assertEquals(
96+
'http://the-domain.com/release',
97+
$this->createRelease->__invoke(
98+
RepositoryName::fromFullName('foo/bar'),
99+
SemVerVersion::fromMilestoneName('1.2.3'),
100+
'A description for my awesome release',
101+
)
102+
);
103+
}
104+
105+
/** @psalm-return non-empty-list<array{positive-int}> */
106+
public function exampleValidResponseCodes(): array
107+
{
108+
return [
109+
[200],
110+
[201],
111+
[204],
112+
];
113+
}
114+
115+
/**
116+
* @psalm-param positive-int $responseCode
117+
*
118+
* @dataProvider exampleFailureResponseCodes
119+
*/
120+
public function testRequestFailedToCreateReleaseDueToInvalidResponseCode(int $responseCode): void
121+
{
122+
$this->messageFactory
123+
->method('createRequest')
124+
->with('POST', 'https://api.github.com/repos/foo/bar/releases')
125+
->willReturn(new Request('https://the-domain.com/the-path'));
126+
127+
$invalidResponse = (new Response())
128+
->withStatus($responseCode);
129+
130+
$invalidResponse->getBody()
131+
->write('{"html_url": "http://the-domain.com/release"}');
132+
133+
$this->httpClient
134+
->expects(self::once())
135+
->method('sendRequest')
136+
->willReturn($invalidResponse);
137+
138+
$this->expectException(InvariantViolationException::class);
139+
$this->expectExceptionMessage('Failed to create release through GitHub API.');
140+
141+
$this->createRelease->__invoke(
142+
RepositoryName::fromFullName('foo/bar'),
143+
SemVerVersion::fromMilestoneName('1.2.3'),
144+
'A description for my awesome release',
145+
);
146+
}
147+
148+
/** @psalm-return non-empty-list<array{positive-int}> */
149+
public function exampleFailureResponseCodes(): array
150+
{
151+
return [
152+
[199],
153+
[400],
154+
[401],
155+
[500],
156+
];
157+
}
158+
159+
public function testRequestFailedToCreateReleaseDueToInvalidResponseBody(): void
160+
{
161+
$this->messageFactory
162+
->method('createRequest')
163+
->with('POST', 'https://api.github.com/repos/foo/bar/releases')
164+
->willReturn(new Request('https://the-domain.com/the-path'));
165+
166+
$invalidResponse = (new Response())
167+
->withStatus(200);
168+
169+
$invalidResponse->getBody()
170+
->write('{"invalid": "response"}');
171+
172+
$this->httpClient
173+
->expects(self::once())
174+
->method('sendRequest')
175+
->willReturn($invalidResponse);
176+
177+
$this->expectException(DecodeException::class);
178+
$this->expectExceptionMessage('"array{\'html_url\': non-empty-string}"');
179+
180+
$this->createRelease->__invoke(
181+
RepositoryName::fromFullName('foo/bar'),
182+
SemVerVersion::fromMilestoneName('1.2.3'),
183+
'A description for my awesome release',
184+
);
185+
}
186+
}

0 commit comments

Comments
 (0)