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..2cf20b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,34 @@ # 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. +- Added the `endpoint` client option (REC1, REC2), which is now the only way to choose where the client connects. The default primary domain is `main.realtime.ably.net`, with fallback hosts `main.[a-e].fallback.ably-realtime.com`. +- Removed the `environment` and `restHost` client options in favour of `endpoint`. They are now ignored if passed, so a client still setting `environment` connects to production. +- Setting `port` or `tlsPort` no longer disables the default fallback hosts; a hostname `endpoint` does. +- `ClientOptions::getPrimaryRestHost()` is now `getPrimaryDomain()`. `Defaults::$restHost`, `Defaults::$realtimeHost` and `Defaults::getEnvironmentFallbackHosts()` are replaced by `Defaults::$endpoint`, `Defaults::getPrimaryDomain()` and `Defaults::getEndpointFallbackHosts()`. +- 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/CONTRIBUTING.md b/CONTRIBUTING.md index def08b9..e22e01f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,6 +3,6 @@ 1. Fork it 2. Create your feature branch (`git checkout -b my-new-feature`) 3. Commit your changes (`git commit -am 'Add some feature'`) -4. Ensure you have added suitable tests and the test suite is passing (run `vendor/bin/phpunit`) +4. Ensure you have added suitable tests and the test suite is passing (run `vendor/bin/phpunit`). Tests run against the `nonprod:sandbox` endpoint; set `ABLY_ENDPOINT` to use another. 4. Push to the branch (`git push origin my-new-feature`) 5. Create a new Pull Request \ No newline at end of file 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..7778488 --- /dev/null +++ b/UPDATING.md @@ -0,0 +1,122 @@ +# 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 and crypto behave exactly as they did in 1.x. The one change to `ClientOptions` is how the client chooses where to connect: the `endpoint` option replaces `environment` and `restHost` (see [Endpoint](#endpoint-replaces-environment-and-resthost)). For most applications the migration is confined to the `composer require` line, the `use` statements, the constructor call, and any host options. + +### 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)) | +| `'environment' => 'sandbox'` / `'restHost' => '…'` | `'endpoint' => …` (see [Endpoint](#endpoint-replaces-environment-and-resthost)) | + +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. + +### Endpoint replaces environment and restHost + +The `endpoint` client option is now the only way to choose where the client connects. `environment` and `restHost` are removed. Like any other unknown key, they are now ignored, so a client still passing `'environment' => 'sandbox'` connects to production: replace them before upgrading. + +| 1.x | 2.0 | +| --- | --- | +| no host options | no change; traffic moves from `rest.ably.io` to `main.realtime.ably.net` | +| `'environment' => 'sandbox'` | `'endpoint' => 'nonprod:sandbox'` | +| `'environment' => 'acme'` (dedicated cluster) | `'endpoint' => 'acme'` | +| `'restHost' => 'localhost'` | `'endpoint' => 'localhost'` | +| `'restHost' => 'rest.example.com'` | `'endpoint' => 'rest.example.com'` | +| custom `'fallbackHosts' => […]` | unchanged | + +How `endpoint` resolves: + +| `endpoint` | Primary domain | Default fallback hosts | +| --- | --- | --- | +| unset (`main`) | `main.realtime.ably.net` | `main.[a-e].fallback.ably-realtime.com` | +| `[id]`, e.g. `acme` | `[id].realtime.ably.net` | `[id].[a-e].fallback.ably-realtime.com` | +| `nonprod:[id]`, e.g. `nonprod:sandbox` | `[id].realtime.ably-nonprod.net` | `[id].[a-e].fallback.ably-realtime-nonprod.com` | +| a hostname: contains `.` or `::`, or is `localhost` | the value as given | none | + +A non-empty `fallbackHosts` still replaces the default fallback hosts. Setting `port` or `tlsPort` no longer disables the default fallback hosts; only a hostname `endpoint` does. + +```php +// 1.x +$ably = new AblyRest(['key' => getenv('ABLY_API_KEY'), 'environment' => 'sandbox']); + +// 2.0 +$ably = Server::createHttpClient(['key' => getenv('ABLY_API_KEY'), 'endpoint' => 'nonprod:sandbox']); +``` + +`ClientOptions::getPrimaryRestHost()` is renamed `getPrimaryDomain()`, and `Defaults::$restHost`, `Defaults::$realtimeHost` and `Defaults::getEnvironmentFallbackHosts()` are replaced by `Defaults::$endpoint`, `Defaults::getPrimaryDomain()` and `Defaults::getEndpointFallbackHosts()`. + +Firewall allowlists and outbound proxies must allow `*.realtime.ably.net` and `*.fallback.ably-realtime.com` in place of `rest.ably.io` and `*.ably-realtime.com`. + +### 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 `environment` and `restHost` client options — replaced by `endpoint`. They are ignored if passed. +* 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 other than the host options above, 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..00b6ce0 100644 --- a/src/Defaults.php +++ b/src/Defaults.php @@ -1,12 +1,11 @@ primaryHost = $clientOptions->getPrimaryRestHost(); + $this->primaryHost = $clientOptions->getPrimaryDomain(); $this->fallbackHosts = $clientOptions->getFallbackHosts(); $this->hostCache = new HostCache($clientOptions->fallbackRetryTimeout); } diff --git a/src/HostCache.php b/src/HostCache.php index 3f5bf24..a7d9e05 100644 --- a/src/HostCache.php +++ b/src/HostCache.php @@ -1,9 +1,9 @@ 'sandbox-rest.ably.io' - */ - public $environment; - - /** - * @var string[] fallback hosts, used when connection to default host fails, populated automatically + * @var string[] fallback hosts, used when connection to the primary domain fails; when empty, derived from $endpoint */ public $fallbackHosts = []; /** * @var integer – default 600000 (10 minutes) the period in milliseconds - * before HTTP requests are retried against the default endpoint + * before HTTP requests are retried against the primary domain */ public $fallbackRetryTimeout = 600000; /** - * @var \Ably\Models\TokenParams defaultTokenParams – overrides the client library defaults described in TokenParams + * @var \Ably\PubSub\Models\TokenParams defaultTokenParams – overrides the client library defaults described in TokenParams */ public $defaultTokenParams; @@ -99,7 +97,7 @@ class ClientOptions extends AuthOptions { * @var string a class that should be used for making HTTP connections * To allow mocking in tests. */ - public $httpClass = 'Ably\Http'; + public $httpClass = 'Ably\PubSub\Http'; /** * @var bool defaults to false for clients with version < 1.2, otherwise true @@ -110,38 +108,68 @@ class ClientOptions extends AuthOptions { * @var string a class that should be used for Auth * To allow mocking in tests. */ - public $authClass = 'Ably\Auth'; - - - private function isProductionEnvironment() { - return empty($this->environment) || strcasecmp($this->environment, "production") == 0; - } + public $authClass = 'Ably\PubSub\Auth'; + + /** + * Additional agent entries appended to the `Ably-Agent` request header. + * + * This should only be used by Ably-authored SDKs and wrappers layered on + * top of this package. An identifier used here has to be registered in + * the ably-common agents registry first: + * https://github.com/ably/ably-common/blob/main/protocol/agents.json + * + * Keys are agent identifiers, values are agent versions. A `null` or + * empty-string value renders the identifier as a bare flag carrying no + * version, which is how the registry declares runtime flags such as + * `browser` and `ably-pubsub-server`. + * + * @var array + */ + 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 ]; + } - private function isDefaultPort() { - return $this->tls ? $this->tlsPort == Defaults::$tlsPort : $this->port == Defaults::$port; + return $options; } private function activePort() { return $this->tls ? $this->tlsPort : $this->port; } - private function isDefaultRestHost() { - return $this->restHost == Defaults::$restHost; - } - - public function getPrimaryRestHost() { - if ($this->isDefaultRestHost()) { - return $this->isProductionEnvironment() ? $this->restHost : $this->environment.'-'.$this->restHost; - } - return $this->restHost; + /** + * The domain both REST requests and, in SDKs that have one, the realtime + * connection use (REC1, RSC25). + */ + public function getPrimaryDomain() { + return Defaults::getPrimaryDomain($this->endpoint); } + /** + * An explicit, non-empty `fallbackHosts` replaces the defaults (REC2a2); + * otherwise the defaults are derived from `endpoint` (REC2c). + */ public function getFallbackHosts() { - $fallbacks = $this->fallbackHosts ?? []; - if (empty($this->fallbackHosts) && $this->isDefaultRestHost() && $this->isDefaultPort()) { - $fallbacks = $this->isProductionEnvironment() ? Defaults::$fallbackHosts : Defaults::getEnvironmentFallbackHosts($this->environment); + if (!empty($this->fallbackHosts)) { + return $this->fallbackHosts; } - return $fallbacks; + return Defaults::getEndpointFallbackHosts($this->endpoint); } public function getHostUrl($host) { @@ -150,8 +178,8 @@ public function getHostUrl($host) { public function __construct( $options = [] ) { parent::__construct( $options ); - if (empty($this->restHost)) { - $this->restHost = Defaults::$restHost; + if (empty($this->endpoint)) { + $this->endpoint = Defaults::$endpoint; } if (empty($this->port)) { $this->port = Defaults::$port; 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 75% rename from src/AblyRest.php rename to src/PubSubHttpClient.php index 86f6a1c..b03330c 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 ); @@ -207,7 +219,7 @@ public function requestInternal( $method, $path, $headers = [], $params = [], $r $hostUrl = $this->options->getHostUrl($host). $path; try { $updatedHeaders = $mergedHeaders; - if ($host != $this->options->getPrimaryRestHost()) { // set hostHeader for fallback host (RSC15j) + if ($host != $this->options->getPrimaryDomain()) { // set hostHeader for fallback host (RSC15j) $updatedHeaders[] = "Host: " . $host; } $response = $this->http->request( $method, $hostUrl, $updatedHeaders, $params ); @@ -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..142e8fc 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,14 +163,14 @@ 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', 'fallbackHosts' => [ - self::$ably->options->getPrimaryRestHost(), - self::$ably->options->getPrimaryRestHost(), - self::$ably->options->getPrimaryRestHost(), + self::$ably->options->getPrimaryDomain(), + self::$ably->options->getPrimaryDomain(), + self::$ably->options->getPrimaryDomain(), ], ] ) ); diff --git a/tests/ChannelMessagesTest.php b/tests/ChannelMessagesTest.php index 4f4f9c6..dc3de2c 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,9 +466,9 @@ 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 - $server = 'https://' . $defaultOpts->getPrimaryRestHost(); + $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->getPrimaryDomain(); $messages = []; foreach ($fixture->messages as $i => $testMsgData) { @@ -512,9 +512,9 @@ 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 - $server = 'https://' . $defaultOpts->getPrimaryRestHost(); + $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->getPrimaryDomain(); $messages = []; foreach ($fixture->messages as $i => $testMsgData) { @@ -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..690cbb1 100644 --- a/tests/ClientOptionsTest.php +++ b/tests/ClientOptionsTest.php @@ -1,19 +1,19 @@ getPrimaryRestHost()); + self::assertEquals('main', $clientOptions->endpoint); + self::assertEquals('main.realtime.ably.net', $clientOptions->getPrimaryDomain()); self::assertTrue($clientOptions->tls); self::assertEquals(443, $clientOptions->tlsPort); $fallbackHosts = $clientOptions->getFallbackHosts(); @@ -22,74 +22,77 @@ public function testWithDefaultOptions() { } /** - * @testdox RSC15h + * @testdox REC1b4 REC2c4 */ - public function testWithProductionEnvironment() { - $clientOptions = new ClientOptions(); - $clientOptions->environment = "Production"; - self::assertEquals('rest.ably.io', $clientOptions->getPrimaryRestHost()); - self::assertTrue($clientOptions->tls); - self::assertEquals(443, $clientOptions->tlsPort); - $fallbackHosts = $clientOptions->getFallbackHosts(); - sort($fallbackHosts); - $this->assertEquals(Defaults::$fallbackHosts, $fallbackHosts); + public function testWithRoutingPolicyEndpoint() { + $clientOptions = new ClientOptions(['endpoint' => 'acme']); + self::assertEquals('acme.realtime.ably.net', $clientOptions->getPrimaryDomain()); + self::assertEquals([ + 'acme.a.fallback.ably-realtime.com', + 'acme.b.fallback.ably-realtime.com', + 'acme.c.fallback.ably-realtime.com', + 'acme.d.fallback.ably-realtime.com', + 'acme.e.fallback.ably-realtime.com', + ], $clientOptions->getFallbackHosts()); } /** - * @testdox RSC15g2 RTC1e + * @testdox REC1b3 REC2c3 */ - public function testWithCustomEnvironment() { - $clientOptions = new ClientOptions(); - $clientOptions->environment = "sandbox"; - self::assertEquals('sandbox-rest.ably.io', $clientOptions->getPrimaryRestHost()); - self::assertTrue($clientOptions->tls); - self::assertEquals(443, $clientOptions->tlsPort); - $fallbackHosts = $clientOptions->getFallbackHosts(); - sort($fallbackHosts); - $this->assertEquals(Defaults::getEnvironmentFallbackHosts('sandbox'), $fallbackHosts); + public function testWithNonprodEndpoint() { + $clientOptions = new ClientOptions(['endpoint' => 'nonprod:sandbox']); + self::assertEquals('sandbox.realtime.ably-nonprod.net', $clientOptions->getPrimaryDomain()); + self::assertEquals([ + 'sandbox.a.fallback.ably-realtime-nonprod.com', + 'sandbox.b.fallback.ably-realtime-nonprod.com', + 'sandbox.c.fallback.ably-realtime-nonprod.com', + 'sandbox.d.fallback.ably-realtime-nonprod.com', + 'sandbox.e.fallback.ably-realtime-nonprod.com', + ], $clientOptions->getFallbackHosts()); + } + + public static function hostnameEndpointProvider() { + return [ + ['foo.example.com'], + ['localhost'], + ['127.0.0.1'], + ['::1'], + ]; } /** - * @testdox RSC11b RTN17b RTC1e + * @testdox REC1b2 REC2c2 + * @dataProvider hostnameEndpointProvider */ - public function testWithCustomEnvironmentAndNonDefaultPorts() { - $clientOptions = new ClientOptions(); - $clientOptions->environment = "local"; - $clientOptions->port = 8080; - $clientOptions->tlsPort = 8081; - self::assertEquals('local-rest.ably.io', $clientOptions->getPrimaryRestHost()); - self::assertEquals(8080, $clientOptions->port); - self::assertEquals(8081, $clientOptions->tlsPort); - self::assertTrue($clientOptions->tls); - $fallbackHosts = $clientOptions->getFallbackHosts(); - self::assertEmpty($fallbackHosts); + public function testWithHostnameEndpoint($endpoint) { + $clientOptions = new ClientOptions(['endpoint' => $endpoint]); + self::assertEquals($endpoint, $clientOptions->getPrimaryDomain()); + self::assertEmpty($clientOptions->getFallbackHosts()); } /** - * @testdox RSC11 + * Non-default ports do not suppress the default fallback hosts; only a + * hostname endpoint does (REC2c). + * @testdox REC2c1 */ - public function testWithCustomRestHost() { - $clientOptions = new ClientOptions(); - $clientOptions->restHost = "test.org"; - self::assertEquals('test.org', $clientOptions->getPrimaryRestHost()); - self::assertEquals(80, $clientOptions->port); - self::assertEquals(443, $clientOptions->tlsPort); - self::assertTrue($clientOptions->tls); - $fallbackHosts = $clientOptions->getFallbackHosts(); - self::assertEmpty($fallbackHosts); + public function testWithNonDefaultPorts() { + $clientOptions = new ClientOptions(['port' => 8080, 'tlsPort' => 8081]); + self::assertEquals(8080, $clientOptions->port); + self::assertEquals(8081, $clientOptions->tlsPort); + self::assertEquals('main.realtime.ably.net', $clientOptions->getPrimaryDomain()); + self::assertEquals(Defaults::$fallbackHosts, $clientOptions->getFallbackHosts()); } /** - * @testdox RSC15g1 + * @testdox RSC15g1 REC2a2 */ public function testWithFallbacks() { - $clientOptions = new ClientOptions(); - $clientOptions->fallbackHosts = ["a.example.com", "b.example.com"]; - self::assertEquals('rest.ably.io', $clientOptions->getPrimaryRestHost()); - self::assertTrue($clientOptions->tls); - self::assertEquals(443, $clientOptions->tlsPort); - $fallbackHosts = $clientOptions->getFallbackHosts(); - sort($fallbackHosts); - self::assertEquals(["a.example.com", "b.example.com"], $fallbackHosts); + foreach (['main', 'nonprod:sandbox', 'foo.example.com'] as $endpoint) { + $clientOptions = new ClientOptions([ + 'endpoint' => $endpoint, + 'fallbackHosts' => ["a.example.com", "b.example.com"], + ]); + self::assertEquals(["a.example.com", "b.example.com"], $clientOptions->getFallbackHosts()); + } } } diff --git a/tests/CryptoTest.php b/tests/CryptoTest.php index 73443ae..dd77951 100644 --- a/tests/CryptoTest.php +++ b/tests/CryptoTest.php @@ -1,9 +1,9 @@ $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..1f975e0 100644 --- a/tests/DefaultsTest.php +++ b/tests/DefaultsTest.php @@ -1,37 +1,29 @@ assertEquals($expectedFallbackHosts, $fallbackHosts); + $this->assertEquals($expectedFallbackHosts, Defaults::$fallbackHosts); + $this->assertEquals($expectedFallbackHosts, Defaults::getEndpointFallbackHosts(Defaults::$endpoint)); } /** - * @testdox RSC15i + * @testdox REC1a */ - public function testEnvironmentFallbackHosts() { - $expectedFallbackHosts = [ - "sandbox-a-fallback.ably-realtime.com", - "sandbox-b-fallback.ably-realtime.com", - "sandbox-c-fallback.ably-realtime.com", - "sandbox-d-fallback.ably-realtime.com", - "sandbox-e-fallback.ably-realtime.com" - ]; - $fallbackHosts = Defaults::getEnvironmentFallbackHosts("sandbox"); - $this->assertEquals($expectedFallbackHosts, $fallbackHosts); + public function testDefaultEndpoint() { + $this->assertEquals('main', Defaults::$endpoint); + $this->assertEquals('main.realtime.ably.net', Defaults::getPrimaryDomain(Defaults::$endpoint)); } } diff --git a/tests/HostCacheTest.php b/tests/HostCacheTest.php index 373ff26..55eb042 100644 --- a/tests/HostCacheTest.php +++ b/tests/HostCacheTest.php @@ -1,8 +1,7 @@ put(Defaults::$restHost); - self::assertEquals("rest.ably.io", $hostCache->get()); + $hostCache->put("main.realtime.ably.net"); + self::assertEquals("main.realtime.ably.net", $hostCache->get()); } /** @@ -23,7 +22,7 @@ public function testHost() { */ public function testExpiredHost() { $hostCache = new HostCache(999); - $hostCache->put(Defaults::$restHost); + $hostCache->put("main.realtime.ably.net"); sleep(1); self::assertEquals("", $hostCache->get()); } diff --git a/tests/HostTest.php b/tests/HostTest.php index eaeff17..0a10b2c 100644 --- a/tests/HostTest.php +++ b/tests/HostTest.php @@ -1,8 +1,8 @@ getPreferredHost(); - self::assertEquals("rest.ably.io", $prefHost); + self::assertEquals("main.realtime.ably.net", $prefHost); $fallbacks = iterator_to_array($restHosts->fallbackHosts($prefHost)); self::assertNotEquals($expectedFallbackHosts, $fallbacks); @@ -42,20 +42,20 @@ public function testFallbacksOtherThanPreferredHost() { $restHosts = new Host($clientOptions); // All expected hosts supposed to be tried upon $expectedFallbackHosts = [ - "rest.ably.io", - "a.ably-realtime.com", - "c.ably-realtime.com", - "d.ably-realtime.com", - "e.ably-realtime.com", + "main.realtime.ably.net", + "main.a.fallback.ably-realtime.com", + "main.c.fallback.ably-realtime.com", + "main.d.fallback.ably-realtime.com", + "main.e.fallback.ably-realtime.com", ]; - $restHosts->setPreferredHost("b.ably-realtime.com"); + $restHosts->setPreferredHost("main.b.fallback.ably-realtime.com"); $prefHost = $restHosts->getPreferredHost(); - self::assertEquals("b.ably-realtime.com", $prefHost); + self::assertEquals("main.b.fallback.ably-realtime.com", $prefHost); $fallbacks = iterator_to_array($restHosts->fallbackHosts($prefHost)); - self::assertEquals("rest.ably.io", $fallbacks[0]); + self::assertEquals("main.realtime.ably.net", $fallbacks[0]); sort($fallbacks); sort($expectedFallbackHosts); @@ -70,16 +70,16 @@ public function testGetAllFallbacksWithNoPreferredHost() { $restHosts = new Host($clientOptions); // All expected hosts supposed to be tried upon $expectedFallbackHosts = [ - "rest.ably.io", - "b.ably-realtime.com", - "a.ably-realtime.com", - "c.ably-realtime.com", - "d.ably-realtime.com", - "e.ably-realtime.com", + "main.realtime.ably.net", + "main.b.fallback.ably-realtime.com", + "main.a.fallback.ably-realtime.com", + "main.c.fallback.ably-realtime.com", + "main.d.fallback.ably-realtime.com", + "main.e.fallback.ably-realtime.com", ]; $fallbacks = iterator_to_array($restHosts->fallbackHosts("")); - self::assertEquals("rest.ably.io", $fallbacks[0]); + self::assertEquals("main.realtime.ably.net", $fallbacks[0]); sort($fallbacks); sort($expectedFallbackHosts); @@ -93,7 +93,7 @@ public function testGetPrimaryHostIfNothingIsCached() { $clientOptions = new ClientOptions(); $restHosts = new Host($clientOptions); $prefHost = $restHosts->getPreferredHost(); - self::assertEquals("rest.ably.io", $prefHost); + self::assertEquals("main.realtime.ably.net", $prefHost); } } diff --git a/tests/HttpTest.php b/tests/HttpTest.php index af4af8d..132b57c 100644 --- a/tests/HttpTest.php +++ b/tests/HttpTest.php @@ -1,12 +1,14 @@ 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 75% rename from tests/AblyRestTest.php rename to tests/PubSubHttpClientTest.php index 704fab7..60c344d 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" ), ] ); @@ -76,15 +76,16 @@ public function testInitLibWithTokenDetailsOption() { } /** - * Init library with a specified host + * Init library with a hostname endpoint + * @testdox REC1b2 */ public function testInitLibWithSpecifiedHost() { $opts = [ 'key' => 'fake.key:veryFake', - 'restHost' => 'some.other.host', + 'endpoint' => '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' ); } @@ -95,62 +96,63 @@ public function testInitLibWithSpecifiedHost() { public function testInitLibWithSpecifiedPort() { $opts = [ 'key' => 'fake.key:veryFake', - 'restHost' => 'some.other.host', + 'endpoint' => 'some.other.host', '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'], + 'https://' . $opts['endpoint'] . ':' . $opts['tlsPort'], $ably->http->lastUrl, 'Unexpected host/port mismatch' ); $opts = [ 'token' => 'fakeToken', - 'restHost' => 'some.other.host', + 'endpoint' => 'some.other.host', 'port' => 999, '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'], + 'http://' . $opts['endpoint'] . ':' . $opts['port'], $ably->http->lastUrl, 'Unexpected host/port mismatch' ); } /** - * Init library with specified environment + * Init library with a nonprod routing policy endpoint + * @testdox REC1b3 */ - public function testInitLibWithSpecifiedEnv() { - $ably = new AblyRest( [ + public function testInitLibWithNonprodEndpoint() { + $ably = new PubSubHttpClient( [ 'key' => 'fake.key:veryFake', - 'environment' => 'sandbox', + 'endpoint' => 'nonprod:sandbox', 'httpClass' => 'tests\HttpMockInitTest', ] ); $ably->time(); // make a request - $this->assertEquals( 'https://sandbox-rest.ably.io:443/time', $ably->http->lastUrl, 'Unexpected host mismatch' ); + $this->assertEquals( 'https://sandbox.realtime.ably-nonprod.net:443/time', $ably->http->lastUrl, 'Unexpected host mismatch' ); } /** - * Verify encrypted defaults to true, makes a request to https://rest.ably.io/... + * Verify encrypted defaults to true, makes a request to https://main.realtime.ably.net/... */ public function testTLSDefaultIsTrue() { $opts = [ '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' ); + $this->assertMatchesRegularExpression( '/^https:\/\/main\.realtime\.ably\.net/', $ably->http->lastUrl, 'Unexpected scheme/url mismatch' ); } /** - * Verify encrypted can be set to false, makes a request to http://rest.ably.io/... + * Verify encrypted can be set to false, makes a request to http://main.realtime.ably.net/... */ public function testTLSCanBeFalse() { $opts = [ @@ -158,13 +160,13 @@ 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' ); + $this->assertMatchesRegularExpression( '/^http:\/\/main\.realtime\.ably\.net/', $ably->http->lastUrl, 'Unexpected scheme/url mismatch' ); } /** - * Verify that connection is encrypted when set to true explicitly, makes a request to https://rest.ably.io/... + * Verify that connection is encrypted when set to true explicitly, makes a request to https://main.realtime.ably.net/... */ public function testTLSExplicitTrue() { $opts = [ @@ -172,9 +174,9 @@ 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' ); + $this->assertMatchesRegularExpression( '/^https:\/\/main\.realtime\.ably\.net/', $ably->http->lastUrl, 'Unexpected scheme/url mismatch' ); } @@ -189,7 +191,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,15 +210,15 @@ 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'); } catch(AblyRequestException $e) { $this->assertCount(6, $ably->http->visitedHosts); - $this->assertEquals( 'rest.ably.io' , $ably->http->visitedHosts[0],'Expected to try primary restHost first' ); + $this->assertEquals( 'main.realtime.ably.net' , $ably->http->visitedHosts[0],'Expected to try primary domain first' ); - $expectedFallbackHosts = array_merge( [ 'rest.ably.io' ], Defaults::$fallbackHosts ); + $expectedFallbackHosts = array_merge( [ 'main.realtime.ably.net' ], Defaults::$fallbackHosts ); $this->assertNotEquals( $expectedFallbackHosts, $ably->http->visitedHosts,'Expected to have fallback hosts randomized' ); sort($expectedFallbackHosts); @@ -227,25 +229,25 @@ public function testFallbackHosts() { } /** - * When using custom environment, should use custom env. fallback hosts - * @testdox RSC15b1,RSC15g2 + * When using a routing policy endpoint, should use that endpoint's fallback hosts + * @testdox REC1b4, REC2c4 */ - public function testEnvFallbackHosts() { + public function testEndpointFallbackHosts() { $opts = [ 'key' => 'fake.key:veryFake', 'httpClass' => 'tests\HttpMockInitTestTimeout', 'httpMaxRetryCount' => 5, - 'environment' => 'alpha' + 'endpoint' => 'alpha' ]; - $ably = new AblyRest( $opts ); + $ably = new PubSubHttpClient( $opts ); try { $ably->time(); // make a request $this->fail('Expected the request to fail'); } catch(AblyRequestException $e) { $this->assertCount(6, $ably->http->visitedHosts); - $this->assertEquals( 'alpha-rest.ably.io' , $ably->http->visitedHosts[0],'Expected to try primary restHost first' ); + $this->assertEquals( 'alpha.realtime.ably.net' , $ably->http->visitedHosts[0],'Expected to try primary domain first' ); - $expectedFallbackHosts = array_merge( [ 'alpha-rest.ably.io' ], Defaults::getEnvironmentFallbackHosts('alpha')); + $expectedFallbackHosts = array_merge( [ 'alpha.realtime.ably.net' ], Defaults::getEndpointFallbackHosts('alpha')); $this->assertNotEquals( $expectedFallbackHosts, $ably->http->visitedHosts,'Expected to have fallback hosts randomized' ); sort($expectedFallbackHosts); @@ -263,10 +265,10 @@ public function testNoFallbackOnEmptyCustomFallbacks() { $opts = [ 'key' => 'fake.key:veryFake', 'httpClass' => 'tests\HttpMockInitTestTimeout', - 'restHost' => 'custom.host.com', + 'endpoint' => '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'); @@ -277,7 +279,7 @@ public function testNoFallbackOnEmptyCustomFallbacks() { } /** - * Verify that custom restHost and custom fallbackHosts are working + * Verify that a hostname endpoint and custom fallbackHosts are working * @testdox RSC15b2, RSC15g1 */ public function testCustomHostAndFallbacks() { @@ -287,7 +289,7 @@ public function testCustomHostAndFallbacks() { 'third-fallback.custom.com', ]; $defaultOpts = new ClientOptions([ - 'restHost' => 'rest.custom.com', + 'endpoint' => 'rest.custom.com', 'fallbackHosts' => $customFallbacks, ]); @@ -296,15 +298,15 @@ 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'); } catch(AblyRequestException $e) { $this->assertCount(4, $ably->http->visitedHosts); - $this->assertEquals( 'rest.custom.com' , $ably->http->visitedHosts[0],'Expected to try primary restHost first' ); + $this->assertEquals( 'rest.custom.com' , $ably->http->visitedHosts[0],'Expected to try primary domain first' ); - $expectedFallbackHosts = array_merge( [ $defaultOpts->restHost ], $customFallbacks); + $expectedFallbackHosts = array_merge( [ $defaultOpts->getPrimaryDomain() ], $customFallbacks); sort($expectedFallbackHosts); $actualVisitedHosts = $ably->http->visitedHosts; // copied by value; sort($actualVisitedHosts); @@ -323,7 +325,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 @@ -332,21 +334,22 @@ public function testNoFallbackOnClientError() { $this->fail('Expected the request to fail'); } catch(AblyRequestException $e) { $this->assertCount(1, $ably->http->visitedHosts); - $this->assertEquals( [ 'rest.ably.io' ], $ably->http->visitedHosts, 'Expected to have tried only the default host' ); + $this->assertEquals( [ 'main.realtime.ably.net' ], $ably->http->visitedHosts, 'Expected to have tried only the default host' ); } } /** - * Verify that default fallback hosts are NOT used when using a custom host - * @testdox RSC15k + * Verify that default fallback hosts are NOT used with a hostname endpoint, + * so a qualifying error fails immediately + * @testdox RSC15k, REC2c2, RSC15m */ public function testNoFallbackOnCustomHost() { $opts = [ 'key' => 'fake.key:veryFake', 'httpClass' => 'tests\HttpMockInitTestTimeout', - 'restHost' => 'custom.host.com', + 'endpoint' => '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 +369,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 @@ -385,12 +388,12 @@ public function testFallbackHostHeader() { 'third-fallback.custom.com', ]; $opts = new ClientOptions([ - 'restHost' => 'rest.custom.com', + 'endpoint' => 'rest.custom.com', 'fallbackHosts' => $customFallbacks, '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,46 +411,46 @@ 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', - 'restHost' => 'custom.host.com', + 'endpoint' => 'custom.host.com', 'fallbackHosts' => [ 'fallback1', - 'c.ably-realtime.com', // valid fallback host + 'main.c.fallback.ably-realtime.com', // valid fallback host 'fallback2', ], ])); $ably->time(); - $this->assertEquals("c.ably-realtime.com", $ably->host->getPreferredHost()); // check for cached host + $this->assertEquals("main.c.fallback.ably-realtime.com", $ably->host->getPreferredHost()); // check for cached host $this->assertGreaterThanOrEqual(1, $ably->http->fallbackRetries ); $ably->http->resetRetries(); $ably->time(); $ably->time(); - $this->assertEquals("c.ably-realtime.com", $ably->host->getPreferredHost()); // check for cached host + $this->assertEquals("main.c.fallback.ably-realtime.com", $ably->host->getPreferredHost()); // check for cached host $this->assertEquals( 0, $ably->http->fallbackRetries); // zero retries since cached fallback is used sleep( 2); // expire cached host $ably->time(); - $this->assertEquals("c.ably-realtime.com", $ably->host->getPreferredHost()); // check for cached host + $this->assertEquals("main.c.fallback.ably-realtime.com", $ably->host->getPreferredHost()); // check for cached host $this->assertGreaterThanOrEqual( 1, $ably->http->fallbackRetries ); } /** * 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 main.realtime.ably.net/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 +468,7 @@ public function testActiveInternetConnection() { $opts = [ 'key' => 'fake.key:veryFake', ]; - $ably = new AblyRest( $opts ); + $ably = new PubSubHttpClient( $opts ); $this->assertTrue($ably ->hasActiveInternetConnection()); } @@ -473,9 +476,9 @@ 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', + 'endpoint' => 'this.host.does.not.exist', ]); $this->expectException(AblyRequestException::class); @@ -487,19 +490,19 @@ 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 ]); - $ably->http->get('https://rest.ably.io/time'); // should work + $ably->http->get('https://main.realtime.ably.net/time'); // should work $this->expectException(AblyRequestException::class); $this->expectExceptionCode(50003); - $ablyTimeout->http->get('https://rest.ably.io/time'); // guaranteed to take more than 20 ms + $ablyTimeout->http->get('https://main.realtime.ably.net/time'); // guaranteed to take more than 20 ms } } @@ -564,7 +567,7 @@ public function __construct( $clientOptions ) { } public function request( $method, $url, $headers = [], $params = [] ) { - if ( parse_url($url, PHP_URL_HOST) == "c.ably-realtime.com" ) { // cache specific host + if ( parse_url($url, PHP_URL_HOST) == "main.c.fallback.ably-realtime.com" ) { // cache specific host return parent::request($method, $url, $headers, $params); } $this->fallbackRetries++; 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..0d28c6a 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', @@ -141,8 +141,7 @@ public function testClientOptionsType() { 'authHeaders', 'authParams', 'queryTime', - 'environment', - 'restHost', + 'endpoint', 'port', 'tlsPort', 'httpOpenTimeout', @@ -151,7 +150,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 +161,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 +169,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 +189,7 @@ public function testAuthOptionsType() { } public function testTokenParamsType() { - $this->verifyClassMembers( '\Ably\Models\TokenParams', [ + $this->verifyClassMembers( '\Ably\PubSub\Models\TokenParams', [ 'ttl', 'capability', 'clientId', @@ -199,13 +198,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 +213,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 +230,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 +243,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 +274,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 @@ options = $settings; - $this->server = $clientOpts->getHostUrl($clientOpts->getPrimaryRestHost()); + $this->server = $clientOpts->getHostUrl($clientOpts->getPrimaryDomain()); $this->init(); return $this; @@ -127,7 +127,9 @@ private function request( $mode, $url, $headers = [], $params = '' ) { var_dump(curl_error($ch)); // Prints curl request error if exists } - curl_close ($ch); + // curl_close() has no effect since PHP 8.0 and is deprecated since 8.5; + // the handle is released when $ch goes out of scope. The floor is 8.1. + unset($ch); if ($this->debugRequests) { var_dump($raw);