Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
vendor/
.idea
.phpunit.result.cache
coverage/
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,16 @@ $apiClient = new ApiClient();
$response = $apiClient->get("https://httpbin.org/get");
```

## Testing

Run the test suite:

```bash
./vendor/bin/phpunit
```

See the [tests/README.md](tests/README.md) for more information.

## License

MIT
7 changes: 6 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"type": "library",
"require": {
"guzzlehttp/guzzle": "^7.0",
"php": "^7.3",
"php": "^7.3|^8.0",
"ext-json": "*",
"ext-simplexml": "*",
"liopoos/http-code": "^1.0"
Expand All @@ -23,5 +23,10 @@
"psr-4": {
"Liopoos\\Booze\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Liopoos\\Booze\\Tests\\": "tests/"
}
}
}
17 changes: 17 additions & 0 deletions phpunit.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
colors="true"
verbose="true">
<testsuites>
<testsuite name="Unit">
<directory suffix="Test.php">./tests/Unit</directory>
</testsuite>
</testsuites>
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">./src</directory>
</include>
</coverage>
</phpunit>
43 changes: 43 additions & 0 deletions tests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Booze Tests

This directory contains unit tests for the Booze HTTP library.

## Running Tests

To run all tests:

```bash
./vendor/bin/phpunit
```

To run tests with detailed output:

```bash
./vendor/bin/phpunit --testdox
```

To run tests with code coverage (requires Xdebug or PCOV):

```bash
./vendor/bin/phpunit --coverage-html coverage
```

## Test Structure

- `Unit/` - Unit tests for individual components
- `ClientTest.php` - Tests for the Client class
- `Http/HttpClientTest.php` - Tests for the HttpClient class
- `Http/Middleware/RequestHandlerMiddlewareTest.php` - Tests for request middleware
- `Http/Middleware/ResponseHandlerMiddlewareTest.php` - Tests for response middleware
- `Utils/ResponseStreamTest.php` - Tests for the ResponseStream utility
- `Exception/ExceptionTest.php` - Tests for exception classes

## Test Coverage

The test suite covers:
- All HTTP methods (GET, POST, PUT, PATCH, DELETE)
- JSON and multipart requests
- Request and response middleware
- Response stream parsing (JSON, XML, plain text)
- Exception handling
- Header management
129 changes: 129 additions & 0 deletions tests/Unit/ClientTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
<?php

namespace Liopoos\Booze\Tests\Unit;

use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Response;
use Liopoos\Booze\Client;
use PHPUnit\Framework\TestCase;

class TestClient extends Client
{
// Concrete implementation for testing abstract class
}

class ClientTest extends TestCase
{
private $mockHandler;
private $handlerStack;

protected function setUp(): void
{
$this->mockHandler = new MockHandler();
$this->handlerStack = HandlerStack::create($this->mockHandler);
}

public function testConstructorWithDefaultHandler()
{
$client = new TestClient();

$this->assertInstanceOf(Client::class, $client);
}

public function testConstructorWithCustomHandler()
{
$client = new TestClient(['handler' => $this->handlerStack]);

$this->assertInstanceOf(Client::class, $client);
}

public function testWithHeadersReturnsClientInstance()
{
$client = new TestClient(['handler' => $this->handlerStack]);

$result = $client->withHeaders(['Authorization' => 'Bearer token']);

$this->assertInstanceOf(Client::class, $result);
}

public function testWithHeadersWithMultipleHeaders()
{
$client = new TestClient(['handler' => $this->handlerStack]);

$headers = [
'Authorization' => 'Bearer token123',
'Content-Type' => 'application/json',
'Accept' => 'application/json'
];

$result = $client->withHeaders($headers);

$this->assertInstanceOf(Client::class, $result);
}

public function testGetHandlerReturnsHandlerStack()
{
$client = new TestClient(['handler' => $this->handlerStack]);

// Use reflection to access protected method
$reflection = new \ReflectionClass($client);
$method = $reflection->getMethod('getHandler');
$method->setAccessible(true);

$handler = $method->invoke($client);

$this->assertInstanceOf(HandlerStack::class, $handler);
}

public function testClientWithGuzzleOptions()
{
$options = [
'handler' => $this->handlerStack,
'verify' => false,
'timeout' => 30
];

$client = new TestClient($options);

$this->assertInstanceOf(Client::class, $client);
}

public function testClientCanMakeRequest()
{
$this->mockHandler->append(
new Response(200, ['Content-Type' => 'text/plain'], 'Success')
);

$client = new TestClient(['handler' => $this->handlerStack]);
$result = $client->get('https://httpbin.org/get');

$this->assertEquals('Success', $result);
}

public function testClientWithHeadersCanMakeRequest()
{
$this->mockHandler->append(
new Response(200, ['Content-Type' => 'text/plain'], 'Success with headers')
);

$client = new TestClient(['handler' => $this->handlerStack]);
$client->withHeaders(['Authorization' => 'Bearer token']);

$result = $client->get('https://httpbin.org/get');

$this->assertEquals('Success with headers', $result);
}

public function testClientChainedWithHeaders()
{
$this->mockHandler->append(
new Response(200, ['Content-Type' => 'text/plain'], 'Chained success')
);

$client = new TestClient(['handler' => $this->handlerStack]);
$result = $client->withHeaders(['X-Custom' => 'Value'])->get('https://httpbin.org/get');

$this->assertEquals('Chained success', $result);
}
}
80 changes: 80 additions & 0 deletions tests/Unit/Exception/ExceptionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?php

namespace Liopoos\Booze\Tests\Unit\Exception;

use GuzzleHttp\Exception\TransferException;
use Liopoos\Booze\Exception\AccessDeniedHttpException;
use Liopoos\Booze\Exception\ApiException;
use Liopoos\Booze\Exception\NotFoundHttpException;
use Liopoos\Booze\Exception\UnauthorizedHttpException;
use PHPUnit\Framework\TestCase;

class ExceptionTest extends TestCase
{
public function testApiExceptionExtendsTransferException()
{
$exception = new ApiException('Test error');

$this->assertInstanceOf(TransferException::class, $exception);
$this->assertEquals('Test error', $exception->getMessage());
}

public function testNotFoundHttpExceptionExtendsApiException()
{
$exception = new NotFoundHttpException('Not found');

$this->assertInstanceOf(ApiException::class, $exception);
$this->assertInstanceOf(TransferException::class, $exception);
$this->assertEquals('Not found', $exception->getMessage());
}

public function testUnauthorizedHttpExceptionExtendsApiException()
{
$exception = new UnauthorizedHttpException('Unauthorized');

$this->assertInstanceOf(ApiException::class, $exception);
$this->assertInstanceOf(TransferException::class, $exception);
$this->assertEquals('Unauthorized', $exception->getMessage());
}

public function testAccessDeniedHttpExceptionExtendsApiException()
{
$exception = new AccessDeniedHttpException('Access denied');

$this->assertInstanceOf(ApiException::class, $exception);
$this->assertInstanceOf(TransferException::class, $exception);
$this->assertEquals('Access denied', $exception->getMessage());
}

public function testApiExceptionWithCode()
{
$exception = new ApiException('Error', 500);

$this->assertEquals('Error', $exception->getMessage());
$this->assertEquals(500, $exception->getCode());
}

public function testNotFoundHttpExceptionWithCode()
{
$exception = new NotFoundHttpException('Resource not found', 404);

$this->assertEquals('Resource not found', $exception->getMessage());
$this->assertEquals(404, $exception->getCode());
}

public function testUnauthorizedHttpExceptionWithCode()
{
$exception = new UnauthorizedHttpException('Invalid credentials', 401);

$this->assertEquals('Invalid credentials', $exception->getMessage());
$this->assertEquals(401, $exception->getCode());
}

public function testAccessDeniedHttpExceptionWithCode()
{
$exception = new AccessDeniedHttpException('Forbidden resource', 403);

$this->assertEquals('Forbidden resource', $exception->getMessage());
$this->assertEquals(403, $exception->getCode());
}
}
Loading