From 9322b75a709731fa5accfaa45f7fc060e1cb2577 Mon Sep 17 00:00:00 2001 From: Aditya Agarwal Date: Fri, 20 Mar 2026 17:13:14 +0100 Subject: [PATCH 1/2] update sdk --- src/Generated/ChatTrait.php | 62 ++++ src/Generated/VideoTrait.php | 14 + .../AIImageLabelDefinition.php | 18 + src/GeneratedModels/AWSRekognitionRule.php | 2 +- src/GeneratedModels/AppResponseFields.php | 1 + src/GeneratedModels/CallEndedEvent.php | 3 + src/GeneratedModels/ChannelConfig.php | 1 + src/GeneratedModels/ChannelConfigWithInfo.php | 1 + .../ChannelPushPreferencesResponse.php | 1 + src/GeneratedModels/ChannelTypeConfig.php | 1 + src/GeneratedModels/ChatPreferences.php | 22 ++ src/GeneratedModels/ChatPreferencesInput.php | 21 ++ .../ChatPreferencesResponse.php | 21 ++ src/GeneratedModels/CheckResponse.php | 1 + .../ConfigOverridesRequest.php | 2 + src/GeneratedModels/ConfigResponse.php | 3 + .../CreateChannelTypeRequest.php | 1 + .../CreateChannelTypeResponse.php | 1 + src/GeneratedModels/CreateSIPTrunkRequest.php | 2 + .../DeleteRetentionPolicyRequest.php | 15 + .../DeleteRetentionPolicyResponse.php | 18 + src/GeneratedModels/FilterConfigResponse.php | 1 + .../GetChannelTypeResponse.php | 1 + .../GetRetentionPolicyResponse.php | 21 ++ .../GetRetentionPolicyRunsResponse.php | 21 ++ src/GeneratedModels/PolicyConfig.php | 15 + src/GeneratedModels/PushPreferenceInput.php | 1 + .../PushPreferencesResponse.php | 1 + .../QueryModerationRulesResponse.php | 4 + src/GeneratedModels/ResolveSipAuthRequest.php | 21 ++ .../ResolveSipAuthResponse.php | 22 ++ .../ResolveSipInboundRequest.php | 1 + src/GeneratedModels/RetentionCleanupRun.php | 24 ++ src/GeneratedModels/RetentionPolicy.php | 18 + src/GeneratedModels/RuleBuilderAction.php | 1 + src/GeneratedModels/RunStats.php | 16 + src/GeneratedModels/SIPTrunkResponse.php | 1 + .../SetRetentionPolicyRequest.php | 16 + .../SetRetentionPolicyResponse.php | 19 + src/GeneratedModels/TriggeredRuleResponse.php | 19 + src/GeneratedModels/UpdateAppRequest.php | 1 + .../UpdateChannelTypeRequest.php | 1 + .../UpdateChannelTypeResponse.php | 1 + src/GeneratedModels/UpdateSIPTrunkRequest.php | 2 + .../ChatChannelIntegrationTest.php | 47 +-- .../ChatMessageIntegrationTest.php | 198 ++++++++--- tests/Integration/ChatMiscIntegrationTest.php | 161 ++++++--- .../ChatModerationIntegrationTest.php | 27 +- .../Integration/ChatPollsIntegrationTest.php | 71 ++-- .../ChatReactionIntegrationTest.php | 39 +- tests/Integration/ChatTestCase.php | 148 ++++---- tests/Integration/ChatUserIntegrationTest.php | 25 +- .../FeedBookmarkFollowIntegrationTest.php | 1 - tests/Integration/VideoIntegrationTest.php | 334 ++++++++++-------- tests/WebhookTest.php | 109 ++++-- 55 files changed, 1144 insertions(+), 455 deletions(-) create mode 100644 src/GeneratedModels/AIImageLabelDefinition.php create mode 100644 src/GeneratedModels/ChatPreferences.php create mode 100644 src/GeneratedModels/ChatPreferencesInput.php create mode 100644 src/GeneratedModels/ChatPreferencesResponse.php create mode 100644 src/GeneratedModels/DeleteRetentionPolicyRequest.php create mode 100644 src/GeneratedModels/DeleteRetentionPolicyResponse.php create mode 100644 src/GeneratedModels/GetRetentionPolicyResponse.php create mode 100644 src/GeneratedModels/GetRetentionPolicyRunsResponse.php create mode 100644 src/GeneratedModels/PolicyConfig.php create mode 100644 src/GeneratedModels/ResolveSipAuthRequest.php create mode 100644 src/GeneratedModels/ResolveSipAuthResponse.php create mode 100644 src/GeneratedModels/RetentionCleanupRun.php create mode 100644 src/GeneratedModels/RetentionPolicy.php create mode 100644 src/GeneratedModels/RunStats.php create mode 100644 src/GeneratedModels/SetRetentionPolicyRequest.php create mode 100644 src/GeneratedModels/SetRetentionPolicyResponse.php create mode 100644 src/GeneratedModels/TriggeredRuleResponse.php diff --git a/src/Generated/ChatTrait.php b/src/Generated/ChatTrait.php index bb589996..0fefa1f7 100644 --- a/src/Generated/ChatTrait.php +++ b/src/Generated/ChatTrait.php @@ -1321,6 +1321,68 @@ public function queryReminders(GeneratedModels\QueryRemindersRequest $requestDat // Use the provided request data array directly return StreamResponse::fromJson($this->makeRequest('POST', $path, $queryParams, $requestData), GeneratedModels\QueryRemindersResponse::class); } + /** + * Returns all retention policies configured for the app. Server-side only. + * + * @return StreamResponse + * @throws StreamException + */ + public function getRetentionPolicy(): StreamResponse { + $path = '/api/v2/chat/retention_policy'; + + $queryParams = []; + $requestData = null; + return StreamResponse::fromJson($this->makeRequest('GET', $path, $queryParams, $requestData), GeneratedModels\GetRetentionPolicyResponse::class); + } + /** + * Creates or updates a retention policy for the app. Server-side only. + * + * @param GeneratedModels\SetRetentionPolicyRequest $requestData + * @return StreamResponse + * @throws StreamException + */ + public function setRetentionPolicy(GeneratedModels\SetRetentionPolicyRequest $requestData): StreamResponse { + $path = '/api/v2/chat/retention_policy'; + + $queryParams = []; + // Use the provided request data array directly + return StreamResponse::fromJson($this->makeRequest('POST', $path, $queryParams, $requestData), GeneratedModels\SetRetentionPolicyResponse::class); + } + /** + * Removes a retention policy for the app. Server-side only. + * + * @param GeneratedModels\DeleteRetentionPolicyRequest $requestData + * @return StreamResponse + * @throws StreamException + */ + public function deleteRetentionPolicy(GeneratedModels\DeleteRetentionPolicyRequest $requestData): StreamResponse { + $path = '/api/v2/chat/retention_policy/delete'; + + $queryParams = []; + // Use the provided request data array directly + return StreamResponse::fromJson($this->makeRequest('POST', $path, $queryParams, $requestData), GeneratedModels\DeleteRetentionPolicyResponse::class); + } + /** + * Returns paginated retention cleanup run history for the app. Server-side only. + * + * @param int $limit + * @param int $offset + * @return StreamResponse + * @throws StreamException + */ + public function getRetentionPolicyRuns(int $limit, int $offset): StreamResponse { + $path = '/api/v2/chat/retention_policy/runs'; + + $queryParams = []; + if ($limit !== null) { + $queryParams['limit'] = $limit; + } + if ($offset !== null) { + $queryParams['offset'] = $offset; + } + $requestData = null; + return StreamResponse::fromJson($this->makeRequest('GET', $path, $queryParams, $requestData), GeneratedModels\GetRetentionPolicyRunsResponse::class); + } /** * Search messages across channels * diff --git a/src/Generated/VideoTrait.php b/src/Generated/VideoTrait.php index 5a187575..54304ff7 100644 --- a/src/Generated/VideoTrait.php +++ b/src/Generated/VideoTrait.php @@ -1115,6 +1115,20 @@ public function getEdges(): StreamResponse { $requestData = null; return StreamResponse::fromJson($this->makeRequest('GET', $path, $queryParams, $requestData), GeneratedModels\GetEdgesResponse::class); } + /** + * Determine authentication requirements for an inbound SIP call before sending a digest challenge + * + * @param GeneratedModels\ResolveSipAuthRequest $requestData + * @return StreamResponse + * @throws StreamException + */ + public function resolveSipAuth(GeneratedModels\ResolveSipAuthRequest $requestData): StreamResponse { + $path = '/api/v2/video/sip/auth'; + + $queryParams = []; + // Use the provided request data array directly + return StreamResponse::fromJson($this->makeRequest('POST', $path, $queryParams, $requestData), GeneratedModels\ResolveSipAuthResponse::class); + } /** * List all SIP Inbound Routing Rules for the application * diff --git a/src/GeneratedModels/AIImageLabelDefinition.php b/src/GeneratedModels/AIImageLabelDefinition.php new file mode 100644 index 00000000..e7d861ca --- /dev/null +++ b/src/GeneratedModels/AIImageLabelDefinition.php @@ -0,0 +1,18 @@ +|null */ + #[ArrayOf(MemberResponse::class)] + public ?array $members = null, // The list of members in the call ) { } diff --git a/src/GeneratedModels/ChannelConfig.php b/src/GeneratedModels/ChannelConfig.php index fc89708d..a283115c 100644 --- a/src/GeneratedModels/ChannelConfig.php +++ b/src/GeneratedModels/ChannelConfig.php @@ -40,6 +40,7 @@ public function __construct( public ?string $partitionTtl = null, public ?bool $skipLastMsgUpdateForSystemMsgs = null, public ?string $pushLevel = null, + public ?ChatPreferences $chatPreferences = null, public ?\DateTime $createdAt = null, public ?\DateTime $updatedAt = null, public ?array $commands = null, // List of commands that channel supports diff --git a/src/GeneratedModels/ChannelConfigWithInfo.php b/src/GeneratedModels/ChannelConfigWithInfo.php index da40d649..d27c3603 100644 --- a/src/GeneratedModels/ChannelConfigWithInfo.php +++ b/src/GeneratedModels/ChannelConfigWithInfo.php @@ -42,6 +42,7 @@ public function __construct( public ?string $partitionTtl = null, public ?bool $skipLastMsgUpdateForSystemMsgs = null, public ?string $pushLevel = null, + public ?ChatPreferences $chatPreferences = null, /** @var array|null */ #[ArrayOf(Command::class)] public ?array $commands = null, diff --git a/src/GeneratedModels/ChannelPushPreferencesResponse.php b/src/GeneratedModels/ChannelPushPreferencesResponse.php index 6bbc4f4b..12bedeaa 100644 --- a/src/GeneratedModels/ChannelPushPreferencesResponse.php +++ b/src/GeneratedModels/ChannelPushPreferencesResponse.php @@ -7,6 +7,7 @@ class ChannelPushPreferencesResponse extends BaseModel { public function __construct( public ?string $chatLevel = null, + public ?ChatPreferencesResponse $chatPreferences = null, public ?\DateTime $disabledUntil = null, ) { } diff --git a/src/GeneratedModels/ChannelTypeConfig.php b/src/GeneratedModels/ChannelTypeConfig.php index 8a541301..275da872 100644 --- a/src/GeneratedModels/ChannelTypeConfig.php +++ b/src/GeneratedModels/ChannelTypeConfig.php @@ -46,6 +46,7 @@ public function __construct( public ?string $partitionTtl = null, public ?bool $skipLastMsgUpdateForSystemMsgs = null, public ?string $pushLevel = null, + public ?ChatPreferences $chatPreferences = null, /** @var array|null */ #[ArrayOf(Command::class)] public ?array $commands = null, diff --git a/src/GeneratedModels/ChatPreferences.php b/src/GeneratedModels/ChatPreferences.php new file mode 100644 index 00000000..9d356fad --- /dev/null +++ b/src/GeneratedModels/ChatPreferences.php @@ -0,0 +1,22 @@ +|null */ + #[ArrayOf(AIImageLabelDefinition::class)] + public ?array $aiImageLabelDefinitions = null, // Configurable image moderation label definitions for dashboard rendering ) { } diff --git a/src/GeneratedModels/CreateChannelTypeRequest.php b/src/GeneratedModels/CreateChannelTypeRequest.php index 5982dae0..cf4b3e6e 100644 --- a/src/GeneratedModels/CreateChannelTypeRequest.php +++ b/src/GeneratedModels/CreateChannelTypeRequest.php @@ -42,6 +42,7 @@ public function __construct( public ?string $partitionTtl = null, // Partition TTL public ?bool $skipLastMsgUpdateForSystemMsgs = null, public ?string $pushLevel = null, // Default push notification level for the channel type. One of: all, all_mentions, mentions, direct_mentions, none + public ?ChatPreferences $chatPreferences = null, ) { } diff --git a/src/GeneratedModels/CreateChannelTypeResponse.php b/src/GeneratedModels/CreateChannelTypeResponse.php index eca07629..44954b1b 100644 --- a/src/GeneratedModels/CreateChannelTypeResponse.php +++ b/src/GeneratedModels/CreateChannelTypeResponse.php @@ -40,6 +40,7 @@ public function __construct( public ?string $partitionTtl = null, public ?bool $skipLastMsgUpdateForSystemMsgs = null, public ?string $pushLevel = null, + public ?ChatPreferences $chatPreferences = null, public ?\DateTime $createdAt = null, public ?\DateTime $updatedAt = null, public ?array $commands = null, diff --git a/src/GeneratedModels/CreateSIPTrunkRequest.php b/src/GeneratedModels/CreateSIPTrunkRequest.php index 47fe3fbc..d99c6caa 100644 --- a/src/GeneratedModels/CreateSIPTrunkRequest.php +++ b/src/GeneratedModels/CreateSIPTrunkRequest.php @@ -11,6 +11,8 @@ class CreateSIPTrunkRequest extends BaseModel public function __construct( public ?string $name = null, // Name of the SIP trunk public ?array $numbers = null, // Phone numbers associated with this SIP trunk + public ?string $password = null, // Optional password for SIP trunk authentication + public ?array $allowedIps = null, // Optional list of allowed IPv4/IPv6 addresses or CIDR blocks ) { } diff --git a/src/GeneratedModels/DeleteRetentionPolicyRequest.php b/src/GeneratedModels/DeleteRetentionPolicyRequest.php new file mode 100644 index 00000000..4ba34c85 --- /dev/null +++ b/src/GeneratedModels/DeleteRetentionPolicyRequest.php @@ -0,0 +1,15 @@ +|null */ #[ArrayOf(Command::class)] public ?array $commands = null, diff --git a/src/GeneratedModels/GetRetentionPolicyResponse.php b/src/GeneratedModels/GetRetentionPolicyResponse.php new file mode 100644 index 00000000..bdfaf01f --- /dev/null +++ b/src/GeneratedModels/GetRetentionPolicyResponse.php @@ -0,0 +1,21 @@ +|null */ + #[ArrayOf(RetentionPolicy::class)] + public ?array $policies = null, + public ?string $duration = null, // Duration of the request in milliseconds + ) { + } + + // BaseModel automatically handles jsonSerialize(), toArray(), and fromJson() using constructor types! + // Use #[JsonKey('user_id')] to override field names if needed. +} diff --git a/src/GeneratedModels/GetRetentionPolicyRunsResponse.php b/src/GeneratedModels/GetRetentionPolicyRunsResponse.php new file mode 100644 index 00000000..52399ffe --- /dev/null +++ b/src/GeneratedModels/GetRetentionPolicyRunsResponse.php @@ -0,0 +1,21 @@ +|null */ + #[ArrayOf(RetentionCleanupRun::class)] + public ?array $runs = null, + public ?string $duration = null, // Duration of the request in milliseconds + ) { + } + + // BaseModel automatically handles jsonSerialize(), toArray(), and fromJson() using constructor types! + // Use #[JsonKey('user_id')] to override field names if needed. +} diff --git a/src/GeneratedModels/PolicyConfig.php b/src/GeneratedModels/PolicyConfig.php new file mode 100644 index 00000000..9cac13e2 --- /dev/null +++ b/src/GeneratedModels/PolicyConfig.php @@ -0,0 +1,15 @@ +|null */ + #[ArrayOf(AIImageLabelDefinition::class)] + public ?array $aiImageLabelDefinitions = null, // AI image label definitions with metadata for dashboard rendering + public ?array $aiImageSubclassifications = null, // Stream L1 to leaf-level label name mapping for AI image rules public ?string $next = null, public ?string $prev = null, public ?string $duration = null, diff --git a/src/GeneratedModels/ResolveSipAuthRequest.php b/src/GeneratedModels/ResolveSipAuthRequest.php new file mode 100644 index 00000000..be0f67de --- /dev/null +++ b/src/GeneratedModels/ResolveSipAuthRequest.php @@ -0,0 +1,21 @@ +getData(); self::assertNotEmpty($data->channels); - self::assertEquals($channelID, $data->channels[0]->channel->id); - self::assertEquals('messaging', $data->channels[0]->channel->type); + self::assertSame($channelID, $data->channels[0]->channel->id); + self::assertSame('messaging', $data->channels[0]->channel->type); } /** @@ -97,7 +92,7 @@ public function testCreateDistinctChannel(): void )); $this->assertResponseSuccess($resp2, 'create distinct channel (second call)'); - self::assertEquals($resp1->getData()->channel->cid, $resp2->getData()->channel->cid); + self::assertSame($resp1->getData()->channel->cid, $resp2->getData()->channel->cid); // Track for cleanup $this->createdChannels[] = ['type' => 'messaging', 'id' => $resp1->getData()->channel->id]; @@ -116,7 +111,7 @@ public function testQueryChannels(): void $this->assertResponseSuccess($resp, 'query channels'); self::assertNotEmpty($resp->getData()->channels); - self::assertEquals($channelID, $resp->getData()->channels[0]->channel->id); + self::assertSame($channelID, $resp->getData()->channels[0]->channel->id); } /** @@ -138,7 +133,7 @@ public function testUpdateChannel(): void $this->assertResponseSuccess($resp, 'update channel'); self::assertNotNull($resp->getData()->channel); - self::assertEquals('blue', $resp->getData()->channel->custom->color ?? null); + self::assertSame('blue', $resp->getData()->channel->custom->color ?? null); } /** @@ -157,7 +152,7 @@ public function testPartialUpdateChannel(): void )); $this->assertResponseSuccess($resp, 'partial update channel (set)'); self::assertNotNull($resp->getData()->channel); - self::assertEquals('red', $resp->getData()->channel->custom->color ?? null); + self::assertSame('red', $resp->getData()->channel->custom->color ?? null); // Unset fields $resp = $this->updateChannelPartial($type, $channelID, new GeneratedModels\UpdateChannelPartialRequest( @@ -205,7 +200,7 @@ public function testHardDeleteChannels(): void $cid2 = "messaging:{$channelID2}"; // Remove from cleanup tracking since we're hard-deleting them here - $this->createdChannels = array_filter($this->createdChannels, function ($ch) use ($channelID1, $channelID2) { + $this->createdChannels = array_filter($this->createdChannels, static function ($ch) use ($channelID1, $channelID2) { return $ch['id'] !== $channelID1 && $ch['id'] !== $channelID2; }); @@ -217,7 +212,7 @@ public function testHardDeleteChannels(): void self::assertNotEmpty($resp->getData()->taskID); $taskResult = $this->waitForTask($resp->getData()->taskID); - self::assertEquals('completed', $taskResult->status); + self::assertSame('completed', $taskResult->status); } /** @@ -253,7 +248,7 @@ public function testAddRemoveMembers(): void // Verify member removed $stateResp = $this->getOrCreateChannel($type, $channelID, new GeneratedModels\ChannelGetOrCreateRequest()); $this->assertResponseSuccess($stateResp, 'get channel after remove member'); - $memberIDs = array_map(fn($m) => $m->userID, $stateResp->getData()->members ?? []); + $memberIDs = array_map(static fn ($m) => $m->userID, $stateResp->getData()->members ?? []); self::assertNotContains($this->memberID3, $memberIDs, 'memberID3 should have been removed'); } @@ -437,7 +432,7 @@ public function testMuteUnmuteChannel(): void )); $this->assertResponseSuccess($qResp, 'query muted channels'); self::assertCount(1, $qResp->getData()->channels, 'Should find exactly 1 muted channel'); - self::assertEquals($cid, $qResp->getData()->channels[0]->channel->cid); + self::assertSame($cid, $qResp->getData()->channels[0]->channel->cid); // Unmute $resp = $this->unmuteChannel(new GeneratedModels\UnmuteChannelRequest( @@ -474,7 +469,7 @@ public function testMemberPartialUpdate(): void )); $this->assertResponseSuccess($resp, 'member partial update (set)'); self::assertNotNull($resp->getData()->channelMember); - self::assertEquals('moderator', $resp->getData()->channelMember->custom->role_label ?? null); + self::assertSame('moderator', $resp->getData()->channelMember->custom->role_label ?? null); // Unset a custom field $resp = $this->updateMemberPartial($type, $channelID, $this->memberID1, new GeneratedModels\UpdateMemberPartialRequest( @@ -518,7 +513,7 @@ public function testAssignRoles(): void )); $this->assertResponseSuccess($qResp, 'query members after assign roles'); self::assertNotEmpty($qResp->getData()->members); - self::assertEquals('channel_moderator', $qResp->getData()->members[0]->channelRole); + self::assertSame('channel_moderator', $qResp->getData()->members[0]->channelRole); } /** @@ -545,7 +540,7 @@ public function testAddDemoteModerators(): void )); $this->assertResponseSuccess($qResp, 'query members after add moderator'); self::assertNotEmpty($qResp->getData()->members); - self::assertEquals('channel_moderator', $qResp->getData()->members[0]->channelRole); + self::assertSame('channel_moderator', $qResp->getData()->members[0]->channelRole); // Demote moderator back to member $resp = $this->updateChannel($type, $channelID, new GeneratedModels\UpdateChannelRequest( @@ -561,7 +556,7 @@ public function testAddDemoteModerators(): void )); $this->assertResponseSuccess($qResp, 'query members after demote moderator'); self::assertNotEmpty($qResp->getData()->members); - self::assertEquals('channel_member', $qResp->getData()->members[0]->channelRole); + self::assertSame('channel_member', $qResp->getData()->members[0]->channelRole); } /** @@ -647,7 +642,7 @@ public function testPinUnpinChannel(): void )); $this->assertResponseSuccess($qResp, 'query pinned channels'); self::assertCount(1, $qResp->getData()->channels, 'Should find 1 pinned channel'); - self::assertEquals("messaging:{$channelID}", $qResp->getData()->channels[0]->channel->cid); + self::assertSame("messaging:{$channelID}", $qResp->getData()->channels[0]->channel->cid); // Unpin $resp = $this->updateMemberPartial($type, $channelID, $this->memberID1, new GeneratedModels\UpdateMemberPartialRequest( @@ -746,8 +741,8 @@ public function testAddMembersWithRoles(): void $roleMap[$m->userID] = $m->channelRole; } } - self::assertEquals('channel_moderator', $roleMap[$modUserID] ?? null, 'First user should be channel_moderator'); - self::assertEquals('channel_member', $roleMap[$memberUserID] ?? null, 'Second user should be channel_member'); + self::assertSame('channel_moderator', $roleMap[$modUserID] ?? null, 'First user should be channel_moderator'); + self::assertSame('channel_member', $roleMap[$memberUserID] ?? null, 'Second user should be channel_member'); } /** @@ -997,7 +992,8 @@ public function testUploadAndDeleteImage(): void $tmpFile = tempnam(sys_get_temp_dir(), 'chat-test-') . '.png'; // Minimal valid PNG: 1x1 white pixel $pngData = base64_decode( - 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==' + 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==', + true ); file_put_contents($tmpFile, $pngData); @@ -1034,4 +1030,9 @@ public function testUploadAndDeleteImage(): void @unlink($tmpFile); } } + + protected static function sharedUserCount(): int + { + return 4; + } } diff --git a/tests/Integration/ChatMessageIntegrationTest.php b/tests/Integration/ChatMessageIntegrationTest.php index 26c6f104..0d266066 100644 --- a/tests/Integration/ChatMessageIntegrationTest.php +++ b/tests/Integration/ChatMessageIntegrationTest.php @@ -17,11 +17,6 @@ class ChatMessageIntegrationTest extends ChatTestCase private string $userID; private string $userID2; - protected static function sharedUserCount(): int - { - return 2; - } - protected function setUp(): void { parent::setUp(); @@ -34,7 +29,10 @@ protected function setUp(): void // Send / Get / Update / Delete // ========================================================================= - public function testSendAndGetMessage(): void + /** + * @test + */ + public function sendAndGetMessage(): void { [$type, $channelID] = $this->createTestChannelWithMembers($this->userID, [$this->userID]); @@ -47,11 +45,14 @@ public function testSendAndGetMessage(): void $data = $resp->getData(); self::assertNotNull($data->message); - self::assertEquals($msgID, $data->message->id); - self::assertEquals($msgText, $data->message->text); + self::assertSame($msgID, $data->message->id); + self::assertSame($msgText, $data->message->text); } - public function testGetManyMessages(): void + /** + * @test + */ + public function getManyMessages(): void { [$type, $channelID] = $this->createTestChannelWithMembers($this->userID, [$this->userID]); @@ -66,7 +67,10 @@ public function testGetManyMessages(): void self::assertCount(3, $data->messages); } - public function testUpdateMessage(): void + /** + * @test + */ + public function updateMessage(): void { [$type, $channelID] = $this->createTestChannelWithMembers($this->userID, [$this->userID]); $msgID = $this->sendTestMessage($type, $channelID, $this->userID, 'Original text'); @@ -79,10 +83,13 @@ public function testUpdateMessage(): void ), )); $this->assertResponseSuccess($resp, 'update message'); - self::assertEquals($updatedText, $resp->getData()->message->text); + self::assertSame($updatedText, $resp->getData()->message->text); } - public function testPartialUpdateMessage(): void + /** + * @test + */ + public function partialUpdateMessage(): void { [$type, $channelID] = $this->createTestChannelWithMembers($this->userID, [$this->userID]); $msgID = $this->sendTestMessage($type, $channelID, $this->userID, 'Partial update test'); @@ -107,7 +114,10 @@ public function testPartialUpdateMessage(): void self::assertNotNull($resp->getData()->message); } - public function testDeleteMessage(): void + /** + * @test + */ + public function deleteMessage(): void { [$type, $channelID] = $this->createTestChannelWithMembers($this->userID, [$this->userID]); $msgID = $this->sendTestMessage($type, $channelID, $this->userID, 'Message to delete'); @@ -115,24 +125,30 @@ public function testDeleteMessage(): void // Soft delete $resp = $this->deleteMessageApi($msgID); $this->assertResponseSuccess($resp, 'delete message'); - self::assertEquals('deleted', $resp->getData()->message->type); + self::assertSame('deleted', $resp->getData()->message->type); } - public function testHardDeleteMessage(): void + /** + * @test + */ + public function hardDeleteMessage(): void { [$type, $channelID] = $this->createTestChannelWithMembers($this->userID, [$this->userID]); $msgID = $this->sendTestMessage($type, $channelID, $this->userID, 'Message to hard delete'); $resp = $this->deleteMessageApi($msgID, hard: true); $this->assertResponseSuccess($resp, 'hard delete message'); - self::assertEquals('deleted', $resp->getData()->message->type); + self::assertSame('deleted', $resp->getData()->message->type); } // ========================================================================= // Pin / Unpin // ========================================================================= - public function testPinUnpinMessage(): void + /** + * @test + */ + public function pinUnpinMessage(): void { [$type, $channelID] = $this->createTestChannelWithMembers($this->userID, [$this->userID]); @@ -157,7 +173,10 @@ public function testPinUnpinMessage(): void self::assertFalse($resp->getData()->message->pinned); } - public function testPinExpiration(): void + /** + * @test + */ + public function pinExpiration(): void { [$type, $channelID] = $this->createTestChannelWithMembers($this->userID, [$this->userID, $this->userID2]); @@ -189,7 +208,10 @@ public function testPinExpiration(): void // Translate // ========================================================================= - public function testTranslateMessage(): void + /** + * @test + */ + public function translateMessage(): void { [$type, $channelID] = $this->createTestChannelWithMembers($this->userID, [$this->userID]); $msgID = $this->sendTestMessage($type, $channelID, $this->userID, 'Hello, how are you?'); @@ -206,7 +228,10 @@ public function testTranslateMessage(): void // Threads // ========================================================================= - public function testThreadReply(): void + /** + * @test + */ + public function threadReply(): void { [$type, $channelID] = $this->createTestChannelWithMembers($this->userID, [$this->userID, $this->userID2]); @@ -234,7 +259,10 @@ public function testThreadReply(): void // Search // ========================================================================= - public function testSearchMessages(): void + /** + * @test + */ + public function searchMessages(): void { [$type, $channelID] = $this->createTestChannelWithMembers($this->userID, [$this->userID]); @@ -252,7 +280,10 @@ public function testSearchMessages(): void self::assertNotEmpty($resp->getData()->results, 'Search should return at least one result'); } - public function testSearchWithMessageFilters(): void + /** + * @test + */ + public function searchWithMessageFilters(): void { [$type, $channelID] = $this->createTestChannelWithMembers($this->userID, [$this->userID]); @@ -272,7 +303,10 @@ public function testSearchWithMessageFilters(): void self::assertGreaterThanOrEqual(2, count($resp->getData()->results), 'Should find at least 2 messages with MessageFilterConditions'); } - public function testSearchQueryAndMessageFiltersError(): void + /** + * @test + */ + public function searchQueryAndMessageFiltersError(): void { // Using both query and messageFilterConditions together should error $this->expectException(\Exception::class); @@ -283,8 +317,10 @@ public function testSearchQueryAndMessageFiltersError(): void )); } - - public function testSearchOffsetAndNextError(): void + /** + * @test + */ + public function searchOffsetAndNextError(): void { // Using offset with next should error $this->expectException(\Exception::class); @@ -300,7 +336,10 @@ public function testSearchOffsetAndNextError(): void // Special Message Types // ========================================================================= - public function testSilentMessage(): void + /** + * @test + */ + public function silentMessage(): void { [$type, $channelID] = $this->createTestChannelWithMembers($this->userID, [$this->userID]); @@ -315,7 +354,10 @@ public function testSilentMessage(): void self::assertTrue($resp->getData()->message->silent); } - public function testSystemMessage(): void + /** + * @test + */ + public function systemMessage(): void { [$type, $channelID] = $this->createTestChannelWithMembers($this->userID, [$this->userID]); @@ -327,14 +369,17 @@ public function testSystemMessage(): void ), )); $this->assertResponseSuccess($resp, 'send system message'); - self::assertEquals('system', $resp->getData()->message->type); + self::assertSame('system', $resp->getData()->message->type); } // ========================================================================= // Pending Messages // ========================================================================= - public function testPendingMessage(): void + /** + * @test + */ + public function pendingMessage(): void { [$type, $channelID] = $this->createTestChannelWithMembers($this->userID, [$this->userID]); @@ -350,8 +395,9 @@ public function testPendingMessage(): void )); } catch (\Exception $e) { if (str_contains($e->getMessage(), 'pending messages not enabled') || str_contains($e->getMessage(), 'feature flag')) { - $this->markTestSkipped('Pending messages feature not enabled for this app'); + self::markTestSkipped('Pending messages feature not enabled for this app'); } + throw $e; } $this->assertResponseSuccess($sendResp, 'send pending message'); @@ -362,10 +408,13 @@ public function testPendingMessage(): void $commitResp = $this->commitMessage($msgID); $this->assertResponseSuccess($commitResp, 'commit pending message'); self::assertNotNull($commitResp->getData()->message); - self::assertEquals($msgID, $commitResp->getData()->message->id); + self::assertSame($msgID, $commitResp->getData()->message->id); } - public function testPendingFalse(): void + /** + * @test + */ + public function pendingFalse(): void { [$type, $channelID] = $this->createTestChannelWithMembers($this->userID, [$this->userID]); @@ -382,14 +431,17 @@ public function testPendingFalse(): void // Get the message to verify it's immediately available $getResp = $this->getMessage($sendResp->getData()->message->id); $this->assertResponseSuccess($getResp, 'get non-pending message'); - self::assertEquals('Non-pending message', $getResp->getData()->message->text); + self::assertSame('Non-pending message', $getResp->getData()->message->text); } // ========================================================================= // Message History // ========================================================================= - public function testQueryMessageHistory(): void + /** + * @test + */ + public function queryMessageHistory(): void { [$type, $channelID] = $this->createTestChannelWithMembers($this->userID, [$this->userID, $this->userID2]); @@ -429,8 +481,9 @@ public function testQueryMessageHistory(): void )); } catch (\Exception $e) { if (str_contains($e->getMessage(), 'feature flag') || str_contains($e->getMessage(), 'not enabled')) { - $this->markTestSkipped('QueryMessageHistory feature not enabled for this app'); + self::markTestSkipped('QueryMessageHistory feature not enabled for this app'); } + throw $e; } $this->assertResponseSuccess($histResp, 'query message history'); @@ -438,17 +491,20 @@ public function testQueryMessageHistory(): void // Verify history entries reference the correct message foreach ($histResp->getData()->messageHistory as $entry) { - self::assertEquals($msgID, $entry->messageID); + self::assertSame($msgID, $entry->messageID); } // Verify text values in history (descending order by default) - self::assertEquals('updated text', $histResp->getData()->messageHistory[0]->text); - self::assertEquals($this->userID, $histResp->getData()->messageHistory[0]->messageUpdatedByID); - self::assertEquals('initial text', $histResp->getData()->messageHistory[1]->text); - self::assertEquals($this->userID, $histResp->getData()->messageHistory[1]->messageUpdatedByID); + self::assertSame('updated text', $histResp->getData()->messageHistory[0]->text); + self::assertSame($this->userID, $histResp->getData()->messageHistory[0]->messageUpdatedByID); + self::assertSame('initial text', $histResp->getData()->messageHistory[1]->text); + self::assertSame($this->userID, $histResp->getData()->messageHistory[1]->messageUpdatedByID); } - public function testQueryMessageHistorySort(): void + /** + * @test + */ + public function queryMessageHistorySort(): void { [$type, $channelID] = $this->createTestChannelWithMembers($this->userID, [$this->userID, $this->userID2]); @@ -483,23 +539,27 @@ public function testQueryMessageHistorySort(): void )); } catch (\Exception $e) { if (str_contains($e->getMessage(), 'feature flag') || str_contains($e->getMessage(), 'not enabled')) { - $this->markTestSkipped('QueryMessageHistory feature not enabled for this app'); + self::markTestSkipped('QueryMessageHistory feature not enabled for this app'); } + throw $e; } $this->assertResponseSuccess($histResp, 'query message history with sort'); self::assertGreaterThanOrEqual(2, count($histResp->getData()->messageHistory)); // Ascending: oldest first - self::assertEquals('sort initial', $histResp->getData()->messageHistory[0]->text); - self::assertEquals($this->userID, $histResp->getData()->messageHistory[0]->messageUpdatedByID); + self::assertSame('sort initial', $histResp->getData()->messageHistory[0]->text); + self::assertSame($this->userID, $histResp->getData()->messageHistory[0]->messageUpdatedByID); } // ========================================================================= // URL Enrichment // ========================================================================= - public function testSkipEnrichUrl(): void + /** + * @test + */ + public function skipEnrichUrl(): void { [$type, $channelID] = $this->createTestChannelWithMembers($this->userID, [$this->userID]); @@ -525,7 +585,10 @@ public function testSkipEnrichUrl(): void // Channel Hidden / Visibility // ========================================================================= - public function testKeepChannelHidden(): void + /** + * @test + */ + public function keepChannelHidden(): void { [$type, $channelID] = $this->createTestChannelWithMembers($this->userID, [$this->userID]); @@ -558,7 +621,10 @@ public function testKeepChannelHidden(): void // Delete / Undelete Variants // ========================================================================= - public function testUndeleteMessage(): void + /** + * @test + */ + public function undeleteMessage(): void { [$type, $channelID] = $this->createTestChannelWithMembers($this->userID, [$this->userID]); $msgID = $this->sendTestMessage($type, $channelID, $this->userID, 'Message to undelete'); @@ -570,7 +636,7 @@ public function testUndeleteMessage(): void // Verify it's deleted $getResp = $this->getMessage($msgID); $this->assertResponseSuccess($getResp, 'get deleted message'); - self::assertEquals('deleted', $getResp->getData()->message->type); + self::assertSame('deleted', $getResp->getData()->message->type); // Undelete the message try { @@ -579,17 +645,21 @@ public function testUndeleteMessage(): void )); } catch (\Exception $e) { if (str_contains($e->getMessage(), 'undeleted_by') || str_contains($e->getMessage(), 'required field')) { - $this->markTestSkipped('UndeleteMessage requires field not yet in generated request struct'); + self::markTestSkipped('UndeleteMessage requires field not yet in generated request struct'); } + throw $e; } $this->assertResponseSuccess($undelResp, 'undelete message'); self::assertNotNull($undelResp->getData()->message); - self::assertNotEquals('deleted', $undelResp->getData()->message->type); - self::assertEquals('Message to undelete', $undelResp->getData()->message->text); + self::assertNotSame('deleted', $undelResp->getData()->message->type); + self::assertSame('Message to undelete', $undelResp->getData()->message->text); } - public function testRestrictedVisibility(): void + /** + * @test + */ + public function restrictedVisibility(): void { [$type, $channelID] = $this->createTestChannelWithMembers($this->userID, [$this->userID, $this->userID2]); @@ -604,15 +674,19 @@ public function testRestrictedVisibility(): void )); } catch (\Exception $e) { if (str_contains($e->getMessage(), 'private messaging is not allowed') || str_contains($e->getMessage(), 'not enabled')) { - $this->markTestSkipped('RestrictedVisibility (private messaging) is not enabled for this app'); + self::markTestSkipped('RestrictedVisibility (private messaging) is not enabled for this app'); } + throw $e; } $this->assertResponseSuccess($sendResp, 'send restricted visibility message'); - self::assertEquals([$this->userID], $sendResp->getData()->message->restrictedVisibility); + self::assertSame([$this->userID], $sendResp->getData()->message->restrictedVisibility); } - public function testDeleteMessageForMe(): void + /** + * @test + */ + public function deleteMessageForMe(): void { [$type, $channelID] = $this->createTestChannelWithMembers($this->userID, [$this->userID]); $msgID = $this->sendTestMessage($type, $channelID, $this->userID, 'test message to delete for me'); @@ -626,7 +700,10 @@ public function testDeleteMessageForMe(): void // Channel Role in Member // ========================================================================= - public function testChannelRoleInMember(): void + /** + * @test + */ + public function channelRoleInMember(): void { $roleUserIDs = $this->createTestUsers(2); $memberUserID = $roleUserIDs[0]; @@ -656,7 +733,7 @@ public function testChannelRoleInMember(): void )); $this->assertResponseSuccess($respMember, 'send from channel_member'); self::assertNotNull($respMember->getData()->message->member, 'Member should be present in message response'); - self::assertEquals('channel_member', $respMember->getData()->message->member->channelRole); + self::assertSame('channel_member', $respMember->getData()->message->member->channelRole); // Send message from channel_moderator $respMod = $this->sendMessage($channelType, $channelID, new GeneratedModels\SendMessageRequest( @@ -667,6 +744,11 @@ public function testChannelRoleInMember(): void )); $this->assertResponseSuccess($respMod, 'send from channel_moderator'); self::assertNotNull($respMod->getData()->message->member, 'Member should be present in message response'); - self::assertEquals('channel_moderator', $respMod->getData()->message->member->channelRole); + self::assertSame('channel_moderator', $respMod->getData()->message->member->channelRole); + } + + protected static function sharedUserCount(): int + { + return 2; } } diff --git a/tests/Integration/ChatMiscIntegrationTest.php b/tests/Integration/ChatMiscIntegrationTest.php index 68c3c4f0..86c5fec9 100644 --- a/tests/Integration/ChatMiscIntegrationTest.php +++ b/tests/Integration/ChatMiscIntegrationTest.php @@ -16,16 +16,14 @@ #[Group('integration')] class ChatMiscIntegrationTest extends ChatTestCase { - protected static function sharedUserCount(): int - { - return 2; - } - // ========================================================================= // Devices // ========================================================================= - public function testCreateListDeleteDevice(): void + /** + * @test + */ + public function createListDeleteDevice(): void { $userID = $this->getSharedUserIDs()[0]; $deviceID = 'integration-test-device-' . $this->randomString(12); @@ -40,8 +38,9 @@ public function testCreateListDeleteDevice(): void $this->assertResponseSuccess($resp, 'create device'); } catch (\Exception $e) { if (str_contains($e->getMessage(), 'no push providers configured') || str_contains($e->getMessage(), 'push')) { - $this->markTestSkipped('Push providers not configured for this app'); + self::markTestSkipped('Push providers not configured for this app'); } + throw $e; } @@ -53,7 +52,7 @@ public function testCreateListDeleteDevice(): void foreach ($listResp->getData()->devices ?? [] as $device) { if ($device->id === $deviceID) { $found = true; - self::assertEquals('firebase', $device->pushProvider); + self::assertSame('firebase', $device->pushProvider); } } self::assertTrue($found, 'Created device should appear in list'); @@ -67,7 +66,7 @@ public function testCreateListDeleteDevice(): void $this->assertResponseSuccess($listResp2, 'list devices after delete'); foreach ($listResp2->getData()->devices ?? [] as $device) { - self::assertNotEquals($deviceID, $device->id, 'Device should be deleted'); + self::assertNotSame($deviceID, $device->id, 'Device should be deleted'); } } @@ -75,7 +74,10 @@ public function testCreateListDeleteDevice(): void // Blocklists // ========================================================================= - public function testCreateListDeleteBlocklist(): void + /** + * @test + */ + public function createListDeleteBlocklist(): void { $blocklistName = 'test-blocklist-' . $this->randomString(8); @@ -105,7 +107,7 @@ public function testCreateListDeleteBlocklist(): void // Get blocklist $getResp = $this->client->getBlockList($blocklistName, ''); $this->assertResponseSuccess($getResp, 'get blocklist'); - self::assertEquals($blocklistName, $getResp->getData()->blocklist->name); + self::assertSame($blocklistName, $getResp->getData()->blocklist->name); // Delete blocklist $delResp = $this->client->deleteBlockList($blocklistName, ''); @@ -116,6 +118,7 @@ public function testCreateListDeleteBlocklist(): void $this->client->deleteBlockList($blocklistName, ''); } catch (\Exception $ignore) { } + throw $e; } } @@ -124,7 +127,10 @@ public function testCreateListDeleteBlocklist(): void // Commands // ========================================================================= - public function testCreateListDeleteCommand(): void + /** + * @test + */ + public function createListDeleteCommand(): void { $cmdName = 'testcmd' . $this->randomString(6); @@ -136,13 +142,13 @@ public function testCreateListDeleteCommand(): void )); $this->assertResponseSuccess($createResp, 'create command'); self::assertNotNull($createResp->getData()->command); - self::assertEquals($cmdName, $createResp->getData()->command->name); + self::assertSame($cmdName, $createResp->getData()->command->name); // Get command $getResp = $this->getCommand($cmdName); $this->assertResponseSuccess($getResp, 'get command'); - self::assertEquals($cmdName, $getResp->getData()->name); - self::assertEquals('A test command', $getResp->getData()->description); + self::assertSame($cmdName, $getResp->getData()->name); + self::assertSame('A test command', $getResp->getData()->description); // Commands are eventually consistent sleep(2); @@ -162,13 +168,14 @@ public function testCreateListDeleteCommand(): void // Delete command $delResp = $this->deleteCommandApi($cmdName); $this->assertResponseSuccess($delResp, 'delete command'); - self::assertEquals($cmdName, $delResp->getData()->name); + self::assertSame($cmdName, $delResp->getData()->name); } catch (\Exception $e) { // Clean up on failure try { $this->deleteCommandApi($cmdName); } catch (\Exception $ignore) { } + throw $e; } } @@ -177,7 +184,10 @@ public function testCreateListDeleteCommand(): void // Channel Types // ========================================================================= - public function testCreateUpdateDeleteChannelType(): void + /** + * @test + */ + public function createUpdateDeleteChannelType(): void { $typeName = 'testtype' . $this->randomString(6); @@ -190,16 +200,17 @@ public function testCreateUpdateDeleteChannelType(): void maxMessageLength: 3000, )); $this->assertResponseSuccess($createResp, 'create channel type'); - self::assertEquals($typeName, $createResp->getData()->name); - self::assertEquals(3000, $createResp->getData()->maxMessageLength); + self::assertSame($typeName, $createResp->getData()->name); + self::assertSame(3000, $createResp->getData()->maxMessageLength); // Channel types are eventually consistent - retry with delay $getResp = $this->retryUntilSuccess(function () use ($typeName) { $resp = $this->getChannelType($typeName); $this->assertResponseSuccess($resp, 'get channel type'); + return $resp; }, maxAttempts: 10, sleepMs: 2000); - self::assertEquals($typeName, $getResp->getData()->name); + self::assertSame($typeName, $getResp->getData()->name); // Update channel type — retry until the response reflects the updated // value, because the backend may return stale data from its local @@ -212,8 +223,9 @@ public function testCreateUpdateDeleteChannelType(): void typingEvents: false, )); $this->assertResponseSuccess($resp, 'update channel type'); - self::assertEquals(4000, $resp->getData()->maxMessageLength); + self::assertSame(4000, $resp->getData()->maxMessageLength); self::assertFalse($resp->getData()->typingEvents); + return $resp; }, maxAttempts: 5, sleepMs: 2000); @@ -223,6 +235,7 @@ public function testCreateUpdateDeleteChannelType(): void try { $this->deleteChannelType($typeName); $deleteErr = null; + break; } catch (\Exception $e) { $deleteErr = $e; @@ -238,11 +251,15 @@ public function testCreateUpdateDeleteChannelType(): void $this->deleteChannelType($typeName); } catch (\Exception $ignore) { } + throw $e; } } - public function testListChannelTypes(): void + /** + * @test + */ + public function listChannelTypes(): void { $resp = $this->listChannelTypes(); $this->assertResponseSuccess($resp, 'list channel types'); @@ -256,7 +273,10 @@ public function testListChannelTypes(): void // Permissions // ========================================================================= - public function testListPermissions(): void + /** + * @test + */ + public function listPermissions(): void { $resp = $this->client->listPermissions(); $this->assertResponseSuccess($resp, 'list permissions'); @@ -264,19 +284,25 @@ public function testListPermissions(): void self::assertNotEmpty($resp->getData()->permissions, 'Should have at least one permission'); } - public function testCreatePermission(): void + /** + * @test + */ + public function createPermission(): void { // CreatePermission is hidden from the generated spec (Ignore: true in backend) // per Go SDK reference. Skip this test. - $this->markTestSkipped('CreatePermission is not available in the generated SDK'); + self::markTestSkipped('CreatePermission is not available in the generated SDK'); } - public function testGetPermission(): void + /** + * @test + */ + public function getPermission(): void { $resp = $this->client->getPermission('create-channel'); $this->assertResponseSuccess($resp, 'get permission'); - self::assertEquals('create-channel', $resp->getData()->permission->id); + self::assertSame('create-channel', $resp->getData()->permission->id); self::assertNotEmpty($resp->getData()->permission->action); } @@ -284,7 +310,10 @@ public function testGetPermission(): void // Query Banned Users // ========================================================================= - public function testQueryBannedUsers(): void + /** + * @test + */ + public function queryBannedUsers(): void { $shared = $this->getSharedUserIDs(); $adminID = $shared[0]; @@ -325,7 +354,10 @@ public function testQueryBannedUsers(): void // Mute / Unmute User // ========================================================================= - public function testMuteUnmuteUser(): void + /** + * @test + */ + public function muteUnmuteUser(): void { $shared = $this->getSharedUserIDs(); $muterID = $shared[0]; @@ -356,7 +388,10 @@ public function testMuteUnmuteUser(): void // App Settings // ========================================================================= - public function testGetAppSettings(): void + /** + * @test + */ + public function getAppSettings(): void { $resp = $this->client->getApp(); $this->assertResponseSuccess($resp, 'get app settings'); @@ -368,7 +403,10 @@ public function testGetAppSettings(): void // Export Channels // ========================================================================= - public function testExportChannels(): void + /** + * @test + */ + public function exportChannels(): void { $userID = $this->getSharedUserIDs()[0]; @@ -385,14 +423,17 @@ public function testExportChannels(): void // Wait for the export task to complete $taskResult = $this->waitForTask($exportResp->getData()->taskID); - self::assertEquals('completed', $taskResult->status); + self::assertSame('completed', $taskResult->status); } // ========================================================================= // Threads // ========================================================================= - public function testThreads(): void + /** + * @test + */ + public function threads(): void { $shared = $this->getSharedUserIDs(); $userID = $shared[0]; @@ -436,7 +477,7 @@ public function testThreads(): void foreach ($resp->getData()->threads as $thread) { if ($thread->parentMessageID === $parentID) { $found = true; - self::assertEquals($userID2, $thread->createdByUserID); + self::assertSame($userID2, $thread->createdByUserID); } } self::assertTrue($found, "Thread should appear in query results for channel {$channelCID}"); @@ -446,7 +487,10 @@ public function testThreads(): void // Unread Counts // ========================================================================= - public function testGetUnreadCounts(): void + /** + * @test + */ + public function getUnreadCounts(): void { $shared = $this->getSharedUserIDs(); $userID = $shared[0]; @@ -460,7 +504,10 @@ public function testGetUnreadCounts(): void self::assertGreaterThanOrEqual(0, $resp->getData()->totalUnreadCount); } - public function testGetUnreadCountsBatch(): void + /** + * @test + */ + public function getUnreadCountsBatch(): void { $shared = $this->getSharedUserIDs(); $userID = $shared[0]; @@ -482,7 +529,10 @@ public function testGetUnreadCountsBatch(): void // Reminders // ========================================================================= - public function testReminders(): void + /** + * @test + */ + public function reminders(): void { $userID = $this->getSharedUserIDs()[0]; @@ -491,6 +541,7 @@ public function testReminders(): void // Create a reminder $remindAt = new \DateTime('+24 hours'); + try { $createResp = $this->createReminder($msgID, new GeneratedModels\CreateReminderRequest( remindAt: $remindAt, @@ -499,13 +550,14 @@ public function testReminders(): void $this->assertResponseSuccess($createResp, 'create reminder'); } catch (\Exception $e) { if (str_contains($e->getMessage(), 'not enabled') || str_contains($e->getMessage(), 'reminder')) { - $this->markTestSkipped('Reminders are not enabled for this app'); + self::markTestSkipped('Reminders are not enabled for this app'); } + throw $e; } - self::assertEquals($msgID, $createResp->getData()->messageID); - self::assertEquals($userID, $createResp->getData()->userID); + self::assertSame($msgID, $createResp->getData()->messageID); + self::assertSame($userID, $createResp->getData()->userID); self::assertNotNull($createResp->getData()->remindAt, 'RemindAt should be set'); // Update reminder @@ -515,7 +567,7 @@ public function testReminders(): void userID: $userID, )); $this->assertResponseSuccess($updateResp, 'update reminder'); - self::assertEquals($msgID, $updateResp->getData()->reminder->messageID); + self::assertSame($msgID, $updateResp->getData()->reminder->messageID); // Query reminders $qResp = $this->queryReminders(new GeneratedModels\QueryRemindersRequest( @@ -525,7 +577,7 @@ public function testReminders(): void )); $this->assertResponseSuccess($qResp, 'query reminders'); self::assertNotEmpty($qResp->getData()->reminders, 'Should find the reminder'); - self::assertEquals($msgID, $qResp->getData()->reminders[0]->messageID); + self::assertSame($msgID, $qResp->getData()->reminders[0]->messageID); // Delete reminder $delResp = $this->deleteReminderApi($msgID, $userID); @@ -536,7 +588,10 @@ public function testReminders(): void // Send User Custom Event // ========================================================================= - public function testSendUserCustomEvent(): void + /** + * @test + */ + public function sendUserCustomEvent(): void { $userID = $this->getSharedUserIDs()[0]; @@ -553,7 +608,10 @@ public function testSendUserCustomEvent(): void // Query Team Usage Stats // ========================================================================= - public function testQueryTeamUsageStats(): void + /** + * @test + */ + public function queryTeamUsageStats(): void { try { $resp = $this->queryTeamUsageStats(new GeneratedModels\QueryTeamUsageStatsRequest()); @@ -561,8 +619,9 @@ public function testQueryTeamUsageStats(): void self::assertNotNull($resp->getData()->teams); } catch (\Exception $e) { if (str_contains($e->getMessage(), 'Token signature is invalid') || str_contains($e->getMessage(), 'not available')) { - $this->markTestSkipped('QueryTeamUsageStats not available on this app'); + self::markTestSkipped('QueryTeamUsageStats not available on this app'); } + throw $e; } } @@ -571,10 +630,18 @@ public function testQueryTeamUsageStats(): void // Channel Batch Update // ========================================================================= - public function testChannelBatchUpdate(): void + /** + * @test + */ + public function channelBatchUpdate(): void { // ChannelBatchUpdate is behind Ignore+Beta in the backend spec, // so the generated SDK doesn't include it yet. Per Go SDK reference. - $this->markTestSkipped('ChannelBatchUpdate is not yet available in the generated SDK'); + self::markTestSkipped('ChannelBatchUpdate is not yet available in the generated SDK'); + } + + protected static function sharedUserCount(): int + { + return 2; } } diff --git a/tests/Integration/ChatModerationIntegrationTest.php b/tests/Integration/ChatModerationIntegrationTest.php index d489532d..6c9e43e2 100644 --- a/tests/Integration/ChatModerationIntegrationTest.php +++ b/tests/Integration/ChatModerationIntegrationTest.php @@ -19,16 +19,14 @@ #[Group('integration')] class ChatModerationIntegrationTest extends ChatTestCase { - protected static function sharedUserCount(): int - { - return 4; - } - // ========================================================================= // Ban / Unban // ========================================================================= - public function testBanUnbanUser(): void + /** + * @test + */ + public function banUnbanUser(): void { $shared = $this->getSharedUserIDs(); $userIDs = [$shared[0], $shared[1], $shared[2]]; @@ -55,7 +53,7 @@ public function testBanUnbanUser(): void self::assertNotEmpty($qResp->getData()->bans, 'Should find the banned user'); $ban = $qResp->getData()->bans[0]; - self::assertEquals('test ban reason', $ban->reason); + self::assertSame('test ban reason', $ban->reason); // When timeout is set, expires should be populated self::assertNotNull($ban->expires, 'Ban with timeout should have Expires set'); @@ -100,7 +98,10 @@ public function testBanUnbanUser(): void // Mute / Unmute // ========================================================================= - public function testMuteUnmuteUser(): void + /** + * @test + */ + public function muteUnmuteUser(): void { $userIDs = $this->getSharedUserIDs(); $muterID = $userIDs[0]; @@ -157,7 +158,10 @@ public function testMuteUnmuteUser(): void // Flag // ========================================================================= - public function testFlagMessageAndUser(): void + /** + * @test + */ + public function flagMessageAndUser(): void { $shared = $this->getSharedUserIDs(); $userID = $shared[0]; @@ -204,4 +208,9 @@ public function testFlagMessageAndUser(): void $this->assertResponseSuccess($flagUserResp, 'flag user'); self::assertNotEmpty($flagUserResp->getData()->itemID, 'Flag user should return an item ID'); } + + protected static function sharedUserCount(): int + { + return 4; + } } diff --git a/tests/Integration/ChatPollsIntegrationTest.php b/tests/Integration/ChatPollsIntegrationTest.php index 4f600cbb..426946d4 100644 --- a/tests/Integration/ChatPollsIntegrationTest.php +++ b/tests/Integration/ChatPollsIntegrationTest.php @@ -5,7 +5,6 @@ namespace GetStream\Tests\Integration; use GetStream\GeneratedModels; -use GetStream\StreamResponse; use PHPUnit\Framework\Attributes\Group; /** @@ -21,11 +20,6 @@ class ChatPollsIntegrationTest extends ChatTestCase /** @var string|null User ID to use for poll cleanup */ private ?string $pollCleanupUserID = null; - protected static function sharedUserCount(): int - { - return 2; - } - protected function tearDown(): void { // Delete polls before users/channels @@ -42,8 +36,10 @@ protected function tearDown(): void /** * Test creating a poll with options, querying it by ID, and verifying the results. + * + * @test */ - public function testCreateAndQueryPoll(): void + public function createAndQueryPoll(): void { $userIDs = [$this->getSharedUserIDs()[0]]; $this->pollCleanupUserID = $userIDs[0]; @@ -63,20 +59,21 @@ public function testCreateAndQueryPoll(): void )); } catch (\Exception $e) { if (str_contains($e->getMessage(), 'Polls are not enabled') || str_contains($e->getMessage(), 'polls') && str_contains($e->getMessage(), 'not enabled')) { - $this->markTestSkipped('Polls feature not enabled for this app'); + self::markTestSkipped('Polls feature not enabled for this app'); } + throw $e; } $this->assertResponseSuccess($createResp, 'create poll'); $poll = $createResp->getData()->poll; - $this->assertNotNull($poll); - $this->assertNotEmpty($poll->id); - $this->assertEquals('Favorite color?', $poll->name); - $this->assertEquals('Pick your favorite color', $poll->description); - $this->assertTrue($poll->enforceUniqueVote); - $this->assertNotNull($poll->options); - $this->assertCount(3, $poll->options); + self::assertNotNull($poll); + self::assertNotEmpty($poll->id); + self::assertSame('Favorite color?', $poll->name); + self::assertSame('Pick your favorite color', $poll->description); + self::assertTrue($poll->enforceUniqueVote); + self::assertNotNull($poll->options); + self::assertCount(3, $poll->options); $pollID = $poll->id; $this->createdPollIDs[] = $pollID; @@ -84,31 +81,34 @@ public function testCreateAndQueryPoll(): void // Get the poll by ID $getResp = $this->getPoll($pollID); $this->assertResponseSuccess($getResp, 'get poll'); - $this->assertEquals($pollID, $getResp->getData()->poll->id); - $this->assertEquals('Favorite color?', $getResp->getData()->poll->name); + self::assertSame($pollID, $getResp->getData()->poll->id); + self::assertSame('Favorite color?', $getResp->getData()->poll->name); // Query polls with filter by ID $queryResp = $this->queryPolls(new GeneratedModels\QueryPollsRequest( filter: (object) ['id' => $pollID], ), $userIDs[0]); $this->assertResponseSuccess($queryResp, 'query polls'); - $this->assertNotNull($queryResp->getData()->polls); - $this->assertGreaterThanOrEqual(1, count($queryResp->getData()->polls)); + self::assertNotNull($queryResp->getData()->polls); + self::assertGreaterThanOrEqual(1, count($queryResp->getData()->polls)); $found = false; foreach ($queryResp->getData()->polls as $p) { if ($p->id === $pollID) { $found = true; + break; } } - $this->assertTrue($found, 'Poll should be found in query results'); + self::assertTrue($found, 'Poll should be found in query results'); } /** * Test creating a poll, attaching it to a message, and casting a vote. + * + * @test */ - public function testCastPollVote(): void + public function castPollVote(): void { $userIDs = $this->getSharedUserIDs(); $this->pollCleanupUserID = $userIDs[0]; @@ -126,21 +126,22 @@ public function testCastPollVote(): void )); } catch (\Exception $e) { if (str_contains($e->getMessage(), 'Polls are not enabled') || str_contains($e->getMessage(), 'polls') && str_contains($e->getMessage(), 'not enabled')) { - $this->markTestSkipped('Polls feature not enabled for this app'); + self::markTestSkipped('Polls feature not enabled for this app'); } + throw $e; } $this->assertResponseSuccess($createResp, 'create vote test poll'); $poll = $createResp->getData()->poll; - $this->assertNotNull($poll); + self::assertNotNull($poll); $pollID = $poll->id; $this->createdPollIDs[] = $pollID; - $this->assertNotNull($poll->options); - $this->assertGreaterThanOrEqual(2, count($poll->options)); + self::assertNotNull($poll->options); + self::assertGreaterThanOrEqual(2, count($poll->options)); $optionID = $poll->options[0]->id; - $this->assertNotEmpty($optionID); + self::assertNotEmpty($optionID); // Create a channel and send a message with the poll attached [$type, $id] = $this->createTestChannelWithMembers($userIDs[0], $userIDs); @@ -155,13 +156,14 @@ public function testCastPollVote(): void )); } catch (\Exception $e) { if (str_contains($e->getMessage(), 'polls not enabled') || str_contains($e->getMessage(), 'Polls are not enabled')) { - $this->markTestSkipped('Polls not enabled for this channel type'); + self::markTestSkipped('Polls not enabled for this channel type'); } + throw $e; } $this->assertResponseSuccess($msgResp, 'send message with poll'); $msgID = $msgResp->getData()->message->id; - $this->assertNotEmpty($msgID); + self::assertNotEmpty($msgID); // Cast a vote from the second user $voteResp = $this->castPollVote($msgID, $pollID, new GeneratedModels\CastPollVoteRequest( @@ -171,13 +173,18 @@ public function testCastPollVote(): void ), )); $this->assertResponseSuccess($voteResp, 'cast poll vote'); - $this->assertNotNull($voteResp->getData()->vote); - $this->assertEquals($optionID, $voteResp->getData()->vote->optionID); + self::assertNotNull($voteResp->getData()->vote); + self::assertSame($optionID, $voteResp->getData()->vote->optionID); // Verify the poll has votes by getting it again $getResp = $this->getPoll($pollID); $this->assertResponseSuccess($getResp, 'get poll after vote'); - $this->assertNotNull($getResp->getData()->poll); - $this->assertEquals(1, $getResp->getData()->poll->voteCount); + self::assertNotNull($getResp->getData()->poll); + self::assertSame(1, $getResp->getData()->poll->voteCount); + } + + protected static function sharedUserCount(): int + { + return 2; } } diff --git a/tests/Integration/ChatReactionIntegrationTest.php b/tests/Integration/ChatReactionIntegrationTest.php index 8f5831c5..ccbca96c 100644 --- a/tests/Integration/ChatReactionIntegrationTest.php +++ b/tests/Integration/ChatReactionIntegrationTest.php @@ -5,7 +5,6 @@ namespace GetStream\Tests\Integration; use GetStream\GeneratedModels; -use GetStream\StreamResponse; use PHPUnit\Framework\Attributes\Group; /** @@ -15,15 +14,12 @@ #[Group('integration')] class ChatReactionIntegrationTest extends ChatTestCase { - protected static function sharedUserCount(): int - { - return 2; - } - /** * Test sending a reaction and retrieving reactions on a message. + * + * @test */ - public function testSendAndGetReactions(): void + public function sendAndGetReactions(): void { $userIDs = $this->getSharedUserIDs(); [$type, $id] = $this->createTestChannelWithMembers($userIDs[0], $userIDs); @@ -37,9 +33,9 @@ public function testSendAndGetReactions(): void ), )); $this->assertResponseSuccess($resp, 'send like reaction'); - $this->assertNotNull($resp->getData()->reaction); - $this->assertEquals('like', $resp->getData()->reaction->type); - $this->assertEquals($userIDs[0], $resp->getData()->reaction->userID); + self::assertNotNull($resp->getData()->reaction); + self::assertSame('like', $resp->getData()->reaction->type); + self::assertSame($userIDs[0], $resp->getData()->reaction->userID); // Send a "love" reaction from user 2 $resp = $this->sendReaction($msgID, new GeneratedModels\SendReactionRequest( @@ -49,19 +45,21 @@ public function testSendAndGetReactions(): void ), )); $this->assertResponseSuccess($resp, 'send love reaction'); - $this->assertEquals('love', $resp->getData()->reaction->type); + self::assertSame('love', $resp->getData()->reaction->type); // Get reactions and verify both are present $getResp = $this->getReactions($msgID); $this->assertResponseSuccess($getResp, 'get reactions'); - $this->assertNotNull($getResp->getData()->reactions); - $this->assertGreaterThanOrEqual(2, count($getResp->getData()->reactions)); + self::assertNotNull($getResp->getData()->reactions); + self::assertGreaterThanOrEqual(2, count($getResp->getData()->reactions)); } /** * Test deleting a reaction from a message. + * + * @test */ - public function testDeleteReaction(): void + public function deleteReaction(): void { $userIDs = [$this->getSharedUserIDs()[0]]; [$type, $id] = $this->createTestChannelWithMembers($userIDs[0], $userIDs); @@ -87,15 +85,17 @@ public function testDeleteReaction(): void $reactions = $getResp->getData()->reactions ?? []; foreach ($reactions as $r) { if ($r->userID === $userIDs[0]) { - $this->assertNotEquals('like', $r->type, 'Like reaction should have been deleted'); + self::assertNotSame('like', $r->type, 'Like reaction should have been deleted'); } } } /** * Test enforce_unique: sending a second reaction with enforce_unique replaces the first. + * + * @test */ - public function testEnforceUniqueReaction(): void + public function enforceUniqueReaction(): void { $userIDs = [$this->getSharedUserIDs()[0]]; [$type, $id] = $this->createTestChannelWithMembers($userIDs[0], $userIDs); @@ -132,6 +132,11 @@ public function testEnforceUniqueReaction(): void $userReactions++; } } - $this->assertEquals(1, $userReactions, 'EnforceUnique should keep only one reaction per user'); + self::assertSame(1, $userReactions, 'EnforceUnique should keep only one reaction per user'); + } + + protected static function sharedUserCount(): int + { + return 2; } } diff --git a/tests/Integration/ChatTestCase.php b/tests/Integration/ChatTestCase.php index be138cbf..d2f8e4f6 100644 --- a/tests/Integration/ChatTestCase.php +++ b/tests/Integration/ChatTestCase.php @@ -6,7 +6,6 @@ use GetStream\Client; use GetStream\ClientBuilder; -use GetStream\Exceptions\StreamApiException; use GetStream\GeneratedModels; use GetStream\StreamResponse; use PHPUnit\Framework\Attributes\Group; @@ -32,6 +31,19 @@ abstract class ChatTestCase extends TestCase /** @var array{type: string, id: string}[] Channels created during the test, cleaned up in tearDown */ protected array $createdChannels = []; + // ========================================================================= + // Video API Wrappers + // ========================================================================= + + /** @var string[] Call type names created during the test, cleaned up in tearDown */ + protected array $createdCallTypes = []; + + /** @var array{type: string, id: string}[] Calls created during the test, cleaned up in tearDown */ + protected array $createdCalls = []; + + /** @var string[] External storage names created during the test, cleaned up in tearDown */ + protected array $createdExternalStorages = []; + // ------------------------------------------------------------------ // Class-level shared user management (created once per test class) // ------------------------------------------------------------------ @@ -42,15 +54,6 @@ abstract class ChatTestCase extends TestCase /** @var array Class-level shared user IDs keyed by class name */ private static array $classUserIDs = []; - /** - * Override to declare how many shared users this test class needs. - * Return 0 (default) to opt-out and create users per-test instead. - */ - protected static function sharedUserCount(): int - { - return 0; - } - public static function setUpBeforeClass(): void { parent::setUpBeforeClass(); @@ -93,50 +96,6 @@ public static function tearDownAfterClass(): void parent::tearDownAfterClass(); } - /** - * Delete users with retry and jitter to avoid the DeleteUsers rate limit (6/min). - * With 8 parallel paratest workers all tearing down at the same time, a burst - * of concurrent calls easily exceeds the limit. A random initial jitter spreads - * the calls across the minute window before any retry backoff kicks in. - * - * @param string[] $userIDs - */ - private static function deleteUsersHardWithRetry(Client $client, array $userIDs): void - { - // Jitter 0-4s to spread concurrent teardown calls from parallel workers - usleep(random_int(0, 4000000)); - - for ($i = 0; $i < 8; $i++) { - try { - $client->deleteUsers(new GeneratedModels\DeleteUsersRequest( - userIds: $userIDs, - user: 'hard', - messages: 'hard', - conversations: 'hard', - )); - return; - } catch (\Exception $e) { - if (strpos($e->getMessage(), 'Too many requests') === false && - strpos($e->getMessage(), '429') === false) { - // Non-rate-limit error during cleanup — ignore it - return; - } - // Exponential backoff: 2s, 4s, 8s, 16s … capped at 30s - sleep(min(2 ** ($i + 1), 30)); - } - } - } - - /** - * Get the class-level shared user IDs. - * - * @return string[] - */ - protected function getSharedUserIDs(): array - { - return self::$classUserIDs[static::class] ?? []; - } - // ------------------------------------------------------------------ // Per-test setup / teardown // ------------------------------------------------------------------ @@ -166,6 +125,25 @@ protected function tearDown(): void parent::tearDown(); } + /** + * Override to declare how many shared users this test class needs. + * Return 0 (default) to opt-out and create users per-test instead. + */ + protected static function sharedUserCount(): int + { + return 0; + } + + /** + * Get the class-level shared user IDs. + * + * @return string[] + */ + protected function getSharedUserIDs(): array + { + return self::$classUserIDs[static::class] ?? []; + } + // ========================================================================= // Test Helpers // ========================================================================= @@ -227,6 +205,7 @@ protected function createTestChannel(string $creatorID): array * Create a messaging channel with members. * * @param string[] $memberIDs + * * @return array{0: string, 1: string} [channelType, channelID] */ protected function createTestChannelWithMembers(string $creatorID, array $memberIDs): array @@ -235,7 +214,7 @@ protected function createTestChannelWithMembers(string $creatorID, array $member $channelID = 'test-ch-' . uniqid(); $members = array_map( - fn(string $id) => new GeneratedModels\ChannelMemberRequest(userID: $id), + static fn (string $id) => new GeneratedModels\ChannelMemberRequest(userID: $id), $memberIDs, ); @@ -280,9 +259,10 @@ protected function sendTestMessage(string $channelType, string $channelID, strin * Useful for eventually consistent API operations. * * @template T + * * @param callable(): T $fn - * @param int $maxAttempts - * @param int $sleepMs milliseconds to wait between attempts + * @param int $sleepMs milliseconds to wait between attempts + * * @return T */ protected function retryUntilSuccess(callable $fn, int $maxAttempts = 5, int $sleepMs = 500): mixed @@ -296,6 +276,7 @@ protected function retryUntilSuccess(callable $fn, int $maxAttempts = 5, int $sl usleep($sleepMs * 1000); } } + throw $lastException; } @@ -524,6 +505,7 @@ protected function updateMemberPartial(string $type, string $id, string $userID, protected function queryMembers(GeneratedModels\QueryMembersPayload $payload): StreamResponse { $queryParams = ['payload' => json_encode($payload->toArray())]; + return StreamResponse::fromJson( $this->client->makeRequest('GET', '/api/v2/chat/members', $queryParams), GeneratedModels\MembersResponse::class, @@ -1007,6 +989,7 @@ protected function flagContent(GeneratedModels\FlagRequest $request): StreamResp protected function queryMessageFlags(GeneratedModels\QueryMessageFlagsPayload $payload): StreamResponse { $queryParams = ['payload' => json_encode($payload->toArray())]; + return StreamResponse::fromJson( $this->client->makeRequest('GET', '/api/v2/chat/moderation/flags/message', $queryParams), GeneratedModels\QueryMessageFlagsResponse::class, @@ -1147,19 +1130,6 @@ protected function queryTeamUsageStats(GeneratedModels\QueryTeamUsageStatsReques ); } - // ========================================================================= - // Video API Wrappers - // ========================================================================= - - /** @var string[] Call type names created during the test, cleaned up in tearDown */ - protected array $createdCallTypes = []; - - /** @var array{type: string, id: string}[] Calls created during the test, cleaned up in tearDown */ - protected array $createdCalls = []; - - /** @var string[] External storage names created during the test, cleaned up in tearDown */ - protected array $createdExternalStorages = []; - /** * @return StreamResponse */ @@ -1398,8 +1368,6 @@ protected function cleanupVideoResources(): void /** * Create a call and track it for cleanup. - * - * @return GeneratedModels\GetOrCreateCallResponse */ protected function createTrackedCall(string $type, string $id, string $creatorID): GeneratedModels\GetOrCreateCallResponse { @@ -1410,6 +1378,7 @@ protected function createTrackedCall(string $type, string $id, string $creatorID )); $this->assertResponseSuccess($response, 'create call'); $this->createdCalls[] = ['type' => $type, 'id' => $id]; + return $response->getData(); } @@ -1424,4 +1393,39 @@ protected function assertResponseSuccess(StreamResponse $response, string $opera "Failed to {$operation}. Status: {$response->getStatusCode()}, Body: {$response->getRawBody()}" ); } + + /** + * Delete users with retry and jitter to avoid the DeleteUsers rate limit (6/min). + * With 8 parallel paratest workers all tearing down at the same time, a burst + * of concurrent calls easily exceeds the limit. A random initial jitter spreads + * the calls across the minute window before any retry backoff kicks in. + * + * @param string[] $userIDs + */ + private static function deleteUsersHardWithRetry(Client $client, array $userIDs): void + { + // Jitter 0-4s to spread concurrent teardown calls from parallel workers + usleep(random_int(0, 4000000)); + + for ($i = 0; $i < 8; $i++) { + try { + $client->deleteUsers(new GeneratedModels\DeleteUsersRequest( + userIds: $userIDs, + user: 'hard', + messages: 'hard', + conversations: 'hard', + )); + + return; + } catch (\Exception $e) { + if (strpos($e->getMessage(), 'Too many requests') === false + && strpos($e->getMessage(), '429') === false) { + // Non-rate-limit error during cleanup — ignore it + return; + } + // Exponential backoff: 2s, 4s, 8s, 16s … capped at 30s + sleep(min(2 ** ($i + 1), 30)); + } + } + } } diff --git a/tests/Integration/ChatUserIntegrationTest.php b/tests/Integration/ChatUserIntegrationTest.php index 58d2620d..8702e457 100644 --- a/tests/Integration/ChatUserIntegrationTest.php +++ b/tests/Integration/ChatUserIntegrationTest.php @@ -48,9 +48,9 @@ public function testQueryUsers(): void self::assertNotNull($data->users); self::assertGreaterThanOrEqual(2, count($data->users)); - $foundIDs = array_map(fn($u) => $u->id, $data->users); + $foundIDs = array_map(static fn ($u) => $u->id, $data->users); foreach ($userIDs as $id) { - self::assertContains($id, $foundIDs, "User $id should be found in query results"); + self::assertContains($id, $foundIDs, "User {$id} should be found in query results"); } } @@ -132,7 +132,7 @@ public function testBlockUnblockUser(): void $this->assertResponseSuccess($blockedResp, 'get blocked users'); $blocks = $blockedResp->getData()->blocks; self::assertNotNull($blocks); - $blockedIDs = array_map(fn($b) => $b->blockedUserID, $blocks); + $blockedIDs = array_map(static fn ($b) => $b->blockedUserID, $blocks); self::assertContains($bob, $blockedIDs, 'Bob should be in blocked list'); // Unblock bob @@ -146,7 +146,7 @@ public function testBlockUnblockUser(): void $blockedResp = $this->client->getBlockedUsers($alice); $this->assertResponseSuccess($blockedResp, 'get blocked users after unblock'); $blocks = $blockedResp->getData()->blocks ?? []; - $blockedIDs = array_map(fn($b) => $b->blockedUserID, $blocks); + $blockedIDs = array_map(static fn ($b) => $b->blockedUserID, $blocks); self::assertNotContains($bob, $blockedIDs, 'Bob should not be in blocked list after unblock'); } @@ -202,6 +202,7 @@ public function testDeleteUsers(): void conversations: 'hard', )); $this->assertResponseSuccess($resp, 'delete users'); + return $resp; }, maxAttempts: 5, sleepMs: 3000); @@ -210,7 +211,7 @@ public function testDeleteUsers(): void // Wait for task to complete $taskResult = $this->waitForTask($taskID); - self::assertEquals('completed', $taskResult->status, 'Delete task should complete'); + self::assertSame('completed', $taskResult->status, 'Delete task should complete'); } /** @@ -242,7 +243,7 @@ public function testCreateGuest(): void )); } catch (\Exception $e) { // Guest access may be disabled for this app - $this->markTestSkipped('Guest user creation not enabled: ' . $e->getMessage()); + self::markTestSkipped('Guest user creation not enabled: ' . $e->getMessage()); } $this->assertResponseSuccess($response, 'create guest'); @@ -282,9 +283,9 @@ public function testUpsertUsersWithRoleAndTeamsRole(): void self::assertArrayHasKey($userID, $data->users); $user = $data->users[$userID]; - self::assertEquals('admin', $user->role); - self::assertEquals(['blue'], $user->teams); - self::assertEquals(['blue' => 'admin'], $user->teamsRole); + self::assertSame('admin', $user->role); + self::assertSame(['blue'], $user->teams); + self::assertSame(['blue' => 'admin'], $user->teamsRole); } /** @@ -313,8 +314,8 @@ public function testPartialUpdateUserWithTeam(): void self::assertArrayHasKey($userID, $data->users); $user = $data->users[$userID]; - self::assertEquals(['blue'], $user->teams); - self::assertEquals(['blue' => 'admin'], $user->teamsRole); + self::assertSame(['blue'], $user->teams); + self::assertSame(['blue' => 'admin'], $user->teamsRole); } /** @@ -468,7 +469,7 @@ public function testDeactivateUsersPlural(): void self::assertNotEmpty($taskID, 'Task ID should not be empty'); $taskResult = $this->waitForTask($taskID); - self::assertEquals('completed', $taskResult->status, 'Deactivate task should complete'); + self::assertSame('completed', $taskResult->status, 'Deactivate task should complete'); // Verify both are deactivated — query without flag should return 0 $queryResp = $this->client->queryUsers(new GeneratedModels\QueryUsersPayload( diff --git a/tests/Integration/FeedBookmarkFollowIntegrationTest.php b/tests/Integration/FeedBookmarkFollowIntegrationTest.php index 94347b48..eb5c25c3 100644 --- a/tests/Integration/FeedBookmarkFollowIntegrationTest.php +++ b/tests/Integration/FeedBookmarkFollowIntegrationTest.php @@ -7,7 +7,6 @@ use GetStream\Client; use GetStream\ClientBuilder; use GetStream\Exceptions\StreamApiException; -use GetStream\Exceptions\StreamException; use GetStream\Feed; use GetStream\FeedsV3Client; use GetStream\GeneratedModels; diff --git a/tests/Integration/VideoIntegrationTest.php b/tests/Integration/VideoIntegrationTest.php index 32ae3c0f..0dd8d81d 100644 --- a/tests/Integration/VideoIntegrationTest.php +++ b/tests/Integration/VideoIntegrationTest.php @@ -22,104 +22,29 @@ protected function tearDown(): void parent::tearDown(); } - /** - * Helper: create a unique call type and track for cleanup. - */ - private function createTrackedCallType(string $name): GeneratedModels\CreateCallTypeResponse - { - // Clean up excess call types first (same pattern as Go SDK) - try { - $listResp = $this->listCallTypesVideo(); - if ($listResp->isSuccessful() && $listResp->getData()->callTypes !== null && count($listResp->getData()->callTypes) > 10) { - $builtIn = ['default', 'livestream', 'audio_room', 'development']; - foreach ($listResp->getData()->callTypes as $ctName => $ct) { - if (!in_array($ctName, $builtIn, true)) { - try { - $this->deleteCallTypeVideo($ctName); - } catch (\Exception $e) { - // Ignore - } - } - } - } - } catch (\Exception $e) { - // Ignore list errors - } - - $response = $this->createCallTypeVideo(new GeneratedModels\CreateCallTypeRequest( - name: $name, - grants: [ - 'admin' => ['send-audio', 'send-video', 'mute-users'], - 'user' => ['send-audio', 'send-video'], - ], - settings: new GeneratedModels\CallSettingsRequest( - audio: new GeneratedModels\AudioSettingsRequest( - defaultDevice: 'speaker', - micDefaultOn: true, - ), - screensharing: new GeneratedModels\ScreensharingSettingsRequest( - accessRequestEnabled: false, - enabled: true, - ), - ), - notificationSettings: new GeneratedModels\NotificationSettingsRequest( - enabled: true, - callNotification: new GeneratedModels\EventNotificationSettingsRequest( - enabled: true, - apns: new GeneratedModels\APNSPayload( - title: '{{ user.display_name }} invites you to a call', - body: '', - ), - ), - sessionStarted: new GeneratedModels\EventNotificationSettingsRequest( - enabled: false, - apns: new GeneratedModels\APNSPayload( - title: '{{ user.display_name }} invites you to a call', - body: '', - ), - ), - callLiveStarted: new GeneratedModels\EventNotificationSettingsRequest( - enabled: false, - apns: new GeneratedModels\APNSPayload( - title: '{{ user.display_name }} invites you to a call', - body: '', - ), - ), - callRing: new GeneratedModels\EventNotificationSettingsRequest( - enabled: false, - apns: new GeneratedModels\APNSPayload( - title: '{{ user.display_name }} invites you to a call', - body: '', - ), - ), - ), - )); - $this->assertResponseSuccess($response, 'create call type'); - $this->createdCallTypes[] = $name; - return $response->getData(); - } - // ========================================================================= // Tests // ========================================================================= /** * CRUDCallTypeOperations: Create call type with settings, update, read, delete. + * + * @test */ - public function testCRUDCallTypeOperations(): void + public function cRUDCallTypeOperations(): void { $callTypeName = 'calltype-' . $this->randomString(10); $data = $this->createTrackedCallType($callTypeName); // Verify creation - $this->assertEquals($callTypeName, $data->name); - $this->assertTrue($data->settings->audio->micDefaultOn); - $this->assertEquals('speaker', $data->settings->audio->defaultDevice); - $this->assertFalse($data->settings->screensharing->accessRequestEnabled); - $this->assertTrue($data->settings->screensharing->enabled); - $this->assertTrue($data->notificationSettings->enabled); - $this->assertTrue($data->notificationSettings->callNotification->enabled); - $this->assertFalse($data->notificationSettings->sessionStarted->enabled); + self::assertSame($callTypeName, $data->name); + self::assertTrue($data->settings->audio->micDefaultOn); + self::assertSame('speaker', $data->settings->audio->defaultDevice); + self::assertFalse($data->settings->screensharing->accessRequestEnabled); + self::assertTrue($data->settings->screensharing->enabled); + self::assertTrue($data->notificationSettings->enabled); + self::assertTrue($data->notificationSettings->callNotification->enabled); + self::assertFalse($data->notificationSettings->sessionStarted->enabled); // Update call type settings (retry until eventual consistency settles) $updateResp = $this->retryUntilSuccess(fn () => $this->updateCallTypeVideo($callTypeName, new GeneratedModels\UpdateCallTypeRequest( @@ -141,28 +66,30 @@ public function testCRUDCallTypeOperations(): void )), maxAttempts: 10, sleepMs: 1000); $this->assertResponseSuccess($updateResp, 'update call type'); $updated = $updateResp->getData(); - $this->assertFalse($updated->settings->audio->micDefaultOn); - $this->assertEquals('earpiece', $updated->settings->audio->defaultDevice); - $this->assertEquals('disabled', $updated->settings->recording->mode); - $this->assertTrue($updated->settings->backstage->enabled); + self::assertFalse($updated->settings->audio->micDefaultOn); + self::assertSame('earpiece', $updated->settings->audio->defaultDevice); + self::assertSame('disabled', $updated->settings->recording->mode); + self::assertTrue($updated->settings->backstage->enabled); // Read call type $getResp = $this->getCallTypeVideo($callTypeName); $this->assertResponseSuccess($getResp, 'get call type'); - $this->assertEquals($callTypeName, $getResp->getData()->name); + self::assertSame($callTypeName, $getResp->getData()->name); // Delete call type (handled by cleanup, but verify it works) $delResp = $this->deleteCallTypeVideo($callTypeName); $this->assertResponseSuccess($delResp, 'delete call type'); // Remove from tracked list since we already deleted it - $this->createdCallTypes = array_filter($this->createdCallTypes, fn($n) => $n !== $callTypeName); + $this->createdCallTypes = array_filter($this->createdCallTypes, static fn ($n) => $n !== $callTypeName); } /** * CreateCallWithMembers: Create call, add members. + * + * @test */ - public function testCreateCallWithMembers(): void + public function createCallWithMembers(): void { $userIDs = $this->createTestUsers(2); $callID = 'test-call-' . $this->randomString(10); @@ -180,15 +107,17 @@ public function testCreateCallWithMembers(): void $this->createdCalls[] = ['type' => 'default', 'id' => $callID]; $data = $response->getData(); - $this->assertNotNull($data->call); - $this->assertNotNull($data->members); - $this->assertCount(2, $data->members); + self::assertNotNull($data->call); + self::assertNotNull($data->members); + self::assertCount(2, $data->members); } /** * BlockUnblockUserFromCalls: Block user from call, verify; unblock. + * + * @test */ - public function testBlockUnblockUserFromCalls(): void + public function blockUnblockUserFromCalls(): void { $userIDs = $this->createTestUsers(2); $callID = 'test-call-' . $this->randomString(10); @@ -207,7 +136,7 @@ public function testBlockUnblockUserFromCalls(): void $getResp = $this->getCall('default', $callID); $this->assertResponseSuccess($getResp, 'get call after block'); $blockedIds = $getResp->getData()->call->blockedUserIds ?? []; - $this->assertContains($userIDs[1], $blockedIds, 'User should be in blocked list. Got: ' . json_encode($blockedIds)); + self::assertContains($userIDs[1], $blockedIds, 'User should be in blocked list. Got: ' . json_encode($blockedIds)); // Unblock user $unblockResp = $this->unblockUserInCall('default', $callID, new GeneratedModels\UnblockUserRequest( @@ -219,13 +148,15 @@ public function testBlockUnblockUserFromCalls(): void $getResp2 = $this->getCall('default', $callID); $this->assertResponseSuccess($getResp2, 'get call after unblock'); $blocked = $getResp2->getData()->call->blockedUserIds ?? []; - $this->assertNotContains($userIDs[1], $blocked); + self::assertNotContains($userIDs[1], $blocked); } /** * SendCustomEvent: Send custom event in a call. + * + * @test */ - public function testSendCustomEvent(): void + public function sendCustomEvent(): void { $userIDs = $this->createTestUsers(1); $callID = 'test-call-' . $this->randomString(10); @@ -241,8 +172,10 @@ public function testSendCustomEvent(): void /** * MuteAll: Mute all users in a call. + * + * @test */ - public function testMuteAll(): void + public function muteAll(): void { $userIDs = $this->createTestUsers(1); $callID = 'test-call-' . $this->randomString(10); @@ -259,8 +192,10 @@ public function testMuteAll(): void /** * MuteSomeUsers: Mute specific users (audio, video, screenshare). + * + * @test */ - public function testMuteSomeUsers(): void + public function muteSomeUsers(): void { $userIDs = $this->createTestUsers(3); $callID = 'test-call-' . $this->randomString(10); @@ -280,8 +215,10 @@ public function testMuteSomeUsers(): void /** * UpdateUserPermissions: Revoke/grant permissions in a call. + * + * @test */ - public function testUpdateUserPermissions(): void + public function updateUserPermissions(): void { $userIDs = $this->createTestUsers(2); $callID = 'test-call-' . $this->randomString(10); @@ -305,8 +242,10 @@ public function testUpdateUserPermissions(): void /** * DeactivateUser: Deactivate/reactivate users, batch deactivate. + * + * @test */ - public function testDeactivateUser(): void + public function deactivateUser(): void { $userIDs = $this->createTestUsers(2); @@ -324,16 +263,18 @@ public function testDeactivateUser(): void )); $this->assertResponseSuccess($batchResp, 'batch deactivate users'); $taskID = $batchResp->getData()->taskID; - $this->assertNotEmpty($taskID); + self::assertNotEmpty($taskID); $taskResult = $this->waitForTask($taskID); - $this->assertEquals('completed', $taskResult->status); + self::assertSame('completed', $taskResult->status); } /** * CreateCallWithSessionTimer: Create call with max_duration_seconds, update. + * + * @test */ - public function testCreateCallWithSessionTimer(): void + public function createCallWithSessionTimer(): void { $userIDs = $this->createTestUsers(1); $callID = 'test-call-' . $this->randomString(10); @@ -351,7 +292,7 @@ public function testCreateCallWithSessionTimer(): void )); $this->assertResponseSuccess($response, 'create call with session timer'); $this->createdCalls[] = ['type' => 'default', 'id' => $callID]; - $this->assertEquals(3600, $response->getData()->call->settings->limits->maxDurationSeconds); + self::assertSame(3600, $response->getData()->call->settings->limits->maxDurationSeconds); // Update to 7200 $updateResp = $this->updateCall('default', $callID, new GeneratedModels\UpdateCallRequest( @@ -362,7 +303,7 @@ public function testCreateCallWithSessionTimer(): void ), )); $this->assertResponseSuccess($updateResp, 'update session timer'); - $this->assertEquals(7200, $updateResp->getData()->call->settings->limits->maxDurationSeconds); + self::assertSame(7200, $updateResp->getData()->call->settings->limits->maxDurationSeconds); // Reset to 0 $resetResp = $this->updateCall('default', $callID, new GeneratedModels\UpdateCallRequest( @@ -373,13 +314,15 @@ public function testCreateCallWithSessionTimer(): void ), )); $this->assertResponseSuccess($resetResp, 'reset session timer'); - $this->assertEquals(0, $resetResp->getData()->call->settings->limits->maxDurationSeconds); + self::assertSame(0, $resetResp->getData()->call->settings->limits->maxDurationSeconds); } /** * UserBlocking: Block/unblock user, verify blocked list. + * + * @test */ - public function testUserBlocking(): void + public function userBlocking(): void { $userIDs = $this->createTestUsers(2); @@ -394,9 +337,9 @@ public function testUserBlocking(): void $getResp = $this->client->getBlockedUsers($userIDs[0]); $this->assertResponseSuccess($getResp, 'get blocked users'); $blocks = $getResp->getData()->blocks; - $this->assertCount(1, $blocks); - $this->assertEquals($userIDs[0], $blocks[0]->userID); - $this->assertEquals($userIDs[1], $blocks[0]->blockedUserID); + self::assertCount(1, $blocks); + self::assertSame($userIDs[0], $blocks[0]->userID); + self::assertSame($userIDs[1], $blocks[0]->blockedUserID); // Unblock $unblockResp = $this->client->unblockUsers(new GeneratedModels\UnblockUsersRequest( @@ -408,13 +351,15 @@ public function testUserBlocking(): void // Verify empty $getResp2 = $this->client->getBlockedUsers($userIDs[0]); $this->assertResponseSuccess($getResp2, 'get blocked users after unblock'); - $this->assertEmpty($getResp2->getData()->blocks); + self::assertEmpty($getResp2->getData()->blocks); } /** * CreateCallWithBackstageAndJoinAhead: Create call with backstage + join_ahead_time. + * + * @test */ - public function testCreateCallWithBackstageAndJoinAhead(): void + public function createCallWithBackstageAndJoinAhead(): void { $userIDs = $this->createTestUsers(1); $callID = 'test-call-' . $this->randomString(10); @@ -435,7 +380,7 @@ public function testCreateCallWithBackstageAndJoinAhead(): void )); $this->assertResponseSuccess($response, 'create call with backstage'); $this->createdCalls[] = ['type' => 'default', 'id' => $callID]; - $this->assertEquals(300, $response->getData()->call->joinAheadTimeSeconds); + self::assertSame(300, $response->getData()->call->joinAheadTimeSeconds); // Update join ahead to 600 $updateResp = $this->updateCall('default', $callID, new GeneratedModels\UpdateCallRequest( @@ -446,7 +391,7 @@ public function testCreateCallWithBackstageAndJoinAhead(): void ), )); $this->assertResponseSuccess($updateResp, 'update join ahead time'); - $this->assertEquals(600, $updateResp->getData()->call->joinAheadTimeSeconds); + self::assertSame(600, $updateResp->getData()->call->joinAheadTimeSeconds); // Reset to 0 $resetResp = $this->updateCall('default', $callID, new GeneratedModels\UpdateCallRequest( @@ -457,13 +402,15 @@ public function testCreateCallWithBackstageAndJoinAhead(): void ), )); $this->assertResponseSuccess($resetResp, 'reset join ahead time'); - $this->assertEquals(0, $resetResp->getData()->call->joinAheadTimeSeconds); + self::assertSame(0, $resetResp->getData()->call->joinAheadTimeSeconds); } /** * DeleteCall (soft): Soft delete call, verify not found. + * + * @test */ - public function testDeleteCallSoft(): void + public function deleteCallSoft(): void { $userIDs = $this->createTestUsers(1); $callID = 'test-call-' . $this->randomString(10); @@ -473,25 +420,27 @@ public function testDeleteCallSoft(): void // Soft delete $delResp = $this->deleteCall('default', $callID, new GeneratedModels\DeleteCallRequest()); $this->assertResponseSuccess($delResp, 'soft delete call'); - $this->assertNotNull($delResp->getData()->call); - $this->assertNull($delResp->getData()->taskID); + self::assertNotNull($delResp->getData()->call); + self::assertNull($delResp->getData()->taskID); // Verify not found try { $this->getCall('default', $callID); - $this->fail('Expected error when getting soft-deleted call'); + self::fail('Expected error when getting soft-deleted call'); } catch (\Exception $e) { - $this->assertStringContainsString("Can't find call with id", $e->getMessage()); + self::assertStringContainsString("Can't find call with id", $e->getMessage()); } // Remove from tracked list since already deleted - $this->createdCalls = array_filter($this->createdCalls, fn($c) => $c['id'] !== $callID); + $this->createdCalls = array_filter($this->createdCalls, static fn ($c) => $c['id'] !== $callID); } /** * HardDeleteCall: Hard delete with task polling. + * + * @test */ - public function testHardDeleteCall(): void + public function hardDeleteCall(): void { $userIDs = $this->createTestUsers(1); $callID = 'test-call-' . $this->randomString(10); @@ -504,28 +453,30 @@ public function testHardDeleteCall(): void )); $this->assertResponseSuccess($delResp, 'hard delete call'); $taskID = $delResp->getData()->taskID; - $this->assertNotNull($taskID); + self::assertNotNull($taskID); // Wait for task completion $taskResult = $this->waitForTask($taskID); - $this->assertEquals('completed', $taskResult->status); + self::assertSame('completed', $taskResult->status); // Verify not found try { $this->getCall('default', $callID); - $this->fail('Expected error when getting hard-deleted call'); + self::fail('Expected error when getting hard-deleted call'); } catch (\Exception $e) { - $this->assertStringContainsString("Can't find call with id", $e->getMessage()); + self::assertStringContainsString("Can't find call with id", $e->getMessage()); } // Remove from tracked list since already deleted - $this->createdCalls = array_filter($this->createdCalls, fn($c) => $c['id'] !== $callID); + $this->createdCalls = array_filter($this->createdCalls, static fn ($c) => $c['id'] !== $callID); } /** * Teams: Create user with teams, create call with team, query. + * + * @test */ - public function testTeams(): void + public function teams(): void { $userID = 'test-user-' . uniqid(); $callID = 'test-call-' . $this->randomString(10); @@ -550,7 +501,7 @@ public function testTeams(): void )); $this->assertResponseSuccess($response, 'create call with team'); $this->createdCalls[] = ['type' => 'default', 'id' => $callID]; - $this->assertEquals('blue', $response->getData()->call->team); + self::assertSame('blue', $response->getData()->call->team); // Query users with team filter $usersResp = $this->client->queryUsers(new GeneratedModels\QueryUsersPayload( @@ -560,9 +511,9 @@ public function testTeams(): void ], )); $this->assertResponseSuccess($usersResp, 'query users with teams'); - $this->assertGreaterThan(0, count($usersResp->getData()->users)); - $foundIDs = array_map(fn($u) => $u->id, $usersResp->getData()->users); - $this->assertContains($userID, $foundIDs); + self::assertGreaterThan(0, count($usersResp->getData()->users)); + $foundIDs = array_map(static fn ($u) => $u->id, $usersResp->getData()->users); + self::assertContains($userID, $foundIDs); // Query calls with team filter $callsResp = $this->queryCalls(new GeneratedModels\QueryCallsRequest( @@ -572,13 +523,15 @@ public function testTeams(): void ], )); $this->assertResponseSuccess($callsResp, 'query calls with team'); - $this->assertGreaterThan(0, count($callsResp->getData()->calls)); + self::assertGreaterThan(0, count($callsResp->getData()->calls)); } /** * ExternalStorageOperations: Create/list/delete external storage. + * + * @test */ - public function testExternalStorageOperations(): void + public function externalStorageOperations(): void { $uniqueName = 'test-storage-' . $this->randomString(10); @@ -604,16 +557,17 @@ public function testExternalStorageOperations(): void $listResp = $this->client->listExternalStorage(); $this->assertResponseSuccess($listResp, 'list external storage'); $storages = $listResp->getData()->externalStorages; - $this->assertNotEmpty($storages); + self::assertNotEmpty($storages); $found = false; foreach ($storages as $storage) { if ($storage->name === $uniqueName) { $found = true; + break; } } - $this->assertTrue($found, 'Created external storage should be in the list'); + self::assertTrue($found, 'Created external storage should be in the list'); // Delete with retry for eventual consistency $this->retryUntilSuccess(function () use ($uniqueName) { @@ -622,13 +576,15 @@ public function testExternalStorageOperations(): void }, maxAttempts: 5, sleepMs: 2000); // Remove from tracked list - $this->createdExternalStorages = array_filter($this->createdExternalStorages, fn($n) => $n !== $uniqueName); + $this->createdExternalStorages = array_filter($this->createdExternalStorages, static fn ($n) => $n !== $uniqueName); } /** * EnableCallRecordingAndBackstageMode: Update call settings for recording and backstage. + * + * @test */ - public function testEnableCallRecordingAndBackstageMode(): void + public function enableCallRecordingAndBackstageMode(): void { $userIDs = $this->createTestUsers(1); $callID = 'test-call-' . $this->randomString(10); @@ -645,7 +601,7 @@ public function testEnableCallRecordingAndBackstageMode(): void ), )); $this->assertResponseSuccess($recResp, 'enable recording'); - $this->assertEquals('available', $recResp->getData()->call->settings->recording->mode); + self::assertSame('available', $recResp->getData()->call->settings->recording->mode); // Enable backstage $bsResp = $this->updateCall('default', $callID, new GeneratedModels\UpdateCallRequest( @@ -656,13 +612,15 @@ public function testEnableCallRecordingAndBackstageMode(): void ), )); $this->assertResponseSuccess($bsResp, 'enable backstage'); - $this->assertTrue($bsResp->getData()->call->settings->backstage->enabled); + self::assertTrue($bsResp->getData()->call->settings->backstage->enabled); } /** * DeleteRecordingsAndTranscriptions: Attempt to delete non-existent recording/transcription (error case). + * + * @test */ - public function testDeleteRecordingsAndTranscriptions(): void + public function deleteRecordingsAndTranscriptions(): void { $userIDs = $this->createTestUsers(1); $callID = 'test-call-' . $this->randomString(10); @@ -672,19 +630,97 @@ public function testDeleteRecordingsAndTranscriptions(): void // Delete non-existent recording should fail try { $this->deleteRecording('default', $callID, 'non-existent-session', 'non-existent-filename'); - $this->fail('Expected error when deleting non-existent recording'); + self::fail('Expected error when deleting non-existent recording'); } catch (\Exception $e) { // Expected - API returns error for non-existent recording - $this->assertNotEmpty($e->getMessage()); + self::assertNotEmpty($e->getMessage()); } // Delete non-existent transcription should fail try { $this->deleteTranscription('default', $callID, 'non-existent-session', 'non-existent-filename'); - $this->fail('Expected error when deleting non-existent transcription'); + self::fail('Expected error when deleting non-existent transcription'); } catch (\Exception $e) { // Expected - API returns error for non-existent transcription - $this->assertNotEmpty($e->getMessage()); + self::assertNotEmpty($e->getMessage()); } } + + /** + * Helper: create a unique call type and track for cleanup. + */ + private function createTrackedCallType(string $name): GeneratedModels\CreateCallTypeResponse + { + // Clean up excess call types first (same pattern as Go SDK) + try { + $listResp = $this->listCallTypesVideo(); + if ($listResp->isSuccessful() && $listResp->getData()->callTypes !== null && count($listResp->getData()->callTypes) > 10) { + $builtIn = ['default', 'livestream', 'audio_room', 'development']; + foreach ($listResp->getData()->callTypes as $ctName => $ct) { + if (!in_array($ctName, $builtIn, true)) { + try { + $this->deleteCallTypeVideo($ctName); + } catch (\Exception $e) { + // Ignore + } + } + } + } + } catch (\Exception $e) { + // Ignore list errors + } + + $response = $this->createCallTypeVideo(new GeneratedModels\CreateCallTypeRequest( + name: $name, + grants: [ + 'admin' => ['send-audio', 'send-video', 'mute-users'], + 'user' => ['send-audio', 'send-video'], + ], + settings: new GeneratedModels\CallSettingsRequest( + audio: new GeneratedModels\AudioSettingsRequest( + defaultDevice: 'speaker', + micDefaultOn: true, + ), + screensharing: new GeneratedModels\ScreensharingSettingsRequest( + accessRequestEnabled: false, + enabled: true, + ), + ), + notificationSettings: new GeneratedModels\NotificationSettingsRequest( + enabled: true, + callNotification: new GeneratedModels\EventNotificationSettingsRequest( + enabled: true, + apns: new GeneratedModels\APNSPayload( + title: '{{ user.display_name }} invites you to a call', + body: '', + ), + ), + sessionStarted: new GeneratedModels\EventNotificationSettingsRequest( + enabled: false, + apns: new GeneratedModels\APNSPayload( + title: '{{ user.display_name }} invites you to a call', + body: '', + ), + ), + callLiveStarted: new GeneratedModels\EventNotificationSettingsRequest( + enabled: false, + apns: new GeneratedModels\APNSPayload( + title: '{{ user.display_name }} invites you to a call', + body: '', + ), + ), + callRing: new GeneratedModels\EventNotificationSettingsRequest( + enabled: false, + apns: new GeneratedModels\APNSPayload( + title: '{{ user.display_name }} invites you to a call', + body: '', + ), + ), + ), + )); + $this->assertResponseSuccess($response, 'create call type'); + $this->createdCallTypes[] = $name; + + return $response->getData(); + } } diff --git a/tests/WebhookTest.php b/tests/WebhookTest.php index 97cd21e4..21f74623 100644 --- a/tests/WebhookTest.php +++ b/tests/WebhookTest.php @@ -1,88 +1,115 @@ -computeSignature($this->body, $this->secret); - $this->assertTrue(Webhook::verifySignature($this->body, $signature, $this->secret)); + self::assertTrue(Webhook::verifySignature($this->body, $signature, $this->secret)); } - public function testVerifySignatureWrongSignature(): void + /** + * @test + */ + public function verifySignatureWrongSignature(): void { - $this->assertFalse(Webhook::verifySignature($this->body, 'invalidsignature', $this->secret)); + self::assertFalse(Webhook::verifySignature($this->body, 'invalidsignature', $this->secret)); } - public function testVerifySignatureTamperedBody(): void + /** + * @test + */ + public function verifySignatureTamperedBody(): void { $signature = $this->computeSignature($this->body, $this->secret); - $this->assertFalse(Webhook::verifySignature('{"type":"tampered"}', $signature, $this->secret)); + self::assertFalse(Webhook::verifySignature('{"type":"tampered"}', $signature, $this->secret)); } - public function testVerifySignatureWrongSecret(): void + /** + * @test + */ + public function verifySignatureWrongSecret(): void { $signature = $this->computeSignature($this->body, $this->secret); - $this->assertFalse(Webhook::verifySignature($this->body, $signature, 'wrong-secret')); + self::assertFalse(Webhook::verifySignature($this->body, $signature, 'wrong-secret')); } - public function testVerifySignatureEmptySignature(): void + /** + * @test + */ + public function verifySignatureEmptySignature(): void { - $this->assertFalse(Webhook::verifySignature($this->body, '', $this->secret)); + self::assertFalse(Webhook::verifySignature($this->body, '', $this->secret)); } - public function testGetEventTypeValid(): void + /** + * @test + */ + public function getEventTypeValid(): void { - $this->assertEquals('message.new', Webhook::getEventType('{"type":"message.new"}')); + self::assertSame('message.new', Webhook::getEventType('{"type":"message.new"}')); } - public function testGetEventTypeFromArray(): void + /** + * @test + */ + public function getEventTypeFromArray(): void { - $this->assertEquals('message.new', Webhook::getEventType(['type' => 'message.new'])); + self::assertSame('message.new', Webhook::getEventType(['type' => 'message.new'])); } - public function testGetEventTypeMissingField(): void + /** + * @test + */ + public function getEventTypeMissingField(): void { - $this->assertNull(Webhook::getEventType('{"foo":"bar"}')); + self::assertNull(Webhook::getEventType('{"foo":"bar"}')); } - public function testGetEventTypeInvalidJson(): void + /** + * @test + */ + public function getEventTypeInvalidJson(): void { $this->expectException(\InvalidArgumentException::class); Webhook::getEventType('not json'); } - public function testGetEventTypeEmptyObject(): void + /** + * @test + */ + public function getEventTypeEmptyObject(): void { - $this->assertNull(Webhook::getEventType('{}')); + self::assertNull(Webhook::getEventType('{}')); } /** - * @dataProvider webhookEventProvider + * @dataProvider provideParseWebhookEventCases + * + * @test */ - public function testParseWebhookEvent(string $eventType, string $expectedClassName): void + public function parseWebhookEvent(string $eventType, string $expectedClassName): void { $payload = '{"type":"' . $eventType . '"}'; $event = Webhook::parseWebhookEvent($payload); - $this->assertNotNull($event); + self::assertNotNull($event); $parts = explode('\\', get_class($event)); - $this->assertEquals($expectedClassName, end($parts)); + self::assertSame($expectedClassName, end($parts)); } - public static function webhookEventProvider(): array + public static function provideParseWebhookEventCases(): iterable { return [ '*' => ['*', 'CustomEvent'], @@ -254,24 +281,38 @@ public static function webhookEventProvider(): array ]; } - public function testParseWebhookEventUnknownType(): void + /** + * @test + */ + public function parseWebhookEventUnknownType(): void { $this->expectException(\InvalidArgumentException::class); $this->expectExceptionMessage('Unknown webhook event type'); Webhook::parseWebhookEvent('{"type":"unknown.event"}'); } - public function testParseWebhookEventMissingType(): void + /** + * @test + */ + public function parseWebhookEventMissingType(): void { $this->expectException(\InvalidArgumentException::class); $this->expectExceptionMessage("missing 'type' field"); Webhook::parseWebhookEvent('{"foo":"bar"}'); } - public function testParseWebhookEventInvalidJson(): void + /** + * @test + */ + public function parseWebhookEventInvalidJson(): void { $this->expectException(\InvalidArgumentException::class); $this->expectExceptionMessage('Invalid JSON'); Webhook::parseWebhookEvent('not json'); } + + private function computeSignature(string $body, string $secret): string + { + return hash_hmac('sha256', $body, $secret); + } } From a3ceb3b638f2c01f374017487133302dc435daef Mon Sep 17 00:00:00 2001 From: Aditya Agarwal Date: Fri, 20 Mar 2026 17:35:13 +0100 Subject: [PATCH 2/2] [FEEDS-1303] fix chat integration test method collisions Rename chat integration test methods that were shadowing shared ChatTestCase helpers with incompatible signatures so PHPUnit can load the suite and unit CI can run again. Made-with: Cursor --- tests/Integration/ChatMessageIntegrationTest.php | 12 ++++++------ tests/Integration/ChatMiscIntegrationTest.php | 14 +++++++------- tests/Integration/ChatPollsIntegrationTest.php | 2 +- tests/Integration/ChatReactionIntegrationTest.php | 2 +- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/tests/Integration/ChatMessageIntegrationTest.php b/tests/Integration/ChatMessageIntegrationTest.php index 0d266066..bbce851e 100644 --- a/tests/Integration/ChatMessageIntegrationTest.php +++ b/tests/Integration/ChatMessageIntegrationTest.php @@ -52,7 +52,7 @@ public function sendAndGetMessage(): void /** * @test */ - public function getManyMessages(): void + public function getsManyMessages(): void { [$type, $channelID] = $this->createTestChannelWithMembers($this->userID, [$this->userID]); @@ -70,7 +70,7 @@ public function getManyMessages(): void /** * @test */ - public function updateMessage(): void + public function updatesMessage(): void { [$type, $channelID] = $this->createTestChannelWithMembers($this->userID, [$this->userID]); $msgID = $this->sendTestMessage($type, $channelID, $this->userID, 'Original text'); @@ -211,7 +211,7 @@ public function pinExpiration(): void /** * @test */ - public function translateMessage(): void + public function translatesMessage(): void { [$type, $channelID] = $this->createTestChannelWithMembers($this->userID, [$this->userID]); $msgID = $this->sendTestMessage($type, $channelID, $this->userID, 'Hello, how are you?'); @@ -262,7 +262,7 @@ public function threadReply(): void /** * @test */ - public function searchMessages(): void + public function searchesMessages(): void { [$type, $channelID] = $this->createTestChannelWithMembers($this->userID, [$this->userID]); @@ -441,7 +441,7 @@ public function pendingFalse(): void /** * @test */ - public function queryMessageHistory(): void + public function queriesMessageHistory(): void { [$type, $channelID] = $this->createTestChannelWithMembers($this->userID, [$this->userID, $this->userID2]); @@ -624,7 +624,7 @@ public function keepChannelHidden(): void /** * @test */ - public function undeleteMessage(): void + public function undeletesMessage(): void { [$type, $channelID] = $this->createTestChannelWithMembers($this->userID, [$this->userID]); $msgID = $this->sendTestMessage($type, $channelID, $this->userID, 'Message to undelete'); diff --git a/tests/Integration/ChatMiscIntegrationTest.php b/tests/Integration/ChatMiscIntegrationTest.php index 86c5fec9..26e38539 100644 --- a/tests/Integration/ChatMiscIntegrationTest.php +++ b/tests/Integration/ChatMiscIntegrationTest.php @@ -259,7 +259,7 @@ public function createUpdateDeleteChannelType(): void /** * @test */ - public function listChannelTypes(): void + public function listsChannelTypes(): void { $resp = $this->listChannelTypes(); $this->assertResponseSuccess($resp, 'list channel types'); @@ -313,7 +313,7 @@ public function getPermission(): void /** * @test */ - public function queryBannedUsers(): void + public function queriesBannedUsers(): void { $shared = $this->getSharedUserIDs(); $adminID = $shared[0]; @@ -406,7 +406,7 @@ public function getAppSettings(): void /** * @test */ - public function exportChannels(): void + public function exportsChannels(): void { $userID = $this->getSharedUserIDs()[0]; @@ -490,7 +490,7 @@ public function threads(): void /** * @test */ - public function getUnreadCounts(): void + public function getsUnreadCounts(): void { $shared = $this->getSharedUserIDs(); $userID = $shared[0]; @@ -507,7 +507,7 @@ public function getUnreadCounts(): void /** * @test */ - public function getUnreadCountsBatch(): void + public function getsUnreadCountsBatch(): void { $shared = $this->getSharedUserIDs(); $userID = $shared[0]; @@ -591,7 +591,7 @@ public function reminders(): void /** * @test */ - public function sendUserCustomEvent(): void + public function sendsUserCustomEvent(): void { $userID = $this->getSharedUserIDs()[0]; @@ -611,7 +611,7 @@ public function sendUserCustomEvent(): void /** * @test */ - public function queryTeamUsageStats(): void + public function queriesTeamUsageStats(): void { try { $resp = $this->queryTeamUsageStats(new GeneratedModels\QueryTeamUsageStatsRequest()); diff --git a/tests/Integration/ChatPollsIntegrationTest.php b/tests/Integration/ChatPollsIntegrationTest.php index 426946d4..59a2b74c 100644 --- a/tests/Integration/ChatPollsIntegrationTest.php +++ b/tests/Integration/ChatPollsIntegrationTest.php @@ -108,7 +108,7 @@ public function createAndQueryPoll(): void * * @test */ - public function castPollVote(): void + public function castsPollVote(): void { $userIDs = $this->getSharedUserIDs(); $this->pollCleanupUserID = $userIDs[0]; diff --git a/tests/Integration/ChatReactionIntegrationTest.php b/tests/Integration/ChatReactionIntegrationTest.php index ccbca96c..fb92a2d4 100644 --- a/tests/Integration/ChatReactionIntegrationTest.php +++ b/tests/Integration/ChatReactionIntegrationTest.php @@ -59,7 +59,7 @@ public function sendAndGetReactions(): void * * @test */ - public function deleteReaction(): void + public function deletesReaction(): void { $userIDs = [$this->getSharedUserIDs()[0]]; [$type, $id] = $this->createTestChannelWithMembers($userIDs[0], $userIDs);