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
5 changes: 4 additions & 1 deletion .horde.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,13 @@ dependencies:
psr/http-client: ^1.0.3
dev:
composer:
horde/test: ^3
horde/url: ^3
optional:
ext:
curl: '*'
http: '*'
vendor: horde

quality:
phpstan:
level: 9
53 changes: 53 additions & 0 deletions TESTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Testing Horde_Http

## Test Suites

The test suite follows PSR-4 structure. It is divided into two types:

### Unit Tests (test/Unit/)
Fast and isolated tests which don't make real HTTP calls. Run these during development:

```bash
phpunit --testsuite="horde/http unit tests"
```

Time: ~0.4 seconds, 130 tests (includes mock client tests)

### Live Integration Tests (test/Live/)
Slow tests that make real HTTP calls to external servers. Run these as needed to debug specific issues:

```bash
phpunit --testsuite="horde/http live integration tests"
```

Time: ~10+ seconds (network dependent), 24 tests

## Running All Tests

```bash
# Default: runs unit tests only (fast ~0.3s)
phpunit

# Run live integration tests
phpunit --testsuite="horde/http live integration tests"

# Run all tests (unit + live)
phpunit --testsuite="horde/http unit tests" --testsuite="horde/http live integration tests"
```

## Test Configuration

Integration tests use horde.org by default. Set the `HTTP_TEST_CONFIG` environment variable for a different target:

```bash
export HTTP_TEST_CONFIG='{"http":{"server":"example.com"}}'
phpunit --testsuite="horde/http live integration tests"
```

## Test Structure

Tests use PSR-4 autoloading:
- test/Unit/ => Horde\Http\Test\Unit\
- test/Live/ => Horde\Http\Test\Live\

No bootstrap file needed - phpunit.xml.dist directly uses `vendor/autoload.php`.
15 changes: 5 additions & 10 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,19 @@
"role": "lead"
}
],
"time": "2026-03-07",
"time": "2026-03-23",
"repositories": [],
"require": {
"php": "^7.4 || ^8",
"horde/exception": "^3 || dev-FRAMEWORK_6_0",
"horde/support": "^3 || dev-FRAMEWORK_6_0",
"horde/exception": "^3",
"horde/support": "^3",
"psr/http-message": "^2",
"psr/http-factory": "^1.0.2",
"psr/http-client": "^1.0.3"
},
"require-dev": {
"horde/test": "^3 || dev-FRAMEWORK_6_0",
"horde/url": "^3 || dev-FRAMEWORK_6_0"
"horde/test": "^3",
"horde/url": "^3"
},
"suggest": {
"ext-curl": "*",
Expand Down Expand Up @@ -57,10 +57,5 @@
"psr/http-message-implementation": "^2",
"psr/http-factory-implementation": "^1.0.2",
"psr/http-client-implementation": "^1.0.3"
},
"extra": {
"branch-alias": {
"dev-FRAMEWORK_6_0": "3.x-dev"
}
}
}
3 changes: 2 additions & 1 deletion doc/Horde/Http/examples/get-example-dot-com.php
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
<?php

