diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..be2d039 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,11 @@ +# Keep the Packagist dist archive to what a consumer actually installs. +# GitHub zipballs honour export-ignore, so this trims the test suite, the +# ably-common fixture submodule and the CI/planning files from every release. +/tests export-ignore +/ably-common export-ignore +/.github export-ignore +/phpunit.xml export-ignore +/plan.md export-ignore +/.ably export-ignore +/.gitmodules export-ignore +/.gitattributes export-ignore diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index cfdf456..2cbf4f3 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -6,6 +6,7 @@ on: push: branches: - main + - integration/v2 permissions: {} @@ -18,7 +19,7 @@ jobs: strategy: fail-fast: false matrix: - php-version: [7.2, 7.3, 7.4, 8.0, 8.1, 8.2, 8.3, 8.4] + php-version: ['8.1', '8.2', '8.3', '8.4', '8.5'] protocol: [ 'json', 'msgpack' ] ignorePlatformReq: [ '' ] @@ -34,8 +35,8 @@ jobs: php-version: ${{ matrix.php-version }} ini-values: error_reporting=E_ALL - - name: Validate composer.json and composer.lock - run: composer validate + - name: Validate composer.json + run: composer validate --strict - name: Install dependencies env: diff --git a/.github/workflows/features.yml b/.github/workflows/features.yml index 4b8be1e..c9ad975 100644 --- a/.github/workflows/features.yml +++ b/.github/workflows/features.yml @@ -5,6 +5,7 @@ on: push: branches: - main + - integration/v2 permissions: {} diff --git a/CHANGELOG.md b/CHANGELOG.md index 642231c..f300625 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,30 @@ # Change Log +## [2.0.0](https://github.com/ably/ably-pubsub-php/tree/pubsub-server/2.0.0) (unreleased) + +[Full Changelog](https://github.com/ably/ably-pubsub-php/compare/1.1.12...pubsub-server/2.0.0) + +The first release of the `ably/pubsub-server` package, superseding +`ably/ably-php`. See [`UPDATING.md`](./UPDATING.md) for the migration guide and +the full mapping table. + +**Breaking changes:** + +- The package is now `ably/pubsub-server`, installed with `composer require ably/pubsub-server`. `ably/ably-php` is superseded and receives security and critical-bug fixes only for one year from this release, from the `maintenance/1.x` branch. +- The namespace is now `Ably\PubSub\`. Every class other than the client keeps its name, so `Ably\Models\Message` becomes `Ably\PubSub\Models\Message`. +- The client class `Ably\AblyRest` is now `Ably\PubSub\PubSubHttpClient` ([PDR-091d](https://ably.atlassian.net/wiki/spaces/product/pages/5363957781)). Applications that only construct through `Server::createHttpClient()` are unaffected; type hints and DI bindings naming the class must be updated. +- Clients are constructed through `Ably\PubSub\Server::createHttpClient()`, which declares the server side on the wire. It accepts everything the constructor accepted: an options array, an API key string, a token string, or a `ClientOptions` instance. A client constructed directly declares no side, and is rejected on accounts with monthly-active-user pricing enabled. +- Removed `AblyRest::setAblyAgentHeader()` and `AblyRest::setLibraryFlavourString()`, replaced by the per-client `agents` client option. +- Removed `ably-loader.php`; Composer's autoloader is the only supported install path. +- Removed the `demo/` Heroku application and its `Procfile`. +- The minimum supported PHP version is now 8.1; the SDK is tested on 8.1 through 8.5. + +**Other changes:** + +- The SDK agent identifier is now `ably-pubsub-php`, and the server side is declared by an `ably-pubsub-server` entry: `Ably-Agent: ably-pubsub-php/2.0.0 php/8.3.4 ably-pubsub-server`. +- Added the `agents` client option (`array`) so an SDK or framework built on this package can attribute its own traffic. +- `rybakit/msgpack` is now constrained to `^0.9.1 || ^1.0`. + ## [1.1.12](https://github.com/ably/ably-php/tree/1.1.12) (2026-06-23) [Full Changelog](https://github.com/ably/ably-php/compare/1.1.11...1.1.12) diff --git a/Procfile b/Procfile deleted file mode 100644 index 05da7ab..0000000 --- a/Procfile +++ /dev/null @@ -1 +0,0 @@ -web: vendor/bin/heroku-php-apache2 demo/ \ No newline at end of file diff --git a/README.md b/README.md index 4d2251b..6b10af9 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ![Ably Pub/Sub PHP Header](images/php-SDK-github.png) -[![Latest Stable Version](https://poser.pugx.org/ably/ably-php/v/stable)](https://packagist.org/packages/ably/ably-php) -[![License](https://poser.pugx.org/ably/ably-php/license)](https://github.com/ably/ably-pubsub-php/blob/main/LICENSE) +[![Latest Stable Version](https://poser.pugx.org/ably/pubsub-server/v/stable)](https://packagist.org/packages/ably/pubsub-server) +[![License](https://poser.pugx.org/ably/pubsub-server/license)](https://github.com/ably/ably-pubsub-php/blob/main/LICENSE) --- @@ -26,12 +26,29 @@ Everything you need to get started with Ably: --- +## Package + +This SDK ships as a single package, `ably/pubsub-server`. + +The package name declares where your code runs. A server is a trusted runtime: it typically authenticates with an API key, one that a browser or a mobile app must never hold, and its connections are exempt from monthly-active-user counting. That declaration has to reach Ably rather than only the README, so the package sends it on every request in the `Ably-Agent` header: + +``` +Ably-Agent: ably-pubsub-php/2.0.0 php/8.3.4 ably-pubsub-server +``` + +The trailing `ably-pubsub-server` entry is the part the platform matches on. It is stamped by `Ably\PubSub\Server::createHttpClient()`, so a client constructed any other way declares no side, and will be rejected on accounts that have monthly-active-user pricing enabled. + +This is the only Ably Pub/Sub package for PHP. There is no device package and no separate core package to depend on, because PHP is a server-side language: this SDK is REST-only and there is no PHP realtime client. See the [Ably REST API](#ably-rest-api) note below for realtime options. + +--- + ## Supported platforms Ably aims to support a wide range of platforms. If you experience any compatibility issues, open an issue in the repository or contact [Ably support](https://ably.com/support). -> [!IMPORTANT] -> PHP SDK versions < 1.1.9 will be [deprecated](https://ably.com/docs/platform/deprecate/protocol-v1) from November 1, 2025. +| Platform | Support | +| --- | --- | +| PHP | 8.1, 8.2, 8.3, 8.4, 8.5 | --- @@ -42,6 +59,8 @@ For Laravel applications, consider these framework-integrated alternatives that * **[Ably Pub/Sub PHP Laravel SDK](https://github.com/ably/ably-php-laravel)** - Laravel integration package with clean facade and dependency injection interface. * **[Ably Broadcaster for Laravel](https://github.com/ably/laravel-broadcaster)** - Official Laravel broadcaster for real-time event broadcasting. +Each needs a new major version to run on `ably/pubsub-server`; their current releases depend on `ably/ably-php` 1.x. Those majors ship in the same release window as this package. + --- ## Installation @@ -49,7 +68,7 @@ For Laravel applications, consider these framework-integrated alternatives that To get started with your project, install the package: ```sh -composer require ably/ably-php +composer require ably/pubsub-server ``` --- @@ -59,8 +78,10 @@ composer require ably/ably-php The following code connects to Ably's REST messaging service, gets reference to a channel to receive messages, and publishes a test message to that same channel: ```php -// Initialize Ably REST client -$ably = new AblyRest(['key' => 'your-ably-api-key', 'clientId' => 'me']); +use Ably\PubSub\Server; + +// Initialize the Ably HTTP (REST) client for a server +$ably = Server::createHttpClient(['key' => 'your-ably-api-key', 'clientId' => 'me']); // Get a reference to the 'test-channel' channel $channel = $ably->channel('test-channel'); @@ -69,6 +90,27 @@ $channel = $ably->channel('test-channel'); $channel->publish('test-event', 'hello world'); ``` +`createHttpClient()` accepts everything the 1.x client constructor accepted: an options array, an API key string, a token string, or a `ClientOptions` instance. + +If your own SDK or framework wraps this package, name it so its traffic is attributed to it: + +```php +$ably = Server::createHttpClient([ + 'key' => 'your-ably-api-key', + 'agents' => ['my-framework' => '1.2.3'], +]); +``` + +--- + +## Migrating from `ably/ably-php` 1.x + +`ably/pubsub-server` 2.0.0 supersedes `ably/ably-php`. The client it returns is the same REST client, so for most applications the migration is confined to the `composer require` line, the `use` statements, and the constructor call. [UPDATING.md](./UPDATING.md) has the full mapping table and a before/after example. + +If you are staying on 1.x for now, it is maintained on the `maintenance/1.x` branch of this repository, and receives security and critical-bug fixes only for one year from the 2.0.0 release. + +--- + ## Releases The [CHANGELOG.md](./CHANGELOG.md) contains details of the latest releases for this SDK. You can also view all Ably releases on [changelog.ably.com](https://changelog.ably.com). @@ -79,6 +121,8 @@ The [CHANGELOG.md](./CHANGELOG.md) contains details of the latest releases for t Read the [CONTRIBUTING.md](./CONTRIBUTING.md) guidelines to contribute to Ably. +Development happens in this repository, `ably-pubsub-php`. The Packagist package is published from a read-only distribution mirror, so issues and pull requests belong here. + --- ## Support, feedback, and troubleshooting @@ -87,4 +131,4 @@ For help or technical support, visit the [Ably Support page](https://ably.com/su ### Ably REST API -This SDK currently supports only the [Ably REST API](https://www.ably.com/docs/rest). For realtime capabilities, you can use the [MQTT adapter](https://www.ably.com/docs/mqtt) alongside [Mosquitto PHP](https://github.com/mgdm/Mosquitto-PHP) to implement Ably's Realtime features. \ No newline at end of file +This SDK currently supports only the [Ably REST API](https://www.ably.com/docs/rest). For realtime capabilities, you can use the [MQTT adapter](https://www.ably.com/docs/mqtt) alongside [Mosquitto PHP](https://github.com/mgdm/Mosquitto-PHP) to implement Ably's Realtime features. diff --git a/UPDATING.md b/UPDATING.md new file mode 100644 index 0000000..f669df4 --- /dev/null +++ b/UPDATING.md @@ -0,0 +1,84 @@ +# Upgrade / Migration Guide + +## 1.x (`ably/ably-php`) → 2.0.0 (`ably/pubsub-server`) + +Version 2.0.0 ships from a new package, `ably/pubsub-server`, under a new namespace, `Ably\PubSub\`. `ably/ably-php` is superseded: it receives security and critical-bug fixes only for one year from the 2.0.0 release date, and is then end-of-life. + +Under monthly-active-user pricing the platform has to classify every connection as device-side or server-side. The new package declares that automatically, on every request, in the `Ably-Agent` header; the old constructor cannot declare anything. That is the forcing function for this migration: once monthly-active-user pricing is live, `new Ably\AblyRest(...)` from `ably/ably-php` is rejected on accounts where it is enabled. + +The client `Server::createHttpClient()` returns is the same REST client as before, renamed from `AblyRest` to `PubSubHttpClient`. Channels, message publishing, history, presence, authentication, push admin, crypto and every `ClientOptions` key behave exactly as they did in 1.x. For most applications the migration is confined to the `composer require` line, the `use` statements, and the constructor call. + +### Mapping + +| 1.x (`ably/ably-php`) | 2.0 (`ably/pubsub-server`) | +| --- | --- | +| `composer require ably/ably-php` | `composer require ably/pubsub-server` | +| `use Ably\AblyRest;` / `new AblyRest($opts)` | `use Ably\PubSub\Server;` / `Server::createHttpClient($opts)` | +| `use Ably\Models\Message;` (any `Ably\X` type) | `use Ably\PubSub\Models\Message;` (`Ably\PubSub\X`) | +| `AblyRest::setAblyAgentHeader('x', 'v')` | `Server::createHttpClient(['agents' => ['x' => 'v'], …])` | +| `AblyRest::setLibraryFlavourString('x')` | removed — use the `agents` option | +| `require 'ably-loader.php';` | removed — use Composer's autoloader (`vendor/autoload.php`) | +| PHP 7.2 – 8.0 | PHP `^8.1` (tested on 8.1 – 8.5) | +| `\Ably\AblyRest` type hints | `\Ably\PubSub\PubSubHttpClient` ([091d](https://ably.atlassian.net/wiki/spaces/product/pages/5363957781)) | + +Every class moves namespace, and `AblyRest` is the only one that also changes name, so the rename is mechanical: replace the prefix `Ably\` with `Ably\PubSub\` throughout — including in type hints, `catch` blocks and fully-qualified string class names — and then `AblyRest` with `PubSubHttpClient`. + +### Example + +```php +// 1.x +use Ably\AblyRest; + +$ably = new AblyRest(['key' => getenv('ABLY_API_KEY'), 'clientId' => 'me']); +$ably->channel('test-channel')->publish('test-event', 'hello world'); + +// 2.0 +use Ably\PubSub\Server; + +$ably = Server::createHttpClient(['key' => getenv('ABLY_API_KEY'), 'clientId' => 'me']); +$ably->channel('test-channel')->publish('test-event', 'hello world'); +``` + +`createHttpClient()` accepts everything the 1.x constructor accepted: an options array, an API key string, a token string, or a `ClientOptions` instance. A `ClientOptions` instance you pass in is copied rather than mutated. + +### Declaring the side + +Construct through the door. `new Ably\PubSub\PubSubHttpClient(...)` still works — the library and its own tests use it — but it declares no side, and will be rejected on monthly-active-user-enabled accounts just as the 1.x constructor is. The door produces: + +``` +Ably-Agent: ably-pubsub-php/2.0.0 php/8.3.4 ably-pubsub-server +``` + +If you are building an SDK or framework on top of this package, name it through the `agents` option instead of the removed static setters. Your entries are preserved, in order, ahead of the side entry: + +```php +$ably = Server::createHttpClient([ + 'key' => getenv('ABLY_API_KEY'), + 'agents' => ['laravel' => '11.0.0', 'laravel-broadcaster' => '1.0.4'], +]); + +// Ably-Agent: ably-pubsub-php/2.0.0 php/8.3.4 laravel/11.0.0 laravel-broadcaster/1.0.4 ably-pubsub-server +``` + +The `agents` option is per-client, unlike the process-global static setters it replaces, so two clients in one process can carry different attribution. + +### Removed in 2.0.0 + +* `AblyRest::setAblyAgentHeader()` and `AblyRest::$agents` — replaced by the per-client `agents` option. +* `AblyRest::setLibraryFlavourString()` — already deprecated in 1.x; replaced by the same option. +* `ably-loader.php`, the hand-rolled autoloader — Composer is the only supported install path. +* The `demo/` Heroku application and its `Procfile`. +* PHP 7.2 – 8.0 support. + +`Auth::authorise()`, the British-spelling alias deprecated in favour of `Auth::authorize()`, is still present in 2.0.0. + +### Unchanged + +* The REST client and its whole surface: `channel()`, `channels`, publishing, message history, presence and presence history, `auth`, token requests and token issuing, `push` admin, `stats`, `time()`, and crypto. +* Every `ClientOptions` key, and the array / key-string / token-string / `ClientOptions` forms of the constructor argument. +* Message and error semantics, including `AblyException` and its codes. +* Requirements: `ext-json`, `ext-curl`, `ext-openssl`, and `rybakit/msgpack` for the msgpack protocol. + +### Staying on 1.x + +`ably/ably-php` 1.x is maintained on the `maintenance/1.x` branch of this repository. It gets security and critical-bug fixes for one year from the 2.0.0 release date and no new features, then reaches end-of-life. Both packages can be installed side by side during a migration: they declare different namespaces and different PSR-4 prefixes, so their autoloading does not collide. diff --git a/ably-loader.php b/ably-loader.php deleted file mode 100644 index 7b6fc6d..0000000 --- a/ably-loader.php +++ /dev/null @@ -1,15 +0,0 @@ -=0.9.1", + "php": "^8.1", + "rybakit/msgpack": "^0.9.1 || ^1.0", "ext-json" : "*", "ext-curl" : "*", "ext-openssl" : "*" }, "require-dev": { - "phpunit/phpunit": "^8.5 || ^9.5" + "phpunit/phpunit": "^9.5" }, "license": "Apache-2.0", "authors": [ @@ -22,7 +26,7 @@ ], "autoload": { "psr-4": { - "Ably\\": "src/" + "Ably\\PubSub\\": "src/" } }, "autoload-dev": { diff --git a/demo/index.php b/demo/index.php deleted file mode 100644 index 95325e3..0000000 --- a/demo/index.php +++ /dev/null @@ -1,212 +0,0 @@ - $apiKey, -); - -if ($host) { - $settings['host'] = $host; -} - -// instantiate Ably -$app = new \Ably\AblyRest($settings); -$channel = $app->channel($channelName); - -if (!empty($_POST)) { - // publish a message - $channel->publish( $eventName, array('handle' => $_POST['handle'], 'message' => $_POST['message']) ); - die(); -} - -// get a list of recent messages and render the interface -$messages = $channel->history( array('direction' => 'backwards') )->items; - -?> - - - - - - Simple Chat Demo - - - - - -
-
-

