Skip to content

Commit

Permalink
Merge pull request #204 from internalsystemerror/hotfix/incorrect-rel…
Browse files Browse the repository at this point in the history
…ease-api-operand

Check the response code is `>= 200 && <= 299` on release
  • Loading branch information
Ocramius committed Aug 17, 2022
2 parents d3f708a + 926b6e0 commit d90f604
Show file tree
Hide file tree
Showing 2 changed files with 187 additions and 1 deletion.
2 changes: 1 addition & 1 deletion src/Github/Api/V3/CreateReleaseThroughApiCall.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public function __invoke(

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

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

$responseBody = $response
->getBody()
Expand Down
186 changes: 186 additions & 0 deletions test/unit/Github/Api/V3/CreateReleaseThroughApiCallTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
<?php

declare(strict_types=1);

namespace Laminas\AutomaticReleases\Test\Unit\Github\Api\V3;

use Laminas\AutomaticReleases\Git\Value\SemVerVersion;
use Laminas\AutomaticReleases\Github\Api\V3\CreateReleaseThroughApiCall;
use Laminas\AutomaticReleases\Github\Value\RepositoryName;
use Laminas\Diactoros\Request;
use Laminas\Diactoros\Response;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Psl\Exception\InvariantViolationException;
use Psl\Json\Exception\DecodeException;
use Psl\SecureRandom;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestFactoryInterface;
use Psr\Http\Message\RequestInterface;

/** @covers \Laminas\AutomaticReleases\Github\Api\V3\CreateReleaseThroughApiCall */
final class CreateReleaseThroughApiCallTest extends TestCase
{
/** @var ClientInterface&MockObject */
private ClientInterface $httpClient;
/** @var RequestFactoryInterface&MockObject */
private RequestFactoryInterface $messageFactory;
/** @psalm-var non-empty-string */
private string $apiToken;
private CreateReleaseThroughApiCall $createRelease;

protected function setUp(): void
{
parent::setUp();

$this->httpClient = $this->createMock(ClientInterface::class);
$this->messageFactory = $this->createMock(RequestFactoryInterface::class);
$this->apiToken = 'apiToken' . SecureRandom\string(8);
$this->createRelease = new CreateReleaseThroughApiCall(
$this->messageFactory,
$this->httpClient,
$this->apiToken
);
}

/**
* @psalm-param positive-int $responseCode
*
* @dataProvider exampleValidResponseCodes
*/
public function testSuccessfulRequest(int $responseCode): void
{
$this->messageFactory
->method('createRequest')
->with('POST', 'https://api.github.com/repos/foo/bar/releases')
->willReturn(new Request('https://the-domain.com/the-path'));

$validResponse = (new Response())
->withStatus($responseCode);

$validResponse->getBody()
->write('{"html_url": "http://the-domain.com/release"}');

$this->httpClient
->expects(self::once())
->method('sendRequest')
->with(self::callback(function (RequestInterface $request): bool {
self::assertSame(
[
'Host' => ['the-domain.com'],
'Content-Type' => ['application/json'],
'User-Agent' => ['Ocramius\'s minimal API V3 client'],
'Authorization' => ['token ' . $this->apiToken],
],
$request->getHeaders()
);

self::assertJsonStringEqualsJsonString(
<<<'JSON'
{
"body": "A description for my awesome release",
"name": "1.2.3",
"tag_name": "1.2.3"
}
JSON
,
$request->getBody()
->__toString()
);

return true;
}))
->willReturn($validResponse);

self::assertEquals(
'http://the-domain.com/release',
$this->createRelease->__invoke(
RepositoryName::fromFullName('foo/bar'),
SemVerVersion::fromMilestoneName('1.2.3'),
'A description for my awesome release',
)
);
}

/** @psalm-return non-empty-list<array{positive-int}> */
public function exampleValidResponseCodes(): array
{
return [
[200],
[201],
[204],
];
}

/**
* @psalm-param positive-int $responseCode
*
* @dataProvider exampleFailureResponseCodes
*/
public function testRequestFailedToCreateReleaseDueToInvalidResponseCode(int $responseCode): void
{
$this->messageFactory
->method('createRequest')
->with('POST', 'https://api.github.com/repos/foo/bar/releases')
->willReturn(new Request('https://the-domain.com/the-path'));

$invalidResponse = (new Response())
->withStatus($responseCode);

$invalidResponse->getBody()
->write('{"html_url": "http://the-domain.com/release"}');

$this->httpClient
->expects(self::once())
->method('sendRequest')
->willReturn($invalidResponse);

$this->expectException(InvariantViolationException::class);
$this->expectExceptionMessage('Failed to create release through GitHub API.');

$this->createRelease->__invoke(
RepositoryName::fromFullName('foo/bar'),
SemVerVersion::fromMilestoneName('1.2.3'),
'A description for my awesome release',
);
}

/** @psalm-return non-empty-list<array{positive-int}> */
public function exampleFailureResponseCodes(): array
{
return [
[199],
[400],
[401],
[500],
];
}

public function testRequestFailedToCreateReleaseDueToInvalidResponseBody(): void
{
$this->messageFactory
->method('createRequest')
->with('POST', 'https://api.github.com/repos/foo/bar/releases')
->willReturn(new Request('https://the-domain.com/the-path'));

$invalidResponse = (new Response())
->withStatus(200);

$invalidResponse->getBody()
->write('{"invalid": "response"}');

$this->httpClient
->expects(self::once())
->method('sendRequest')
->willReturn($invalidResponse);

$this->expectException(DecodeException::class);
$this->expectExceptionMessage('"array{\'html_url\': non-empty-string}"');

$this->createRelease->__invoke(
RepositoryName::fromFullName('foo/bar'),
SemVerVersion::fromMilestoneName('1.2.3'),
'A description for my awesome release',
);
}
}

0 comments on commit d90f604

Please sign in to comment.