/**
* Basic example for fetching a page with Horde_Http_Client
*
* Copyright 2007-2017 Horde LLC (http://www.horde.org/)
* Copyright 2007-2026 Horde LLC (http://www.horde.org/)
*
* @author Chuck Hagenbuch <[email protected]>
* @license http://www.horde.org/licenses/bsd BSD
Expand Down
23 changes: 12 additions & 11 deletions doc/Horde/Http/examples/get-horde.org.php
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
<?php

/**
* Basic example for fetching a page with Horde\Http\Client
*
* Copyright 2007-2017 Horde LLC (http://www.horde.org/)
* Copyright 2007-2026 Horde LLC (http://www.horde.org/)
*
* @author Chuck Hagenbuch <[email protected]>
* @license http://www.horde.org/licenses/bsd BSD
Expand Down Expand Up @@ -34,25 +35,25 @@
// PSR-18 client

$client = new CurlClient(
new ResponseFactory,
new StreamFactory,
new ClientOptions
new ResponseFactory(),
new StreamFactory(),
new ClientOptions()
);
print("Modern PSR-18 Curl Client: Plain HTTP GET\n");
$requestFactory = new RequestFactory;
$requestFactory = new RequestFactory();
$request = $requestFactory->createRequest('GET', 'http://www.horde.org');
$response = $client->sendRequest($request);
print($response->getStatusCode() . "\n\n" . $response->getReasonPhrase() ."\n\n");
print($response->getStatusCode() . "\n\n" . $response->getReasonPhrase() . "\n\n");

print("Modern PSR-18 Curl Client: HTTPS GET\n");
$requestFactory = new RequestFactory;
$requestFactory = new RequestFactory();
$request = $requestFactory->createRequest('GET', 'https://www.horde.org');
$response = $client->sendRequest($request);
print($response->getStatusCode() . "\n\n" . $response->getReasonPhrase() ."\n\n");
print($response->getStatusCode() . "\n\n" . $response->getReasonPhrase() . "\n\n");

print("Modern PSR-18 Curl Client: HTTPS GitHub\n");
$requestFactory = new RequestFactory;
$requestFactory = new RequestFactory();
$request = $requestFactory->createRequest('GET', 'https://api.github.com/');
$response = $client->sendRequest($request);
print($response->getStatusCode() . "\n\n" . $response->getReasonPhrase() ."\n\n");
print($response->getBody()->getContents());
print($response->getStatusCode() . "\n\n" . $response->getReasonPhrase() . "\n\n");
print($response->getBody()->getContents());
7 changes: 7 additions & 0 deletions lib/Horde/Http/Response/Mock.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@
*/
class Horde_Http_Response_Mock extends Horde_Http_Response_Base
{
/**
* Response stream.
*
* @var resource
*/
protected $_stream;

/**
* Constructor
*/
Expand Down
21 changes: 10 additions & 11 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -1,27 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.5/phpunit.xsd"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/11.5/phpunit.xsd"
bootstrap="vendor/autoload.php"
cacheResultFile=".phpunit.cache/test-results"
executionOrder="depends,defects"
forceCoversAnnotation="false"
beStrictAboutCoversAnnotation="true"
requireCoverageMetadata="false"
beStrictAboutOutputDuringTests="true"
beStrictAboutTodoAnnotatedTests="true"
failOnRisky="true"
failOnWarning="true"
verbose="true">
defaultTestSuite="horde/http unit tests">
<testsuites>
<testsuite name="horde/http">
<directory>test</directory>
<testsuite name="horde/http unit tests">
<directory>test/Unit</directory>
</testsuite>
<testsuite name="horde/http live integration tests">
<directory>test/Live</directory>
</testsuite>
</testsuites>

<coverage cacheDirectory=".phpunit.cache/code-coverage"
processUncoveredFiles="true">
<source>
<include>
<directory suffix=".php">lib</directory>
<directory suffix=".php">src</directory>
</include>
</coverage>
</source>
</phpunit>
2 changes: 1 addition & 1 deletion src/Client/Curl.php
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?php

/**
* Copyright 2007-2021 Horde LLC (http://www.horde.org/)
* Copyright 2007-2026 Horde LLC (http://www.horde.org/)
*
* See the enclosed file LICENSE for license information (BSD). If you
* did not receive this file, see http://www.horde.org/licenses/bsd.
Expand Down
10 changes: 5 additions & 5 deletions src/Client/Fopen.php
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?php

/**
* Copyright 2007-2021 Horde LLC (http://www.horde.org/)
* Copyright 2007-2026 Horde LLC (http://www.horde.org/)
*
* See the enclosed file LICENSE for license information (BSD). If you
* did not receive this file, see http://www.horde.org/licenses/bsd.
Expand Down Expand Up @@ -122,13 +122,13 @@ public function sendRequest(RequestInterface $request): ResponseInterface
$opts['ssl']['allow_self_signed'] = true;

$context = stream_context_create($opts);
set_error_handler(array($this, 'errorHandler'), E_WARNING | E_NOTICE);
set_error_handler([$this, 'errorHandler'], E_WARNING | E_NOTICE);
$streamResource = fopen($uri, 'rb', false, $context);
restore_error_handler();
if (!$streamResource) {
if (
isset($this->errors[0]['message']) &&
preg_match('/HTTP\/(\d+\.\d+) (\d{3}) (.*)$/', $this->errors[0]['message'], $matches)
isset($this->errors[0]['message'])
&& preg_match('/HTTP\/(\d+\.\d+) (\d{3}) (.*)$/', $this->errors[0]['message'], $matches)
) {
// Create a Response for the HTTP error code
return $this->responseFactory->createResponse($matches[0]);
Expand All @@ -138,7 +138,7 @@ public function sendRequest(RequestInterface $request): ResponseInterface
}

$meta = stream_get_meta_data($streamResource);
$headers = isset($meta['wrapper_data']) ? $meta['wrapper_data'] : [];
$headers = $meta['wrapper_data'] ?? [];

//return new Response
$headerList = $this->parseHeaders($headers);
Expand Down
17 changes: 11 additions & 6 deletions src/Client/Mock.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?php

/**
* Copyright 2007-2021 Horde LLC (http://www.horde.org/)
* Copyright 2007-2026 Horde LLC (http://www.horde.org/)
*
* See the enclosed file LICENSE for license information (BSD). If you
* did not receive this file, see http://www.horde.org/licenses/bsd.
Expand All @@ -13,7 +14,9 @@
* @package Http
*/
declare(strict_types=1);

namespace Horde\Http\Client;

use OutOfBoundsException;
use Horde\Http\Response;
use Horde\Http\ResponseFactory;
Expand Down Expand Up @@ -95,14 +98,16 @@ public function addResponses(iterable $responses): void
* @return ResponseInterface The response.
*/
public function addResponse(
$body, string|int $code = 200, string $uri = '', array $headers = []
): ResponseInterface
{
$body,
string|int $code = 200,
string $uri = '',
array $headers = []
): ResponseInterface {
// TODO: What about the uri?
if ($body instanceof StreamInterface) {
$stream = clone($body);
} elseif (is_string($body)) {
$stream = $this->streamFactory->createStream($body);
$stream = $this->streamFactory->createStream($body);
} else {
$stream = $this->streamFactory->createStreamFromResource($body);
}
Expand All @@ -111,7 +116,7 @@ public function addResponse(
$response = $response->withAddedHeader($name, $header);
}
$this->responses[] = $response;
return $response;
return $response;
}

/**
Expand Down
9 changes: 6 additions & 3 deletions src/Client/Options.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?php

/**
* Copyright 2020-2021 Horde LLC (http://www.horde.org/)
* Copyright 2020-2026 Horde LLC (http://www.horde.org/)
*
* See the enclosed file LICENSE for license information (BSD). If you
* did not receive this file, see http://www.horde.org/licenses/bsd.
Expand All @@ -11,7 +12,9 @@
* @package Http
*/
declare(strict_types=1);

namespace Horde\Http\Client;

use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\RequestFactoryInterface;
Expand All @@ -38,7 +41,7 @@ class Options
'redirects' => 5,
'timeout' => 5,
'userAgent' => 'Horde\Http H6',
'verifyPeer' => true
'verifyPeer' => true,
];

public function __construct(iterable $param = [])
Expand All @@ -57,4 +60,4 @@ public function getOption(string $name)
{
return $this->options[$name] ?? null;
}
}
}
Loading
Loading