Let's Chat [api_time: time()/1000) ?> | server_time: ]

-
-
-
- - -
- -
-
- -
- - - -
-
-
-
-
    - - - timestamp / 1000); - $day = date($date_format, $timestamp); ?> - -
  • - -
  • - data->handle ?>: data->message ?>
  • - -
-
-
-
-
-
- - - - - - - \ No newline at end of file diff --git a/src/Auth.php b/src/Auth.php index 8423c6e..1584698 100644 --- a/src/Auth.php +++ b/src/Auth.php @@ -1,15 +1,15 @@ defaultAuthOptions = new AuthOptions($options); $this->defaultTokenParams = $options->defaultTokenParams; $this->ably = $ably; @@ -31,7 +31,7 @@ public function __construct( AblyRest $ably, ClientOptions $options ) { $this->basicAuth = empty( $this->defaultAuthOptions->useTokenAuth ) && $this->defaultAuthOptions->key; if ( $this->defaultAuthOptions->key && $this->defaultAuthOptions->clientId == '*' ) { - throw new AblyException ('Instantiating AblyRest with a wildcard clientId (`*`) not allowed.', 40012, 400); + throw new AblyException ('Instantiating PubSubHttpClient with a wildcard clientId (`*`) not allowed.', 40012, 400); } // Basic authentication @@ -130,7 +130,7 @@ public function authorizeInternal( $tokenParams = [], $authOptions = [], $force * Stores the AuthOptions and TokenParams arguments as defaults for subsequent authorisations. * @param array|null $tokenParams Requested token parameters * @param array|null $authOptions Overridable auth options, if you don't wish to use the default ones - * @return \Ably\Models\TokenDetails The new token + * @return \Ably\PubSub\Models\TokenDetails The new token */ public function authorize( $tokenParams = [], $authOptions = [] ) { return $this->authorizeInternal( $tokenParams, $authOptions ); @@ -168,7 +168,7 @@ public function getAuthHeaders() { } /** - * @return \Ably\Models\TokenDetails Token currently in use + * @return \Ably\PubSub\Models\TokenDetails Token currently in use */ public function getTokenDetails() { return $this->tokenDetails; @@ -178,9 +178,9 @@ public function getTokenDetails() { * Request a new token. * @param array|null $tokenParams Requested token parameters * @param array|null $authOptions Overridable auth options, if you don't wish to use the default ones - * @param \Ably\Models\ClientOptions|array $options - * @throws \Ably\Exceptions\AblyException - * @return \Ably\Models\TokenDetails The new token + * @param \Ably\PubSub\Models\ClientOptions|array $options + * @throws \Ably\PubSub\Exceptions\AblyException + * @return \Ably\PubSub\Models\TokenDetails The new token */ public function requestToken( $tokenParams = [], $authOptions = [] ) { // token clientId priority: @@ -207,9 +207,9 @@ public function requestToken( $tokenParams = [], $authOptions = [] ) { $data = $callback($tokenParamsMerged); // returned data can be either a signed TokenRequest or TokenDetails or just a token string - if ( is_a( $data, '\Ably\Models\TokenRequest' ) ) { + if ( is_a( $data, '\Ably\PubSub\Models\TokenRequest' ) ) { $signedTokenRequest = $data; - } else if ( is_a( $data, '\Ably\Models\TokenDetails' ) ) { + } else if ( is_a( $data, '\Ably\PubSub\Models\TokenDetails' ) ) { return $data; } else if ( is_string( $data ) ) { return new TokenDetails( $data ); @@ -281,9 +281,9 @@ public function requestToken( $tokenParams = [], $authOptions = [] ) { * Create a signed token request based on known credentials * and the given token params. This would typically be used if creating * signed requests for submission by another client. - * @param \Ably\Models\TokenParams $tokenParams - * @param \Ably\Models\AuthOptions $authOptions - * @return \Ably\Models\TokenRequest A signed token request + * @param \Ably\PubSub\Models\TokenParams $tokenParams + * @param \Ably\PubSub\Models\AuthOptions $authOptions + * @return \Ably\PubSub\Models\TokenRequest A signed token request */ public function createTokenRequest( $tokenParams = [], $authOptions = [] ) { $tokenClientId = $this->defaultTokenParams->clientId; diff --git a/src/Channel.php b/src/Channel.php index a31a63c..f089ca8 100644 --- a/src/Channel.php +++ b/src/Channel.php @@ -1,12 +1,12 @@ ably = $ably; $this->name = $name; $this->channelPath = "/channels/" . urlencode( $name ); @@ -57,13 +57,13 @@ public function __get( $name ) { /** * Posts a message to this channel * @param mixed ... Either a Message, array of Message-s, or (string eventName, string data) - * @throws \Ably\Exceptions\AblyException + * @throws \Ably\PubSub\Exceptions\AblyException */ public function __publish_request_body($first) { // Process arguments $messages = []; - if ( is_a( $first, 'Ably\Models\Message' ) ) { // single Message + if ( is_a( $first, 'Ably\PubSub\Models\Message' ) ) { // single Message $messages[] = $first; } else if ( is_array( $first ) ) { // array of Messages $messages = $first; @@ -161,7 +161,7 @@ public function publish(...$args) { * @return PaginatedResult */ public function history( $params = [] ) { - return new PaginatedResult( $this->ably, 'Ably\Models\Message', + return new PaginatedResult( $this->ably, 'Ably\PubSub\Models\Message', $this->getCipherParams(), 'GET', $this->getPath() . '/messages', $params ); diff --git a/src/Channels.php b/src/Channels.php index bdc7eb1..55bb092 100644 --- a/src/Channels.php +++ b/src/Channels.php @@ -1,5 +1,5 @@ ably = $ably; } @@ -20,7 +20,7 @@ public function __construct( AblyRest $ably ) { * of the channel will be updated. * @param string $name Name of the channel * @param array|null $options ChannelOptions for the channel - * @return \Ably\Channel + * @return \Ably\PubSub\Channel */ public function get( $name, $options = null ) { diff --git a/src/Defaults.php b/src/Defaults.php index 9fca704..151178f 100644 --- a/src/Defaults.php +++ b/src/Defaults.php @@ -1,9 +1,9 @@ + */ + public $agents = []; + + /** + * Normalises the single argument that the client constructor and the + * factory door both accept into something ClientOptions can be built from. + * + * A bare string is an API key when it contains a colon and a token + * otherwise. An array or an existing ClientOptions instance passes through + * unchanged, so an argument of any other type reaches the constructor and + * raises the constructor's own error rather than a vaguer failure later. + * + * @param \Ably\PubSub\Models\ClientOptions|array|string $options + * @return \Ably\PubSub\Models\ClientOptions|array + */ + public static function normalizeConstructorArgument( $options ) { + if ( is_string( $options ) ) { + return strpos( $options, ':' ) === false + ? [ 'token' => $options ] + : [ 'key' => $options ]; + } + return $options; + } private function isProductionEnvironment() { return empty($this->environment) || strcasecmp($this->environment, "production") == 0; diff --git a/src/Models/DeviceDetails.php b/src/Models/DeviceDetails.php index 9a915a7..ccd7705 100644 --- a/src/Models/DeviceDetails.php +++ b/src/Models/DeviceDetails.php @@ -1,5 +1,5 @@ ably = $ably; $this->model = $model; diff --git a/src/Models/PresenceMessage.php b/src/Models/PresenceMessage.php index 0909620..4bdfc15 100644 --- a/src/Models/PresenceMessage.php +++ b/src/Models/PresenceMessage.php @@ -1,5 +1,5 @@ ably = $ably; $this->channel = $channel; } @@ -24,7 +24,7 @@ public function __construct( AblyRest $ably, Channel $channel ) { * @return PaginatedResult */ public function get( $params = [] ) { - return new PaginatedResult( $this->ably, 'Ably\Models\PresenceMessage', $this->channel->getCipherParams(), 'GET', $this->channel->getPath() . '/presence', $params ); + return new PaginatedResult( $this->ably, 'Ably\PubSub\Models\PresenceMessage', $this->channel->getCipherParams(), 'GET', $this->channel->getPath() . '/presence', $params ); } /** @@ -33,6 +33,6 @@ public function get( $params = [] ) { * @return PaginatedResult */ public function history( $params = [] ) { - return new PaginatedResult( $this->ably, 'Ably\Models\PresenceMessage', $this->channel->getCipherParams(), 'GET', $this->channel->getPath() . '/presence/history', $params ); + return new PaginatedResult( $this->ably, 'Ably\PubSub\Models\PresenceMessage', $this->channel->getCipherParams(), 'GET', $this->channel->getPath() . '/presence/history', $params ); } } \ No newline at end of file diff --git a/src/AblyRest.php b/src/PubSubHttpClient.php similarity index 76% rename from src/AblyRest.php rename to src/PubSubHttpClient.php index 86f6a1c..78560bd 100644 --- a/src/AblyRest.php +++ b/src/PubSubHttpClient.php @@ -1,30 +1,27 @@ $agentVersion) { + foreach($this->options->agents as $agentIdentifier => $agentVersion) { $agentHeader.= ' '.$agentIdentifier; if (!empty($agentVersion)) { $agentHeader.= '/'.$agentVersion; @@ -48,15 +57,15 @@ static function ablyAgentHeader() return $agentHeader; } /** - * @var \Ably\Http $http object for making HTTP requests + * @var \Ably\PubSub\Http $http object for making HTTP requests */ public $http; /** - * @var \Ably\Auth $auth object providing authorisation functionality + * @var \Ably\PubSub\Auth $auth object providing authorisation functionality */ public $auth; /** - * @var \Ably\Channels $channels object for creating and releasing channels + * @var \Ably\PubSub\Channels $channels object for creating and releasing channels */ public $channels; @@ -65,19 +74,22 @@ static function ablyAgentHeader() public $push; /** - * Constructor - * @param \Ably\Models\ClientOptions|string array with options or a string with app key or token + * Constructor. + * + * @internal Construct clients through the factory door, + * {@see \Ably\PubSub\Server::createHttpClient()}, which is the only + * documented entry point of this package. A client built by calling this + * constructor directly declares no side in its `Ably-Agent` header, and + * so does not qualify for the server exemption from monthly-active-user + * counting. + * + * @param \Ably\PubSub\Models\ClientOptions|array|string $options array with + * options, a ClientOptions instance, or a string with an app key or token */ public function __construct( $options = [] ) { - # convert to options if a single key is provided - if ( is_string( $options ) ) { - if ( strpos( $options, ':' ) === false ) { - $options = [ 'token' => $options ]; - } else { - $options = [ 'key' => $options ]; - } - } + # convert to options if a single key or token string is provided + $options = ClientOptions::normalizeConstructorArgument( $options ); $this->options = new ClientOptions( $options ); @@ -100,7 +112,7 @@ public function __construct( $options = [] ) { /** * Shorthand to $this->channels->get() - * @return \Ably\Channel Channel + * @return \Ably\PubSub\Channel Channel */ public function channel( $name, $options = [] ) { return $this->channels->get( $name, $options ); @@ -112,7 +124,7 @@ public function channel( $name, $options = [] ) { * @return array Statistics */ public function stats( $params = [] ) { - return new PaginatedResult( $this, 'Ably\Models\Stats', $cipher = false, 'GET', '/stats', $params ); + return new PaginatedResult( $this, 'Ably\PubSub\Models\Stats', $cipher = false, 'GET', '/stats', $params ); } /** @@ -126,7 +138,7 @@ public function time() { /** * Does a GET request, automatically injecting auth headers and handling fallback on server failure - * @see AblyRest::request() + * @see PubSubHttpClient::request() */ public function get( $path, $headers = [], $params = [], $returnHeaders = false, $auth = true ) { return $this->requestInternal( 'GET', $path, $headers, $params, $returnHeaders, $auth ); @@ -134,7 +146,7 @@ public function get( $path, $headers = [], $params = [], $returnHeaders = false, /** * Does a POST request, automatically injecting auth headers and handling fallback on server failure - * @see AblyRest::request() + * @see PubSubHttpClient::request() */ public function post( $path, $headers = [], $params = [], $returnHeaders = false, $auth = true ) { return $this->requestInternal( 'POST', $path, $headers, $params, $returnHeaders, $auth ); @@ -142,7 +154,7 @@ public function post( $path, $headers = [], $params = [], $returnHeaders = false /** * Does a PUT request, automatically injecting auth headers and handling fallback on server failure - * @see AblyRest::request() + * @see PubSubHttpClient::request() */ public function put( $path, $headers = [], $params = [], $returnHeaders = false, $auth = true ) { return $this->requestInternal( 'PUT', $path, $headers, $params, $returnHeaders, $auth ); @@ -150,7 +162,7 @@ public function put( $path, $headers = [], $params = [], $returnHeaders = false, /** * Does a DELETE request, automatically injecting auth headers and handling fallback on server failure - * @see AblyRest::request() + * @see PubSubHttpClient::request() */ public function delete( $path, $headers = [], $params = [], $returnHeaders = false, $auth = true ) { return $this->requestInternal( 'DELETE', $path, $headers, $params, $returnHeaders, $auth ); @@ -184,7 +196,7 @@ public function requestInternal( $method, $path, $headers = [], $params = [], $r $mergedHeaders = array_merge( [ 'Accept: ' . $this->getAcceptHeader(), 'X-Ably-Version: ' .Defaults::API_VERSION, - 'Ably-Agent: ' .self::ablyAgentHeader(), + 'Ably-Agent: ' .$this->ablyAgentHeader(), ], $headers ); if ( $auth ) { // inject auth headers $mergedHeaders = array_merge( $this->auth->getAuthHeaders(), $mergedHeaders ); @@ -255,7 +267,7 @@ public function requestInternal( $method, $path, $headers = [], $params = [], $r * @param array $params GET parameters to append to $path * @param array|object $body JSON-encodable structure to send in the body - leave empty for GET requests * @param array $headers HTTP headers to send - * @return \Ably\Models\HttpPaginatedResponse + * @return \Ably\PubSub\Models\HttpPaginatedResponse * @throws AblyRequestException This exception is only thrown for status codes >= 500 */ public function request( $method, $path, $params = [], $body = '', $headers = []) { @@ -267,7 +279,7 @@ public function request( $method, $path, $params = [], $body = '', $headers = [] throw new AblyException( 'GET requests cannot have a JSON body', 400, 40000 ); } - return new HttpPaginatedResponse( $this, 'Ably\Models\Untyped', null, $method, $path, $body, $headers ); // RSC19d + return new HttpPaginatedResponse( $this, 'Ably\PubSub\Models\Untyped', null, $method, $path, $body, $headers ); // RSC19d } // RTN17c @@ -275,29 +287,4 @@ function hasActiveInternetConnection() { $response = $this->http->get(Defaults::$internetCheckUrl); return $response["body"] == Defaults::$internetCheckOk; } - - /** - * @deprecated - * Sets a "flavour string", that is sent in the `Ably-Agent` request header. - * Used for internal statistics. - * For instance setting 'laravel' results in: `Ably-Agent: laravel` - */ - public static function setLibraryFlavourString( $flavour = '' ) { - if (!empty($flavour)) { - self::setAblyAgentHeader($flavour); - } - } - - /** - * @param string $agentName represents agent_identifier - * @param string $agentVersion represents agent_identifier_version (optional) - * @return void - * @throws AblyException - */ - public static function setAblyAgentHeader($agentName, $agentVersion = '' ) { - if (empty($agentName)) { - throw new AblyException("agentName cannot be empty"); - } - self::$agents[$agentName] = $agentVersion; - } } diff --git a/src/Push.php b/src/Push.php index 60d5b56..caed260 100644 --- a/src/Push.php +++ b/src/Push.php @@ -1,5 +1,5 @@ ably = $ably; $this->admin = new PushAdmin( $ably ); } diff --git a/src/PushAdmin.php b/src/PushAdmin.php index f56246f..de04ff5 100644 --- a/src/PushAdmin.php +++ b/src/PushAdmin.php @@ -1,5 +1,5 @@ ably = $ably; $this->deviceRegistrations = new PushDeviceRegistrations( $ably ); $this->channelSubscriptions = new PushChannelSubscriptions ( $ably ); diff --git a/src/PushChannelSubscriptions.php b/src/PushChannelSubscriptions.php index c6b363f..1f0531b 100644 --- a/src/PushChannelSubscriptions.php +++ b/src/PushChannelSubscriptions.php @@ -1,8 +1,8 @@ ably = $ably; } @@ -39,7 +39,7 @@ public function save ( $subscription ) { */ public function list_ (array $params = []) { $path = '/push/channelSubscriptions'; - return new PaginatedResult( $this->ably, 'Ably\Models\PushChannelSubscription', + return new PaginatedResult( $this->ably, 'Ably\PubSub\Models\PushChannelSubscription', $cipher = false, 'GET', $path, $params ); } diff --git a/src/PushDeviceRegistrations.php b/src/PushDeviceRegistrations.php index edcb8b3..e478155 100644 --- a/src/PushDeviceRegistrations.php +++ b/src/PushDeviceRegistrations.php @@ -1,8 +1,8 @@ ably = $ably; } @@ -51,7 +51,7 @@ public function get ($deviceId) { */ public function list_ (array $params = []) { $path = '/push/deviceRegistrations'; - return new PaginatedResult( $this->ably, 'Ably\Models\DeviceDetails', $cipher = false, 'GET', $path, $params ); + return new PaginatedResult( $this->ably, 'Ably\PubSub\Models\DeviceDetails', $cipher = false, 'GET', $path, $params ); } /** diff --git a/src/Server.php b/src/Server.php new file mode 100644 index 0000000..b425e55 --- /dev/null +++ b/src/Server.php @@ -0,0 +1,98 @@ + null ]; + + if ( $options instanceof ClientOptions ) { + $options = clone $options; + $options->agents = array_merge( $options->agents ?: [], $sideAgent ); + + return $options; + } + + if ( is_array( $options ) ) { + $callerAgents = isset( $options['agents'] ) ? $options['agents'] : []; + $options['agents'] = array_merge( $callerAgents, $sideAgent ); + + return $options; + } + + return $options; + } +} diff --git a/src/Utils/Crypto.php b/src/Utils/Crypto.php index 6b0b6e3..bca32e9 100644 --- a/src/Utils/Crypto.php +++ b/src/Utils/Crypto.php @@ -1,8 +1,8 @@ getOptions(); - self::$ably = new AblyRest( array_merge( self::$defaultOptions, [ + self::$ably = new PubSubHttpClient( array_merge( self::$defaultOptions, [ 'key' => self::$testApp->getAppKeyDefault()->string, ] ) ); @@ -67,7 +67,7 @@ public static function tearDownAfterClass(): void { * Check if stats are automatically populated by zeroes */ public function testStatsDefaultValues() { - $stats = new \Ably\Models\Stats(); + $stats = new \Ably\PubSub\Models\Stats(); $this->assertTrue( $this->iterateObjectCheck0( $stats ), 'Expected newly created Stats to have zero values.' ); } diff --git a/tests/AuthTest.php b/tests/AuthTest.php index 0da208c..c992597 100644 --- a/tests/AuthTest.php +++ b/tests/AuthTest.php @@ -1,13 +1,13 @@ expectException(AblyException::class); $this->expectExceptionCode(40103); - $ably = new AblyRest( array_merge( self::$defaultOptions, [ + $ably = new PubSubHttpClient( array_merge( self::$defaultOptions, [ 'key' => self::$testApp->getAppKeyDefault()->string, 'tls' => false, ] ) ); @@ -48,21 +48,21 @@ public function testNoAuthParams() { $this->expectException(AblyException::class); $this->expectExceptionCode(40103); - $ably = new AblyRest( ); + $ably = new PubSubHttpClient( ); } /** * Init library with a token */ public function testAuthWithToken() { - $ably_for_token = new AblyRest( array_merge( self::$defaultOptions, [ + $ably_for_token = new PubSubHttpClient( array_merge( self::$defaultOptions, [ 'key' => self::$testApp->getAppKeyDefault()->string, ] ) ); $tokenDetails = $ably_for_token->auth->requestToken(); $this->assertNotNull($tokenDetails->token, 'Expected token id' ); - $ably = new AblyRest( array_merge( self::$defaultOptions, [ + $ably = new PubSubHttpClient( array_merge( self::$defaultOptions, [ 'tokenDetails' => $tokenDetails, ] ) ); @@ -75,7 +75,7 @@ public function testAuthWithToken() { * Init library with a key, force use of token with useTokenAuth */ public function testAuthWithKeyForceToken() { - $ably = new AblyRest( [ + $ably = new PubSubHttpClient( [ 'key' => 'fake.key:totallyFake', 'useTokenAuth' => true, ] ); @@ -90,7 +90,7 @@ public function testAuthEmptyForceToken() { $this->expectException(AblyException::class); $this->expectExceptionCode(40103); - $ably = new AblyRest( [ + $ably = new PubSubHttpClient( [ 'useTokenAuth' => true, ] ); } @@ -99,14 +99,14 @@ public function testAuthEmptyForceToken() { * Verify than token auth works without TLS */ public function testTokenWithoutTLS() { - $ably_for_token = new AblyRest( array_merge( self::$defaultOptions, [ + $ably_for_token = new PubSubHttpClient( array_merge( self::$defaultOptions, [ 'key' => self::$testApp->getAppKeyDefault()->string, ] ) ); $tokenDetails = $ably_for_token->auth->requestToken(); $this->assertNotNull($tokenDetails->token, 'Expected token id' ); - $ablyInsecure = new AblyRest( array_merge( self::$defaultOptions, [ + $ablyInsecure = new PubSubHttpClient( array_merge( self::$defaultOptions, [ 'tokenDetails' => $tokenDetails, 'tls' => false, ] ) ); @@ -122,7 +122,7 @@ public function testTokenWithoutTLS() { public function testTokenRequestWithAuthCallbackReturningSignedRequest() { $callbackCalled = false; - $ably = new AblyRest( array_merge( self::$defaultOptions, [ + $ably = new PubSubHttpClient( array_merge( self::$defaultOptions, [ 'authCallback' => function( $tokenParams ) use( &$callbackCalled ) { $callbackCalled = true; @@ -149,7 +149,7 @@ public function testTokenRequestWithAuthCallbackReturningSignedRequest() { public function testTokenRequestWithAuthCallbackReturningTokenDetails() { $callbackCalled = false; - $ably = new AblyRest( array_merge( self::$defaultOptions, [ + $ably = new PubSubHttpClient( array_merge( self::$defaultOptions, [ 'authCallback' => function( $tokenParams ) use( &$callbackCalled ) { $callbackCalled = true; @@ -175,7 +175,7 @@ public function testTokenRequestWithAuthCallbackReturningTokenDetails() { public function testTokenRequestWithAuthCallbackReturningTokenString() { $callbackCalled = false; - $ably = new AblyRest( array_merge( self::$defaultOptions, [ + $ably = new PubSubHttpClient( array_merge( self::$defaultOptions, [ 'authCallback' => function( $tokenParams ) use( &$callbackCalled ) { $callbackCalled = true; @@ -197,7 +197,7 @@ public function testTokenRequestWithAuthCallbackReturningTokenString() { public function testTokenRequestWithAuthUrlReturningSignedRequest() { $method = 'POST'; - $ably = new AblyRest( array_merge( self::$defaultOptions, [ + $ably = new PubSubHttpClient( array_merge( self::$defaultOptions, [ 'authUrl' => 'https://TEST/tokenRequest', 'httpClass' => 'authTest\HttpMock', ] ) ); @@ -212,7 +212,7 @@ public function testTokenRequestWithAuthUrlReturningSignedRequest() { * Init library with an authUrl that returns TokenDetails */ public function testTokenRequestWithAuthUrlReturningTokenDetails() { - $ably = new AblyRest( array_merge( self::$defaultOptions, [ + $ably = new PubSubHttpClient( array_merge( self::$defaultOptions, [ 'authUrl' => 'https://TEST/tokenDetails', 'httpClass' => 'authTest\HttpMock', ] ) ); @@ -227,7 +227,7 @@ public function testTokenRequestWithAuthUrlReturningTokenDetails() { * Init library with an authUrl that returns a token string */ public function testTokenRequestWithAuthUrlReturningTokenString() { - $ably = new AblyRest( array_merge( self::$defaultOptions, [ + $ably = new PubSubHttpClient( array_merge( self::$defaultOptions, [ 'authUrl' => 'https://TEST/tokenString', 'httpClass' => 'authTest\HttpMock', ] ) ); @@ -250,7 +250,7 @@ public function testTokenRequestWithAuthUrlParams() { $expectedAuthParams = [ 'param1' => 'value1', 'test' => 1, 'ttl' => 360000 ]; $method = 'POST'; - $ably = new AblyRest( array_merge( self::$defaultOptions, [ + $ably = new PubSubHttpClient( array_merge( self::$defaultOptions, [ 'authUrl' => 'https://TEST/tokenRequest', 'authHeaders' => $headers, 'authParams' => $authParams, @@ -285,7 +285,7 @@ public function testTokenRequestWithAuthUrlParams() { * and checks if ttl can be left blank */ public function testCreateTokenRequestValidity() { - $ably = new AblyRest( array_merge( self::$defaultOptions, [ + $ably = new PubSubHttpClient( array_merge( self::$defaultOptions, [ 'key' => self::$testApp->getAppKeyDefault()->string, ] ) ); @@ -298,7 +298,7 @@ public function testCreateTokenRequestValidity() { $timestamp = $ably->time(); - $ably = new AblyRest( array_merge( self::$defaultOptions, [ + $ably = new PubSubHttpClient( array_merge( self::$defaultOptions, [ 'authCallback' => function( $tokenParams ) use( $tokenRequest ) { return $tokenRequest; }, @@ -327,7 +327,7 @@ private function stripTokenRequestVariableParams($tokenRequest) { * Verify that createTokenRequest() supports tokenparams, authparams and overrides values correctly */ public function testCreateTokenRequestParams() { - $ablyKey = new AblyRest( array_merge( self::$defaultOptions, [ + $ablyKey = new PubSubHttpClient( array_merge( self::$defaultOptions, [ 'key' => self::$testApp->getAppKeyDefault()->string, 'httpClass' => 'authTest\HttpMock', 'clientId' => 'libClientId', @@ -413,7 +413,7 @@ public function testCreateTokenRequestParams() { * keeps using the same token, and renews it when forced */ public function testAuthorize() { - $ably = new AblyRest( array_merge( self::$defaultOptions, [ + $ably = new PubSubHttpClient( array_merge( self::$defaultOptions, [ 'key' => self::$testApp->getAppKeyDefault()->string, 'authClass' => 'authTest\AuthMock' ] ) ); @@ -428,7 +428,7 @@ public function testAuthorize() { $this->assertTrue( $ably->auth->requestTokenCalled, 'Expected authorize() to call requestToken()' ); $this->assertFalse( $ably->auth->isUsingBasicAuth(), 'Expected token auth to be used' ); - $this->assertInstanceOf( 'Ably\Models\TokenDetails', $tokenOriginal, + $this->assertInstanceOf( 'Ably\PubSub\Models\TokenDetails', $tokenOriginal, 'Expected authorize to return a TokenDetails object' ); $ably->auth->authorize(); @@ -440,7 +440,7 @@ public function testAuthorize() { * Verify that all the parameters are supported and saved as defaults */ public function testAuthorizeParams() { - $ably = new AblyRest( array_merge( self::$defaultOptions, [ + $ably = new PubSubHttpClient( array_merge( self::$defaultOptions, [ 'key' => self::$testApp->getAppKeyDefault()->string, 'authClass' => 'authTest\AuthMock' ] ) ); @@ -516,7 +516,7 @@ public function testAuthorizeParams() { * Verify that authorize() stores the provided parameters and uses them as defaults from then on */ public function testAuthorizeRememberDefaults() { - $ably = new AblyRest( array_merge( self::$defaultOptions, [ + $ably = new PubSubHttpClient( array_merge( self::$defaultOptions, [ 'key' => self::$testApp->getAppKeyDefault()->string, 'clientId' => 'originalClientId', ] ) ); @@ -541,7 +541,7 @@ public function testAuthorizeRememberDefaults() { */ public function testHTTPHeadersKey() { $fakeKey = 'fake.key:totallyFake'; - $ably = new AblyRest( [ + $ably = new PubSubHttpClient( [ 'key' => $fakeKey, 'httpClass' => 'authTest\HttpMock', ] ); @@ -556,7 +556,7 @@ public function testHTTPHeadersKey() { */ public function testHTTPHeadersToken() { $fakeToken = 'fakeToken'; - $ably = new AblyRest( [ + $ably = new PubSubHttpClient( [ 'token' => $fakeToken, 'httpClass' => 'authTest\HttpMock', ] ); diff --git a/tests/ChannelHistoryTest.php b/tests/ChannelHistoryTest.php index 74ebb45..76f21bf 100644 --- a/tests/ChannelHistoryTest.php +++ b/tests/ChannelHistoryTest.php @@ -1,8 +1,8 @@ getOptions(); - self::$ably = new AblyRest( array_merge( self::$defaultOptions, [ + self::$ably = new PubSubHttpClient( array_merge( self::$defaultOptions, [ 'key' => self::$testApp->getAppKeyDefault()->string, ] ) ); } diff --git a/tests/ChannelIdempotentTest.php b/tests/ChannelIdempotentTest.php index 9ad18ee..a8e23ee 100644 --- a/tests/ChannelIdempotentTest.php +++ b/tests/ChannelIdempotentTest.php @@ -1,10 +1,10 @@ getOptions(); - self::$ably = new AblyRest( array_merge( self::$defaultOptions, [ + self::$ably = new PubSubHttpClient( array_merge( self::$defaultOptions, [ 'key' => self::$testApp->getAppKeyDefault()->string, 'idempotentRestPublishing' => true, ] ) ); @@ -163,7 +163,7 @@ public function testIdempotentMixedIds() { * RSL1k4 */ public function testIdempotentLibraryGeneratedPublish() { - $ably = new AblyRest( array_merge( self::$defaultOptions, [ + $ably = new PubSubHttpClient( array_merge( self::$defaultOptions, [ 'key' => self::$testApp->getAppKeyDefault()->string, 'idempotentRestPublishing' => true, 'httpClass' => 'tests\HttpMockIdempotent', diff --git a/tests/ChannelMessagesTest.php b/tests/ChannelMessagesTest.php index 4f4f9c6..638ec98 100644 --- a/tests/ChannelMessagesTest.php +++ b/tests/ChannelMessagesTest.php @@ -1,12 +1,12 @@ getOptions(); - self::$ably = new AblyRest( array_merge( self::$defaultOptions, [ + self::$ably = new PubSubHttpClient( array_merge( self::$defaultOptions, [ 'key' => self::$testApp->getAppKeyDefault()->string, ] ) ); } @@ -192,7 +192,7 @@ public function testMessageArraySingleRequest() { $messages[] = $msg; } - $ably = new AblyRest( array_merge( self::$defaultOptions, [ + $ably = new PubSubHttpClient( array_merge( self::$defaultOptions, [ 'key' => self::$testApp->getAppKeyDefault()->string, 'httpClass' => 'tests\HttpMockMsgCounter', ] ) ); @@ -327,7 +327,7 @@ public function testPublishExtras() { public function testEncryptedMessageUnencryptedHistory() { $errorLogged = false; - $ably = new AblyRest( array_merge( self::$defaultOptions, [ + $ably = new PubSubHttpClient( array_merge( self::$defaultOptions, [ 'key' => self::$testApp->getAppKeyDefault()->string, 'logHandler' => function( $level, $args ) use ( &$errorLogged ) { if ( $level == Log::ERROR ) { @@ -378,7 +378,7 @@ public function testUnencryptedMessageEncryptedHistory() { public function testEncryptionKeyMismatch() { $errorLogged = false; - $ably = new AblyRest( array_merge( self::$defaultOptions, [ + $ably = new PubSubHttpClient( array_merge( self::$defaultOptions, [ 'key' => self::$testApp->getAppKeyDefault()->string, 'logHandler' => function( $level, $args ) use ( &$errorLogged ) { if ( $level == Log::ERROR ) { @@ -466,8 +466,8 @@ public function testMessageEncodings() { public function testEncodingInteroperabilityRawToAbly() { $fixture = json_decode( file_get_contents( __DIR__ . '/../ably-common/test-resources/messages-encoding.json' ) ); - $defaultOpts = new \Ably\Models\ClientOptions( self::$defaultOptions ); - $http = new \Ably\Http( $defaultOpts ); // initialize http class for raw requests with default timeouts + $defaultOpts = new \Ably\PubSub\Models\ClientOptions( self::$defaultOptions ); + $http = new \Ably\PubSub\Http( $defaultOpts ); // initialize http class for raw requests with default timeouts $server = 'https://' . $defaultOpts->getPrimaryRestHost(); $messages = []; @@ -512,8 +512,8 @@ public function testEncodingInteroperabilityRawToAbly() { public function testEncodingInteroperabilityAblyToRaw() { $fixture = json_decode( file_get_contents( __DIR__ . '/../ably-common/test-resources/messages-encoding.json' ) ); - $defaultOpts = new \Ably\Models\ClientOptions( self::$defaultOptions ); - $http = new \Ably\Http( $defaultOpts ); // initialize http class for raw requests with default timeouts + $defaultOpts = new \Ably\PubSub\Models\ClientOptions( self::$defaultOptions ); + $http = new \Ably\PubSub\Http( $defaultOpts ); // initialize http class for raw requests with default timeouts $server = 'https://' . $defaultOpts->getPrimaryRestHost(); $messages = []; @@ -552,7 +552,7 @@ public function testEncodingInteroperabilityAblyToRaw() { * Test if null name and data elements are allowed when publishing messages */ public function testNullData() { - $ably = new AblyRest( array_merge( self::$defaultOptions, [ + $ably = new PubSubHttpClient( array_merge( self::$defaultOptions, [ 'key' => self::$testApp->getAppKeyDefault()->string, 'httpClass' => 'tests\HttpSaveWrapper', ] ) ); diff --git a/tests/ChannelStatusTest.php b/tests/ChannelStatusTest.php index 088adc5..178f364 100644 --- a/tests/ChannelStatusTest.php +++ b/tests/ChannelStatusTest.php @@ -1,6 +1,6 @@ getOptions(); - self::$ably = new AblyRest(array_merge(self::$defaultOptions, [ + self::$ably = new PubSubHttpClient(array_merge(self::$defaultOptions, [ 'key' => self::$testApp->getAppKeyDefault()->string, ])); } diff --git a/tests/ClientIdTest.php b/tests/ClientIdTest.php index e1afd3f..7f70f1f 100644 --- a/tests/ClientIdTest.php +++ b/tests/ClientIdTest.php @@ -1,9 +1,9 @@ self::$testApp->getAppKeyDefault()->string, 'clientId' => 'testClientId', ] ) ); @@ -48,7 +48,7 @@ public function testInitWithWildcardClientId() { $this->expectException(AblyException::class); $this->expectExceptionCode(40012); - $ably = new AblyRest( array_merge( self::$defaultOptions, [ + $ably = new PubSubHttpClient( array_merge( self::$defaultOptions, [ 'key' => self::$testApp->getAppKeyDefault()->string, 'clientId' => '*', ] ) ); @@ -59,7 +59,7 @@ public function testInitWithWildcardClientId() { */ public function testGetClientIdNull() { // no clientId provided anywhere, should be null - $ably = new AblyRest( array_merge( self::$defaultOptions, [ + $ably = new PubSubHttpClient( array_merge( self::$defaultOptions, [ 'key' => self::$testApp->getAppKeyDefault()->string, ] ) ); @@ -69,7 +69,7 @@ public function testGetClientIdNull() { $this->assertNull( $ably->auth->clientId, 'Expected clientId to be null' ); // test not yet authorised lib without a clientId specified on ClientOptions - $ablyImplicitCId = new AblyRest( array_merge( self::$defaultOptions, [ + $ablyImplicitCId = new PubSubHttpClient( array_merge( self::$defaultOptions, [ 'key' => self::$testApp->getAppKeyDefault()->string, 'defaultTokenParams' => new TokenParams( [ 'clientId' => 'testClientId', @@ -89,13 +89,13 @@ public function testGetClientIdNull() { */ public function testGetClientIdNonNull() { // test wildcard clientId provided via tokenDetails - $ablyKey = new AblyRest( array_merge( self::$defaultOptions, [ + $ablyKey = new PubSubHttpClient( array_merge( self::$defaultOptions, [ 'key' => self::$testApp->getAppKeyDefault()->string, ] ) ); $wildcardToken = $ablyKey->auth->requestToken( [ 'clientId' => '*' ] ); - $ablyWildcard = new AblyRest( array_merge( self::$defaultOptions, [ + $ablyWildcard = new PubSubHttpClient( array_merge( self::$defaultOptions, [ 'tokenDetails' => $wildcardToken, ] ) ); @@ -104,7 +104,7 @@ public function testGetClientIdNonNull() { $this->assertEquals( '*', $ablyWildcard->auth->clientId, 'Expected clientId to be *' ); // test specified clientId specified in ClientOptions - $ablyCid = new AblyRest( array_merge( self::$defaultOptions, [ + $ablyCid = new PubSubHttpClient( array_merge( self::$defaultOptions, [ 'key' => self::$testApp->getAppKeyDefault()->string, 'clientId' => 'testClientId', ] ) ); @@ -124,13 +124,13 @@ public function testGetClientIdNonNull() { * Check if messages can be assigned a clientId with a wildcard lib instance */ public function testWildcardClientIdMsg() { - $ablyKey = new AblyRest( array_merge( self::$defaultOptions, [ + $ablyKey = new PubSubHttpClient( array_merge( self::$defaultOptions, [ 'key' => self::$testApp->getAppKeyDefault()->string, ] ) ); $wildcardToken = $ablyKey->auth->requestToken( [ 'clientId' => '*' ] ); - $ablyWildcard = new AblyRest( array_merge( self::$defaultOptions, [ + $ablyWildcard = new PubSubHttpClient( array_merge( self::$defaultOptions, [ 'tokenDetails' => $wildcardToken, ] ) ); @@ -157,7 +157,7 @@ public function testWildcardClientIdMsg() { public function testClientIdLib() { $clientId = 'testClientId'; - $ablyCId = new AblyRest( array_merge( self::$defaultOptions, [ + $ablyCId = new PubSubHttpClient( array_merge( self::$defaultOptions, [ 'key' => self::$testApp->getAppKeyDefault()->string, 'clientId' => $clientId, 'useTokenAuth' => true, @@ -201,7 +201,7 @@ public function testClientIdLib() { * operations */ public function testClientIdPrecedence() { - $ablyCId = new AblyRest( array_merge( self::$defaultOptions, [ + $ablyCId = new PubSubHttpClient( array_merge( self::$defaultOptions, [ 'key' => self::$testApp->getAppKeyDefault()->string, 'useTokenAuth' => true, 'clientId' => 'overriddenClientId', @@ -227,13 +227,13 @@ public function testClientIdPrecedence() { * have a clientId. Check that Auth#clientId is null */ public function testRSA8f1() { - $ablyMain = new AblyRest( array_merge( self::$defaultOptions, [ + $ablyMain = new PubSubHttpClient( array_merge( self::$defaultOptions, [ 'key' => self::$testApp->getAppKeyDefault()->string, ] ) ); $tokenDetails = $ablyMain->auth->requestToken(); - $ablyClient = new AblyRest( array_merge( self::$defaultOptions, [ + $ablyClient = new PubSubHttpClient( array_merge( self::$defaultOptions, [ 'tokenDetails' => $tokenDetails, ] ) ); @@ -250,13 +250,13 @@ public function testRSA8f1() { * publish a message with an explicit clientId value, and ensure that the message is rejected */ public function testRSA8f2() { - $ablyMain = new AblyRest( array_merge( self::$defaultOptions, [ + $ablyMain = new PubSubHttpClient( array_merge( self::$defaultOptions, [ 'key' => self::$testApp->getAppKeyDefault()->string, ] ) ); $tokenDetails = $ablyMain->auth->requestToken(); - $ablyClient = new AblyRest( array_merge( self::$defaultOptions, [ + $ablyClient = new PubSubHttpClient( array_merge( self::$defaultOptions, [ 'tokenDetails' => $tokenDetails, ] ) ); @@ -273,13 +273,13 @@ public function testRSA8f2() { * a clientId. Check that Auth#clientId is a string with value '*'. */ public function testRSA8f3() { - $ablyMain = new AblyRest( array_merge( self::$defaultOptions, [ + $ablyMain = new PubSubHttpClient( array_merge( self::$defaultOptions, [ 'key' => self::$testApp->getAppKeyDefault()->string, ] ) ); $tokenDetails = $ablyMain->auth->requestToken( [ 'clientId' => '*' ] ); - $ablyClient = new AblyRest( array_merge( self::$defaultOptions, [ + $ablyClient = new PubSubHttpClient( array_merge( self::$defaultOptions, [ 'tokenDetails' => $tokenDetails, ] ) ); @@ -297,13 +297,13 @@ public function testRSA8f3() { * the provided clientId */ public function testRSA8f4() { - $ablyMain = new AblyRest( array_merge( self::$defaultOptions, [ + $ablyMain = new PubSubHttpClient( array_merge( self::$defaultOptions, [ 'key' => self::$testApp->getAppKeyDefault()->string, ] ) ); $tokenDetails = $ablyMain->auth->requestToken( [ 'clientId' => '*' ] ); - $ablyClient = new AblyRest( array_merge( self::$defaultOptions, [ + $ablyClient = new PubSubHttpClient( array_merge( self::$defaultOptions, [ 'tokenDetails' => $tokenDetails, ] ) ); diff --git a/tests/ClientOptionsTest.php b/tests/ClientOptionsTest.php index f56528e..dbaa813 100644 --- a/tests/ClientOptionsTest.php +++ b/tests/ClientOptionsTest.php @@ -1,7 +1,7 @@ $key ]); - $this->assertInstanceOf( 'Ably\Models\CipherParams', $cipherParams ); + $this->assertInstanceOf( 'Ably\PubSub\Models\CipherParams', $cipherParams ); $this->assertEquals( $key, $cipherParams->key, 'Expected the key to match the provided key' ); $this->assertEquals( 'aes', $cipherParams->algorithm, 'Expected \'aes\' algorithm' ); $this->assertEquals( 128, $cipherParams->keyLength, 'Expected keyLength of 128' ); @@ -54,35 +54,35 @@ public function testGetDefaultParams() { try { Crypto::getDefaultParams( [] ); } catch (\Exception $ex) { - $this->assertInstanceOf( 'Ably\Exceptions\AblyException', $ex, + $this->assertInstanceOf( 'Ably\PubSub\Exceptions\AblyException', $ex, 'Expected to check for key being provided' ); } try { Crypto::getDefaultParams([ 'key' => 'abcd', 'keyLength' => 128 ]); // 32-bit key } catch (\Exception $ex) { - $this->assertInstanceOf( 'Ably\Exceptions\AblyException', $ex, + $this->assertInstanceOf( 'Ably\PubSub\Exceptions\AblyException', $ex, 'Expected to check for key and keyLength mismatch' ); } try { Crypto::getDefaultParams([ 'key' => 'abcd', 'keyLength' => 32 ]); // 32-bit key } catch (\Exception $ex) { - $this->assertInstanceOf( 'Ably\Exceptions\AblyException', $ex, + $this->assertInstanceOf( 'Ably\PubSub\Exceptions\AblyException', $ex, 'Expected to check for an unacceptable key length' ); } try { Crypto::getDefaultParams([ 'key' => Crypto::generateRandomKey(), 'algorithm' => 'fake' ]); } catch (\Exception $ex) { - $this->assertInstanceOf( 'Ably\Exceptions\AblyException', $ex, + $this->assertInstanceOf( 'Ably\PubSub\Exceptions\AblyException', $ex, 'Expected to raise an exception on unknown encryption algorithm' ); } try { Crypto::getDefaultParams([ 'key' => Crypto::generateRandomKey(), 'mode' => 'fake' ]); } catch (\Exception $ex) { - $this->assertInstanceOf( 'Ably\Exceptions\AblyException', $ex, + $this->assertInstanceOf( 'Ably\PubSub\Exceptions\AblyException', $ex, 'Expected to raise an exception on unknown encryption mode' ); } } @@ -102,7 +102,7 @@ public function testNonAESEncryptionSupport() { try { Crypto::getDefaultParams([ 'key' => Crypto::generateRandomKey(), 'algorithm' => 'fake' ]); } catch (\Exception $ex) { - $this->assertInstanceOf( 'Ably\Exceptions\AblyException', $ex, 'Expected to raise an exception on unknown encryption mode' ); + $this->assertInstanceOf( 'Ably\PubSub\Exceptions\AblyException', $ex, 'Expected to raise an exception on unknown encryption mode' ); } } diff --git a/tests/DefaultsTest.php b/tests/DefaultsTest.php index 9923023..82e4d91 100644 --- a/tests/DefaultsTest.php +++ b/tests/DefaultsTest.php @@ -1,7 +1,7 @@ getOptions(); - self::$ably = new AblyRest( array_merge( self::$defaultOptions, [ + self::$ably = new PubSubHttpClient( array_merge( self::$defaultOptions, [ 'key' => self::$testApp->getAppKeyDefault()->string, ] ) ); } @@ -36,52 +38,174 @@ public function testVersionHeaderPresence() { 'key' => 'fake.key:totallyFake', 'httpClass' => 'tests\HttpMock', ]; - $ably = new AblyRest( $opts ); + $ably = new PubSubHttpClient( $opts ); $ably->time(); // make a request $curlParams = $ably->http->getCurlLastParams(); $this->assertContains( 'X-Ably-Version: ' . Defaults::API_VERSION, $curlParams[CURLOPT_HTTPHEADER], 'Expected Ably version header in HTTP request' ); - - AblyRest::setLibraryFlavourString(); } /** - * Verify proper agent header is set as per RSC7d + * Mock options that never reach the network. */ - public function testAblyAgentHeader() { - $opts = [ + private static function mockOptions( $extra = [] ) { + return array_merge( [ 'key' => 'fake.key:totallyFake', 'httpClass' => 'tests\HttpMock', - ]; - $ably = new AblyRest( $opts ); + ], $extra ); + } + + /** + * Makes one request through the given client and returns the value it sent + * in the Ably-Agent request header. + */ + private static function sentAgentHeader( $ably ) { $ably->time(); // make a request $curlParams = $ably->http->getCurlLastParams(); - $expectedAgentHeader = 'ably-php/'.Defaults::LIB_VERSION.' '.'php/'.Miscellaneous::getNumeric(phpversion()); - $this->assertContains( 'Ably-Agent: '. $expectedAgentHeader, $curlParams[CURLOPT_HTTPHEADER], - 'Expected Ably agent header in HTTP request' ); + foreach ( $curlParams[CURLOPT_HTTPHEADER] as $header ) { + if ( strpos( $header, 'Ably-Agent: ' ) === 0 ) { + return substr( $header, strlen( 'Ably-Agent: ' ) ); + } + } - $ably = new AblyRest( $opts ); - $ably->time(); // make a request + return null; + } - $curlParams = $ably->http->getCurlLastParams(); + /** + * The prefix every Ably-Agent header carries: this SDK and the runtime. + */ + private static function expectedPrefix() { + return 'ably-pubsub-php/'.Defaults::LIB_VERSION.' php/'.Miscellaneous::getNumeric( phpversion() ); + } - $this->assertContains( 'Ably-Agent: '. $expectedAgentHeader, $curlParams[CURLOPT_HTTPHEADER], - 'Expected Ably agent header in HTTP request' ); + /** + * Verify proper agent header is set as per RSC7d + */ + public function testAblyAgentHeader() { + $ably = new PubSubHttpClient( self::mockOptions() ); - AblyRest::setLibraryFlavourString( 'laravel'); - AblyRest::setAblyAgentHeader('customLib', '2.3.5'); - $ably = new AblyRest( $opts ); - $ably->time(); // make a request + $this->assertSame( self::expectedPrefix(), self::sentAgentHeader( $ably ), + 'Expected Ably agent header in HTTP request' ); - $curlParams = $ably->http->getCurlLastParams(); + // a second client renders the same header: no state leaks between instances + $ably = new PubSubHttpClient( self::mockOptions() ); - $expectedAgentHeader = 'ably-php/'.Defaults::LIB_VERSION.' '.'php/'.Miscellaneous::getNumeric(phpversion()).' laravel'.' customLib/2.3.5'; - $this->assertContains( 'Ably-Agent: '. $expectedAgentHeader, $curlParams[CURLOPT_HTTPHEADER], + $this->assertSame( self::expectedPrefix(), self::sentAgentHeader( $ably ), 'Expected Ably agent header in HTTP request' ); + } + + /** + * Wrapper attribution now travels as a client option rather than as + * process-global static state (RSC7d). + */ + public function testAblyAgentHeaderWithAgentsOption() { + $ably = new PubSubHttpClient( self::mockOptions( [ + 'agents' => [ 'laravel' => null, 'customLib' => '2.3.5' ], + ] ) ); + + $this->assertSame( self::expectedPrefix().' laravel customLib/2.3.5', self::sentAgentHeader( $ably ), + 'Expected agents option to be rendered in the Ably agent header' ); + } + + /** + * The door declares the server side, and declares it as a versionless flag. + * + * This is what the billing system reads to grant the MAU server exemption, + * so the assertions here are deliberately exact. + */ + public function testDoorDeclaresServerSide() { + $agentHeader = self::sentAgentHeader( Server::createHttpClient( self::mockOptions() ) ); + + $this->assertMatchesRegularExpression( + '/^ably-pubsub-php\/\d+\.\d+\.\d+(\S*)? php\/\S+ ably-pubsub-server$/', + $agentHeader, + 'Expected the door to declare the server side in the Ably agent header' + ); + } + + /** + * A versioned `ably-pubsub-server/` token must never be sent: the + * registry declares the identifier as a flag, and the versioned form does + * not classify. This is the PHP shape of the regression ably-js#2297 + * guards against, where an absent version rendered as `name/undefined`. + * + * @dataProvider sideAgentVersionProvider + */ + public function testSideAgentIsNeverVersioned( $callerVersion ) { + $agentHeader = self::sentAgentHeader( Server::createHttpClient( self::mockOptions( [ + 'agents' => [ Server::SERVER_AGENT_IDENTIFIER => $callerVersion ], + ] ) ) ); + + $this->assertStringContainsString( ' ably-pubsub-server', $agentHeader, + 'Expected the versionless server side flag' ); + $this->assertStringNotContainsString( 'ably-pubsub-server/', $agentHeader, + 'The server side flag must never carry a version' ); + $this->assertSame( self::expectedPrefix().' ably-pubsub-server', $agentHeader, + 'Expected the caller not to be able to override the side entry' ); + } + + public function sideAgentVersionProvider() { + return [ + 'caller supplies a version' => [ 'x' ], + 'caller supplies an empty version' => [ '' ], + 'caller supplies null' => [ null ], + ]; + } + + /** + * The door is the only path that stamps a side. A client built by calling + * the constructor directly declares none. + */ + public function testBareConstructorDeclaresNoSide() { + $agentHeader = self::sentAgentHeader( new PubSubHttpClient( self::mockOptions() ) ); + + $this->assertStringNotContainsString( 'ably-pubsub-server', $agentHeader, + 'A directly constructed client must not declare the server side' ); + $this->assertSame( self::expectedPrefix(), $agentHeader ); + } - AblyRest::setLibraryFlavourString(); + /** + * A caller's own agent entries survive the door and precede the side entry. + */ + public function testDoorPreservesCallerAgents() { + $agentHeader = self::sentAgentHeader( Server::createHttpClient( self::mockOptions( [ + 'agents' => [ 'my-sdk' => '1.0' ], + ] ) ) ); + + $this->assertSame( self::expectedPrefix().' my-sdk/1.0 ably-pubsub-server', $agentHeader, + 'Expected caller agents to be preserved and to precede the side entry' ); + } + + /** + * The door accepts everything the constructor accepts. + */ + public function testDoorAcceptsEveryConstructorArgumentForm() { + $expected = self::expectedPrefix().' ably-pubsub-server'; + + // an options array + $fromArray = Server::createHttpClient( self::mockOptions() ); + $this->assertSame( $expected, self::sentAgentHeader( $fromArray ) ); + $this->assertSame( 'fake.key:totallyFake', $fromArray->options->key ); + + // a ClientOptions instance + $clientOptions = new ClientOptions( self::mockOptions() ); + $fromClientOptions = Server::createHttpClient( $clientOptions ); + $this->assertSame( $expected, self::sentAgentHeader( $fromClientOptions ) ); + $this->assertSame( 'fake.key:totallyFake', $fromClientOptions->options->key ); + $this->assertSame( [], $clientOptions->agents, + 'The door must not mutate the ClientOptions instance it was given' ); + + // a bare API key string (contains a colon) + $fromKey = Server::createHttpClient( 'fake.key:totallyFake' ); + $this->assertSame( 'fake.key:totallyFake', $fromKey->options->key ); + $this->assertSame( [ Server::SERVER_AGENT_IDENTIFIER => null ], $fromKey->options->agents ); + + // a bare token string (no colon) + $fromToken = Server::createHttpClient( 'totallyFakeToken' ); + $this->assertSame( 'totallyFakeToken', $fromToken->options->token ); + $this->assertSame( [ Server::SERVER_AGENT_IDENTIFIER => null ], $fromToken->options->agents ); } /** @@ -96,7 +220,7 @@ public function testGET() { 'clientId' => 'test', ]; - $ably = new AblyRest( [ + $ably = new PubSubHttpClient( [ 'key' => 'fake.key:totallyFake', 'authUrl' => 'http://test.test/tokenRequest', 'authParams' => $authParams, @@ -126,7 +250,7 @@ public function testPOST() { 'clientId' => 'test', ]; - $ably = new AblyRest( [ + $ably = new PubSubHttpClient( [ 'key' => 'fake.key:totallyFake', 'authUrl' => 'http://test.test/tokenRequest', 'authParams' => $authParams, @@ -147,7 +271,7 @@ public function testPOST() { } /** - * RSC19 Test basic AblyRest::request functionality + * RSC19 Test basic PubSubHttpClient::request functionality */ public function testRequestBasic() { $ably = self::$ably; @@ -189,7 +313,7 @@ public function testRequestBasic() { * RSC19 - Test that Response handles various returned structures properly */ public function testRequestReturnValues() { - $ably = new AblyRest( [ + $ably = new PubSubHttpClient( [ 'key' => 'fake.key:totallyFake', 'httpClass' => 'tests\HttpMockReturnData', ] ); @@ -255,7 +379,7 @@ public function getInfo( $handle ) { class HttpMock extends Http { public function __construct() { - parent::__construct(new \Ably\Models\ClientOptions()); + parent::__construct(new \Ably\PubSub\Models\ClientOptions()); $this->curl = new CurlWrapperMock(); } diff --git a/tests/LogTest.php b/tests/LogTest.php index edef956..766ce59 100644 --- a/tests/LogTest.php +++ b/tests/LogTest.php @@ -1,7 +1,7 @@ 'fake.key:totallyFake' ] ); } @@ -33,7 +33,7 @@ public function testLogDefault() { $out .= $args[0] . "\n"; }, ]; - $ably = new AblyRest( $opts ); + $ably = new PubSubHttpClient( $opts ); $this->logMessages(); @@ -58,7 +58,7 @@ public function testLogVerbose() { }, ]; - $ably = new AblyRest( $opts ); + $ably = new PubSubHttpClient( $opts ); $this->logMessages(); $this->assertIsInt( strpos($out, 'This is a test warning.'), 'Expected warning level to be logged.' ); @@ -80,7 +80,7 @@ public function testLogNone() { }, ]; - $ably = new AblyRest( $opts ); + $ably = new PubSubHttpClient( $opts ); $this->logMessages(); $this->assertFalse( $called, 'Log handler incorrectly called' ); } diff --git a/tests/MiscellaneousTest.php b/tests/MiscellaneousTest.php index a4b2cf0..15a8059 100644 --- a/tests/MiscellaneousTest.php +++ b/tests/MiscellaneousTest.php @@ -1,8 +1,8 @@ relative path => namespace + */ + private static function sourceNamespaces() { + $namespaces = []; + $srcDir = self::$rootDir.'/src'; + + $files = new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator( $srcDir ) ); + foreach ( $files as $file ) { + if ( $file->isDir() || $file->getExtension() !== 'php' ) { + continue; + } + + $relative = substr( $file->getPathname(), strlen( $srcDir ) + 1 ); + $matched = preg_match( '/^\s*namespace\s+([^;]+);/m', file_get_contents( $file->getPathname() ), $m ); + $namespaces[$relative] = $matched ? trim( $m[1] ) : ''; + } + + return $namespaces; + } + + public function testComposerPackageName() { + $this->assertSame( 'ably/pubsub-server', self::composerJson()['name'], + 'This repository publishes the ably/pubsub-server package' ); + } + + public function testPsr4MapsOnlyThePubSubNamespace() { + $psr4 = self::composerJson()['autoload']['psr-4']; + + $this->assertSame( [ 'Ably\\PubSub\\' => 'src/' ], $psr4, + 'The package must not claim the bare Ably\\ prefix, which the legacy ' + .'ably/ably-php package owns: in a side-by-side install the autoloader ' + .'would resolve Ably\\AblyRest to whichever prefix path is searched first' ); + } + + public function testNoSourceFileDeclaresANamespaceOutsidePubSub() { + $offenders = []; + foreach ( self::sourceNamespaces() as $relative => $namespace ) { + if ( $namespace !== 'Ably\\PubSub' && strpos( $namespace, 'Ably\\PubSub\\' ) !== 0 ) { + $offenders[$relative] = $namespace; + } + } + + $this->assertSame( [], $offenders, + 'Every file under src/ must declare Ably\\PubSub or a child of it; ' + .'a file missed by the namespace move would collide with the legacy package' ); + } + + public function testSourceTreeIsNotEmpty() { + $this->assertNotEmpty( self::sourceNamespaces(), + 'Sanity check: the namespace scan actually found files to check' ); + } + + public function testLibVersionMatchesTheTopChangelogEntry() { + $changelog = file_get_contents( self::$rootDir.'/CHANGELOG.md' ); + + $this->assertSame( 1, preg_match( '/^## \[([^\]]+)\]/m', $changelog, $m ), + 'Expected a "## [version](...)" heading in CHANGELOG.md' ); + $this->assertSame( $m[1], Defaults::LIB_VERSION, + 'Defaults::LIB_VERSION is the only version site and must match the ' + .'top CHANGELOG.md entry; the release pre-flight checks both against the tag' ); + } + + /** + * The PHP analogue of "the wheel contains the files it should": every class + * declared under src/ resolves through Composer's generated autoloader. + */ + public function testEveryClassInSrcIsAutoloadable() { + $unloadable = []; + + foreach ( self::sourceNamespaces() as $relative => $namespace ) { + $className = $namespace.'\\'.basename( $relative, '.php' ); + + if ( !class_exists( $className ) && !interface_exists( $className ) && !trait_exists( $className ) ) { + $unloadable[] = $className; + } + } + + $this->assertSame( [], $unloadable, + 'Every class under src/ must be loadable through the Composer autoloader' ); + } + + public function testTheDoorIsLoadableAndFinal() { + $door = new \ReflectionClass( \Ably\PubSub\Server::class ); + + $this->assertTrue( $door->isFinal(), 'The door must be final' ); + $this->assertFalse( $door->getConstructor()->isPublic(), 'The door must not be instantiable' ); + $this->assertSame( 'ably-pubsub-server', \Ably\PubSub\Server::SERVER_AGENT_IDENTIFIER ); + $this->assertStringEndsWith( '-server', \Ably\PubSub\Server::SERVER_AGENT_IDENTIFIER, + 'The -server suffix is what grants the MAU server exemption' ); + } +} diff --git a/tests/PresenceTest.php b/tests/PresenceTest.php index 65405fe..f674841 100644 --- a/tests/PresenceTest.php +++ b/tests/PresenceTest.php @@ -1,9 +1,9 @@ getOptions(); - self::$ably = new AblyRest( array_merge( self::$defaultOptions, [ + self::$ably = new PubSubHttpClient( array_merge( self::$defaultOptions, [ 'key' => self::$testApp->getAppKeyDefault()->string, ] ) ); diff --git a/tests/AblyRestRequestTest.php b/tests/PubSubHttpClientRequestTest.php similarity index 90% rename from tests/AblyRestRequestTest.php rename to tests/PubSubHttpClientRequestTest.php index 753a92c..24a9f2b 100644 --- a/tests/AblyRestRequestTest.php +++ b/tests/PubSubHttpClientRequestTest.php @@ -1,9 +1,9 @@ getOptions(); - self::$ably = new AblyRest( array_merge( self::$defaultOptions, [ + self::$ably = new PubSubHttpClient( array_merge( self::$defaultOptions, [ 'key' => self::$testApp->getAppKeyDefault()->string, ] ) ); } @@ -25,7 +25,7 @@ public static function tearDownAfterClass(): void { * Batch publishes messages for given list of channels * RSC19 * https://ably.com/docs/api/rest-api#batch-publish - * @throws \Ably\Exceptions\AblyRequestException + * @throws \Ably\PubSub\Exceptions\AblyRequestException */ public function testBatchPublishMultipleChannelsUsingPostRequest() { diff --git a/tests/AblyRestTest.php b/tests/PubSubHttpClientTest.php similarity index 91% rename from tests/AblyRestTest.php rename to tests/PubSubHttpClientTest.php index 704fab7..0436bca 100644 --- a/tests/AblyRestTest.php +++ b/tests/PubSubHttpClientTest.php @@ -1,16 +1,16 @@ assertTrue( $ably->auth->isUsingBasicAuth(), 'Expected basic auth to be used' ); } @@ -40,7 +40,7 @@ public function testInitLibWithKeyString() { */ public function testInitLibWithKeyOption() { $key = 'fake.key:veryFake'; - $ably = new AblyRest( ['key' => $key ] ); + $ably = new PubSubHttpClient( ['key' => $key ] ); $this->assertTrue( $ably->auth->isUsingBasicAuth(), 'Expected basic auth to be used' ); } @@ -49,7 +49,7 @@ public function testInitLibWithKeyOption() { */ public function testInitLibWithTokenString() { $token = 'fake_token'; // token string never contains a colon - $ably = new AblyRest( $token ); + $ably = new PubSubHttpClient( $token ); $this->assertFalse( $ably->auth->isUsingBasicAuth(), 'Expected token auth to be used' ); } @@ -57,7 +57,7 @@ public function testInitLibWithTokenString() { * Init library with a token string in options */ public function testInitLibWithTokenOption() { - $ably = new AblyRest( [ + $ably = new PubSubHttpClient( [ 'token' => "this_is_not_really_a_token", ] ); @@ -68,7 +68,7 @@ public function testInitLibWithTokenOption() { * Init library with a tokenDetails in options */ public function testInitLibWithTokenDetailsOption() { - $ably = new AblyRest( [ + $ably = new PubSubHttpClient( [ 'tokenDetails' => new TokenDetails( "this_is_not_really_a_token" ), ] ); @@ -84,7 +84,7 @@ public function testInitLibWithSpecifiedHost() { 'restHost' => 'some.other.host', 'httpClass' => 'tests\HttpMockInitTest', ]; - $ably = new AblyRest( $opts ); + $ably = new PubSubHttpClient( $opts ); $ably->time(); // make a request $this->assertMatchesRegularExpression( '/^https?:\/\/some\.other\.host/', $ably->http->lastUrl, 'Unexpected host mismatch' ); } @@ -99,7 +99,7 @@ public function testInitLibWithSpecifiedPort() { 'tlsPort' => 999, 'httpClass' => 'tests\HttpMockInitTest', ]; - $ably = new AblyRest( $opts ); + $ably = new PubSubHttpClient( $opts ); $ably->time(); // make a request $this->assertStringContainsString( 'https://' . $opts['restHost'] . ':' . $opts['tlsPort'], @@ -114,7 +114,7 @@ public function testInitLibWithSpecifiedPort() { 'tls' => false, 'httpClass' => 'tests\HttpMockInitTest', ]; - $ably = new AblyRest( $opts ); + $ably = new PubSubHttpClient( $opts ); $ably->time(); // make a request $this->assertStringContainsString( 'http://' . $opts['restHost'] . ':' . $opts['port'], @@ -127,7 +127,7 @@ public function testInitLibWithSpecifiedPort() { * Init library with specified environment */ public function testInitLibWithSpecifiedEnv() { - $ably = new AblyRest( [ + $ably = new PubSubHttpClient( [ 'key' => 'fake.key:veryFake', 'environment' => 'sandbox', 'httpClass' => 'tests\HttpMockInitTest', @@ -144,7 +144,7 @@ public function testTLSDefaultIsTrue() { 'key' => 'fake.key:veryFake', 'httpClass' => 'tests\HttpMockInitTest', ]; - $ably = new AblyRest( $opts ); + $ably = new PubSubHttpClient( $opts ); $ably->time(); // make a request $this->assertMatchesRegularExpression( '/^https:\/\/rest\.ably\.io/', $ably->http->lastUrl, 'Unexpected scheme/url mismatch' ); } @@ -158,7 +158,7 @@ public function testTLSCanBeFalse() { 'httpClass' => 'tests\HttpMockInitTest', 'tls' => false, ]; - $ably = new AblyRest( $opts ); + $ably = new PubSubHttpClient( $opts ); $ably->time(); // make a request $this->assertMatchesRegularExpression( '/^http:\/\/rest\.ably\.io/', $ably->http->lastUrl, 'Unexpected scheme/url mismatch' ); } @@ -172,7 +172,7 @@ public function testTLSExplicitTrue() { 'httpClass' => 'tests\HttpMockInitTest', 'tls' => true, ]; - $ably = new AblyRest( $opts ); + $ably = new PubSubHttpClient( $opts ); $ably->time(); // make a request $this->assertMatchesRegularExpression( '/^https:\/\/rest\.ably\.io/', $ably->http->lastUrl, 'Unexpected scheme/url mismatch' ); } @@ -189,7 +189,7 @@ public function testMaxRetryCount() { 'httpMaxRetryCount' => 2, ]; - $ably = new AblyRest( $opts ); + $ably = new PubSubHttpClient( $opts ); try { $ably->time(); // make a request $this->fail('Expected the request to fail'); @@ -208,7 +208,7 @@ public function testFallbackHosts() { 'httpClass' => 'tests\HttpMockInitTestTimeout', 'httpMaxRetryCount' => 5, ]; - $ably = new AblyRest( $opts ); + $ably = new PubSubHttpClient( $opts ); try { $ably->time(); // make a request $this->fail('Expected the request to fail'); @@ -237,7 +237,7 @@ public function testEnvFallbackHosts() { 'httpMaxRetryCount' => 5, 'environment' => 'alpha' ]; - $ably = new AblyRest( $opts ); + $ably = new PubSubHttpClient( $opts ); try { $ably->time(); // make a request $this->fail('Expected the request to fail'); @@ -266,7 +266,7 @@ public function testNoFallbackOnEmptyCustomFallbacks() { 'restHost' => 'custom.host.com', 'fallbackHosts' => [], ]; - $ably = new AblyRest( $opts ); + $ably = new PubSubHttpClient( $opts ); try { $ably->time(); // make a request $this->fail('Expected the request to fail'); @@ -296,7 +296,7 @@ public function testCustomHostAndFallbacks() { 'httpClass' => 'tests\HttpMockInitTestTimeout', 'httpMaxRetryCount' => 3, ]); - $ably = new AblyRest( $opts ); + $ably = new PubSubHttpClient( $opts ); try { $ably->time(); // make a request $this->fail('Expected the request to fail'); @@ -323,7 +323,7 @@ public function testNoFallbackOnClientError() { 'httpClass' => 'tests\HttpMockInitTestTimeout', ]; - $ably = new AblyRest( $opts ); + $ably = new PubSubHttpClient( $opts ); $ably->http->httpErrorCode = 401; $ably->http->errorCode = 40101; // auth error @@ -346,7 +346,7 @@ public function testNoFallbackOnCustomHost() { 'httpClass' => 'tests\HttpMockInitTestTimeout', 'restHost' => 'custom.host.com', ]; - $ably = new AblyRest( $opts ); + $ably = new PubSubHttpClient( $opts ); try { $ably->time(); // make a request $this->fail('Expected the request to fail'); @@ -366,7 +366,7 @@ public function testFallbackHostsFailFirst3() { 'httpClass' => 'tests\HttpMockInitTestTimeout', 'httpMaxRetryCount' => 5, ]; - $ably = new AblyRest( $opts ); + $ably = new PubSubHttpClient( $opts ); $ably->http->hostFailures = 3; $data = $ably->time(); // make a request @@ -390,7 +390,7 @@ public function testFallbackHostHeader() { 'key' => 'fake.key:veryFake', 'httpClass' => 'tests\HttpMockInitTestTimeout' ]); - $ably = new AblyRest( $opts ); + $ably = new PubSubHttpClient( $opts ); try { $ably->time(); // make a request $this->fail('Expected the request to fail'); @@ -408,7 +408,7 @@ public function testFallbackHostHeader() { */ public function testCachedFallback() { $fallbackCacheTimeoutInMs = 1999; - $ably = new AblyRest( array_merge( self::$defaultOptions, [ + $ably = new PubSubHttpClient( array_merge( self::$defaultOptions, [ 'key' => self::$testApp->getAppKeyDefault()->string, 'fallbackRetryTimeout' => $fallbackCacheTimeoutInMs, 'httpClass' => 'tests\HttpMockCachedFallback', @@ -440,14 +440,14 @@ public function testCachedFallback() { /** * Verify accuracy of time (to within 2 seconds of actual time) * - * RSC16 RestClient#time function sends a get request to rest.ably.io/time + * RSC16 PubSubHttpClient#time function sends a get request to rest.ably.io/time * and returns the server time in milliseconds since epoch */ public function testTimeAndAccuracy() { $opts = [ 'key' => 'fake.key:veryFake', ]; - $ably = new AblyRest( $opts ); + $ably = new PubSubHttpClient( $opts ); $time = $ably->time(); $this->assertIsInt( $time ); @@ -465,7 +465,7 @@ public function testActiveInternetConnection() { $opts = [ 'key' => 'fake.key:veryFake', ]; - $ably = new AblyRest( $opts ); + $ably = new PubSubHttpClient( $opts ); $this->assertTrue($ably ->hasActiveInternetConnection()); } @@ -473,7 +473,7 @@ public function testActiveInternetConnection() { * Verify that time fails without valid host */ public function testTimeFailsWithInvalidHost() { - $ablyInvalidHost = new AblyRest( [ + $ablyInvalidHost = new PubSubHttpClient( [ 'key' => 'fake.key:veryFake', 'restHost' => 'this.host.does.not.exist', ]); @@ -487,11 +487,11 @@ public function testTimeFailsWithInvalidHost() { * Connection/open timeout not reliably testable. */ public function testHttpTimeout() { - $ably = new AblyRest( [ + $ably = new PubSubHttpClient( [ 'key' => 'fake.key:veryFake', ]); - $ablyTimeout = new AblyRest( [ + $ablyTimeout = new PubSubHttpClient( [ 'key' => 'fake.key:veryFake', 'httpRequestTimeout' => 20, // 20 ms ]); diff --git a/tests/PushAdminTest.php b/tests/PushAdminTest.php index 38e9d93..0c28dc0 100644 --- a/tests/PushAdminTest.php +++ b/tests/PushAdminTest.php @@ -1,7 +1,7 @@ getOptions(); - self::$ably = new AblyRest( array_merge( self::$defaultOptions, [ + self::$ably = new PubSubHttpClient( array_merge( self::$defaultOptions, [ 'key' => self::$testApp->getAppKeyDefault()->string, ] ) ); } diff --git a/tests/PushChannelSubscriptionsTest.php b/tests/PushChannelSubscriptionsTest.php index 569d9ef..252edf8 100644 --- a/tests/PushChannelSubscriptionsTest.php +++ b/tests/PushChannelSubscriptionsTest.php @@ -1,9 +1,9 @@ getOptions(); - self::$ably = new AblyRest( array_merge( self::$defaultOptions, [ + self::$ably = new PubSubHttpClient( array_merge( self::$defaultOptions, [ 'key' => self::$testApp->getAppKeyDefault()->string, ] ) ); } diff --git a/tests/PushDeviceRegistrationsTest.php b/tests/PushDeviceRegistrationsTest.php index 4ec2dfb..ee5f358 100644 --- a/tests/PushDeviceRegistrationsTest.php +++ b/tests/PushDeviceRegistrationsTest.php @@ -1,10 +1,10 @@ getOptions(); - self::$ably = new AblyRest( array_merge( self::$defaultOptions, [ + self::$ably = new PubSubHttpClient( array_merge( self::$defaultOptions, [ 'key' => self::$testApp->getAppKeyDefault()->string, ] ) ); } diff --git a/tests/TokenTest.php b/tests/TokenTest.php index 777098c..74e1072 100644 --- a/tests/TokenTest.php +++ b/tests/TokenTest.php @@ -1,8 +1,8 @@ getOptions(); - self::$ably = new AblyRest( array_merge( self::$defaultOptions, [ + self::$ably = new PubSubHttpClient( array_merge( self::$defaultOptions, [ 'key' => self::$testApp->getAppKeyDefault()->string, ] ) ); @@ -143,7 +143,7 @@ public function testTokenGenerationWithSpecifiedKey() { 'key' => $key->string, ]; - $ably = new AblyRest( array_merge( self::$defaultOptions, [ + $ably = new PubSubHttpClient( array_merge( self::$defaultOptions, [ 'key' => 'fake.key:veryFake', ] ) ); @@ -211,7 +211,7 @@ public function testTokenRenewalKnownExpiration() { } ] ); - $ablyTokenAuth = new AblyRest( $options ); + $ablyTokenAuth = new PubSubHttpClient( $options ); $ablyTokenAuth->auth->authorize(); $tokenBefore = $ablyTokenAuth->auth->getTokenDetails()->token; @@ -248,7 +248,7 @@ public function testTokenRenewalUnknownExpiration() { } ] ); - $ablyTokenAuth = new AblyRest( $options ); + $ablyTokenAuth = new PubSubHttpClient( $options ); $ablyTokenAuth->auth->authorize(); $tokenBefore = $ablyTokenAuth->auth->getTokenDetails()->token; @@ -285,7 +285,7 @@ public function testTokenRenewalUnknownExpirationFailure() { } ] ); - $ablyTokenAuth = new AblyRest( $options ); + $ablyTokenAuth = new PubSubHttpClient( $options ); $channel = $ablyTokenAuth->channel( 'testchannel' ); // do an authorised request with the valid token @@ -312,7 +312,7 @@ public function testFailingTokenRenewalUnknownExpiration() { $options = array_merge( self::$defaultOptions, [ 'token' => $tokenDetails->token, ] ); - $ablyTokenAuth = new AblyRest( $options ); + $ablyTokenAuth = new PubSubHttpClient( $options ); $channel = $ablyTokenAuth->channel( 'testchannel' ); $channel->publish( 'test', 'test' ); // this should work diff --git a/tests/TypesTest.php b/tests/TypesTest.php index 9269fa4..ec3196f 100644 --- a/tests/TypesTest.php +++ b/tests/TypesTest.php @@ -1,9 +1,9 @@ verifyClassMembers( '\Ably\Models\Message', [ + $this->verifyClassMembers( '\Ably\PubSub\Models\Message', [ 'id', 'clientId', 'connectionId', @@ -59,7 +59,7 @@ public function testMessageType() { } public function testPresenceMessageType() { - $this->verifyClassMembers( '\Ably\Models\PresenceMessage', [ + $this->verifyClassMembers( '\Ably\PubSub\Models\PresenceMessage', [ 'id', 'action', 'clientId', @@ -70,7 +70,7 @@ public function testPresenceMessageType() { 'memberKey' ] ); - $this->verifyClassConstants( '\Ably\Models\PresenceMessage', [ + $this->verifyClassConstants( '\Ably\PubSub\Models\PresenceMessage', [ 'ABSENT' => 0, 'PRESENT' => 1, 'ENTER' => 2, @@ -80,7 +80,7 @@ public function testPresenceMessageType() { } public function testTokenRequestType() { - $this->verifyClassMembers( '\Ably\Models\TokenRequest', [ + $this->verifyClassMembers( '\Ably\PubSub\Models\TokenRequest', [ 'keyName', 'clientId', 'nonce', @@ -91,7 +91,7 @@ public function testTokenRequestType() { } public function testTokenDetailsType() { - $this->verifyClassMembers( '\Ably\Models\TokenDetails', [ + $this->verifyClassMembers( '\Ably\PubSub\Models\TokenDetails', [ 'token', 'expires', 'issued', @@ -101,7 +101,7 @@ public function testTokenDetailsType() { } public function testStatsType() { - $this->verifyClassMembers( '\Ably\Models\Stats', [ + $this->verifyClassMembers( '\Ably\PubSub\Models\Stats', [ 'all', 'apiRequests', 'channels', @@ -117,7 +117,7 @@ public function testStatsType() { } public function testErrorInfoType() { - $this->verifyClassMembers( '\Ably\Models\ErrorInfo', [ + $this->verifyClassMembers( '\Ably\PubSub\Models\ErrorInfo', [ 'code', 'statusCode', 'message', @@ -125,7 +125,7 @@ public function testErrorInfoType() { } public function testClientOptionsType() { - $this->verifyClassMembers( '\Ably\Models\ClientOptions', [ + $this->verifyClassMembers( '\Ably\PubSub\Models\ClientOptions', [ 'clientId', 'logLevel', 'logHandler', @@ -151,7 +151,7 @@ public function testClientOptionsType() { 'idempotentRestPublishing', ] ); - $co = new \Ably\Models\ClientOptions(); + $co = new \Ably\PubSub\Models\ClientOptions(); $this->assertEquals( 4000, $co->httpOpenTimeout ); $this->assertEquals( 10000, $co->httpRequestTimeout ); $this->assertEquals( 3, $co->httpMaxRetryCount ); @@ -162,7 +162,7 @@ public function testClientOptionsType() { public function testClientOptionsIdempotent() { // Test default value - $co = new \Ably\Models\ClientOptions(); + $co = new \Ably\PubSub\Models\ClientOptions(); if (Defaults::API_VERSION <= '1.1') { $this->assertEquals( false, $co->idempotentRestPublishing ); } else { @@ -170,15 +170,15 @@ public function testClientOptionsIdempotent() } // Test explicit value - $co = new \Ably\Models\ClientOptions( array( 'idempotentRestPublishing' => true ) ); + $co = new \Ably\PubSub\Models\ClientOptions( array( 'idempotentRestPublishing' => true ) ); $this->assertEquals( true, $co->idempotentRestPublishing ); - $co = new \Ably\Models\ClientOptions( array( 'idempotentRestPublishing' => false ) ); + $co = new \Ably\PubSub\Models\ClientOptions( array( 'idempotentRestPublishing' => false ) ); $this->assertEquals( false, $co->idempotentRestPublishing ); } public function testAuthOptionsType() { - $this->verifyClassMembers( '\Ably\Models\ClientOptions', [ + $this->verifyClassMembers( '\Ably\PubSub\Models\ClientOptions', [ 'key', 'authCallback', 'authUrl', @@ -190,7 +190,7 @@ public function testAuthOptionsType() { } public function testTokenParamsType() { - $this->verifyClassMembers( '\Ably\Models\TokenParams', [ + $this->verifyClassMembers( '\Ably\PubSub\Models\TokenParams', [ 'ttl', 'capability', 'clientId', @@ -199,13 +199,13 @@ public function testTokenParamsType() { } public function testChannelOptionsType() { - $this->verifyClassMembers( '\Ably\Models\ChannelOptions', [ + $this->verifyClassMembers( '\Ably\PubSub\Models\ChannelOptions', [ 'cipher', ] ); } public function testCipherParamsType() { - $this->verifyClassMembers( '\Ably\Models\CipherParams', [ + $this->verifyClassMembers( '\Ably\PubSub\Models\CipherParams', [ 'algorithm', 'key', 'keyLength', @@ -214,16 +214,16 @@ public function testCipherParamsType() { } public function testStatsTypes() { - $stats = new \Ably\Models\Stats(); + $stats = new \Ably\PubSub\Models\Stats(); $this->verifyObjectTypes( $stats, [ - 'all' => 'Ably\Models\Stats\MessageTypes', - 'inbound' => 'Ably\Models\Stats\MessageTraffic', - 'outbound' => 'Ably\Models\Stats\MessageTraffic', - 'persisted' => 'Ably\Models\Stats\MessageTypes', - 'connections' => 'Ably\Models\Stats\ConnectionTypes', - 'channels' => 'Ably\Models\Stats\ResourceCount', - 'apiRequests' => 'Ably\Models\Stats\RequestCount', - 'tokenRequests' => 'Ably\Models\Stats\RequestCount', + 'all' => 'Ably\PubSub\Models\Stats\MessageTypes', + 'inbound' => 'Ably\PubSub\Models\Stats\MessageTraffic', + 'outbound' => 'Ably\PubSub\Models\Stats\MessageTraffic', + 'persisted' => 'Ably\PubSub\Models\Stats\MessageTypes', + 'connections' => 'Ably\PubSub\Models\Stats\ConnectionTypes', + 'channels' => 'Ably\PubSub\Models\Stats\ResourceCount', + 'apiRequests' => 'Ably\PubSub\Models\Stats\RequestCount', + 'tokenRequests' => 'Ably\PubSub\Models\Stats\RequestCount', 'intervalId' => 'string', 'intervalGranularity' => 'string', 'intervalTime' => 'integer', @@ -231,9 +231,9 @@ public function testStatsTypes() { // verify MessageTypes $this->verifyObjectTypes( $stats->all, [ - 'all' => 'Ably\Models\Stats\MessageCount', - 'messages' => 'Ably\Models\Stats\MessageCount', - 'presence' => 'Ably\Models\Stats\MessageCount', + 'all' => 'Ably\PubSub\Models\Stats\MessageCount', + 'messages' => 'Ably\PubSub\Models\Stats\MessageCount', + 'presence' => 'Ably\PubSub\Models\Stats\MessageCount', ] ); // verify MessageCount @@ -244,17 +244,17 @@ public function testStatsTypes() { // verify MessageTraffic $this->verifyObjectTypes( $stats->inbound, [ - 'all' => 'Ably\Models\Stats\MessageTypes', - 'realtime' => 'Ably\Models\Stats\MessageTypes', - 'rest' => 'Ably\Models\Stats\MessageTypes', - 'webhook' => 'Ably\Models\Stats\MessageTypes', + 'all' => 'Ably\PubSub\Models\Stats\MessageTypes', + 'realtime' => 'Ably\PubSub\Models\Stats\MessageTypes', + 'rest' => 'Ably\PubSub\Models\Stats\MessageTypes', + 'webhook' => 'Ably\PubSub\Models\Stats\MessageTypes', ] ); // verify ConnectionTypes $this->verifyObjectTypes( $stats->connections, [ - 'all' => 'Ably\Models\Stats\ResourceCount', - 'plain' => 'Ably\Models\Stats\ResourceCount', - 'tls' => 'Ably\Models\Stats\ResourceCount', + 'all' => 'Ably\PubSub\Models\Stats\ResourceCount', + 'plain' => 'Ably\PubSub\Models\Stats\ResourceCount', + 'tls' => 'Ably\PubSub\Models\Stats\ResourceCount', ] ); // verify ResourceCount @@ -275,7 +275,7 @@ public function testStatsTypes() { } public function testHttpPaginatedResponseType() { - $this->verifyClassMembers( '\Ably\Models\HttpPaginatedResponse', [ + $this->verifyClassMembers( '\Ably\PubSub\Models\HttpPaginatedResponse', [ 'items', 'statusCode', 'success', diff --git a/tests/UtilsTest.php b/tests/UtilsTest.php index 4df2901..bccdbf4 100644 --- a/tests/UtilsTest.php +++ b/tests/UtilsTest.php @@ -1,7 +1,7 @@ debugRequests) { var_dump($raw);