diff --git a/examples/Configuration.php b/examples/Configuration.php new file mode 100644 index 00000000..8e4a53fa --- /dev/null +++ b/examples/Configuration.php @@ -0,0 +1,215 @@ +setSubscribeKey(getenv('SUBSCRIBE_KEY') ?? 'demo'); + +// Set publish key (only required if publishing) +$pnConfiguration->setPublishKey(getenv('PUBLISH_KEY') ?? 'demo'); + +// Set UUID (required to connect) +$pnConfiguration->setUserId('php-config-demo-user'); +// snippet.end + +// Verify configuration was set correctly +assert($pnConfiguration->getSubscribeKey() === (getenv('SUBSCRIBE_KEY') ?? 'demo')); +assert($pnConfiguration->getPublishKey() === (getenv('PUBLISH_KEY') ?? 'demo')); +assert($pnConfiguration->getUserId() === 'php-config-demo-user'); + +// snippet.basic_configuration +// Create a new configuration instance +$pnConfiguration = new PNConfiguration(); + +// Set subscribe key (required) +$pnConfiguration->setSubscribeKey(getenv('SUBSCRIBE_KEY') ?? 'demo'); + +// Set publish key (only required if publishing) +$pnConfiguration->setPublishKey(getenv('PUBLISH_KEY') ?? 'demo'); + +// Set UUID (required to connect) +$pnConfiguration->setUserId("php-sdk-example-user"); + +// Set up cryptography for message encryption (optional) +// Uncomment the line below to enable encryption +// $pnConfiguration->setCryptoModule(CryptoModule::aesCbcCryptor("your-cipher-key", true)); + +// Set authentication key (optional, required only when using Access Manager) +// $pnConfiguration->setAuthKey("my_auth_key"); + +// Configure connection timeout in seconds +$pnConfiguration->setConnectTimeout(10); + +// Configure subscribe request timeout in seconds +$pnConfiguration->setSubscribeTimeout(310); + +// Configure non-subscribe request timeout in seconds +$pnConfiguration->setNonSubscribeRequestTimeout(10); + +// Set filter expression (optional) +// $pnConfiguration->setFilterExpression("channel == 'my-channel'"); + +// Create PubNub instance with the configured settings +$pubnub = new PubNub($pnConfiguration); + +// Display configuration information +echo "PubNub Configuration:\n"; +echo "Subscribe Key: " . $pnConfiguration->getSubscribeKey() . "\n"; +echo "Publish Key: " . $pnConfiguration->getPublishKey() . "\n"; +echo "User ID: " . $pnConfiguration->getUserId() . "\n"; +echo "Encryption: " . ($pnConfiguration->getCryptoSafe() ? "enabled" : "disabled") . "\n"; + +// Now you can use this PubNub instance to publish and subscribe + +// Example: Create a simple message +$message = ["text" => "Hello from PHP SDK!"]; + +// Example: Publish the message (uncomment to execute) +/* +$pubnub->publish() + ->channel("demo-channel") + ->message($message) + ->sync(); + +echo "Message published to 'demo-channel'\n"; +*/ + +// Keep this code running only if you plan to subscribe to messages +// Otherwise, the script will exit after publishing +// snippet.end + +// Verify configuration values +assert($pnConfiguration->getSubscribeKey() === getenv('SUBSCRIBE_KEY') ?? 'demo'); +assert($pnConfiguration->getPublishKey() === getenv('PUBLISH_KEY') ?? 'demo'); +assert($pnConfiguration->getUserId() === "php-sdk-example-user"); +assert($pnConfiguration->getConnectTimeout() === 10); +assert($pnConfiguration->getSubscribeTimeout() === 310); +assert($pnConfiguration->getNonSubscribeRequestTimeout() === 10); + +// Verify PubNub instance was created +assert($pubnub instanceof PubNub); + +// snippet.init_basic +$pnconf = new PNConfiguration(); + +$pnconf->setSubscribeKey(getenv('SUBSCRIBE_KEY') ?? 'demo'); +$pnconf->setPublishKey(getenv('PUBLISH_KEY') ?? 'demo'); +$pnconf->setSecure(false); +$pnconf->setUserId("myUniqueUserId"); +$pubnub = new PubNub($pnconf); + +// snippet.end + +// Verify configuration +assert($pnconf->getSubscribeKey() === getenv('SUBSCRIBE_KEY') ?? 'demo'); +assert($pnconf->getPublishKey() === getenv('PUBLISH_KEY') ?? 'demo'); +assert($pnconf->getUserId() === "myUniqueUserId"); +assert($pubnub instanceof PubNub); + +// snippet.init_access_manager +$pnConfiguration = new PNConfiguration(); + +$pnConfiguration->setSubscribeKey(getenv('SUBSCRIBE_KEY') ?? 'demo'); +$pnConfiguration->setPublishKey(getenv('PUBLISH_KEY') ?? 'demo'); +//NOTE: only server side should have secret key +$pnConfiguration->setSecretKey(getenv('SECRET_KEY') ?? 'demo'); +$pnConfiguration->setUserId("myUniqueUserId"); +$pubnub = new PubNub($pnConfiguration); +// snippet.end + +// Verify configuration +assert($pnConfiguration->getSubscribeKey() === getenv('SUBSCRIBE_KEY') ?? 'demo'); +assert($pnConfiguration->getPublishKey() === getenv('PUBLISH_KEY') ?? 'demo'); +assert($pnConfiguration->getSecretKey() === getenv('SECRET_KEY') ?? 'demo'); +assert($pnConfiguration->getUserId() === "myUniqueUserId"); +assert($pubnub instanceof PubNub); + +// snippet.event_listeners +class MySubscribeCallback extends SubscribeCallback +{ + function status($pubnub, $status) + { + if ($status->getCategory() === PNStatusCategory::PNUnexpectedDisconnectCategory) { + // This event happens when connectivity is lost + } elseif ($status->getCategory() === PNStatusCategory::PNConnectedCategory) { + // Connect event. You can do stuff like publish, and know you'll get it + } elseif ($status->getCategory() === PNStatusCategory::PNDecryptionErrorCategory) { + // Handle message decryption error. + } + } + + function message($pubnub, $message) + { + // Handle new message stored in message.message + } + function presence($pubnub, $presence) + { + // handle incoming presence data + } +} + +$pnconf = new PNConfiguration(); + +$pnconf->setSubscribeKey(getenv('SUBSCRIBE_KEY') ?? 'demo'); +$pnconf->setPublishKey(getenv('PUBLISH_KEY') ?? 'demo'); +$pnconf->setUserId("event-listener-demo-user"); + +$pubnub = new PubNub($pnconf); + +$subscribeCallback = new MySubscribeCallback(); + +$pubnub->addListener($subscribeCallback); + +// Subscribe to a channel, this is not async. +// Note: This would block +// $pubnub->subscribe() +// ->channels("hello_world") +// ->execute(); + +// Use the publish command separately from the Subscribe code shown above. +// Subscribe is not async and will block the execution until complete. +// Note: Commented out for testing to avoid network calls +// $result = $pubnub->publish() +// ->channel("hello_world") +// ->message("Hello PubNub") +// ->sync(); +// +// // Verify publish result +// assert($result->getTimetoken() > 0); +// +// print_r($result); +// snippet.end + +// snippet.set_filter_expression +$pnconf = new PNConfiguration(); + +$pnconf->setSubscribeKey(getenv('SUBSCRIBE_KEY') ?? 'demo'); +$pnconf->setUserId("filter-demo-user"); +$pnconf->setFilterExpression("userid == 'my_userid'"); + +$pubnub = new PubNub($pnconf); +// snippet.end + +// Verify configuration +assert($pnconf->getSubscribeKey() === "my_sub_key"); +assert($pnconf->getPublishKey() === "my_pub_key"); +assert($pnconf->getUserId() === "event-listener-demo-user"); +assert($pubnub instanceof PubNub); +// Verify callback instance +assert($subscribeCallback instanceof SubscribeCallback); +// Verify configuration +assert($pnconf->getSubscribeKey() === "my_sub_key"); +assert($pnconf->getFilterExpression() === "userid == 'my_userid'"); +assert($pubnub instanceof PubNub); diff --git a/examples/Publishing.php b/examples/Publishing.php index fd5518ee..ea0fd4bb 100644 --- a/examples/Publishing.php +++ b/examples/Publishing.php @@ -72,10 +72,12 @@ // snippet.publish_with_post $result = $pubnub->publish() ->channel("my_channel") - ->message([ + ->message( + [ "text" => "Message using POST", "description" => str_repeat("Post allows to publish longer messages", 750) - ]) + ] + ) ->usePost(true) ->sync(); assert($result->getTimetoken() > 0); @@ -86,10 +88,12 @@ try { $result = $pubnub->publish() ->channel("my_channel") - ->message([ + ->message( + [ "text" => "Message using POST", "description" => str_repeat("Post allows to publish longer messages", 1410) - ]) + ] + ) ->usePost(true) ->sync(); assert($result->getTimetoken() > 0); @@ -119,3 +123,16 @@ assert($result->getTimetoken() > 0); echo "Signal timetoken: {$result->getTimetoken()}\n"; // snippet.end + +// snippet.publish_array +try { + $result = $pubnub->publish() + ->channel("my_channel") + ->message(["hello", "there"]) + ->meta(["name" => "Alex", "online" => true]) + ->sync(); + print_r($result->getTimetoken()); +} catch (PubNubException $error) { + echo "Error: " . $error->getMessage() . "\n"; +} +// snippet.end diff --git a/examples/Subscribing.php b/examples/Subscribing.php index ac8958b5..08c99906 100644 --- a/examples/Subscribing.php +++ b/examples/Subscribing.php @@ -23,6 +23,8 @@ $pubnub = new PubNub($pnconfig); // snippet.end +// Disable for "one class per file" rule +// phpcs:disable // snippet.callback // Create a custom callback class to handle messages and status updates class MySubscribeCallback extends SubscribeCallback @@ -62,6 +64,7 @@ public function presence($pubnub, $presence) } } // snippet.end +// phpcs:enable // snippet.subscribe // Add the callback to PubNub @@ -80,6 +83,8 @@ public function presence($pubnub, $presence) } // snippet.end +// Disable for the "declare symbols vs side effects" rule +// phpcs:disable // snippet.history // Get message history function getHistory($pubnub, $channels) @@ -100,6 +105,110 @@ function getHistory($pubnub, $channels) } } // snippet.end +// phpcs:enable + +// snippet.basic_subscribe_with_logging +use Monolog\Handler\ErrorLogHandler; + +$pnconf = new PNConfiguration(); + +$pnconf->setPublishKey("demo"); +$pnconf->setSubscribeKey("demo"); +$pnconf->setUserId("php-subscriber-with-logging"); + +$pubnub = new PubNub($pnconf); + +$pubnub->getLogger()->pushHandler(new ErrorLogHandler()); + +$pubnub->subscribe()->channels("my_channel")->execute(); +// snippet.end + +// Disable for the "one class per file" rule +// phpcs:disable +// snippet.subscribe_with_state +class MySubscribeCallbackWithState extends SubscribeCallback +{ + public function status($pubnub, $status) + { + if ($status->getCategory() === PNStatusCategory::PNConnectedCategory) { + $result = $pubnub + ->setState() + ->channels("awesomeChannel") + ->channelGroups("awesomeChannelGroup") + ->state([ + "fieldA" => "awesome", + "fieldB" => 10, + ]) + ->sync(); + print_r($result); + } + } + + public function message($pubnub, $message) + { + } + + public function presence($pubnub, $presence) + { + } +} + +$subscribeCallback = new MySubscribeCallbackWithState(); + +$pubnub->addListener($subscribeCallback); + +$pubnub->subscribe() + ->channels("my_channel") + ->execute(); +// snippet.end +// phpcs:enable + +// Disable for the "one class per file" rule +// phpcs:disable +// snippet.unsubscribe_from_channel +use PubNub\Exceptions\PubNubUnsubscribeException; + +class MyUnsubscribeCallback extends SubscribeCallback +{ + public function status($pubnub, $status) + { + if ($this->checkUnsubscribeCondition()) { + throw (new PubNubUnsubscribeException())->setChannels("awesomeChannel"); + } + } + + public function message($pubnub, $message) + { + } + + public function presence($pubnub, $presence) + { + } + + public function checkUnsubscribeCondition() + { + // return true or false + return false; + } +} + +$pnconfig = new PNConfiguration(); + +$pnconfig->setPublishKey("demo"); +$pnconfig->setSubscribeKey("demo"); +$pnconfig->setUserId("php-unsubscribe-demo"); + +$pubnub = new PubNub($pnconfig); + +$subscribeCallback = new MyUnsubscribeCallback(); + +$pubnub->addListener($subscribeCallback); + +$pubnub->subscribe() + ->channels("awesomeChannel") + ->execute(); +// snippet.end +// phpcs:enable echo "Starting PubNub Subscriber...\n"; echo "Press Ctrl+C to exit\n"; diff --git a/examples/Time.php b/examples/Time.php index 954dfc4d..28dccbf7 100755 --- a/examples/Time.php +++ b/examples/Time.php @@ -11,4 +11,3 @@ $result = $pubnub->time()->sync(); printf("Server Time is: %s", date("Y-m-d H:i:s", $result->getTimetoken())); - diff --git a/examples/handleServerSideError.php b/examples/handleServerSideError.php index ab8837f6..66b8b08d 100644 --- a/examples/handleServerSideError.php +++ b/examples/handleServerSideError.php @@ -33,4 +33,3 @@ } catch (\PubNub\Exceptions\PubNubException $exception) { print_r("Message: " . $exception->getMessage()); } - diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 2944a1aa..e2e479a6 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -7110,102 +7110,3 @@ parameters: count: 1 path: tests/unit/CryptoTest.php - - - message: "#^Method ExposedListenerManager\\:\\:count\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/unit/ListenerManagerTest.php - - - - message: "#^Method ListenerManagerTest\\:\\:testUrlEncode\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/unit/ListenerManagerTest.php - - - - message: "#^Method MySubscribeCallback\\:\\:message\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/unit/ListenerManagerTest.php - - - - message: "#^Method MySubscribeCallback\\:\\:message\\(\\) has parameter \\$message with no type specified\\.$#" - count: 1 - path: tests/unit/ListenerManagerTest.php - - - - message: "#^Method MySubscribeCallback\\:\\:message\\(\\) has parameter \\$pubnub with no type specified\\.$#" - count: 1 - path: tests/unit/ListenerManagerTest.php - - - - message: "#^Method MySubscribeCallback\\:\\:presence\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/unit/ListenerManagerTest.php - - - - message: "#^Method MySubscribeCallback\\:\\:presence\\(\\) has parameter \\$presence with no type specified\\.$#" - count: 1 - path: tests/unit/ListenerManagerTest.php - - - - message: "#^Method MySubscribeCallback\\:\\:presence\\(\\) has parameter \\$pubnub with no type specified\\.$#" - count: 1 - path: tests/unit/ListenerManagerTest.php - - - - message: "#^Method PNConfigurationTest\\:\\:testInitWithUUID\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/unit/PNConfigurationTest.php - - - - message: "#^Method PNConfigurationTest\\:\\:testInitWithUserId\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/unit/PNConfigurationTest.php - - - - message: "#^Method PNConfigurationTest\\:\\:testThrowOnEmptyUserId\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/unit/PNConfigurationTest.php - - - - message: "#^Method PNConfigurationTest\\:\\:testThrowOnUserIdAndUUID\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/unit/PNConfigurationTest.php - - - - message: "#^Method PublishTest\\:\\:testSequenceCounterRestartsAfterMaxReached\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/unit/PublishTest.php - - - - message: "#^Method TelemetryManagerTest\\:\\:testAverageLatency\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/unit/TelemetryManagerTest.php - - - - message: "#^Method TelemetryManagerTest\\:\\:testValidQueries\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/unit/TelemetryManagerTest.php - - - - message: "#^Method UtilsTest\\:\\:testJoinQuery\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/unit/UtilsTest.php - - - - message: "#^Method UtilsTest\\:\\:testPamEncode\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/unit/UtilsTest.php - - - - message: "#^Method UtilsTest\\:\\:testSignSha256\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/unit/UtilsTest.php - - - - message: "#^Method UtilsTest\\:\\:testUrlEncode\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/unit/UtilsTest.php - - - - message: "#^Method UtilsTest\\:\\:testWriteValueAsString\\(\\) has no return type specified\\.$#" - count: 1 - path: tests/unit/UtilsTest.php diff --git a/tests/Examples/ConfigurationTest.php b/tests/Examples/ConfigurationTest.php new file mode 100644 index 00000000..2fa7e93f --- /dev/null +++ b/tests/Examples/ConfigurationTest.php @@ -0,0 +1,20 @@ +assertTrue(true); // If we reach this point, all examples passed + } +} diff --git a/tests/functional/AddChannelToChannelGroupTest.php b/tests/functional/AddChannelToChannelGroupTest.php index bdcb41e1..a5267738 100644 --- a/tests/functional/AddChannelToChannelGroupTest.php +++ b/tests/functional/AddChannelToChannelGroupTest.php @@ -1,5 +1,6 @@ testFilePath = sys_get_temp_dir() . '/test_file_' . uniqid() . '.txt'; + file_put_contents($this->testFilePath, 'Test file content for publish file message'); + } + + public function tearDown(): void + { + // Clean up temporary file + if (file_exists($this->testFilePath)) { + unlink($this->testFilePath); + } + + // Clean up uploaded files + try { + $listResponse = $this->pubnub->listFiles()->channel($this->channel)->sync(); + foreach ($listResponse->getData() as $file) { + $this->pubnub->deleteFile() + ->channel($this->channel) + ->fileId($file['id']) + ->fileName($file['name']) + ->sync(); + } + } catch (\Exception $e) { + // Ignore cleanup errors + } + + parent::tearDown(); + } + + public function testPublishFileMessageWithBasicMessage(): void + { + // First upload a file to get file ID + $file = fopen($this->testFilePath, "r"); + $fileName = basename($this->testFilePath); + + $uploadResponse = $this->pubnub->sendFile() + ->channel($this->channel) + ->fileHandle($file) + ->fileName($fileName) + ->message("Initial file upload") + ->sync(); + + fclose($file); + + $this->assertNotEmpty($uploadResponse->getFileId()); + + // Now publish a file message notification + $response = $this->pubnub->publishFileMessage() + ->channel($this->channel) + ->fileId($uploadResponse->getFileId()) + ->fileName($fileName) + ->message("File notification message") + ->sync(); + + $this->assertNotEmpty($response); + $this->assertNotEmpty($response->getTimestamp()); + } + + public function testPublishFileMessageWithMetadata(): void + { + // Upload a file first + $file = fopen($this->testFilePath, "r"); + $fileName = basename($this->testFilePath); + + $uploadResponse = $this->pubnub->sendFile() + ->channel($this->channel) + ->fileHandle($file) + ->fileName($fileName) + ->message("File with metadata") + ->sync(); + + fclose($file); + + // Publish file message with metadata + $metadata = [ + "author" => "test-user", + "fileType" => "text", + "importance" => "high" + ]; + + $response = $this->pubnub->publishFileMessage() + ->channel($this->channel) + ->fileId($uploadResponse->getFileId()) + ->fileName($fileName) + ->message("File with metadata notification") + ->meta($metadata) + ->sync(); + + $this->assertNotEmpty($response); + $this->assertNotEmpty($response->getTimestamp()); + } + + public function testPublishFileMessageWithCustomMessageType(): void + { + // Upload a file first + $file = fopen($this->testFilePath, "r"); + $fileName = basename($this->testFilePath); + + $uploadResponse = $this->pubnub->sendFile() + ->channel($this->channel) + ->fileHandle($file) + ->fileName($fileName) + ->message("File upload") + ->sync(); + + fclose($file); + + // Publish file message with custom message type + $response = $this->pubnub->publishFileMessage() + ->channel($this->channel) + ->fileId($uploadResponse->getFileId()) + ->fileName($fileName) + ->message("Custom type notification") + ->customMessageType("file_notification") + ->sync(); + + $this->assertNotEmpty($response); + $this->assertNotEmpty($response->getTimestamp()); + } + + public function testPublishFileMessageWithTTL(): void + { + // Upload a file first + $file = fopen($this->testFilePath, "r"); + $fileName = basename($this->testFilePath); + + $uploadResponse = $this->pubnub->sendFile() + ->channel($this->channel) + ->fileHandle($file) + ->fileName($fileName) + ->message("File upload") + ->sync(); + + fclose($file); + + // Publish file message with TTL + $response = $this->pubnub->publishFileMessage() + ->channel($this->channel) + ->fileId($uploadResponse->getFileId()) + ->fileName($fileName) + ->message("Message with TTL") + ->ttl(60) // 60 minutes + ->sync(); + + $this->assertNotEmpty($response); + $this->assertNotEmpty($response->getTimestamp()); + } + + public function testPublishFileMessageWithShouldStore(): void + { + // Upload a file first + $file = fopen($this->testFilePath, "r"); + $fileName = basename($this->testFilePath); + + $uploadResponse = $this->pubnub->sendFile() + ->channel($this->channel) + ->fileHandle($file) + ->fileName($fileName) + ->message("File upload") + ->sync(); + + fclose($file); + + // Publish file message with shouldStore flag + $response = $this->pubnub->publishFileMessage() + ->channel($this->channel) + ->fileId($uploadResponse->getFileId()) + ->fileName($fileName) + ->message("Stored message") + ->shouldStore(true) + ->sync(); + + $this->assertNotEmpty($response); + $this->assertNotEmpty($response->getTimestamp()); + } + + public function testPublishFileMessageWithAllOptions(): void + { + // Upload a file first + $file = fopen($this->testFilePath, "r"); + $fileName = basename($this->testFilePath); + + $uploadResponse = $this->pubnub->sendFile() + ->channel($this->channel) + ->fileHandle($file) + ->fileName($fileName) + ->message("File upload") + ->sync(); + + fclose($file); + + // Publish file message with all options + $metadata = [ + "category" => "documents", + "tags" => ["important", "urgent"] + ]; + + $response = $this->pubnub->publishFileMessage() + ->channel($this->channel) + ->fileId($uploadResponse->getFileId()) + ->fileName($fileName) + ->message("Complete file notification") + ->meta($metadata) + ->customMessageType("complete_notification") + ->ttl(120) + ->shouldStore(true) + ->sync(); + + $this->assertNotEmpty($response); + $this->assertNotEmpty($response->getTimestamp()); + } + + public function testPublishFileMessageWithEncryption(): void + { + // Use encrypted pubnub instance + $file = fopen($this->testFilePath, "r"); + $fileName = basename($this->testFilePath); + + $uploadResponse = $this->pubnub_enc->sendFile() + ->channel($this->channel) + ->fileHandle($file) + ->fileName($fileName) + ->message("Encrypted file upload") + ->sync(); + + fclose($file); + + // Publish encrypted file message + $response = $this->pubnub_enc->publishFileMessage() + ->channel($this->channel) + ->fileId($uploadResponse->getFileId()) + ->fileName($fileName) + ->message("Encrypted file notification") + ->sync(); + + $this->assertNotEmpty($response); + $this->assertNotEmpty($response->getTimestamp()); + } + + public function testPublishFileMessageWithComplexMessage(): void + { + // Upload a file first + $file = fopen($this->testFilePath, "r"); + $fileName = basename($this->testFilePath); + + $uploadResponse = $this->pubnub->sendFile() + ->channel($this->channel) + ->fileHandle($file) + ->fileName($fileName) + ->message("File upload") + ->sync(); + + fclose($file); + + // Publish file message with complex message structure + $complexMessage = [ + "type" => "file_uploaded", + "details" => [ + "uploader" => "test-user", + "timestamp" => time(), + "description" => "Important document" + ], + "actions" => ["review", "approve", "archive"] + ]; + + $response = $this->pubnub->publishFileMessage() + ->channel($this->channel) + ->fileId($uploadResponse->getFileId()) + ->fileName($fileName) + ->message($complexMessage) + ->sync(); + + $this->assertNotEmpty($response); + $this->assertNotEmpty($response->getTimestamp()); + } + + public function testPublishFileMessageMultipleTimes(): void + { + // Upload a file first + $file = fopen($this->testFilePath, "r"); + $fileName = basename($this->testFilePath); + + $uploadResponse = $this->pubnub->sendFile() + ->channel($this->channel) + ->fileHandle($file) + ->fileName($fileName) + ->message("File upload") + ->sync(); + + fclose($file); + + // Publish multiple file messages for the same file + $response1 = $this->pubnub->publishFileMessage() + ->channel($this->channel) + ->fileId($uploadResponse->getFileId()) + ->fileName($fileName) + ->message("First notification") + ->sync(); + + $response2 = $this->pubnub->publishFileMessage() + ->channel($this->channel) + ->fileId($uploadResponse->getFileId()) + ->fileName($fileName) + ->message("Second notification") + ->sync(); + + $this->assertNotEmpty($response1->getTimestamp()); + $this->assertNotEmpty($response2->getTimestamp()); + $this->assertNotEquals($response1->getTimestamp(), $response2->getTimestamp()); + } + + public function testPublishFileMessageWithEmptyChannel(): void + { + $this->expectException(\PubNub\Exceptions\PubNubValidationException::class); + + $this->pubnub->publishFileMessage() + ->channel('') + ->fileId('some-file-id') + ->fileName('test.txt') + ->message("Empty channel") + ->sync(); + } +} diff --git a/tests/integrational/TimeTest.php b/tests/integrational/TimeTest.php index 1a075945..e6abccb3 100755 --- a/tests/integrational/TimeTest.php +++ b/tests/integrational/TimeTest.php @@ -2,7 +2,6 @@ namespace Tests\Integrational; - class TimeTest extends \PubNubTestCase { /** diff --git a/tests/integrational/objects/channel/GetChannelMetadataEndpointTest.php b/tests/integrational/objects/channel/GetChannelMetadataEndpointTest.php index e0dad568..f4fcabe6 100644 --- a/tests/integrational/objects/channel/GetChannelMetadataEndpointTest.php +++ b/tests/integrational/objects/channel/GetChannelMetadataEndpointTest.php @@ -4,7 +4,6 @@ use PubNubTestCase; - class GetChannelMetadataEndpointTest extends PubNubTestCase { public function testGetMetadataFromChannel() diff --git a/tests/integrational/objects/channel/RemoveChannelMetadataEndpointTest.php b/tests/integrational/objects/channel/RemoveChannelMetadataEndpointTest.php index b1e4c8d7..d4d4bb4c 100644 --- a/tests/integrational/objects/channel/RemoveChannelMetadataEndpointTest.php +++ b/tests/integrational/objects/channel/RemoveChannelMetadataEndpointTest.php @@ -4,7 +4,6 @@ use PubNubTestCase; - class RemoveChannelMetadataEndpointTest extends PubNubTestCase { public function testRemoveMetadataFromChannel() diff --git a/tests/integrational/objects/uuid/GetAllUUIDMetadataEndpointTest.php b/tests/integrational/objects/uuid/GetAllUUIDMetadataEndpointTest.php index bd422783..bbbaf6a5 100644 --- a/tests/integrational/objects/uuid/GetAllUUIDMetadataEndpointTest.php +++ b/tests/integrational/objects/uuid/GetAllUUIDMetadataEndpointTest.php @@ -4,7 +4,6 @@ use PubNubTestCase; - class GetAllUUIDMetadataEndpointTest extends PubNubTestCase { public function testGetAllUUIDMetadata() @@ -87,7 +86,7 @@ public function testGetAllUUIDMetadata() // $custom = $value->getCustom(); // $this->assertNotEmpty($custom); // $this->assertEquals("aa1", $custom->a); - // $this->assertEquals("bb1", $custom->b); + // $this->assertEquals("bb1", $custom->b); // $value = $data[2]; // $this->assertEquals("uuid2", $value->getId()); @@ -98,6 +97,6 @@ public function testGetAllUUIDMetadata() // $custom = $value->getCustom(); // $this->assertNotEmpty($custom); // $this->assertEquals("aa2", $custom->a); - // $this->assertEquals("bb2", $custom->b); + // $this->assertEquals("bb2", $custom->b); } } diff --git a/tests/integrational/objects/uuid/RemoveUUIDMetadataEndpointTest.php b/tests/integrational/objects/uuid/RemoveUUIDMetadataEndpointTest.php index e730f233..a111a07b 100644 --- a/tests/integrational/objects/uuid/RemoveUUIDMetadataEndpointTest.php +++ b/tests/integrational/objects/uuid/RemoveUUIDMetadataEndpointTest.php @@ -4,7 +4,6 @@ use PubNubTestCase; - class RemoveUUIDMetadataEndpointTest extends PubNubTestCase { public function testRemoveMetadataFromUUID() diff --git a/tests/unit/BasePathManagerTest.php b/tests/unit/BasePathManagerTest.php new file mode 100644 index 00000000..a92c6079 --- /dev/null +++ b/tests/unit/BasePathManagerTest.php @@ -0,0 +1,152 @@ +setSubscribeKey('demo'); + $config->setUserId('test-user'); + + $manager = new BasePathManager($config); + + $basePath = $manager->getBasePath(); + + $this->assertEquals('https://ps.pndsn.com', $basePath); + } + + public function testGetBasePathWithCustomOrigin(): void + { + $config = new PNConfiguration(); + $config->setSubscribeKey('demo'); + $config->setUserId('test-user'); + $config->setOrigin('custom.pubnub.com'); + + $manager = new BasePathManager($config); + + $basePath = $manager->getBasePath(); + + $this->assertEquals('https://custom.pubnub.com', $basePath); + } + + public function testGetBasePathWithCustomHost(): void + { + $config = new PNConfiguration(); + $config->setSubscribeKey('demo'); + $config->setUserId('test-user'); + + $manager = new BasePathManager($config); + + $basePath = $manager->getBasePath('special.pubnub.com'); + + $this->assertEquals('https://special.pubnub.com', $basePath); + } + + public function testGetBasePathWithCustomHostOverridesOrigin(): void + { + $config = new PNConfiguration(); + $config->setSubscribeKey('demo'); + $config->setUserId('test-user'); + $config->setOrigin('config-origin.pubnub.com'); + + $manager = new BasePathManager($config); + + $basePath = $manager->getBasePath('param-host.pubnub.com'); + + $this->assertEquals('https://param-host.pubnub.com', $basePath); + } + + public function testGetBasePathWithInsecureConnection(): void + { + $config = new PNConfiguration(); + $config->setSubscribeKey('demo'); + $config->setUserId('test-user'); + $config->setSecure(false); + + $manager = new BasePathManager($config); + + $basePath = $manager->getBasePath(); + + $this->assertEquals('http://ps.pndsn.com', $basePath); + } + + public function testGetBasePathWithInsecureAndCustomOrigin(): void + { + $config = new PNConfiguration(); + $config->setSubscribeKey('demo'); + $config->setUserId('test-user'); + $config->setSecure(false); + $config->setOrigin('insecure.pubnub.com'); + + $manager = new BasePathManager($config); + + $basePath = $manager->getBasePath(); + + $this->assertEquals('http://insecure.pubnub.com', $basePath); + } + + public function testGetBasePathWithIPAddress(): void + { + $config = new PNConfiguration(); + $config->setSubscribeKey('demo'); + $config->setUserId('test-user'); + $config->setOrigin('192.168.1.100'); + + $manager = new BasePathManager($config); + + $basePath = $manager->getBasePath(); + + $this->assertEquals('https://192.168.1.100', $basePath); + } + + public function testGetBasePathWithPortNumber(): void + { + $config = new PNConfiguration(); + $config->setSubscribeKey('demo'); + $config->setUserId('test-user'); + $config->setOrigin('localhost:8080'); + + $manager = new BasePathManager($config); + + $basePath = $manager->getBasePath(); + + $this->assertEquals('https://localhost:8080', $basePath); + } + + public function testGetBasePathMultipleCalls(): void + { + $config = new PNConfiguration(); + $config->setSubscribeKey('demo'); + $config->setUserId('test-user'); + + $manager = new BasePathManager($config); + + $basePath1 = $manager->getBasePath(); + $basePath2 = $manager->getBasePath(); + $basePath3 = $manager->getBasePath(); + + $this->assertEquals($basePath1, $basePath2); + $this->assertEquals($basePath2, $basePath3); + } + + public function testGetBasePathWithDifferentCustomHosts(): void + { + $config = new PNConfiguration(); + $config->setSubscribeKey('demo'); + $config->setUserId('test-user'); + + $manager = new BasePathManager($config); + + $basePath1 = $manager->getBasePath('host1.pubnub.com'); + $basePath2 = $manager->getBasePath('host2.pubnub.com'); + + $this->assertEquals('https://host1.pubnub.com', $basePath1); + $this->assertEquals('https://host2.pubnub.com', $basePath2); + } +} diff --git a/tests/unit/CryptoCryptorGettersTest.php b/tests/unit/CryptoCryptorGettersTest.php new file mode 100644 index 00000000..dc974ef0 --- /dev/null +++ b/tests/unit/CryptoCryptorGettersTest.php @@ -0,0 +1,348 @@ +assertInstanceOf(AesCbcCryptor::class, $cryptor); + } + + public function testAesCbcCryptorGetCipherKey(): void + { + $cryptor = new AesCbcCryptor('my-cipher-key'); + + $this->assertEquals('my-cipher-key', $cryptor->getCipherKey()); + } + + public function testAesCbcCryptorGetCipherKeyWithNullParameter(): void + { + $cryptor = new AesCbcCryptor('my-cipher-key'); + + $this->assertEquals('my-cipher-key', $cryptor->getCipherKey(null)); + } + + public function testAesCbcCryptorGetCipherKeyWithCustomParameter(): void + { + $cryptor = new AesCbcCryptor('default-key'); + + // If a custom key is provided, it should return that instead + $this->assertEquals('custom-key', $cryptor->getCipherKey('custom-key')); + } + + public function testAesCbcCryptorGetIV(): void + { + $cryptor = new AesCbcCryptor('test-key'); + $iv = $cryptor->getIV(); + + $this->assertIsString($iv); + $this->assertEquals(16, strlen($iv)); // IV should be 16 bytes + } + + public function testAesCbcCryptorGetIVReturnsRandomValues(): void + { + $cryptor = new AesCbcCryptor('test-key'); + $iv1 = $cryptor->getIV(); + $iv2 = $cryptor->getIV(); + + // Each call should return a different random IV + $this->assertNotEquals($iv1, $iv2); + } + + public function testAesCbcCryptorEncryptReturnsPayload(): void + { + $cryptor = new AesCbcCryptor('test-key'); + $result = $cryptor->encrypt('test message'); + + $this->assertInstanceOf(\PubNub\Crypto\Payload::class, $result); + } + + public function testAesCbcCryptorEncryptPayloadHasCryptorId(): void + { + $cryptor = new AesCbcCryptor('test-key'); + $result = $cryptor->encrypt('test message'); + + $this->assertEquals('ACRH', $result->getCryptorId()); + } + + public function testAesCbcCryptorEncryptPayloadHasData(): void + { + $cryptor = new AesCbcCryptor('test-key'); + $result = $cryptor->encrypt('test message'); + + $this->assertNotEmpty($result->getData()); + } + + public function testAesCbcCryptorEncryptPayloadHasCryptorData(): void + { + $cryptor = new AesCbcCryptor('test-key'); + $result = $cryptor->encrypt('test message'); + + // Cryptor data should contain the IV (16 bytes) + $this->assertEquals(16, strlen($result->getCryptorData())); + } + + public function testAesCbcCryptorEncryptDecryptRoundTrip(): void + { + $cryptor = new AesCbcCryptor('test-key'); + $original = 'Hello, World!'; + + $encrypted = $cryptor->encrypt($original); + $decrypted = $cryptor->decrypt($encrypted); + + $this->assertEquals($original, $decrypted); + } + + public function testAesCbcCryptorWithDifferentKeys(): void + { + $cryptor1 = new AesCbcCryptor('key1'); + $cryptor2 = new AesCbcCryptor('key2'); + + $this->assertEquals('key1', $cryptor1->getCipherKey()); + $this->assertEquals('key2', $cryptor2->getCipherKey()); + } + + // ============================================================================ + // LegacyCryptor TESTS + // ============================================================================ + + public function testLegacyCryptorConstructor(): void + { + $cryptor = new LegacyCryptor('test-key', false); + + $this->assertInstanceOf(LegacyCryptor::class, $cryptor); + } + + public function testLegacyCryptorConstructorWithRandomIV(): void + { + $cryptor = new LegacyCryptor('test-key', true); + + $this->assertInstanceOf(LegacyCryptor::class, $cryptor); + } + + public function testLegacyCryptorGetCipherKey(): void + { + $cryptor = new LegacyCryptor('my-legacy-key', false); + + $this->assertEquals('my-legacy-key', $cryptor->getCipherKey()); + } + + public function testLegacyCryptorGetIVWithStaticIV(): void + { + $cryptor = new LegacyCryptor('test-key', false); + $iv = $cryptor->getIV(); + + $this->assertIsString($iv); + $this->assertEquals(16, strlen($iv)); // IV should be 16 bytes + $this->assertEquals('0123456789012345', $iv); // Static IV + } + + public function testLegacyCryptorGetIVWithStaticIVIsConsistent(): void + { + $cryptor = new LegacyCryptor('test-key', false); + $iv1 = $cryptor->getIV(); + $iv2 = $cryptor->getIV(); + + // With static IV, should return same value every time + $this->assertEquals($iv1, $iv2); + $this->assertEquals('0123456789012345', $iv1); + } + + public function testLegacyCryptorGetIVWithRandomIV(): void + { + $cryptor = new LegacyCryptor('test-key', true); + $iv = $cryptor->getIV(); + + $this->assertIsString($iv); + $this->assertEquals(16, strlen($iv)); // IV should be 16 bytes + } + + public function testLegacyCryptorGetIVWithRandomIVReturnsRandomValues(): void + { + $cryptor = new LegacyCryptor('test-key', true); + $iv1 = $cryptor->getIV(); + $iv2 = $cryptor->getIV(); + + // With random IV, each call should return different value + $this->assertNotEquals($iv1, $iv2); + } + + public function testLegacyCryptorEncryptReturnsPayload(): void + { + $cryptor = new LegacyCryptor('test-key', false); + $result = $cryptor->encrypt('test message'); + + $this->assertInstanceOf(\PubNub\Crypto\Payload::class, $result); + } + + public function testLegacyCryptorEncryptPayloadHasCryptorId(): void + { + $cryptor = new LegacyCryptor('test-key', false); + $result = $cryptor->encrypt('test message'); + + $this->assertEquals('0000', $result->getCryptorId()); + } + + public function testLegacyCryptorEncryptPayloadHasData(): void + { + $cryptor = new LegacyCryptor('test-key', false); + $result = $cryptor->encrypt('test message'); + + $this->assertNotEmpty($result->getData()); + } + + public function testLegacyCryptorEncryptPayloadCryptorDataIsEmpty(): void + { + $cryptor = new LegacyCryptor('test-key', false); + $result = $cryptor->encrypt('test message'); + + // Legacy cryptor uses empty string for cryptor data + $this->assertEquals('', $result->getCryptorData()); + } + + public function testLegacyCryptorEncryptDecryptRoundTripWithStaticIV(): void + { + $cryptor = new LegacyCryptor('test-key', false); + $original = 'Hello, Legacy!'; + + $encrypted = $cryptor->encrypt($original); + $decrypted = $cryptor->decrypt($encrypted); + + $this->assertEquals($original, $decrypted); + } + + public function testLegacyCryptorEncryptDecryptRoundTripWithRandomIV(): void + { + $cryptor = new LegacyCryptor('test-key', true); + $original = 'Hello, Random IV!'; + + $encrypted = $cryptor->encrypt($original); + $decrypted = $cryptor->decrypt($encrypted); + + $this->assertEquals($original, $decrypted); + } + + public function testLegacyCryptorWithDifferentKeys(): void + { + $cryptor1 = new LegacyCryptor('key1', false); + $cryptor2 = new LegacyCryptor('key2', true); + + $this->assertEquals('key1', $cryptor1->getCipherKey()); + $this->assertEquals('key2', $cryptor2->getCipherKey()); + } + + // ============================================================================ + // COMPARISON TESTS + // ============================================================================ + + public function testAesCbcCryptorAndLegacyCryptorHaveDifferentCryptorIds(): void + { + $aesCbc = new AesCbcCryptor('key'); + $legacy = new LegacyCryptor('key', false); + + $aesEncrypted = $aesCbc->encrypt('test'); + $legacyEncrypted = $legacy->encrypt('test'); + + $this->assertEquals('ACRH', $aesEncrypted->getCryptorId()); + $this->assertEquals('0000', $legacyEncrypted->getCryptorId()); + $this->assertNotEquals($aesEncrypted->getCryptorId(), $legacyEncrypted->getCryptorId()); + } + + public function testBothCryptorsReturnPayloadObjects(): void + { + $aesCbc = new AesCbcCryptor('key'); + $legacy = new LegacyCryptor('key', false); + + $aesResult = $aesCbc->encrypt('test'); + $legacyResult = $legacy->encrypt('test'); + + $this->assertInstanceOf(\PubNub\Crypto\Payload::class, $aesResult); + $this->assertInstanceOf(\PubNub\Crypto\Payload::class, $legacyResult); + } + + public function testBothCryptorsHandleEmptyStrings(): void + { + $aesCbc = new AesCbcCryptor('key'); + $legacy = new LegacyCryptor('key', false); + + $aesResult = $aesCbc->encrypt(''); + $legacyResult = $legacy->encrypt(''); + + $this->assertInstanceOf(\PubNub\Crypto\Payload::class, $aesResult); + $this->assertInstanceOf(\PubNub\Crypto\Payload::class, $legacyResult); + } + + public function testBothCryptorsHandleLongMessages(): void + { + $longMessage = str_repeat('A', 10000); // 10KB message + + $aesCbc = new AesCbcCryptor('key'); + $legacy = new LegacyCryptor('key', false); + + $aesEncrypted = $aesCbc->encrypt($longMessage); + $legacyEncrypted = $legacy->encrypt($longMessage); + + $this->assertEquals($longMessage, $aesCbc->decrypt($aesEncrypted)); + $this->assertEquals($longMessage, $legacy->decrypt($legacyEncrypted)); + } + + public function testBothCryptorsHandleUnicodeCharacters(): void + { + $unicodeMessage = '🔒 Encrypted 文字 مشفر'; + + $aesCbc = new AesCbcCryptor('key'); + $legacy = new LegacyCryptor('key', false); + + $aesEncrypted = $aesCbc->encrypt($unicodeMessage); + $legacyEncrypted = $legacy->encrypt($unicodeMessage); + + $this->assertEquals($unicodeMessage, $aesCbc->decrypt($aesEncrypted)); + $this->assertEquals($unicodeMessage, $legacy->decrypt($legacyEncrypted)); + } + + public function testBothCryptorsHaveGetCipherKeyMethod(): void + { + $aesCbc = new AesCbcCryptor('key1'); + $legacy = new LegacyCryptor('key2', false); + + $this->assertTrue(method_exists($aesCbc, 'getCipherKey')); + $this->assertTrue(method_exists($legacy, 'getCipherKey')); + + $this->assertEquals('key1', $aesCbc->getCipherKey()); + $this->assertEquals('key2', $legacy->getCipherKey()); + } + + public function testBothCryptorsHaveGetIVMethod(): void + { + $aesCbc = new AesCbcCryptor('key'); + $legacy = new LegacyCryptor('key', false); + + $this->assertTrue(method_exists($aesCbc, 'getIV')); + $this->assertTrue(method_exists($legacy, 'getIV')); + + $this->assertEquals(16, strlen($aesCbc->getIV())); + $this->assertEquals(16, strlen($legacy->getIV())); + } + + public function testBothCryptorsImplementEncryptAndDecrypt(): void + { + $aesCbc = new AesCbcCryptor('key'); + $legacy = new LegacyCryptor('key', false); + + $this->assertTrue(method_exists($aesCbc, 'encrypt')); + $this->assertTrue(method_exists($aesCbc, 'decrypt')); + $this->assertTrue(method_exists($legacy, 'encrypt')); + $this->assertTrue(method_exists($legacy, 'decrypt')); + } +} diff --git a/tests/unit/ListenerManagerTest.php b/tests/unit/ListenerManagerTest.php index f4ce19c7..ffa2de24 100644 --- a/tests/unit/ListenerManagerTest.php +++ b/tests/unit/ListenerManagerTest.php @@ -1,12 +1,15 @@ pubnub); @@ -36,7 +39,7 @@ public function testUrlEncode() class ExposedListenerManager extends ListenerManager { - public function count() + public function count(): int { return count($this->listeners); } @@ -44,7 +47,6 @@ public function count() class MySubscribeCallback extends SubscribeCallback { - /** * @param \PubNub\PubNub $pubnub * @param \PubNub\Models\ResponseHelpers\PNStatus $status @@ -55,13 +57,15 @@ function status($pubnub, $status) // TODO: Implement status() method. } + /** @phpstan-ignore-next-line */ function message($pubnub, $message) { // TODO: Implement message() method. } + /** @phpstan-ignore-next-line */ function presence($pubnub, $presence) { // TODO: Implement presence() method. } -} \ No newline at end of file +} diff --git a/tests/unit/PNConfigurationExtendedTest.php b/tests/unit/PNConfigurationExtendedTest.php new file mode 100644 index 00000000..f2e550a0 --- /dev/null +++ b/tests/unit/PNConfigurationExtendedTest.php @@ -0,0 +1,615 @@ +assertEquals(10, $config->getNonSubscribeRequestTimeout()); + } + + public function testSetNonSubscribeRequestTimeout(): void + { + $config = new PNConfiguration(); + $config->setUserId('test-user'); + + $config->setNonSubscribeRequestTimeout(20); + + $this->assertEquals(20, $config->getNonSubscribeRequestTimeout()); + } + + public function testSetNonSubscribeRequestTimeoutWithZero(): void + { + $config = new PNConfiguration(); + $config->setUserId('test-user'); + + $config->setNonSubscribeRequestTimeout(0); + + $this->assertEquals(0, $config->getNonSubscribeRequestTimeout()); + } + + public function testSetNonSubscribeRequestTimeoutWithLargeValue(): void + { + $config = new PNConfiguration(); + $config->setUserId('test-user'); + + $config->setNonSubscribeRequestTimeout(3600); + + $this->assertEquals(3600, $config->getNonSubscribeRequestTimeout()); + } + + public function testGetSubscribeTimeoutReturnsDefault(): void + { + $config = new PNConfiguration(); + + $this->assertEquals(310, $config->getSubscribeTimeout()); + } + + public function testSetSubscribeTimeout(): void + { + $config = new PNConfiguration(); + $config->setUserId('test-user'); + + $config->setSubscribeTimeout(400); + + $this->assertEquals(400, $config->getSubscribeTimeout()); + } + + public function testSetSubscribeTimeoutWithMinimalValue(): void + { + $config = new PNConfiguration(); + $config->setUserId('test-user'); + + $config->setSubscribeTimeout(1); + + $this->assertEquals(1, $config->getSubscribeTimeout()); + } + + public function testSetSubscribeTimeoutWithLargeValue(): void + { + $config = new PNConfiguration(); + $config->setUserId('test-user'); + + $config->setSubscribeTimeout(10000); + + $this->assertEquals(10000, $config->getSubscribeTimeout()); + } + + public function testGetConnectTimeoutReturnsDefault(): void + { + $config = new PNConfiguration(); + + $this->assertEquals(10, $config->getConnectTimeout()); + } + + public function testSetConnectTimeout(): void + { + $config = new PNConfiguration(); + $config->setUserId('test-user'); + + $config->setConnectTimeout(15); + + $this->assertEquals(15, $config->getConnectTimeout()); + } + + public function testSetConnectTimeoutWithMinimalValue(): void + { + $config = new PNConfiguration(); + $config->setUserId('test-user'); + + $config->setConnectTimeout(1); + + $this->assertEquals(1, $config->getConnectTimeout()); + } + + public function testSetConnectTimeoutWithLargeValue(): void + { + $config = new PNConfiguration(); + $config->setUserId('test-user'); + + $config->setConnectTimeout(300); + + $this->assertEquals(300, $config->getConnectTimeout()); + } + + public function testAllTimeoutSettingsTogether(): void + { + $config = new PNConfiguration(); + $config->setUserId('test-user'); + + $config->setNonSubscribeRequestTimeout(25); + $config->setSubscribeTimeout(350); + $config->setConnectTimeout(20); + + $this->assertEquals(25, $config->getNonSubscribeRequestTimeout()); + $this->assertEquals(350, $config->getSubscribeTimeout()); + $this->assertEquals(20, $config->getConnectTimeout()); + } + + // ============================================================================ + // SECURITY CONFIGURATION TESTS + // ============================================================================ + + public function testIsSecureReturnsDefaultTrue(): void + { + $config = new PNConfiguration(); + + $this->assertTrue($config->isSecure()); + } + + public function testSetSecureFalse(): void + { + $config = new PNConfiguration(); + $config->setUserId('test-user'); + + $config->setSecure(false); + + $this->assertFalse($config->isSecure()); + } + + public function testSetSecureTrue(): void + { + $config = new PNConfiguration(); + $config->setUserId('test-user'); + + $config->setSecure(false); + $this->assertFalse($config->isSecure()); + + $config->setSecure(true); + $this->assertTrue($config->isSecure()); + } + + public function testSetSecureDefaultsToTrue(): void + { + $config = new PNConfiguration(); + $config->setUserId('test-user'); + + $config->setSecure(false); + $config->setSecure(); // No parameter, should default to true + + $this->assertTrue($config->isSecure()); + } + + public function testSetSecureCanBeToggled(): void + { + $config = new PNConfiguration(); + $config->setUserId('test-user'); + + $this->assertTrue($config->isSecure()); + + $config->setSecure(false); + $this->assertFalse($config->isSecure()); + + $config->setSecure(true); + $this->assertTrue($config->isSecure()); + } + + // ============================================================================ + // ORIGIN CONFIGURATION TESTS + // ============================================================================ + + public function testGetOriginReturnsNullByDefault(): void + { + $config = new PNConfiguration(); + + $this->assertNull($config->getOrigin()); + } + + public function testSetOriginAndGetOrigin(): void + { + $config = new PNConfiguration(); + $config->setUserId('test-user'); + + $config->setOrigin('custom.pubnub.com'); + + $this->assertEquals('custom.pubnub.com', $config->getOrigin()); + } + + public function testSetOriginWithIPAddress(): void + { + $config = new PNConfiguration(); + $config->setUserId('test-user'); + + $config->setOrigin('192.168.1.100'); + + $this->assertEquals('192.168.1.100', $config->getOrigin()); + } + + public function testSetOriginWithPort(): void + { + $config = new PNConfiguration(); + $config->setUserId('test-user'); + + $config->setOrigin('localhost:8080'); + + $this->assertEquals('localhost:8080', $config->getOrigin()); + } + + public function testSetOriginOverwritesPrevious(): void + { + $config = new PNConfiguration(); + $config->setUserId('test-user'); + + $config->setOrigin('first-origin.com'); + $this->assertEquals('first-origin.com', $config->getOrigin()); + + $config->setOrigin('second-origin.com'); + $this->assertEquals('second-origin.com', $config->getOrigin()); + } + + // ============================================================================ + // AUTH KEY CONFIGURATION TESTS + // ============================================================================ + + public function testGetAuthKeyReturnsNullByDefault(): void + { + $config = new PNConfiguration(); + + $this->assertNull($config->getAuthKey()); + } + + public function testSetAuthKeyAndGetAuthKey(): void + { + $config = new PNConfiguration(); + $config->setUserId('test-user'); + + $config->setAuthKey('my-auth-key'); + + $this->assertEquals('my-auth-key', $config->getAuthKey()); + } + + public function testSetAuthKeyWithEmptyString(): void + { + $config = new PNConfiguration(); + $config->setUserId('test-user'); + + $config->setAuthKey(''); + + $this->assertEquals('', $config->getAuthKey()); + } + + public function testSetAuthKeyWithSpecialCharacters(): void + { + $config = new PNConfiguration(); + $config->setUserId('test-user'); + + $authKey = 'auth!@#$%^&*()_+-={}[]|:;<>?,./'; + $config->setAuthKey($authKey); + + $this->assertEquals($authKey, $config->getAuthKey()); + } + + public function testSetAuthKeyOverwritesPrevious(): void + { + $config = new PNConfiguration(); + $config->setUserId('test-user'); + + $config->setAuthKey('first-key'); + $this->assertEquals('first-key', $config->getAuthKey()); + + $config->setAuthKey('second-key'); + $this->assertEquals('second-key', $config->getAuthKey()); + } + + // ============================================================================ + // FILTER EXPRESSION TESTS + // ============================================================================ + + public function testGetFilterExpressionReturnsNullByDefault(): void + { + $config = new PNConfiguration(); + + $this->assertNull($config->getFilterExpression()); + } + + public function testSetFilterExpressionAndGetFilterExpression(): void + { + $config = new PNConfiguration(); + $config->setUserId('test-user'); + + $config->setFilterExpression('uuid == "user123"'); + + $this->assertEquals('uuid == "user123"', $config->getFilterExpression()); + } + + public function testSetFilterExpressionWithComplexExpression(): void + { + $config = new PNConfiguration(); + $config->setUserId('test-user'); + + $expression = 'uuid == "admin" && region == "us-east"'; + $config->setFilterExpression($expression); + + $this->assertEquals($expression, $config->getFilterExpression()); + } + + public function testSetFilterExpressionOverwritesPrevious(): void + { + $config = new PNConfiguration(); + $config->setUserId('test-user'); + + $config->setFilterExpression('first-expression'); + $this->assertEquals('first-expression', $config->getFilterExpression()); + + $config->setFilterExpression('second-expression'); + $this->assertEquals('second-expression', $config->getFilterExpression()); + } + + // ============================================================================ + // CRYPTO CONFIGURATION TESTS + // ============================================================================ + + public function testIsAesEnabledReturnsFalseByDefault(): void + { + $config = new PNConfiguration(); + + $this->assertFalse($config->isAesEnabled()); + } + + public function testIsAesEnabledReturnsTrueWhenCipherKeySet(): void + { + $config = new PNConfiguration(); + $config->setUserId('test-user'); + $config->setCipherKey('test-key'); + + $this->assertTrue($config->isAesEnabled()); + } + + public function testIsAesEnabledReturnsTrueWhenCryptoModuleSet(): void + { + $config = new PNConfiguration(); + $config->setUserId('test-user'); + + $cryptoModule = CryptoModule::legacyCryptor('key', false); + $config->setCryptoModule($cryptoModule); + + $this->assertTrue($config->isAesEnabled()); + } + + public function testGetCryptoSafeReturnsNullWhenNotConfigured(): void + { + $config = new PNConfiguration(); + + $this->assertNull($config->getCryptoSafe()); + } + + public function testGetCryptoSafeReturnsCryptoWhenConfigured(): void + { + $config = new PNConfiguration(); + $config->setUserId('test-user'); + $config->setCipherKey('test-key'); + + $crypto = $config->getCryptoSafe(); + + $this->assertInstanceOf(CryptoModule::class, $crypto); + } + + public function testGetCryptoSafeDoesNotThrowException(): void + { + $config = new PNConfiguration(); + + // This should not throw an exception + $crypto = $config->getCryptoSafe(); + + $this->assertNull($crypto); + } + + public function testSetCryptoModuleAndGetCryptoSafe(): void + { + $config = new PNConfiguration(); + $config->setUserId('test-user'); + + $cryptoModule = CryptoModule::aesCbcCryptor('my-key', true); + $config->setCryptoModule($cryptoModule); + + $crypto = $config->getCryptoSafe(); + + $this->assertSame($cryptoModule, $crypto); + } + + public function testGetUseRandomIVReturnsDefaultTrue(): void + { + $config = new PNConfiguration(); + + $this->assertTrue($config->getUseRandomIV()); + } + + public function testSetUseRandomIVFalse(): void + { + $config = new PNConfiguration(); + $config->setUserId('test-user'); + + $config->setUseRandomIV(false); + + $this->assertFalse($config->getUseRandomIV()); + } + + public function testSetUseRandomIVTrue(): void + { + $config = new PNConfiguration(); + $config->setUserId('test-user'); + + $config->setUseRandomIV(false); + $config->setUseRandomIV(true); + + $this->assertTrue($config->getUseRandomIV()); + } + + // ============================================================================ + // KEY GETTERS TESTS + // ============================================================================ + + public function testGetPublishKeyReturnsNull(): void + { + $config = new PNConfiguration(); + + $this->assertNull($config->getPublishKey()); + } + + public function testGetPublishKeyReturnsSetValue(): void + { + $config = new PNConfiguration(); + $config->setUserId('test-user'); + $config->setPublishKey('pub-key-123'); + + $this->assertEquals('pub-key-123', $config->getPublishKey()); + } + + public function testGetSecretKeyReturnsNull(): void + { + $config = new PNConfiguration(); + + $this->assertNull($config->getSecretKey()); + } + + public function testGetSecretKeyReturnsSetValue(): void + { + $config = new PNConfiguration(); + $config->setUserId('test-user'); + $config->setSecretKey('sec-key-456'); + + $this->assertEquals('sec-key-456', $config->getSecretKey()); + } + + public function testGetSubscribeKeyReturnsSetValue(): void + { + $config = new PNConfiguration(); + $config->setUserId('test-user'); + $config->setSubscribeKey('sub-key-789'); + + $this->assertEquals('sub-key-789', $config->getSubscribeKey()); + } + + // ============================================================================ + // CLONE METHOD TESTS + // ============================================================================ + + public function testCloneCreatesNewInstance(): void + { + $config = new PNConfiguration(); + $config->setUserId('test-user'); + $config->setSubscribeKey('demo'); + + $cloned = $config->clone(); + + $this->assertNotSame($config, $cloned); + } + + public function testClonePreservesValues(): void + { + $config = new PNConfiguration(); + $config->setUserId('test-user'); + $config->setSubscribeKey('sub-key'); + $config->setPublishKey('pub-key'); + $config->setSecretKey('secret-key'); + $config->setOrigin('custom-origin.com'); + $config->setAuthKey('auth-key'); + $config->setSecure(false); + + $cloned = $config->clone(); + + $this->assertEquals('test-user', $cloned->getUserId()); + $this->assertEquals('sub-key', $cloned->getSubscribeKey()); + $this->assertEquals('pub-key', $cloned->getPublishKey()); + $this->assertEquals('secret-key', $cloned->getSecretKey()); + $this->assertEquals('custom-origin.com', $cloned->getOrigin()); + $this->assertEquals('auth-key', $cloned->getAuthKey()); + $this->assertFalse($cloned->isSecure()); + } + + public function testClonePreservesTimeoutSettings(): void + { + $config = new PNConfiguration(); + $config->setUserId('test-user'); + $config->setNonSubscribeRequestTimeout(25); + $config->setSubscribeTimeout(400); + $config->setConnectTimeout(15); + + $cloned = $config->clone(); + + $this->assertEquals(25, $cloned->getNonSubscribeRequestTimeout()); + $this->assertEquals(400, $cloned->getSubscribeTimeout()); + $this->assertEquals(15, $cloned->getConnectTimeout()); + } + + public function testCloneCreatesUnlockedConfiguration(): void + { + $config = new PNConfiguration(); + $config->setUserId('test-user'); + $config->setSubscribeKey('demo'); + $config->lock(); + + $cloned = $config->clone(); + + // Cloned config should not be locked, so this should not throw + $cloned->setPublishKey('new-pub-key'); + $this->assertEquals('new-pub-key', $cloned->getPublishKey()); + } + + public function testCloneIsIndependent(): void + { + $config = new PNConfiguration(); + $config->setUserId('original-user'); + $config->setSubscribeKey('original-key'); + + $cloned = $config->clone(); + $cloned->setUserId('cloned-user'); + $cloned->setSubscribeKey('cloned-key'); + + // Original should be unchanged + $this->assertEquals('original-user', $config->getUserId()); + $this->assertEquals('original-key', $config->getSubscribeKey()); + + // Cloned should have new values + $this->assertEquals('cloned-user', $cloned->getUserId()); + $this->assertEquals('cloned-key', $cloned->getSubscribeKey()); + } + + // ============================================================================ + // COMPREHENSIVE INTEGRATION TESTS + // ============================================================================ + + public function testFullConfigurationSetup(): void + { + $config = new PNConfiguration(); + $config->setUserId('test-user'); + $config->setSubscribeKey('sub-key'); + $config->setPublishKey('pub-key'); + $config->setSecretKey('secret-key'); + $config->setOrigin('custom.pubnub.com'); + $config->setAuthKey('my-auth-key'); + $config->setSecure(true); + $config->setNonSubscribeRequestTimeout(30); + $config->setSubscribeTimeout(320); + $config->setConnectTimeout(12); + $config->setFilterExpression('uuid != "bot"'); + $config->setUseRandomIV(true); + $config->setCipherKey('cipher-key'); + + // Verify all values + $this->assertEquals('test-user', $config->getUserId()); + $this->assertEquals('sub-key', $config->getSubscribeKey()); + $this->assertEquals('pub-key', $config->getPublishKey()); + $this->assertEquals('secret-key', $config->getSecretKey()); + $this->assertEquals('custom.pubnub.com', $config->getOrigin()); + $this->assertEquals('my-auth-key', $config->getAuthKey()); + $this->assertTrue($config->isSecure()); + $this->assertEquals(30, $config->getNonSubscribeRequestTimeout()); + $this->assertEquals(320, $config->getSubscribeTimeout()); + $this->assertEquals(12, $config->getConnectTimeout()); + $this->assertEquals('uuid != "bot"', $config->getFilterExpression()); + $this->assertTrue($config->isAesEnabled()); + $this->assertTrue($config->getUseRandomIV()); + } +} diff --git a/tests/unit/PNConfigurationTest.php b/tests/unit/PNConfigurationTest.php index ff880b68..0184775a 100644 --- a/tests/unit/PNConfigurationTest.php +++ b/tests/unit/PNConfigurationTest.php @@ -1,29 +1,30 @@ setUuid('foo-bar-baz'); $this->assertEquals($config->getUuid(), 'foo-bar-baz'); } - public function testInitWithUserId() + public function testInitWithUserId(): void { $config = new PNConfiguration(); $config->setUserId('foo-bar-baz'); $this->assertEquals($config->getUserId(), 'foo-bar-baz'); } - public function testThrowOnUserIdAndUUID() + public function testThrowOnUserIdAndUUID(): void { $this->expectException(PubNubConfigurationException::class); $this->expectExceptionMessage("Cannot use UserId and UUID simultaneously"); @@ -32,7 +33,7 @@ public function testThrowOnUserIdAndUUID() $config->setUuid('foo-bar-baz'); } - public function testThrowOnEmptyUserId() + public function testThrowOnEmptyUserId(): void { $this->expectException(PubNubConfigurationException::class); $this->expectExceptionMessage("UserID should not be empty"); diff --git a/tests/unit/PubNubCborDecodeTest.php b/tests/unit/PubNubCborDecodeTest.php new file mode 100644 index 00000000..dd6568cf --- /dev/null +++ b/tests/unit/PubNubCborDecodeTest.php @@ -0,0 +1,455 @@ +assertEquals(0, PubNubCborDecode::decode('00')); + $this->assertEquals(1, PubNubCborDecode::decode('01')); + $this->assertEquals(10, PubNubCborDecode::decode('0A')); + $this->assertEquals(23, PubNubCborDecode::decode('17')); + } + + public function testDecodeUnsignedInteger1Byte(): void + { + // Test 1-byte integers (24-255) + $this->assertEquals(24, PubNubCborDecode::decode('1818')); + $this->assertEquals(100, PubNubCborDecode::decode('1864')); + $this->assertEquals(255, PubNubCborDecode::decode('18FF')); + } + + public function testDecodeUnsignedInteger2Bytes(): void + { + // Test 2-byte integers (256-65535) + $this->assertEquals(256, PubNubCborDecode::decode('190100')); + $this->assertEquals(1000, PubNubCborDecode::decode('1903E8')); + $this->assertEquals(65535, PubNubCborDecode::decode('19FFFF')); + } + + public function testDecodeUnsignedInteger4Bytes(): void + { + // Test 4-byte integers + $this->assertEquals(100000, PubNubCborDecode::decode('1A000186A0')); + $this->assertEquals(1000000, PubNubCborDecode::decode('1A000F4240')); + } + + public function testDecodeUnsignedInteger8Bytes(): void + { + // Test 8-byte integers + $this->assertEquals(1000000000, PubNubCborDecode::decode('1B000000003B9ACA00')); + } + + // ============================================================================ + // NEGATIVE INTEGER TESTS + // ============================================================================ + + public function testDecodeNegativeIntegerSmall(): void + { + // Negative integers: -1 to -24 encoded as 0x20-0x37 + $this->assertEquals(-1, PubNubCborDecode::decode('20')); + $this->assertEquals(-10, PubNubCborDecode::decode('29')); + $this->assertEquals(-24, PubNubCborDecode::decode('37')); + } + + public function testDecodeNegativeInteger1Byte(): void + { + // -25 to -256 + $this->assertEquals(-25, PubNubCborDecode::decode('3818')); + $this->assertEquals(-100, PubNubCborDecode::decode('3863')); + $this->assertEquals(-256, PubNubCborDecode::decode('38FF')); + } + + public function testDecodeNegativeInteger2Bytes(): void + { + // -257 to -65536 + $this->assertEquals(-1000, PubNubCborDecode::decode('3903E7')); + } + + // ============================================================================ + // BYTE STRING TESTS + // ============================================================================ + + public function testDecodeByteStringEmpty(): void + { + $this->assertEquals('', PubNubCborDecode::decode('40')); + } + + public function testDecodeByteStringSmall(): void + { + // Byte string "hello" (68656C6C6F in hex) + $this->assertEquals('hello', PubNubCborDecode::decode('4568656C6C6F')); + } + + public function testDecodeByteString1ByteLength(): void + { + // Byte string with 1-byte length indicator + $decoded = PubNubCborDecode::decode('5818' . bin2hex(str_repeat('a', 24))); + $this->assertEquals(str_repeat('a', 24), $decoded); + } + + // ============================================================================ + // TEXT STRING TESTS + // ============================================================================ + + public function testDecodeTextStringEmpty(): void + { + $this->assertEquals('', PubNubCborDecode::decode('60')); + } + + public function testDecodeTextStringSmall(): void + { + // Text string "IETF" + $this->assertEquals('IETF', PubNubCborDecode::decode('6449455446')); + } + + public function testDecodeTextStringUnicode(): void + { + // Text string with unicode + $this->assertEquals('hello', PubNubCborDecode::decode('6568656C6C6F')); + } + + public function testDecodeTextString1ByteLength(): void + { + // Text string with 1-byte length + $text = str_repeat('x', 24); + $decoded = PubNubCborDecode::decode('7818' . bin2hex($text)); + $this->assertEquals($text, $decoded); + } + + // ============================================================================ + // ARRAY TESTS + // ============================================================================ + + public function testDecodeArrayEmpty(): void + { + $this->assertEquals([], PubNubCborDecode::decode('80')); + } + + public function testDecodeArrayWithIntegers(): void + { + // Array [1, 2, 3] + $this->assertEquals([1, 2, 3], PubNubCborDecode::decode('83010203')); + } + + public function testDecodeArrayWithMixedTypes(): void + { + // Array [1, "hello"] + $result = PubNubCborDecode::decode('82016568656C6C6F'); + $this->assertEquals([1, 'hello'], $result); + } + + public function testDecodeArrayNested(): void + { + // Array [[1, 2], [3, 4]] + $result = PubNubCborDecode::decode('828201028203 04'); + $this->assertEquals([[1, 2], [3, 4]], $result); + } + + public function testDecodeArrayWithNegativeNumbers(): void + { + // Array [-1, -2, -3] + $this->assertEquals([-1, -2, -3], PubNubCborDecode::decode('83202122')); + } + + // ============================================================================ + // HASHMAP (OBJECT) TESTS + // ============================================================================ + + public function testDecodeHashmapEmpty(): void + { + $this->assertEquals([], PubNubCborDecode::decode('A0')); + } + + public function testDecodeHashmapWithStrings(): void + { + // Map {"a": "A"} + $result = PubNubCborDecode::decode('A161616141'); + $this->assertEquals(['a' => 'A'], $result); + } + + public function testDecodeHashmapWithNumbers(): void + { + // Map {"a": 1, "b": 2} + $result = PubNubCborDecode::decode('A26161016162 02'); + $this->assertEquals(['a' => 1, 'b' => 2], $result); + } + + public function testDecodeHashmapNested(): void + { + // Map {"a": {"b": "c"}} + $result = PubNubCborDecode::decode('A16161A161626163'); + $this->assertEquals(['a' => ['b' => 'c']], $result); + } + + public function testDecodeHashmapWithArray(): void + { + // Map {"array": [1, 2, 3]} + $result = PubNubCborDecode::decode('A16561727261798301 0203'); + $this->assertEquals(['array' => [1, 2, 3]], $result); + } + + // ============================================================================ + // SIMPLE VALUES TESTS + // ============================================================================ + + public function testDecodeSimpleValueFalse(): void + { + $this->assertFalse(PubNubCborDecode::decode('F4')); + } + + public function testDecodeSimpleValueTrue(): void + { + $this->assertTrue(PubNubCborDecode::decode('F5')); + } + + public function testDecodeSimpleValueNull(): void + { + $this->assertNull(PubNubCborDecode::decode('F6')); + } + + public function testDecodeSimpleValueUndefined(): void + { + // Undefined maps to null in PHP + $this->assertNull(PubNubCborDecode::decode('F7')); + } + + // ============================================================================ + // FLOAT TESTS + // ============================================================================ + + public function testDecodeFloat16Bit(): void + { + // Test 16-bit float (half-precision) + // 1.0 in half precision: 0x3C00 + $result = PubNubCborDecode::decode('F93C00'); + $this->assertEquals(1.0, $result); + } + + public function testDecodeFloat32Bit(): void + { + // Test 32-bit float (single precision) + // 3.14159 approximately in single precision + $result = PubNubCborDecode::decode('FA40490FDA'); + $this->assertEqualsWithDelta(3.14159, $result, 0.00001); + } + + public function testDecodeFloat64Bit(): void + { + // Test 64-bit float (double precision) + // 1.1 in double precision + $result = PubNubCborDecode::decode('FB3FF199999999999A'); + $this->assertEqualsWithDelta(1.1, $result, 0.0000001); + } + + public function testDecodeFloatZero(): void + { + // 0.0 in half precision: 0x0000 + $result = PubNubCborDecode::decode('F90000'); + $this->assertEquals(0.0, $result); + } + + public function testDecodeFloatNegative(): void + { + // -1.0 in half precision: 0xBC00 + $result = PubNubCborDecode::decode('F9BC00'); + $this->assertEquals(-1.0, $result); + } + + public function testDecodeFloatInfinity(): void + { + // Positive infinity in half precision: 0x7C00 + $result = PubNubCborDecode::decode('F97C00'); + $this->assertEquals(INF, $result); + } + + // ============================================================================ + // COMPLEX DATA STRUCTURE TESTS + // ============================================================================ + + public function testDecodeComplexNestedStructure(): void + { + // Complex structure: {"users": [{"name": "Alice", "age": 30}]} + $cbor = 'A1' . // Map with 1 entry + '657573657273' . // key: "users" + '81' . // Array with 1 element + 'A2' . // Map with 2 entries + '646E616D65' . // key: "name" + '65416C696365' . // value: "Alice" + '63616765' . // key: "age" + '181E'; // value: 30 + + $result = PubNubCborDecode::decode($cbor); + + $this->assertIsArray($result); + $this->assertArrayHasKey('users', $result); + $this->assertIsArray($result['users']); + $this->assertEquals('Alice', $result['users'][0]['name']); + $this->assertEquals(30, $result['users'][0]['age']); + } + + public function testDecodeArrayOfMixedPrimitives(): void + { + // Array with: integer, string, boolean, null + // [42, "test", true, null] + $cbor = '84' . // Array with 4 elements + '182A' . // 42 + '6474657374' . // "test" + 'F5' . // true + 'F6'; // null + + $result = PubNubCborDecode::decode($cbor); + + $this->assertEquals([42, 'test', true, null], $result); + } + + // ============================================================================ + // INPUT SANITIZATION TESTS + // ============================================================================ + + public function testDecodeWithSpaces(): void + { + // Should handle spaces in input + $this->assertEquals(42, PubNubCborDecode::decode('18 2A')); + $this->assertEquals([1, 2], PubNubCborDecode::decode('82 01 02')); + } + + public function testDecodeWithLowerCase(): void + { + // Should handle lowercase hex + $this->assertEquals(255, PubNubCborDecode::decode('18ff')); + $this->assertEquals('hello', PubNubCborDecode::decode('6568656c6c6f')); + } + + public function testDecodeWithMixedCase(): void + { + // Should handle mixed case hex + $this->assertEquals(1000, PubNubCborDecode::decode('1903E8')); + $this->assertEquals('Test', PubNubCborDecode::decode('6454657374')); + } + + // ============================================================================ + // ERROR HANDLING TESTS + // ============================================================================ + + public function testDecodeInvalidHexCharacters(): void + { + $this->expectException(\Exception::class); + $this->expectExceptionMessage('Invalid Input'); + + PubNubCborDecode::decode('GG'); + } + + public function testDecodeInvalidHexWithSpecialChars(): void + { + $this->expectException(\Exception::class); + $this->expectExceptionMessage('Invalid Input'); + + PubNubCborDecode::decode('18@A'); + } + + public function testDecodeInvalidHexWithNonHexLetters(): void + { + $this->expectException(\Exception::class); + $this->expectExceptionMessage('Invalid Input'); + + PubNubCborDecode::decode('18ZZ'); + } + + // ============================================================================ + // EDGE CASES + // ============================================================================ + + public function testDecodeEmptyArray(): void + { + $result = PubNubCborDecode::decode('80'); + $this->assertIsArray($result); + $this->assertEmpty($result); + } + + public function testDecodeEmptyMap(): void + { + $result = PubNubCborDecode::decode('A0'); + $this->assertIsArray($result); + $this->assertEmpty($result); + } + + public function testDecodeEmptyString(): void + { + $this->assertEquals('', PubNubCborDecode::decode('60')); + } + + public function testDecodeLargeInteger(): void + { + // Test maximum values for different byte sizes + $this->assertEquals(4294967295, PubNubCborDecode::decode('1AFFFFFFFF')); + } + + public function testDecodeArrayWith1ByteLength(): void + { + // Array with length specified in 1 byte (25+ elements) + $cbor = '9818'; // Array with length in next byte: 24 elements + for ($i = 0; $i < 24; $i++) { + $cbor .= '0' . dechex($i % 16); // Add elements 0-15 repeated + } + + $result = PubNubCborDecode::decode($cbor); + $this->assertCount(24, $result); + } + + public function testDecodeMapWith1ByteLength(): void + { + // Map with length specified in 1 byte + $cbor = 'B818'; // Map with length in next byte: 24 entries + for ($i = 0; $i < 24; $i++) { + $key = chr(65 + $i); // A, B, C, ... + $cbor .= '61' . bin2hex($key); // key + $cbor .= '0' . dechex($i % 16); // value + } + + $result = PubNubCborDecode::decode($cbor); + $this->assertCount(24, $result); + } + + public function testDecodeMultipleNesting(): void + { + // Deep nesting: [[[1]]] + $result = PubNubCborDecode::decode('818181 01'); + $this->assertEquals([[[1]]], $result); + } + + public function testDecodeBooleanArray(): void + { + // Array of booleans: [true, false, true] + $result = PubNubCborDecode::decode('83F5F4F5'); + $this->assertEquals([true, false, true], $result); + } + + public function testDecodeNullArray(): void + { + // Array with nulls: [null, null] + $result = PubNubCborDecode::decode('82F6F6'); + $this->assertEquals([null, null], $result); + } + + public function testDecodeStringWithSpecialCharacters(): void + { + // String with special chars like newline, tab + $specialString = "test\nwith\ttabs"; + $hex = bin2hex($specialString); + $length = strlen($specialString); + $cbor = '6' . dechex($length) . $hex; + + $result = PubNubCborDecode::decode($cbor); + $this->assertEquals($specialString, $result); + } +} diff --git a/tests/unit/PubNubCryptoMethodsTest.php b/tests/unit/PubNubCryptoMethodsTest.php new file mode 100644 index 00000000..210416da --- /dev/null +++ b/tests/unit/PubNubCryptoMethodsTest.php @@ -0,0 +1,356 @@ +setSubscribeKey('demo'); + $config->setUserId('test-user'); + + $pubnub = new PubNub($config); + + $this->assertFalse($pubnub->isCryptoEnabled()); + } + + public function testIsCryptoEnabledReturnsTrueWhenCipherKeySet(): void + { + $config = new PNConfiguration(); + $config->setSubscribeKey('demo'); + $config->setUserId('test-user'); + $config->setCipherKey('test-cipher-key'); + + $pubnub = new PubNub($config); + + $this->assertTrue($pubnub->isCryptoEnabled()); + } + + public function testIsCryptoEnabledReturnsTrueWhenCryptoModuleSet(): void + { + $config = new PNConfiguration(); + $config->setSubscribeKey('demo'); + $config->setUserId('test-user'); + + $pubnub = new PubNub($config); + $cryptoModule = CryptoModule::legacyCryptor('test-key', false); + $pubnub->setCrypto($cryptoModule); + + $this->assertTrue($pubnub->isCryptoEnabled()); + } + + public function testIsCryptoEnabledAfterSettingCryptoOnPubNub(): void + { + $config = new PNConfiguration(); + $config->setSubscribeKey('demo'); + $config->setUserId('test-user'); + + $pubnub = new PubNub($config); + $this->assertFalse($pubnub->isCryptoEnabled()); + + $cryptoModule = CryptoModule::aesCbcCryptor('my-key', true); + $pubnub->setCrypto($cryptoModule); + + $this->assertTrue($pubnub->isCryptoEnabled()); + } + + // ============================================================================ + // getCrypto() TESTS + // ============================================================================ + + public function testGetCryptoReturnsNullWhenNotConfigured(): void + { + $config = new PNConfiguration(); + $config->setSubscribeKey('demo'); + $config->setUserId('test-user'); + + $pubnub = new PubNub($config); + + $this->assertNull($pubnub->getCrypto()); + } + + public function testGetCryptoReturnsModuleWhenCipherKeySet(): void + { + $config = new PNConfiguration(); + $config->setSubscribeKey('demo'); + $config->setUserId('test-user'); + $config->setCipherKey('test-cipher-key'); + + $pubnub = new PubNub($config); + $crypto = $pubnub->getCrypto(); + + $this->assertInstanceOf(CryptoModule::class, $crypto); + } + + public function testGetCryptoReturnsModuleWhenCryptoModuleSet(): void + { + $config = new PNConfiguration(); + $config->setSubscribeKey('demo'); + $config->setUserId('test-user'); + + $pubnub = new PubNub($config); + $cryptoModule = CryptoModule::legacyCryptor('test-key', false); + $pubnub->setCrypto($cryptoModule); + + $crypto = $pubnub->getCrypto(); + + $this->assertInstanceOf(CryptoModule::class, $crypto); + $this->assertSame($cryptoModule, $crypto); + } + + public function testGetCryptoReturnsConfigurationCryptoWhenNoPubNubCrypto(): void + { + $config = new PNConfiguration(); + $config->setSubscribeKey('demo'); + $config->setUserId('test-user'); + $config->setCipherKey('config-key'); + + $pubnub = new PubNub($config); + $crypto = $pubnub->getCrypto(); + + $this->assertInstanceOf(CryptoModule::class, $crypto); + } + + public function testGetCryptoPrefersInstanceCryptoOverConfiguration(): void + { + $config = new PNConfiguration(); + $config->setSubscribeKey('demo'); + $config->setUserId('test-user'); + $config->setCipherKey('config-key'); + + $pubnub = new PubNub($config); + + $instanceCrypto = CryptoModule::aesCbcCryptor('instance-key', true); + $pubnub->setCrypto($instanceCrypto); + + $crypto = $pubnub->getCrypto(); + + $this->assertSame($instanceCrypto, $crypto); + } + + // ============================================================================ + // setCrypto() TESTS + // ============================================================================ + + public function testSetCryptoStoresModule(): void + { + $config = new PNConfiguration(); + $config->setSubscribeKey('demo'); + $config->setUserId('test-user'); + + $pubnub = new PubNub($config); + $cryptoModule = CryptoModule::legacyCryptor('my-cipher-key', false); + + $pubnub->setCrypto($cryptoModule); + + $this->assertSame($cryptoModule, $pubnub->getCrypto()); + } + + public function testSetCryptoOverwritesPreviousCrypto(): void + { + $config = new PNConfiguration(); + $config->setSubscribeKey('demo'); + $config->setUserId('test-user'); + + $pubnub = new PubNub($config); + + $crypto1 = CryptoModule::legacyCryptor('key1', false); + $pubnub->setCrypto($crypto1); + $this->assertSame($crypto1, $pubnub->getCrypto()); + + $crypto2 = CryptoModule::aesCbcCryptor('key2', true); + $pubnub->setCrypto($crypto2); + $this->assertSame($crypto2, $pubnub->getCrypto()); + } + + public function testSetCryptoEnablesCrypto(): void + { + $config = new PNConfiguration(); + $config->setSubscribeKey('demo'); + $config->setUserId('test-user'); + + $pubnub = new PubNub($config); + $this->assertFalse($pubnub->isCryptoEnabled()); + + $cryptoModule = CryptoModule::legacyCryptor('my-key', false); + $pubnub->setCrypto($cryptoModule); + + $this->assertTrue($pubnub->isCryptoEnabled()); + } + + public function testSetCryptoWithLegacyCryptor(): void + { + $config = new PNConfiguration(); + $config->setSubscribeKey('demo'); + $config->setUserId('test-user'); + + $pubnub = new PubNub($config); + $cryptoModule = CryptoModule::legacyCryptor('legacy-key', false); + + $pubnub->setCrypto($cryptoModule); + + $this->assertInstanceOf(CryptoModule::class, $pubnub->getCrypto()); + } + + public function testSetCryptoWithAesCbcCryptor(): void + { + $config = new PNConfiguration(); + $config->setSubscribeKey('demo'); + $config->setUserId('test-user'); + + $pubnub = new PubNub($config); + $cryptoModule = CryptoModule::aesCbcCryptor('aes-key', true); + + $pubnub->setCrypto($cryptoModule); + + $this->assertInstanceOf(CryptoModule::class, $pubnub->getCrypto()); + } + + public function testSetCryptoWithRandomIV(): void + { + $config = new PNConfiguration(); + $config->setSubscribeKey('demo'); + $config->setUserId('test-user'); + + $pubnub = new PubNub($config); + $cryptoModule = CryptoModule::legacyCryptor('test-key', true); + + $pubnub->setCrypto($cryptoModule); + + $crypto = $pubnub->getCrypto(); + $this->assertNotNull($crypto); + } + + public function testSetCryptoWithStaticIV(): void + { + $config = new PNConfiguration(); + $config->setSubscribeKey('demo'); + $config->setUserId('test-user'); + + $pubnub = new PubNub($config); + $cryptoModule = CryptoModule::legacyCryptor('test-key', false); + + $pubnub->setCrypto($cryptoModule); + + $crypto = $pubnub->getCrypto(); + $this->assertNotNull($crypto); + } + + // ============================================================================ + // INTEGRATION TESTS (getCrypto + setCrypto + isCryptoEnabled) + // ============================================================================ + + public function testCryptoWorkflowConfigurationOnly(): void + { + $config = new PNConfiguration(); + $config->setSubscribeKey('demo'); + $config->setUserId('test-user'); + $config->setCipherKey('config-cipher-key'); + + $pubnub = new PubNub($config); + + $this->assertTrue($pubnub->isCryptoEnabled()); + $this->assertInstanceOf(CryptoModule::class, $pubnub->getCrypto()); + } + + public function testCryptoWorkflowInstanceOnly(): void + { + $config = new PNConfiguration(); + $config->setSubscribeKey('demo'); + $config->setUserId('test-user'); + + $pubnub = new PubNub($config); + $this->assertFalse($pubnub->isCryptoEnabled()); + + $cryptoModule = CryptoModule::aesCbcCryptor('instance-key', true); + $pubnub->setCrypto($cryptoModule); + + $this->assertTrue($pubnub->isCryptoEnabled()); + $this->assertSame($cryptoModule, $pubnub->getCrypto()); + } + + public function testCryptoWorkflowBothConfigurationAndInstance(): void + { + $config = new PNConfiguration(); + $config->setSubscribeKey('demo'); + $config->setUserId('test-user'); + $config->setCipherKey('config-key'); + + $pubnub = new PubNub($config); + $this->assertTrue($pubnub->isCryptoEnabled()); + + $instanceCrypto = CryptoModule::legacyCryptor('instance-key', false); + $pubnub->setCrypto($instanceCrypto); + + $this->assertTrue($pubnub->isCryptoEnabled()); + $this->assertSame($instanceCrypto, $pubnub->getCrypto()); + } + + public function testCryptoCanBeUsedForEncryptionDecryption(): void + { + $config = new PNConfiguration(); + $config->setSubscribeKey('demo'); + $config->setUserId('test-user'); + + $pubnub = new PubNub($config); + $cryptoModule = CryptoModule::aesCbcCryptor('encryption-key', false); + $pubnub->setCrypto($cryptoModule); + + $crypto = $pubnub->getCrypto(); + + $plaintext = 'Hello, World!'; + $encrypted = $crypto->encrypt($plaintext); + $decrypted = $crypto->decrypt($encrypted); + + $this->assertEquals($plaintext, $decrypted); + } + + public function testMultiplePubNubInstancesWithDifferentCrypto(): void + { + $config1 = new PNConfiguration(); + $config1->setSubscribeKey('demo'); + $config1->setUserId('user1'); + $pubnub1 = new PubNub($config1); + $crypto1 = CryptoModule::legacyCryptor('key1', false); + $pubnub1->setCrypto($crypto1); + + $config2 = new PNConfiguration(); + $config2->setSubscribeKey('demo'); + $config2->setUserId('user2'); + $pubnub2 = new PubNub($config2); + $crypto2 = CryptoModule::aesCbcCryptor('key2', true); + $pubnub2->setCrypto($crypto2); + + $this->assertNotSame($pubnub1->getCrypto(), $pubnub2->getCrypto()); + $this->assertSame($crypto1, $pubnub1->getCrypto()); + $this->assertSame($crypto2, $pubnub2->getCrypto()); + } + + public function testCryptoModuleCanBeRetrievedAndUsedDirectly(): void + { + $config = new PNConfiguration(); + $config->setSubscribeKey('demo'); + $config->setUserId('test-user'); + $config->setCipherKey('direct-use-key'); + + $pubnub = new PubNub($config); + $crypto = $pubnub->getCrypto(); + + // Use crypto module directly + $message = 'Test message'; + $encrypted = $crypto->encrypt($message); + + $this->assertNotEquals($message, $encrypted); + $this->assertIsString($encrypted); + } +} diff --git a/tests/unit/PubNubFactoryMethodsTest.php b/tests/unit/PubNubFactoryMethodsTest.php new file mode 100644 index 00000000..936648fc --- /dev/null +++ b/tests/unit/PubNubFactoryMethodsTest.php @@ -0,0 +1,268 @@ +assertInstanceOf(PubNub::class, $pubnub); + } + + public function testDemoHasDemoKeys(): void + { + $pubnub = PubNub::demo(); + $config = $pubnub->getConfiguration(); + + $this->assertEquals('demo', $config->getSubscribeKey()); + $this->assertEquals('demo', $config->getPublishKey()); + } + + public function testDemoHasDemoUserId(): void + { + $pubnub = PubNub::demo(); + $config = $pubnub->getConfiguration(); + + $this->assertEquals('demo', $config->getUserId()); + } + + public function testDemoIsImmediatelyUsable(): void + { + $pubnub = PubNub::demo(); + + // Should be able to call methods without configuration errors + $config = $pubnub->getConfiguration(); + + $this->assertNotNull($config->getSubscribeKey()); + $this->assertNotNull($config->getPublishKey()); + $this->assertNotNull($config->getUserId()); + } + + public function testDemoCreatesNewInstanceEachTime(): void + { + $pubnub1 = PubNub::demo(); + $pubnub2 = PubNub::demo(); + + $this->assertNotSame($pubnub1, $pubnub2); + } + + public function testDemoInstancesAreIndependent(): void + { + $pubnub1 = PubNub::demo(); + $pubnub2 = PubNub::demo(); + + // Modify one instance + $pubnub1->setToken('token1'); + $pubnub2->setToken('token2'); + + $this->assertEquals('token1', $pubnub1->getToken()); + $this->assertEquals('token2', $pubnub2->getToken()); + } + + public function testDemoConfigurationIsLocked(): void + { + $pubnub = PubNub::demo(); + $config = $pubnub->getConfiguration(); + + // Configuration should be locked after being passed to PubNub constructor + $this->expectException(\PubNub\Exceptions\PubNubConfigurationException::class); + $config->setPublishKey('new-key'); + } + + public function testDemoCanBeUsedForBasicOperations(): void + { + $pubnub = PubNub::demo(); + + // Should be able to create endpoint builders + $this->assertNotNull($pubnub->publish()); + $this->assertNotNull($pubnub->subscribe()); + $this->assertNotNull($pubnub->time()); + } + + // ============================================================================ + // PNConfiguration::demoKeys() TESTS + // ============================================================================ + + public function testDemoKeysReturnsValidConfiguration(): void + { + $config = PNConfiguration::demoKeys(); + + $this->assertInstanceOf(PNConfiguration::class, $config); + } + + public function testDemoKeysHasSubscribeKey(): void + { + $config = PNConfiguration::demoKeys(); + + $this->assertEquals('demo', $config->getSubscribeKey()); + } + + public function testDemoKeysHasPublishKey(): void + { + $config = PNConfiguration::demoKeys(); + + $this->assertEquals('demo', $config->getPublishKey()); + } + + public function testDemoKeysHasUserId(): void + { + $config = PNConfiguration::demoKeys(); + + $this->assertEquals('demo', $config->getUserId()); + } + + public function testDemoKeysConfigurationIsNotLocked(): void + { + $config = PNConfiguration::demoKeys(); + + // Should be able to modify the configuration + $config->setPublishKey('new-pub-key'); + $this->assertEquals('new-pub-key', $config->getPublishKey()); + + $config->setSubscribeKey('new-sub-key'); + $this->assertEquals('new-sub-key', $config->getSubscribeKey()); + + // Test other modifiable properties (userId has special handling due to UUID/UserId distinction) + $config->setSecure(false); + $this->assertFalse($config->isSecure()); + + $config->setOrigin('custom.origin.com'); + $this->assertEquals('custom.origin.com', $config->getOrigin()); + } + + public function testDemoKeysCreatesNewInstanceEachTime(): void + { + $config1 = PNConfiguration::demoKeys(); + $config2 = PNConfiguration::demoKeys(); + + $this->assertNotSame($config1, $config2); + } + + public function testDemoKeysInstancesAreIndependent(): void + { + $config1 = PNConfiguration::demoKeys(); + $config2 = PNConfiguration::demoKeys(); + + $config1->setPublishKey('key1'); + $config2->setPublishKey('key2'); + + $this->assertEquals('key1', $config1->getPublishKey()); + $this->assertEquals('key2', $config2->getPublishKey()); + } + + public function testDemoKeysCanBeCustomized(): void + { + $config = PNConfiguration::demoKeys(); + + // Customize the demo configuration + $config->setSecure(false); + $config->setOrigin('custom-origin.pubnub.com'); + $config->setAuthKey('auth-key-123'); + + $this->assertFalse($config->isSecure()); + $this->assertEquals('custom-origin.pubnub.com', $config->getOrigin()); + $this->assertEquals('auth-key-123', $config->getAuthKey()); + } + + public function testDemoKeysCanBeUsedToCreatePubNub(): void + { + $config = PNConfiguration::demoKeys(); + $pubnub = new PubNub($config); + + $this->assertInstanceOf(PubNub::class, $pubnub); + + $retrievedConfig = $pubnub->getConfiguration(); + $this->assertEquals('demo', $retrievedConfig->getSubscribeKey()); + $this->assertEquals('demo', $retrievedConfig->getPublishKey()); + $this->assertEquals('demo', $retrievedConfig->getUserId()); + } + + public function testDemoKeysHasDefaultSecureSettings(): void + { + $config = PNConfiguration::demoKeys(); + + $this->assertTrue($config->isSecure()); + } + + public function testDemoKeysHasDefaultTimeouts(): void + { + $config = PNConfiguration::demoKeys(); + + $this->assertEquals(10, $config->getNonSubscribeRequestTimeout()); + $this->assertEquals(310, $config->getSubscribeTimeout()); + $this->assertEquals(10, $config->getConnectTimeout()); + } + + // ============================================================================ + // INTEGRATION TESTS + // ============================================================================ + + public function testDemoMethodUsesDemoKeysInternally(): void + { + $demoConfig = PNConfiguration::demoKeys(); + $demoPubNub = PubNub::demo(); + + $config = $demoPubNub->getConfiguration(); + + // Should have same values as demoKeys() + $this->assertEquals($demoConfig->getSubscribeKey(), $config->getSubscribeKey()); + $this->assertEquals($demoConfig->getPublishKey(), $config->getPublishKey()); + $this->assertEquals($demoConfig->getUserId(), $config->getUserId()); + } + + public function testDemoKeysAndDemoProduceSimilarResults(): void + { + $configFromDemoKeys = PNConfiguration::demoKeys(); + $pubnubFromDemo = PubNub::demo(); + $configFromDemo = $pubnubFromDemo->getConfiguration(); + + $this->assertEquals( + $configFromDemoKeys->getSubscribeKey(), + $configFromDemo->getSubscribeKey() + ); + $this->assertEquals( + $configFromDemoKeys->getPublishKey(), + $configFromDemo->getPublishKey() + ); + $this->assertEquals( + $configFromDemoKeys->getUserId(), + $configFromDemo->getUserId() + ); + } + + public function testDemoKeysCanBeCloned(): void + { + $config = PNConfiguration::demoKeys(); + $cloned = $config->clone(); + + $this->assertNotSame($config, $cloned); + $this->assertEquals('demo', $cloned->getSubscribeKey()); + $this->assertEquals('demo', $cloned->getPublishKey()); + $this->assertEquals('demo', $cloned->getUserId()); + } + + public function testMultipleDemoInstancesCanCoexist(): void + { + $pubnub1 = PubNub::demo(); + $pubnub2 = PubNub::demo(); + $pubnub3 = PubNub::demo(); + + $this->assertInstanceOf(PubNub::class, $pubnub1); + $this->assertInstanceOf(PubNub::class, $pubnub2); + $this->assertInstanceOf(PubNub::class, $pubnub3); + + $this->assertNotSame($pubnub1, $pubnub2); + $this->assertNotSame($pubnub2, $pubnub3); + $this->assertNotSame($pubnub1, $pubnub3); + } +} diff --git a/tests/unit/PubNubSdkInfoTest.php b/tests/unit/PubNubSdkInfoTest.php new file mode 100644 index 00000000..4426d70c --- /dev/null +++ b/tests/unit/PubNubSdkInfoTest.php @@ -0,0 +1,308 @@ +assertIsString($version); + $this->assertNotEmpty($version); + } + + public function testGetSdkVersionFollowsSemanticVersioning(): void + { + $version = PubNub::getSdkVersion(); + + // Should match semantic versioning pattern (e.g., 7.1.0, 7.1.0-beta.1, etc.) + $pattern = '/^\d+\.\d+\.\d+(-[a-zA-Z0-9\.\-]+)?$/'; + $this->assertMatchesRegularExpression($pattern, $version); + } + + public function testGetSdkVersionStartsWithDigit(): void + { + $version = PubNub::getSdkVersion(); + + $this->assertMatchesRegularExpression('/^\d/', $version); + } + + public function testGetSdkVersionContainsMajorMinorPatch(): void + { + $version = PubNub::getSdkVersion(); + + // Split by '.' and check we have at least 3 parts (major.minor.patch) + $parts = explode('.', $version); + $this->assertGreaterThanOrEqual(3, count($parts)); + } + + public function testGetSdkVersionMajorVersionIsNumeric(): void + { + $version = PubNub::getSdkVersion(); + $parts = explode('.', $version); + + $this->assertIsNumeric($parts[0]); + } + + public function testGetSdkVersionMinorVersionIsNumeric(): void + { + $version = PubNub::getSdkVersion(); + $parts = explode('.', $version); + + $this->assertIsNumeric($parts[1]); + } + + public function testGetSdkVersionPatchVersionIsNumeric(): void + { + $version = PubNub::getSdkVersion(); + $parts = explode('.', $version); + + // Patch might have a pre-release suffix (e.g., 0-beta) + // So we extract just the numeric part + preg_match('/^(\d+)/', $parts[2], $matches); + $this->assertIsNumeric($matches[1]); + } + + // ============================================================================ + // getSdkName() TESTS + // ============================================================================ + + public function testGetSdkNameReturnsString(): void + { + $name = PubNub::getSdkName(); + + $this->assertIsString($name); + } + + public function testGetSdkNameIsNotEmpty(): void + { + $name = PubNub::getSdkName(); + + $this->assertNotEmpty($name); + } + + public function testGetSdkNameIsConsistent(): void + { + $name1 = PubNub::getSdkName(); + $name2 = PubNub::getSdkName(); + + $this->assertEquals($name1, $name2); + } + + public function testGetSdkNameContainsPHP(): void + { + $name = PubNub::getSdkName(); + + // SDK name should indicate it's a PHP SDK + $this->assertMatchesRegularExpression('/php/i', $name); + } + + public function testGetSdkNameContainsPubNub(): void + { + $name = PubNub::getSdkName(); + + // SDK name should contain "PubNub" + $this->assertMatchesRegularExpression('/pubnub/i', $name); + } + + public function testGetSdkNameFormat(): void + { + $name = PubNub::getSdkName(); + + // Should be in format like "PubNub-PHP" or similar + $this->assertMatchesRegularExpression('/^[a-zA-Z\-]+$/', $name); + } + + public function testGetSdkNameDoesNotContainVersion(): void + { + $name = PubNub::getSdkName(); + + // Name should not contain version numbers + $this->assertDoesNotMatchRegularExpression('/\d+\.\d+/', $name); + } + + // ============================================================================ + // getSdkFullName() TESTS + // ============================================================================ + + public function testGetSdkFullNameReturnsString(): void + { + $fullName = PubNub::getSdkFullName(); + + $this->assertIsString($fullName); + } + + public function testGetSdkFullNameIsNotEmpty(): void + { + $fullName = PubNub::getSdkFullName(); + + $this->assertNotEmpty($fullName); + } + + public function testGetSdkFullNameIsConsistent(): void + { + $fullName1 = PubNub::getSdkFullName(); + $fullName2 = PubNub::getSdkFullName(); + + $this->assertEquals($fullName1, $fullName2); + } + + public function testGetSdkFullNameContainsSdkName(): void + { + $name = PubNub::getSdkName(); + $fullName = PubNub::getSdkFullName(); + + $this->assertStringContainsString($name, $fullName); + } + + public function testGetSdkFullNameContainsSdkVersion(): void + { + $version = PubNub::getSdkVersion(); + $fullName = PubNub::getSdkFullName(); + + $this->assertStringContainsString($version, $fullName); + } + + public function testGetSdkFullNameFormat(): void + { + $fullName = PubNub::getSdkFullName(); + + // Should be in format like "PubNub-PHP/7.1.0" or "PubNub-PHP-7.1.0" + $pattern = '/^[a-zA-Z\-]+[\/\-]\d+\.\d+\.\d+/'; + $this->assertMatchesRegularExpression($pattern, $fullName); + } + + public function testGetSdkFullNameIsConcatenationOfNameAndVersion(): void + { + $name = PubNub::getSdkName(); + $version = PubNub::getSdkVersion(); + $fullName = PubNub::getSdkFullName(); + + // Full name should be name + separator + version + $expectedPattern = '/' . preg_quote($name, '/') . '[\/\-]' . preg_quote($version, '/') . '/'; + $this->assertMatchesRegularExpression($expectedPattern, $fullName); + } + + public function testGetSdkFullNameLongerThanName(): void + { + $name = PubNub::getSdkName(); + $fullName = PubNub::getSdkFullName(); + + $this->assertGreaterThan(strlen($name), strlen($fullName)); + } + + public function testGetSdkFullNameLongerThanVersion(): void + { + $version = PubNub::getSdkVersion(); + $fullName = PubNub::getSdkFullName(); + + $this->assertGreaterThan(strlen($version), strlen($fullName)); + } + + // ============================================================================ + // INTEGRATION TESTS + // ============================================================================ + + public function testSdkInfoMethodsAreAllConsistent(): void + { + $name = PubNub::getSdkName(); + $version = PubNub::getSdkVersion(); + $fullName = PubNub::getSdkFullName(); + + $this->assertStringContainsString($name, $fullName); + $this->assertStringContainsString($version, $fullName); + } + + public function testSdkInfoMethodsCanBeCalledMultipleTimes(): void + { + // Call each method multiple times + for ($i = 0; $i < 5; $i++) { + $this->assertIsString(PubNub::getSdkName()); + $this->assertIsString(PubNub::getSdkVersion()); + $this->assertIsString(PubNub::getSdkFullName()); + } + } + + public function testSdkInfoMethodsReturnSameValuesAcrossInstances(): void + { + $pubnub1 = PubNub::demo(); + $pubnub2 = PubNub::demo(); + + // Static methods should return same values regardless of instance + $this->assertEquals(PubNub::getSdkName(), PubNub::getSdkName()); + $this->assertEquals(PubNub::getSdkVersion(), PubNub::getSdkVersion()); + $this->assertEquals(PubNub::getSdkFullName(), PubNub::getSdkFullName()); + } + + public function testSdkVersionCanBeParsed(): void + { + $version = PubNub::getSdkVersion(); + + // Should be parsable as a version string + $parts = explode('.', $version); + + $this->assertGreaterThanOrEqual(3, count($parts)); + $this->assertIsNumeric($parts[0]); // Major + $this->assertIsNumeric($parts[1]); // Minor + + // Patch might have pre-release suffix, extract numeric part + preg_match('/^(\d+)/', $parts[2], $matches); + $this->assertNotEmpty($matches); + $this->assertIsNumeric($matches[1]); // Patch + } + + public function testSdkFullNameIsUsableForUserAgent(): void + { + $fullName = PubNub::getSdkFullName(); + + // Should be a valid format for User-Agent headers (no spaces, special chars) + $this->assertMatchesRegularExpression('/^[a-zA-Z0-9\-\.\/]+$/', $fullName); + } + + public function testSdkNameIsUsableAsIdentifier(): void + { + $name = PubNub::getSdkName(); + + // Should be a valid identifier (no spaces, no version numbers) + $this->assertMatchesRegularExpression('/^[a-zA-Z\-]+$/', $name); + } + + public function testSdkVersionIsValidSemanticVersion(): void + { + $version = PubNub::getSdkVersion(); + + // Validate against semantic versioning 2.0.0 spec + $semverPattern = '/^' + . '(\d+)\.(\d+)\.(\d+)' // Major.Minor.Patch + . '(-[0-9A-Za-z\-\.]+)?' // Pre-release (optional) + . '(\+[0-9A-Za-z\-\.]+)?' // Build metadata (optional) + . '$/'; + + $this->assertMatchesRegularExpression($semverPattern, $version); + } + + public function testSdkInfoDoesNotChangeAtRuntime(): void + { + // Capture initial values + $name1 = PubNub::getSdkName(); + $version1 = PubNub::getSdkVersion(); + $fullName1 = PubNub::getSdkFullName(); + + // Create some PubNub instances + $pubnub1 = PubNub::demo(); + $pubnub2 = PubNub::demo(); + + // Values should remain the same + $this->assertEquals($name1, PubNub::getSdkName()); + $this->assertEquals($version1, PubNub::getSdkVersion()); + $this->assertEquals($fullName1, PubNub::getSdkFullName()); + } +} diff --git a/tests/unit/PubNubUtilExtendedTest.php b/tests/unit/PubNubUtilExtendedTest.php new file mode 100644 index 00000000..2e4a6c7e --- /dev/null +++ b/tests/unit/PubNubUtilExtendedTest.php @@ -0,0 +1,587 @@ + 'test-user', 'pnsdk' => 'PubNub-PHP/8.0.0']; + + $url = PubNubUtil::buildUrl($basePath, $path, $params); + + $this->assertStringStartsWith($basePath . $path, $url); + $this->assertStringContainsString('uuid=test-user', $url); + $this->assertStringContainsString('pnsdk=PubNub-PHP/8.0.0', $url); + } + + public function testBuildUrlWithEmptyParams(): void + { + $basePath = 'https://ps.pndsn.com'; + $path = '/v2/time/0'; + $params = []; + + $url = PubNubUtil::buildUrl($basePath, $path, $params); + + $this->assertEquals($basePath . $path . '?', $url); + } + + public function testBuildUrlWithSpecialCharactersInParams(): void + { + $basePath = 'https://ps.pndsn.com'; + $path = '/publish'; + $params = ['message' => 'hello%20world']; + + $url = PubNubUtil::buildUrl($basePath, $path, $params); + + $this->assertStringContainsString('message=hello%20world', $url); + } + + public function testBuildUrlWithMultipleParams(): void + { + $basePath = 'https://ps.pndsn.com'; + $path = '/v2/history'; + $params = [ + 'channel' => 'test-channel', + 'count' => '100', + 'reverse' => 'false' + ]; + + $url = PubNubUtil::buildUrl($basePath, $path, $params); + + $this->assertStringContainsString('channel=test-channel', $url); + $this->assertStringContainsString('count=100', $url); + $this->assertStringContainsString('reverse=false', $url); + } + + // ============================================================================ + // joinChannels() TESTS + // ============================================================================ + + public function testJoinChannelsWithSingleChannel(): void + { + $channels = ['channel1']; + + $result = PubNubUtil::joinChannels($channels); + + $this->assertEquals('channel1', $result); + } + + public function testJoinChannelsWithMultipleChannels(): void + { + $channels = ['channel1', 'channel2', 'channel3']; + + $result = PubNubUtil::joinChannels($channels); + + $this->assertEquals('channel1,channel2,channel3', $result); + } + + public function testJoinChannelsWithEmptyArray(): void + { + $channels = []; + + $result = PubNubUtil::joinChannels($channels); + + $this->assertEquals(',', $result); + } + + public function testJoinChannelsWithSpecialCharacters(): void + { + $channels = ['channel-1', 'channel.2', 'channel_3']; + + $result = PubNubUtil::joinChannels($channels); + + $this->assertEquals('channel-1,channel.2,channel_3', $result); + } + + public function testJoinChannelsEncodesSpecialCharacters(): void + { + $channels = ['channel with spaces', 'channel#special']; + + $result = PubNubUtil::joinChannels($channels); + + $this->assertStringContainsString('channel+with+spaces', $result); + $this->assertStringContainsString('channel%23special', $result); + } + + // ============================================================================ + // joinItems() TESTS + // ============================================================================ + + public function testJoinItemsWithSingleItem(): void + { + $items = ['item1']; + + $result = PubNubUtil::joinItems($items); + + $this->assertEquals('item1', $result); + } + + public function testJoinItemsWithMultipleItems(): void + { + $items = ['item1', 'item2', 'item3']; + + $result = PubNubUtil::joinItems($items); + + $this->assertEquals('item1,item2,item3', $result); + } + + public function testJoinItemsWithEmptyArray(): void + { + $items = []; + + $result = PubNubUtil::joinItems($items); + + $this->assertEquals('', $result); + } + + public function testJoinItemsWithNumericItems(): void + { + $items = ['1', '2', '3']; + + $result = PubNubUtil::joinItems($items); + + $this->assertEquals('1,2,3', $result); + } + + // ============================================================================ + // extendArray() TESTS - Already used in tests but testing edge cases + // ============================================================================ + + public function testExtendArrayWithArrays(): void + { + $existing = ['a', 'b']; + $new = ['c', 'd']; + + $result = PubNubUtil::extendArray($existing, $new); + + $this->assertEquals(['a', 'b', 'c', 'd'], $result); + } + + public function testExtendArrayWithString(): void + { + $existing = ['a', 'b']; + $new = 'c,d'; + + $result = PubNubUtil::extendArray($existing, $new); + + $this->assertEquals(['a', 'b', 'c', 'd'], $result); + } + + public function testExtendArrayWithEmptyExisting(): void + { + $existing = []; + $new = ['a', 'b']; + + $result = PubNubUtil::extendArray($existing, $new); + + $this->assertEquals(['a', 'b'], $result); + } + + public function testExtendArrayWithEmptyNew(): void + { + $existing = ['a', 'b']; + $new = []; + + $result = PubNubUtil::extendArray($existing, $new); + + $this->assertEquals(['a', 'b'], $result); + } + + public function testExtendArrayWithEmptyString(): void + { + $existing = ['a', 'b']; + $new = ''; + + $result = PubNubUtil::extendArray($existing, $new); + + $this->assertEquals(['a', 'b'], $result); + } + + // ============================================================================ + // splitItems() TESTS + // ============================================================================ + + public function testSplitItemsWithSingleItem(): void + { + $items = 'item1'; + + $result = PubNubUtil::splitItems($items); + + $this->assertEquals(['item1'], $result); + } + + public function testSplitItemsWithMultipleItems(): void + { + $items = 'item1,item2,item3'; + + $result = PubNubUtil::splitItems($items); + + $this->assertEquals(['item1', 'item2', 'item3'], $result); + } + + public function testSplitItemsWithEmptyString(): void + { + $items = ''; + + $result = PubNubUtil::splitItems($items); + + $this->assertEquals([], $result); + } + + public function testSplitItemsPreservesSpaces(): void + { + $items = 'item 1,item 2'; + + $result = PubNubUtil::splitItems($items); + + $this->assertEquals(['item 1', 'item 2'], $result); + } + + public function testSplitItemsWithTrailingComma(): void + { + $items = 'item1,item2,'; + + $result = PubNubUtil::splitItems($items); + + $this->assertEquals(['item1', 'item2', ''], $result); + } + + // ============================================================================ + // uuid() TESTS + // ============================================================================ + + public function testUuidReturnsString(): void + { + $uuid = PubNubUtil::uuid(); + + $this->assertIsString($uuid); + } + + public function testUuidHasCorrectFormat(): void + { + $uuid = PubNubUtil::uuid(); + + // UUID format: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX + $this->assertMatchesRegularExpression( + '/^[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$/i', + $uuid + ); + } + + public function testUuidIsUnique(): void + { + $uuid1 = PubNubUtil::uuid(); + $uuid2 = PubNubUtil::uuid(); + + $this->assertNotEquals($uuid1, $uuid2); + } + + public function testUuidGeneratesMultipleUniqueValues(): void + { + $uuids = []; + for ($i = 0; $i < 100; $i++) { + $uuids[] = PubNubUtil::uuid(); + } + + // All UUIDs should be unique + $this->assertEquals(100, count(array_unique($uuids))); + } + + public function testUuidLength(): void + { + $uuid = PubNubUtil::uuid(); + + // UUID with dashes is 36 characters + $this->assertEquals(36, strlen($uuid)); + } + + // ============================================================================ + // fetchPamPermissionsFrom() TESTS + // ============================================================================ + + public function testFetchPamPermissionsFromWithAllPermissions(): void + { + $input = [ + 'r' => 1, + 'w' => 1, + 'm' => 1, + 'd' => 1, + 'g' => 1, + 'u' => 1, + 'j' => 1, + 'ttl' => 1440 + ]; + + $result = PubNubUtil::fetchPamPermissionsFrom($input); + + $this->assertEquals([true, true, true, true, true, true, true, 1440], $result); + } + + public function testFetchPamPermissionsFromWithNoPermissions(): void + { + $input = [ + 'r' => 0, + 'w' => 0, + 'm' => 0, + 'd' => 0, + 'g' => 0, + 'u' => 0, + 'j' => 0, + 'ttl' => 0 + ]; + + $result = PubNubUtil::fetchPamPermissionsFrom($input); + + $this->assertEquals([false, false, false, false, false, false, false, 0], $result); + } + + public function testFetchPamPermissionsFromWithPartialPermissions(): void + { + $input = [ + 'r' => 1, + 'w' => 0, + 'm' => 1, + 'ttl' => 60 + ]; + + $result = PubNubUtil::fetchPamPermissionsFrom($input); + + $this->assertEquals([true, false, true, null, null, null, null, 60], $result); + } + + public function testFetchPamPermissionsFromWithEmptyInput(): void + { + $input = []; + + $result = PubNubUtil::fetchPamPermissionsFrom($input); + + $this->assertEquals([null, null, null, null, null, null, null, null], $result); + } + + public function testFetchPamPermissionsFromWithOnlyTTL(): void + { + $input = ['ttl' => 120]; + + $result = PubNubUtil::fetchPamPermissionsFrom($input); + + $this->assertEquals([null, null, null, null, null, null, null, 120], $result); + } + + // ============================================================================ + // isAssoc() TESTS + // ============================================================================ + + public function testIsAssocWithIndexedArray(): void + { + $array = ['a', 'b', 'c']; + + $result = PubNubUtil::isAssoc($array); + + $this->assertFalse($result); + } + + public function testIsAssocWithAssociativeArray(): void + { + $array = ['key1' => 'value1', 'key2' => 'value2']; + + $result = PubNubUtil::isAssoc($array); + + $this->assertTrue($result); + } + + public function testIsAssocWithNumericKeys(): void + { + $array = [0 => 'a', 1 => 'b', 2 => 'c']; + + $result = PubNubUtil::isAssoc($array); + + $this->assertFalse($result); + } + + public function testIsAssocWithMixedKeys(): void + { + $array = [0 => 'a', 'key' => 'b', 2 => 'c']; + + $result = PubNubUtil::isAssoc($array); + + $this->assertTrue($result); + } + + public function testIsAssocWithEmptyArray(): void + { + $array = []; + + $result = PubNubUtil::isAssoc($array); + + // Empty array returns true because array_keys([]) !== range(0, count([]) - 1) + // array_keys([]) = [], range(0, -1) = [] + // But the comparison returns true (not equal) + $this->assertTrue($result); + } + + public function testIsAssocWithNonArray(): void + { + $result = PubNubUtil::isAssoc('not an array'); + + $this->assertFalse($result); + } + + public function testIsAssocWithNonSequentialKeys(): void + { + $array = [1 => 'a', 3 => 'b', 5 => 'c']; + + $result = PubNubUtil::isAssoc($array); + + $this->assertTrue($result); + } + + // ============================================================================ + // tokenEncode() TESTS + // ============================================================================ + + public function testTokenEncodeWithBasicString(): void + { + $token = 'mytoken123'; + + $result = PubNubUtil::tokenEncode($token); + + $this->assertEquals('mytoken123', $result); + } + + public function testTokenEncodeConvertsSpacesToPercent20(): void + { + $token = 'token with spaces'; + + $result = PubNubUtil::tokenEncode($token); + + $this->assertStringContainsString('%20', $result); + $this->assertStringNotContainsString('+', $result); + } + + public function testTokenEncodeWithSpecialCharacters(): void + { + $token = 'token!@#$%'; + + $result = PubNubUtil::tokenEncode($token); + + $this->assertIsString($result); + } + + public function testTokenEncodeWithPlusSign(): void + { + $token = 'token+with+plus'; + + $result = PubNubUtil::tokenEncode($token); + + // Plus signs are first URL encoded to %2B, then the str_replace doesn't affect them + // because str_replace looks for literal '+' which is already encoded + $this->assertStringContainsString('%2B', $result); + } + + // ============================================================================ + // convertIso8859ToUtf8() TESTS + // ============================================================================ + + public function testConvertIso8859ToUtf8WithAscii(): void + { + $input = 'Hello World'; + + $result = PubNubUtil::convertIso8859ToUtf8($input); + + $this->assertEquals('Hello World', $result); + } + + public function testConvertIso8859ToUtf8WithExtendedCharacters(): void + { + // Test with some ISO-8859-1 characters + $input = chr(0xA9); // Copyright symbol in ISO-8859-1 + + $result = PubNubUtil::convertIso8859ToUtf8($input); + + $this->assertNotEmpty($result); + $this->assertIsString($result); + } + + public function testConvertIso8859ToUtf8WithEmptyString(): void + { + $input = ''; + + $result = PubNubUtil::convertIso8859ToUtf8($input); + + $this->assertEquals('', $result); + } + + public function testConvertIso8859ToUtf8PreservesAsciiCharacters(): void + { + $input = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + + $result = PubNubUtil::convertIso8859ToUtf8($input); + + $this->assertEquals($input, $result); + } + + public function testConvertIso8859ToUtf8WithNumbers(): void + { + $input = '1234567890'; + + $result = PubNubUtil::convertIso8859ToUtf8($input); + + $this->assertEquals($input, $result); + } + + // ============================================================================ + // INTEGRATION TESTS + // ============================================================================ + + public function testChannelWorkflow(): void + { + // Split channels from string + $channelString = 'channel1,channel2,channel3'; + $channels = PubNubUtil::splitItems($channelString); + + $this->assertCount(3, $channels); + + // Join channels back + $joined = PubNubUtil::joinChannels($channels); + + $this->assertEquals('channel1,channel2,channel3', $joined); + } + + public function testArrayExtensionWorkflow(): void + { + $existing = ['channel1', 'channel2']; + $newString = 'channel3,channel4'; + + $extended = PubNubUtil::extendArray($existing, $newString); + + $this->assertCount(4, $extended); + $this->assertEquals(['channel1', 'channel2', 'channel3', 'channel4'], $extended); + } + + public function testUrlBuildingWorkflow(): void + { + $basePath = 'https://ps.pndsn.com'; + $path = '/v2/subscribe/demo/my-channel/0'; + $params = [ + 'uuid' => 'user-123', + 'tt' => '0', + 'pnsdk' => 'PubNub-PHP/8.0.0' + ]; + + $url = PubNubUtil::buildUrl($basePath, $path, $params); + + $this->assertStringStartsWith('https://ps.pndsn.com', $url); + $this->assertStringContainsString('uuid=user-123', $url); + $this->assertStringContainsString('&', $url); + } +} diff --git a/tests/unit/PubNubUtilityMethodsTest.php b/tests/unit/PubNubUtilityMethodsTest.php new file mode 100644 index 00000000..34d07a63 --- /dev/null +++ b/tests/unit/PubNubUtilityMethodsTest.php @@ -0,0 +1,326 @@ +config = new PNConfiguration(); + $this->config->setSubscribeKey('demo'); + $this->config->setPublishKey('demo'); + $this->config->setUserId('test-user'); + + $this->pubnub = new PubNub($this->config); + } + + // ============================================================================ + // TOKEN METHODS TESTS + // ============================================================================ + + public function testGetTokenReturnsNullByDefault(): void + { + $token = $this->pubnub->getToken(); + + $this->assertNull($token); + } + + public function testSetTokenAndGetToken(): void + { + $testToken = 'test-token-abc123'; + + $this->pubnub->setToken($testToken); + + $this->assertEquals($testToken, $this->pubnub->getToken()); + } + + public function testSetTokenOverwritesPreviousToken(): void + { + $this->pubnub->setToken('first-token'); + $this->assertEquals('first-token', $this->pubnub->getToken()); + + $this->pubnub->setToken('second-token'); + $this->assertEquals('second-token', $this->pubnub->getToken()); + } + + public function testSetTokenWithEmptyString(): void + { + $this->pubnub->setToken(''); + + $this->assertEquals('', $this->pubnub->getToken()); + } + + //phpcs:disable + public function testSetTokenWithLongToken(): void + { + $longToken = 'qEF2AkF0GmFtet9DdHRsGDxDcmVzpURjaGFuoWpteS1jaGFubmVsGENDZ3JwoEN1c3KgQ3NwY6BEdXVpZKBDcGF0pURjaGFuoENnc' . + 'nCgQ3VzcqBDc3BjoER1dWlkoERtZXRhoER1dWlkZ215LXV1aWRDc2lnWCAvUKKYbfc0vvvEhYqepG7-_lN5jh_yaA6eo98nAHV8Ug=='; + + $this->pubnub->setToken($longToken); + + $this->assertEquals($longToken, $this->pubnub->getToken()); + } + //phpcs:enable + + public function testSetTokenPersistsAcrossMultipleCalls(): void + { + $token = 'persistent-token'; + $this->pubnub->setToken($token); + + // Call getToken multiple times + $this->assertEquals($token, $this->pubnub->getToken()); + $this->assertEquals($token, $this->pubnub->getToken()); + $this->assertEquals($token, $this->pubnub->getToken()); + } + + // ============================================================================ + // TIMESTAMP METHOD TESTS + // ============================================================================ + + public function testTimestampReturnsCurrentTime(): void + { + $before = time(); + $timestamp = $this->pubnub->timestamp(); + $after = time(); + + $this->assertIsInt($timestamp); + // Timestamp should be between before and after + $this->assertGreaterThanOrEqual($before, $timestamp); + $this->assertLessThanOrEqual($after, $timestamp); + } + + public function testTimestampReturnsUnixTimestamp(): void + { + $timestamp = $this->pubnub->timestamp(); + + // Should be a reasonable Unix timestamp (after year 2020) + $this->assertGreaterThan(1577836800, $timestamp); // Jan 1, 2020 + + // Should be before year 2100 + $this->assertLessThan(4102444800, $timestamp); // Jan 1, 2100 + } + + public function testTimestampChangesOverTime(): void + { + $timestamp1 = $this->pubnub->timestamp(); + usleep(1100000); // Sleep for slightly over 1 second + $timestamp2 = $this->pubnub->timestamp(); + + $this->assertGreaterThan($timestamp1, $timestamp2); + } + + // ============================================================================ + // SEQUENCE ID TESTS + // ============================================================================ + + public function testGetSequenceIdReturnsInteger(): void + { + $sequenceId = $this->pubnub->getSequenceId(); + + $this->assertIsInt($sequenceId); + } + + public function testGetSequenceIdStartsAtOne(): void + { + $config = new PNConfiguration(); + $config->setSubscribeKey('demo'); + $config->setPublishKey('demo'); + $config->setUserId('test-user'); + $pubnub = new PubNub($config); + + $sequenceId = $pubnub->getSequenceId(); + + $this->assertEquals(1, $sequenceId); + } + + public function testGetSequenceIdIncrementsOnEachCall(): void + { + $id1 = $this->pubnub->getSequenceId(); + $id2 = $this->pubnub->getSequenceId(); + $id3 = $this->pubnub->getSequenceId(); + + $this->assertEquals($id1 + 1, $id2); + $this->assertEquals($id2 + 1, $id3); + } + + public function testGetSequenceIdWrapsAtMaxSequence(): void + { + // Get sequence to near max + for ($i = 0; $i < PubNub::$MAX_SEQUENCE; $i++) { + $this->pubnub->getSequenceId(); + } + + // Next call should wrap to 1 + $sequenceId = $this->pubnub->getSequenceId(); + + $this->assertEquals(1, $sequenceId); + } + + public function testGetSequenceIdIsUnique(): void + { + $ids = []; + for ($i = 0; $i < 100; $i++) { + $ids[] = $this->pubnub->getSequenceId(); + } + + // All IDs should be unique + $this->assertEquals(100, count(array_unique($ids))); + } + + // ============================================================================ + // CONFIGURATION GETTER TESTS + // ============================================================================ + + public function testGetConfigurationReturnsConfiguration(): void + { + $config = $this->pubnub->getConfiguration(); + + $this->assertInstanceOf(PNConfiguration::class, $config); + } + + public function testGetConfigurationReturnsSameConfiguration(): void + { + $config1 = $this->pubnub->getConfiguration(); + $config2 = $this->pubnub->getConfiguration(); + + $this->assertSame($config1, $config2); + } + + public function testGetConfigurationReturnsCorrectValues(): void + { + $config = $this->pubnub->getConfiguration(); + + $this->assertEquals('demo', $config->getSubscribeKey()); + $this->assertEquals('demo', $config->getPublishKey()); + $this->assertEquals('test-user', $config->getUserId()); + } + + // ============================================================================ + // BASE PATH TESTS + // ============================================================================ + + public function testGetBasePathReturnsString(): void + { + $basePath = $this->pubnub->getBasePath(); + + $this->assertIsString($basePath); + } + + public function testGetBasePathReturnsValidUrl(): void + { + $basePath = $this->pubnub->getBasePath(); + + $this->assertStringStartsWith('http', $basePath); + } + + public function testGetBasePathWithCustomHost(): void + { + $basePath = $this->pubnub->getBasePath('custom.pubnub.com'); + + $this->assertEquals('https://custom.pubnub.com', $basePath); + } + + public function testGetBasePathUsesConfigurationOrigin(): void + { + $config = new PNConfiguration(); + $config->setSubscribeKey('demo'); + $config->setUserId('test'); + $config->setOrigin('my-origin.pubnub.com'); + + $pubnub = new PubNub($config); + $basePath = $pubnub->getBasePath(); + + $this->assertEquals('https://my-origin.pubnub.com', $basePath); + } + + // ============================================================================ + // HTTP CLIENT TESTS + // ============================================================================ + + public function testSetClientAndGetClient(): void + { + $mockClient = $this->createMock(\Psr\Http\Client\ClientInterface::class); + + $this->pubnub->setClient($mockClient); + + $this->assertSame($mockClient, $this->pubnub->getClient()); + } + + public function testGetClientReturnsSameInstanceByDefault(): void + { + $client1 = $this->pubnub->getClient(); + $client2 = $this->pubnub->getClient(); + + $this->assertSame($client1, $client2); + } + + // ============================================================================ + // REQUEST FACTORY TESTS + // ============================================================================ + + public function testSetRequestFactoryAndGetRequestFactory(): void + { + $mockFactory = $this->createMock(\Psr\Http\Message\RequestFactoryInterface::class); + + $this->pubnub->setRequestFactory($mockFactory); + + $this->assertSame($mockFactory, $this->pubnub->getRequestFactory()); + } + + public function testGetRequestFactoryReturnsSameInstanceByDefault(): void + { + $factory1 = $this->pubnub->getRequestFactory(); + $factory2 = $this->pubnub->getRequestFactory(); + + $this->assertSame($factory1, $factory2); + } + + // ============================================================================ + // LOGGER TESTS + // ============================================================================ + + public function testGetLoggerReturnsLoggerInterface(): void + { + $logger = $this->pubnub->getLogger(); + + $this->assertInstanceOf(LoggerInterface::class, $logger); + } + + public function testSetLoggerAndGetLogger(): void + { + $mockLogger = $this->createMock(LoggerInterface::class); + + $this->pubnub->setLogger($mockLogger); + + $this->assertSame($mockLogger, $this->pubnub->getLogger()); + } + + public function testGetLoggerReturnsSameInstanceByDefault(): void + { + $logger1 = $this->pubnub->getLogger(); + $logger2 = $this->pubnub->getLogger(); + + $this->assertSame($logger1, $logger2); + } + + public function testSetLoggerReplacesDefaultLogger(): void + { + $defaultLogger = $this->pubnub->getLogger(); + $this->assertInstanceOf(\Psr\Log\NullLogger::class, $defaultLogger); + + $customLogger = $this->createMock(LoggerInterface::class); + $this->pubnub->setLogger($customLogger); + + $this->assertNotSame($defaultLogger, $this->pubnub->getLogger()); + $this->assertSame($customLogger, $this->pubnub->getLogger()); + } +} diff --git a/tests/unit/PublishTest.php b/tests/unit/PublishTest.php index a5897aae..8c4d15bc 100644 --- a/tests/unit/PublishTest.php +++ b/tests/unit/PublishTest.php @@ -1,16 +1,17 @@ assertEquals(1, $pubnub->getSequenceId()); diff --git a/tests/unit/TelemetryManagerTest.php b/tests/unit/TelemetryManagerTest.php index 851a59ac..1fbe57c4 100644 --- a/tests/unit/TelemetryManagerTest.php +++ b/tests/unit/TelemetryManagerTest.php @@ -1,13 +1,14 @@ 100, "l" => 10], @@ -22,7 +23,7 @@ public function testAverageLatency() $this->assertEquals(30, $averageLatency); } - public function testValidQueries() + public function testValidQueries(): void { $manager = new TelemetryManager(); diff --git a/tests/unit/UtilsTest.php b/tests/unit/UtilsTest.php index 6d228a69..8b7f81f2 100644 --- a/tests/unit/UtilsTest.php +++ b/tests/unit/UtilsTest.php @@ -1,31 +1,34 @@ assertEquals('blah%2Bnjkl', PubNubUtil::urlEncode("blah+njkl")); $this->assertEquals('%7B%22value%22%3A%20%222%22%7D', PubNubUtil::urlEncode("{\"value\": \"2\"}")); } - public function testWriteValueAsString() + public function testWriteValueAsString(): void { + //phpcs:disable $this->expectException(PubNubBuildRequestException::class); $this->expectExceptionMessage("Value serialization error: Malformed UTF-8 characters, possibly incorrectly encoded"); PubNubUtil::writeValueAsString(["key" => "\xB1\x31"]); + //phpcs:enable } - public function testPamEncode() + public function testPamEncode(): void { $params = [ 'abc' => true, @@ -37,19 +40,7 @@ public function testPamEncode() self::assertEquals("abc=true&def=false&poq=4", $result); } - public function testSignSha256() - { - $signInput = "sub-c-7ba2ac4c-4836-11e6-85a4-0619f8945a4f -pub-c-98863562-19a6-4760-bf0b-d537d1f5c582 -grant -channel=asyncio-pam-FI2FCS0A&pnsdk=PubNub-Python-Asyncio%252F4.0.0&r=1×tamp=1468409553&uuid=a4dbf92e-e5cb-428f-b6e6-35cce03500a2&w=1"; - - $result = PubNubUtil::signSha256("my_key", $signInput); - - self::assertEquals("Dq92jnwRTCikdeP2nUs1__gyJthF8NChwbs5aYy2r_I=", $result); - } - - public function testJoinQuery() + public function testJoinQuery(): void { $elements = [ 'a' => '2',