diff --git a/.github/workflows/sdk-size-metrics.yml b/.github/workflows/sdk-size-metrics.yml index a05407b1cab..e71245a9a0b 100644 --- a/.github/workflows/sdk-size-metrics.yml +++ b/.github/workflows/sdk-size-metrics.yml @@ -18,6 +18,7 @@ jobs: runs-on: macos-15 env: GITHUB_TOKEN: '${{ secrets.CI_BOT_GITHUB_TOKEN }}' + GITHUB_PR_NUM: ${{ github.event.pull_request.number }} steps: - name: Connect Bot uses: webfactory/ssh-agent@v0.7.0 @@ -28,10 +29,13 @@ jobs: - uses: ./.github/actions/bootstrap - - name: Run SDK Size Metrics + - name: Run General SDK Size Metrics run: bundle exec fastlane show_frameworks_sizes timeout-minutes: 30 env: - GITHUB_PR_NUM: ${{ github.event.pull_request.number }} MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} APPSTORE_API_KEY: ${{ secrets.APPSTORE_API_KEY }} + + - name: Run Detailed SDK Size Metrics + run: bundle exec fastlane size_analyze + timeout-minutes: 30 diff --git a/.github/workflows/sync-mock-server.yml b/.github/workflows/sync-mock-server.yml index 11ea21a4ed6..78d7056110d 100644 --- a/.github/workflows/sync-mock-server.yml +++ b/.github/workflows/sync-mock-server.yml @@ -2,8 +2,8 @@ name: Mock Server on: schedule: - # Runs "At 00:00 on day-of-month 1 and 15" - - cron: '0 0 1,15 * *' + # Runs "At 00:00 on day-of-month 1" + - cron: '0 0 1 * *' workflow_dispatch: diff --git a/.gitignore b/.gitignore index cef555267fc..3b590688190 100644 --- a/.gitignore +++ b/.gitignore @@ -99,6 +99,7 @@ App Thinning Size Report.txt app-thinning.plist *.dmg *.pkg* +*LinkMap.txt # gcloud google-cloud-sdk diff --git a/.swiftformat b/.swiftformat index 8f422d51059..9d2986888d0 100644 --- a/.swiftformat +++ b/.swiftformat @@ -17,7 +17,6 @@ --rules fileHeader --rules indent --rules initCoderUnavailable ---rules isEmpty --rules leadingDelimiters --rules linebreakAtEndOfFile --rules linebreaks @@ -35,7 +34,7 @@ --rules redundantRawValues --rules redundantVoidReturnType --rules semicolons ---rules sortedImports +--rules sortImports --rules spaceAroundBraces --rules spaceAroundBrackets --rules spaceAroundComments @@ -81,4 +80,4 @@ --wrapcollections before-first # Exclude paths ---exclude **/Generated,Sources/StreamChatUI/StreamNuke,Sources/StreamChatUI/StreamSwiftyGif,Sources/StreamChatUI/StreamDifferenceKit +--exclude **/Generated,Sources/StreamChatUI/StreamNuke,Sources/StreamChatUI/StreamSwiftyGif,Sources/StreamChatUI/StreamDifferenceKit,vendor/bundle,Pods,spm_cache,derived_data,.build diff --git a/.swiftlint.yml b/.swiftlint.yml index 2765c01df9d..485ef7d1040 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -1,21 +1,16 @@ excluded: - Scripts - - Package.swift - - Dependencies - Sources/StreamChatUI/Generated - Sources/StreamChatUI/StreamSwiftyGif - Sources/StreamChatUI/StreamNuke - Sources/StreamChat/StreamStarscream - Sources/StreamChatUI/StreamDifferenceKit - - vendor - UISDKdocumentation - Tests - - TestTools - Pods - .build - spm_cache - vendor/bundle - - .ruby-lsp - derived_data only_rules: @@ -56,7 +51,6 @@ only_rules: - trailing_comma - trailing_newline - trailing_semicolon - - trailing_whitespace - unneeded_break_in_switch - unneeded_override - unused_closure_parameter @@ -68,9 +62,6 @@ only_rules: multiline_arguments: only_enforce_after_first_closure_on_first_line: true -trailing_whitespace: - ignores_empty_lines: true - file_name_no_space: severity: error diff --git a/CHANGELOG.md b/CHANGELOG.md index d70dcc69558..46c06107253 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,24 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### 🔄 Changed +# [4.91.0](https://github.com/GetStream/stream-chat-swift/releases/tag/4.91.0) +_October 22, 2025_ + +## StreamChat +### ✅ Added +- Add support for deleting messages only for the current user [#3836](https://github.com/GetStream/stream-chat-swift/pull/3836) + - Add `ChatMessageController.deleteMessageForMe()` + - Add `ChatMessage.deletedForMe` +- Allow observing poll changes in `PollVoteListController` [#3849](https://github.com/GetStream/stream-chat-swift/pull/3849) +### 🐞 Fixed +- Fix logout not clearing token when current user had no device registered [#3838](https://github.com/GetStream/stream-chat-swift/pull/3838) +- Fix `PollVoteListController` not updating votes on the vote cast event [#3849](https://github.com/GetStream/stream-chat-swift/pull/3849) +- Fix showing channel when receiving a campaign message with `show_channels` false [#3851](https://github.com/GetStream/stream-chat-swift/pull/3851) + +## StreamChatUI +### 🐞 Fixed +- Fix `PollResultsVoteListVC` not updating the vote count [#3849](https://github.com/GetStream/stream-chat-swift/pull/3849) + # [4.90.0](https://github.com/GetStream/stream-chat-swift/releases/tag/4.90.0) _October 07, 2025_ diff --git a/DemoApp/StreamChat/Components/DemoChatMessageActionsVC.swift b/DemoApp/StreamChat/Components/DemoChatMessageActionsVC.swift index eaccfe150f0..ebf6a0ecae6 100644 --- a/DemoApp/StreamChat/Components/DemoChatMessageActionsVC.swift +++ b/DemoApp/StreamChat/Components/DemoChatMessageActionsVC.swift @@ -14,6 +14,7 @@ final class DemoChatMessageActionsVC: ChatMessageActionsVC { if message?.isSentByCurrentUser == true { if AppConfig.shared.demoAppConfig.isHardDeleteEnabled { actions.append(hardDeleteActionItem()) + actions.append(deleteForMeActionItem()) } } @@ -108,6 +109,22 @@ final class DemoChatMessageActionsVC: ChatMessageActionsVC { ) } + func deleteForMeActionItem() -> ChatMessageActionItem { + DeleteForMeActionItem( + action: { [weak self] _ in + guard let self = self else { return } + self.alertsRouter.showMessageDeletionConfirmationAlert { confirmed in + guard confirmed else { return } + + self.messageController.deleteMessageForMe { _ in + self.delegate?.chatMessageActionsVCDidFinish(self) + } + } + }, + appearance: appearance + ) + } + func translateActionItem() -> ChatMessageActionItem { TranslateActionitem( action: { [weak self] _ in @@ -224,6 +241,21 @@ final class DemoChatMessageActionsVC: ChatMessageActionsVC { } } + struct DeleteForMeActionItem: ChatMessageActionItem { + var title: String { "Delete only for me" } + var isDestructive: Bool { true } + let icon: UIImage + let action: (ChatMessageActionItem) -> Void + + init( + action: @escaping (ChatMessageActionItem) -> Void, + appearance: Appearance = .default + ) { + self.action = action + icon = appearance.images.messageActionDelete + } + } + struct TranslateActionitem: ChatMessageActionItem { var title: String { "Translate to Turkish" } var isDestructive: Bool { false } diff --git a/DemoApp/StreamChat/Components/DemoChatMessageContentView.swift b/DemoApp/StreamChat/Components/DemoChatMessageContentView.swift index 3aa07dab9eb..556873fae06 100644 --- a/DemoApp/StreamChat/Components/DemoChatMessageContentView.swift +++ b/DemoApp/StreamChat/Components/DemoChatMessageContentView.swift @@ -75,6 +75,10 @@ final class DemoChatMessageContentView: ChatMessageContentView { timestampLabel?.text?.append(" - Translated to Turkish") } + if content?.deletedForMe == true { + timestampLabel?.text?.append(" - Deleted only for me") + } + if content?.isPinned == true, let pinInfoLabel = pinInfoLabel { pinInfoLabel.text = "📌 Pinned" if let pinDetails = content?.pinDetails { diff --git a/DemoShare/DemoShareView.swift b/DemoShare/DemoShareView.swift index e0e8fba2d21..bf27b76ad70 100644 --- a/DemoShare/DemoShareView.swift +++ b/DemoShare/DemoShareView.swift @@ -7,7 +7,6 @@ import StreamChatUI import SwiftUI struct DemoShareView: View { - @StateObject var viewModel: DemoShareViewModel init( @@ -42,7 +41,7 @@ struct DemoShareView: View { .padding(.vertical) HStack { - if viewModel.channels.count == 0 { + if viewModel.channels.isEmpty { ProgressView() } else { Text("Select a channel") @@ -61,7 +60,6 @@ struct DemoShareView: View { } struct TopView: View { - @ObservedObject var viewModel: DemoShareViewModel var body: some View { @@ -89,7 +87,6 @@ struct TopView: View { } }) .disabled(viewModel.selectedChannel == nil) - } .padding(.vertical, 8) .padding(.horizontal, 16) @@ -102,7 +99,6 @@ struct TopView: View { } struct ImageToShareView: View { - private let imageHeight: CGFloat = 180 var image: UIImage @@ -115,11 +111,9 @@ struct ImageToShareView: View { .frame(height: imageHeight) .cornerRadius(8) } - } struct ShareChannelsView: View { - @ObservedObject var viewModel: DemoShareViewModel var body: some View { @@ -149,7 +143,6 @@ struct ShareChannelsView: View { } } } - } extension ChatChannel: Identifiable { diff --git a/DemoShare/DemoShareViewModel.swift b/DemoShare/DemoShareViewModel.swift index 78c14b04ef2..9d2b468253e 100644 --- a/DemoShare/DemoShareViewModel.swift +++ b/DemoShare/DemoShareViewModel.swift @@ -4,13 +4,12 @@ import Combine import CoreServices -import UIKit -import StreamChat import Social +import StreamChat +import UIKit @MainActor class DemoShareViewModel: ObservableObject, ChatChannelControllerDelegate { - private let chatClient: ChatClient private let userCredentials: UserCredentials private var channelListController: ChatChannelListController? diff --git a/DemoShare/ShareViewController.swift b/DemoShare/ShareViewController.swift index 4cdf7730aeb..d400b9be16b 100644 --- a/DemoShare/ShareViewController.swift +++ b/DemoShare/ShareViewController.swift @@ -2,14 +2,13 @@ // Copyright © 2025 Stream.io Inc. All rights reserved. // -import UIKit +import CoreServices import Social import StreamChat import SwiftUI -import CoreServices +import UIKit class ShareViewController: UIViewController { - override func viewDidLoad() { super.viewDidLoad() diff --git a/Examples/EdgeCases/ViewController.swift b/Examples/EdgeCases/ViewController.swift index a246a12a329..5158a5be700 100644 --- a/Examples/EdgeCases/ViewController.swift +++ b/Examples/EdgeCases/ViewController.swift @@ -4,9 +4,4 @@ import UIKit -class ViewController: UIViewController { - override func viewDidLoad() { - super.viewDidLoad() - // Do any additional setup after loading the view. - } -} +class ViewController: UIViewController {} diff --git a/Examples/YouTubeClone/Composer/YTChatComposerViewController.swift b/Examples/YouTubeClone/Composer/YTChatComposerViewController.swift index 72138b35131..b1038bc825f 100644 --- a/Examples/YouTubeClone/Composer/YTChatComposerViewController.swift +++ b/Examples/YouTubeClone/Composer/YTChatComposerViewController.swift @@ -58,7 +58,6 @@ final class YTChatComposerViewController: ComposerVC { preferredStyle: .actionSheet ) ["😃", "😇", "😅", "😂"].forEach { emoji in - let action = UIAlertAction(title: emoji, style: .default) { _ in let inputTextView = self.composerView.inputMessageView.textView inputTextView.replaceSelectedText(emoji) diff --git a/Examples/iMessageClone/iMessageComposerVC.swift b/Examples/iMessageClone/iMessageComposerVC.swift index e870f5315d8..1795e645379 100644 --- a/Examples/iMessageClone/iMessageComposerVC.swift +++ b/Examples/iMessageClone/iMessageComposerVC.swift @@ -35,7 +35,6 @@ final class iMessageComposerVC: ComposerVC { ) ["😃", "😇", "😅", "😂"].forEach { emoji in - let action = UIAlertAction(title: emoji, style: .default) { _ in let inputTextView = self.composerView.inputMessageView.textView inputTextView.replaceSelectedText(emoji) diff --git a/Gemfile.lock b/Gemfile.lock index cbc9d3c0f78..8d78597140b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -209,9 +209,11 @@ GEM fastlane pry fastlane-plugin-sonarcloud_metric_kit (0.2.1) - fastlane-plugin-stream_actions (0.3.90) + fastlane-plugin-stream_actions (0.3.101) xctest_list (= 1.2.1) fastlane-plugin-versioning (0.7.1) + fastlane-plugin-xcsize (1.1.0) + xcsize (= 1.1.0) fastlane-sirp (1.0.0) sysrandom (~> 1.0) faye-websocket (0.12.0) @@ -287,7 +289,7 @@ GEM molinillo (0.8.0) multi_json (1.17.0) multipart-post (2.4.1) - mustermann (3.0.3) + mustermann (3.0.4) ruby2_keywords (~> 0.0.1) mutex_m (0.3.0) nanaimo (0.4.0) @@ -322,8 +324,8 @@ GEM puma (6.6.1) nio4r (~> 2.0) racc (1.8.1) - rack (3.2.0) - rack-protection (4.1.1) + rack (3.2.3) + rack-protection (4.2.0) base64 (>= 0.1.0) logger (>= 1.6.0) rack (>= 3.0.0, < 4) @@ -378,11 +380,11 @@ GEM simctl (1.6.10) CFPropertyList naturally - sinatra (4.1.1) + sinatra (4.2.0) logger (>= 1.6.0) mustermann (~> 3.0) rack (>= 3.0.0, < 4) - rack-protection (= 4.1.1) + rack-protection (= 4.2.0) rack-session (>= 2.0.0, < 3) tilt (~> 2.0) slather (2.8.5) @@ -431,6 +433,8 @@ GEM rouge (~> 3.28.0) xcpretty-travis-formatter (1.0.1) xcpretty (~> 0.2, >= 0.0.7) + xcsize (1.1.0) + commander (>= 4.6, < 6.0) xctest_list (1.2.1) PLATFORMS @@ -445,8 +449,9 @@ DEPENDENCIES fastlane-plugin-create_xcframework fastlane-plugin-lizard fastlane-plugin-sonarcloud_metric_kit - fastlane-plugin-stream_actions (= 0.3.90) + fastlane-plugin-stream_actions (= 0.3.101) fastlane-plugin-versioning + fastlane-plugin-xcsize (= 1.1.0) faye-websocket json lefthook diff --git a/Package.swift b/Package.swift index 17c6eec1c8a..57e058a204b 100644 --- a/Package.swift +++ b/Package.swift @@ -25,7 +25,7 @@ let package = Package( .library( name: "StreamChatTestMockServer", targets: ["StreamChatTestMockServer"] - ), + ) ], targets: [ .target( @@ -52,7 +52,7 @@ let package = Package( path: "TestTools/StreamChatTestMockServer", exclude: ["Info.plist"], resources: [.process("Fixtures")] - ), + ) ] ) diff --git a/README.md b/README.md index ad5d7aeefa1..1cb15f51a54 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@

- StreamChat + StreamChat StreamChatUI

diff --git a/Sources/StreamChat/APIClient/Endpoints/MessageEndpoints.swift b/Sources/StreamChat/APIClient/Endpoints/MessageEndpoints.swift index 1c791c22fc6..462849a8ee6 100644 --- a/Sources/StreamChat/APIClient/Endpoints/MessageEndpoints.swift +++ b/Sources/StreamChat/APIClient/Endpoints/MessageEndpoints.swift @@ -15,13 +15,17 @@ extension Endpoint { ) } - static func deleteMessage(messageId: MessageId, hard: Bool) -> Endpoint { - .init( + static func deleteMessage(messageId: MessageId, hard: Bool, deleteForMe: Bool? = nil) -> Endpoint { + var body: [String: AnyEncodable] = ["hard": AnyEncodable(hard)] + if let deleteForMe = deleteForMe { + body["delete_for_me"] = AnyEncodable(deleteForMe) + } + return .init( path: .deleteMessage(messageId), method: .delete, queryItems: nil, requiresConnectionId: false, - body: ["hard": hard] + body: body ) } diff --git a/Sources/StreamChat/APIClient/Endpoints/Payloads/MessagePayloads.swift b/Sources/StreamChat/APIClient/Endpoints/Payloads/MessagePayloads.swift index 29f1eeaae63..1908714f87c 100644 --- a/Sources/StreamChat/APIClient/Endpoints/Payloads/MessagePayloads.swift +++ b/Sources/StreamChat/APIClient/Endpoints/Payloads/MessagePayloads.swift @@ -57,6 +57,8 @@ enum MessagePayloadsCodingKeys: String, CodingKey, CaseIterable { case location = "shared_location" case reminder case member + case deletedForMe = "deleted_for_me" + case campaignId = "created_by_campaign_id" } extension MessagePayload { @@ -119,6 +121,9 @@ class MessagePayload: Decodable { var location: SharedLocationPayload? var reminder: ReminderPayload? var member: MemberInfoPayload? + let deletedForMe: Bool? + + let campaignId: String? /// Only message payload from `getMessage` endpoint contains channel data. It's a convenience workaround for having to /// make an extra call do get channel details. @@ -189,6 +194,8 @@ class MessagePayload: Decodable { location = try container.decodeIfPresent(SharedLocationPayload.self, forKey: .location) reminder = try container.decodeIfPresent(ReminderPayload.self, forKey: .reminder) member = try container.decodeIfPresent(MemberInfoPayload.self, forKey: .member) + deletedForMe = try container.decodeIfPresent(Bool.self, forKey: .deletedForMe) + campaignId = try container.decodeIfPresent(String.self, forKey: .campaignId) } init( @@ -233,7 +240,9 @@ class MessagePayload: Decodable { draft: DraftPayload? = nil, reminder: ReminderPayload? = nil, location: SharedLocationPayload? = nil, - member: MemberInfoPayload? = nil + member: MemberInfoPayload? = nil, + deletedForMe: Bool? = nil, + campaignId: String? = nil ) { self.id = id self.cid = cid @@ -277,6 +286,8 @@ class MessagePayload: Decodable { self.location = location self.reminder = reminder self.member = member + self.deletedForMe = deletedForMe + self.campaignId = campaignId } } diff --git a/Sources/StreamChat/ChatClient.swift b/Sources/StreamChat/ChatClient.swift index 00fd91e1e56..a31ac05fa9d 100644 --- a/Sources/StreamChat/ChatClient.swift +++ b/Sources/StreamChat/ChatClient.swift @@ -515,14 +515,13 @@ public class ChatClient { } self?.authenticationRepository.logOutUser() } + } else { + authenticationRepository.logOutUser() } + // Clear current user id instantly even if pending removing device. authenticationRepository.clearCurrentUserId() - if removeDevice == false { - authenticationRepository.logOutUser() - } - // Stop tracking active components syncRepository.removeAllTracked() diff --git a/Sources/StreamChat/Controllers/MessageController/MessageController.swift b/Sources/StreamChat/Controllers/MessageController/MessageController.swift index ea44356c8df..bafdc7ada27 100644 --- a/Sources/StreamChat/Controllers/MessageController/MessageController.swift +++ b/Sources/StreamChat/Controllers/MessageController/MessageController.swift @@ -355,6 +355,26 @@ public class ChatMessageController: DataController, DelegateCallable, DataStoreP } } + /// Deletes the message this controller manages only for the current user. + /// + /// This method deletes the message only for the current user, making it invisible to them while keeping it visible to other users. + /// This is different from the regular delete which affects all users in the channel. + /// + /// - Parameter completion: The completion. Will be called on a **callbackQueue** when the network request is finished. + /// If request fails, the completion will be called with an error. + /// + public func deleteMessageForMe(completion: ((Error?) -> Void)? = nil) { + messageUpdater.deleteMessage( + messageId: messageId, + hard: false, + deleteForMe: true + ) { error in + self.callback { + completion?(error) + } + } + } + /// Creates a new reply message locally and schedules it for send. /// /// - Parameters: diff --git a/Sources/StreamChat/Controllers/PollController/PollVoteListController+Combine.swift b/Sources/StreamChat/Controllers/PollController/PollVoteListController+Combine.swift index 202e2d33eae..9cdbbac1468 100644 --- a/Sources/StreamChat/Controllers/PollController/PollVoteListController+Combine.swift +++ b/Sources/StreamChat/Controllers/PollController/PollVoteListController+Combine.swift @@ -11,6 +11,11 @@ extension PollVoteListController { basePublishers.state.keepAlive(self) } + /// A publisher emitting a new value every time the poll changes. + public var pollPublisher: AnyPublisher { + basePublishers.poll.keepAlive(self) + } + /// A publisher emitting a new value every time the votes change. public var voteChangesPublisher: AnyPublisher<[ListChange], Never> { basePublishers.voteChanges.keepAlive(self) @@ -26,6 +31,9 @@ extension PollVoteListController { /// A backing subject for `statePublisher`. let state: CurrentValueSubject + /// A backing subject for `pollPublisher`. + let poll: PassthroughSubject = .init() + /// A backing subject for `voteChangesPublisher`. let voteChanges: PassthroughSubject<[ListChange], Never> = .init() @@ -49,4 +57,8 @@ extension PollVoteListController.BasePublishers: PollVoteListControllerDelegate ) { voteChanges.send(changes) } + + func controller(_ controller: PollVoteListController, didUpdatePoll poll: Poll) { + self.poll.send(poll) + } } diff --git a/Sources/StreamChat/Controllers/PollController/PollVoteListController+SwiftUI.swift b/Sources/StreamChat/Controllers/PollController/PollVoteListController+SwiftUI.swift index 40b09c3ddca..25ca4d24f3e 100644 --- a/Sources/StreamChat/Controllers/PollController/PollVoteListController+SwiftUI.swift +++ b/Sources/StreamChat/Controllers/PollController/PollVoteListController+SwiftUI.swift @@ -18,6 +18,9 @@ extension PollVoteListController { /// The poll votes. @Published public private(set) var votes: LazyCachedMapCollection = [] + /// The poll which the votes belong to. + @Published public private(set) var poll: Poll? + /// The current state of the controller. @Published public private(set) var state: DataController.State @@ -29,6 +32,7 @@ extension PollVoteListController { controller.multicastDelegate.add(additionalDelegate: self) votes = controller.votes + poll = controller.poll } } } @@ -41,6 +45,10 @@ extension PollVoteListController.ObservableObject: PollVoteListControllerDelegat votes = controller.votes } + public func controller(_ controller: PollVoteListController, didUpdatePoll poll: Poll) { + self.poll = poll + } + public func controller(_ controller: DataController, didChangeState state: DataController.State) { self.state = state } diff --git a/Sources/StreamChat/Controllers/PollController/PollVoteListController.swift b/Sources/StreamChat/Controllers/PollController/PollVoteListController.swift index ab5d5f59942..86c4ae1c4d8 100644 --- a/Sources/StreamChat/Controllers/PollController/PollVoteListController.swift +++ b/Sources/StreamChat/Controllers/PollController/PollVoteListController.swift @@ -26,6 +26,24 @@ public protocol PollVoteListControllerDelegate: DataControllerStateDelegate { _ controller: PollVoteListController, didChangeVotes changes: [ListChange] ) + + /// The controller updated the poll. + /// + /// - Parameters: + /// - controller: The controller emitting the change callback. + /// - poll: The poll with the new data. + func controller( + _ controller: PollVoteListController, + didUpdatePoll poll: Poll + ) +} + +/// Optional delegate methods. +public extension PollVoteListControllerDelegate { + func controller( + _ controller: PollVoteListController, + didUpdatePoll poll: Poll + ) {} } /// A controller which allows querying and filtering the votes of a poll. @@ -37,14 +55,17 @@ public class PollVoteListController: DataController, DelegateCallable, DataStore public let client: ChatClient /// The votes of the poll the controller represents. - /// - /// To observe changes of the votes, set your class as a delegate of this controller or use the provided - /// `Combine` publishers. public var votes: LazyCachedMapCollection { - startPollVotesListObserverIfNeeded() + startObserversIfNeeded() return pollVotesObserver.items } + /// Returns the poll that this controller represents. + public var poll: Poll? { + startObserversIfNeeded() + return pollObserver?.item + } + /// A Boolean value that returns whether pagination is finished. public private(set) var hasLoadedAllVotes: Bool = false @@ -59,9 +80,7 @@ public class PollVoteListController: DataController, DelegateCallable, DataStore didSet { stateMulticastDelegate.set(mainDelegate: multicastDelegate.mainDelegate) stateMulticastDelegate.set(additionalDelegates: multicastDelegate.additionalDelegates) - - // After setting delegate local changes will be fetched and observed. - startPollVotesListObserverIfNeeded() + startObserversIfNeeded() } } @@ -78,7 +97,6 @@ public class PollVoteListController: DataController, DelegateCallable, DataStore observer.onDidChange = { [weak self] changes in self?.delegateCallback { [weak self] in guard let self = self else { - log.warning("Callback called while self is nil") return } @@ -89,6 +107,31 @@ public class PollVoteListController: DataController, DelegateCallable, DataStore return observer }() + /// Used for observing the poll for changes. + private lazy var pollObserver: BackgroundEntityDatabaseObserver? = { [weak self] in + guard let self = self else { + return nil + } + + let observer = environment.pollObserverBuilder( + self.client.databaseContainer, + PollDTO.fetchRequest(for: query.pollId), + { try $0.asModel() as Poll }, + NSFetchedResultsController.self + ) + .onChange { [weak self] change in + self?.delegateCallback { [weak self] delegate in + guard let self = self else { + log.warning("Callback called while self is nil") + return + } + delegate.controller(self, didUpdatePoll: change.item) + } + } + + return observer + }() + var _basePublishers: Any? /// An internal backing object for all publicly available Combine publishers. We use it to simplify the way we expose /// publishers. Instead of creating custom `Publisher` types, we use `CurrentValueSubject` and `PassthroughSubject` internally, @@ -122,7 +165,7 @@ public class PollVoteListController: DataController, DelegateCallable, DataStore } override public func synchronize(_ completion: ((_ error: Error?) -> Void)? = nil) { - startPollVotesListObserverIfNeeded() + startObserversIfNeeded() pollsRepository.queryPollVotes(query: query) { [weak self] result in guard let self else { return } @@ -140,12 +183,13 @@ public class PollVoteListController: DataController, DelegateCallable, DataStore } /// If the `state` of the controller is `initialized`, this method calls `startObserving` on the - /// `pollVotesObserver` to fetch the local data and start observing the changes. It also changes + /// `pollVotesObserver` and `pollObserver` to fetch the local data and start observing the changes. It also changes /// `state` based on the result. - private func startPollVotesListObserverIfNeeded() { + private func startObserversIfNeeded() { guard state == .initialized else { return } do { try pollVotesObserver.startObserving() + try pollObserver?.startObserving() state = .localDataFetched } catch { state = .localDataFetchFailed(ClientError(with: error)) @@ -198,6 +242,20 @@ extension PollVoteListController { itemReuseKeyPaths: (\PollVote.id, \PollVoteDTO.id) ) } + + var pollObserverBuilder: ( + _ database: DatabaseContainer, + _ fetchRequest: NSFetchRequest, + _ itemCreator: @escaping (PollDTO) throws -> Poll, + _ fetchedResultsControllerType: NSFetchedResultsController.Type + ) -> BackgroundEntityDatabaseObserver = { + BackgroundEntityDatabaseObserver( + database: $0, + fetchRequest: $1, + itemCreator: $2, + fetchedResultsControllerType: $3 + ) + } } } @@ -210,7 +268,13 @@ extension PollVoteListController: EventsControllerDelegate { vote = event.vote } guard let vote else { return } - if vote.isAnswer == true && query.pollId == vote.pollId && query.optionId == nil { + if vote.isAnswer == true + && query.pollId == vote.pollId + && query.optionId == nil { + pollsRepository.link(pollVote: vote, to: query) + } else if vote.isAnswer == false + && query.pollId == vote.pollId + && query.optionId == vote.optionId { pollsRepository.link(pollVote: vote, to: query) } } diff --git a/Sources/StreamChat/Database/DTOs/MessageDTO.swift b/Sources/StreamChat/Database/DTOs/MessageDTO.swift index baf24b7191e..4f937582cef 100644 --- a/Sources/StreamChat/Database/DTOs/MessageDTO.swift +++ b/Sources/StreamChat/Database/DTOs/MessageDTO.swift @@ -118,6 +118,7 @@ class MessageDTO: NSManagedObject { @NSManaged var defaultSortingKey: DBDate! @NSManaged var channelRole: String? + @NSManaged var deletedForMe: Bool override func willSave() { super.willSave() @@ -990,6 +991,10 @@ extension NSManagedObjectContext: MessageDatabaseSession { dto.isSilent = payload.isSilent dto.isShadowed = payload.isShadowed + if let deletedForMe = payload.deletedForMe { + dto.deletedForMe = deletedForMe + } + // Due to backend not working as advertised // (sending `shadowed: true` flag to the shadow banned user) // we have to implement this workaround to get the advertised behavior @@ -1776,6 +1781,7 @@ private extension ChatMessage { let isBounced = dto.isBounced let isSilent = dto.isSilent let isShadowed = dto.isShadowed + let deletedForMe = dto.deletedForMe let reactionScores = dto.reactionScores.mapKeys { MessageReactionType(rawValue: $0) } let reactionCounts = dto.reactionCounts.mapKeys { MessageReactionType(rawValue: $0) } let reactionGroups = dto.reactionGroups.asModel() @@ -1890,6 +1896,7 @@ private extension ChatMessage { isBounced: isBounced, isSilent: isSilent, isShadowed: isShadowed, + deletedForMe: deletedForMe, reactionScores: reactionScores, reactionCounts: reactionCounts, reactionGroups: reactionGroups, diff --git a/Sources/StreamChat/Database/DatabaseSession.swift b/Sources/StreamChat/Database/DatabaseSession.swift index d5313398f57..5041834d920 100644 --- a/Sources/StreamChat/Database/DatabaseSession.swift +++ b/Sources/StreamChat/Database/DatabaseSession.swift @@ -842,6 +842,11 @@ extension DatabaseSession { return } + // Update the message if deleted only for the current user. + if payload.eventType == .messageDeleted && payload.deletedForMe == true { + savedMessage.deletedForMe = true + } + // When a message is updated, make sure to update // the messages quoting the edited message by triggering a DB Update. if payload.eventType == .messageUpdated { diff --git a/Sources/StreamChat/Database/StreamChatModel.xcdatamodeld/StreamChatModel.xcdatamodel/contents b/Sources/StreamChat/Database/StreamChatModel.xcdatamodeld/StreamChatModel.xcdatamodel/contents index 28fdba2509f..84f9daccc8e 100644 --- a/Sources/StreamChat/Database/StreamChatModel.xcdatamodeld/StreamChatModel.xcdatamodel/contents +++ b/Sources/StreamChat/Database/StreamChatModel.xcdatamodeld/StreamChatModel.xcdatamodel/contents @@ -219,6 +219,7 @@ + diff --git a/Sources/StreamChat/Generated/SystemEnvironment+Version.swift b/Sources/StreamChat/Generated/SystemEnvironment+Version.swift index d2448581849..2ac0818ca69 100644 --- a/Sources/StreamChat/Generated/SystemEnvironment+Version.swift +++ b/Sources/StreamChat/Generated/SystemEnvironment+Version.swift @@ -7,5 +7,5 @@ import Foundation extension SystemEnvironment { /// A Stream Chat version. - public static let version: String = "4.90.0" + public static let version: String = "4.91.0" } diff --git a/Sources/StreamChat/Info.plist b/Sources/StreamChat/Info.plist index a1240892fe5..cfcbcd45dc8 100644 --- a/Sources/StreamChat/Info.plist +++ b/Sources/StreamChat/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 4.90.0 + 4.91.0 CFBundleVersion $(CURRENT_PROJECT_VERSION) diff --git a/Sources/StreamChat/Models/ChatMessage.swift b/Sources/StreamChat/Models/ChatMessage.swift index aa4f5f59d28..d1b59777163 100644 --- a/Sources/StreamChat/Models/ChatMessage.swift +++ b/Sources/StreamChat/Models/ChatMessage.swift @@ -85,6 +85,11 @@ public struct ChatMessage { /// public let isShadowed: Bool + /// A flag indicating whether the message was deleted only for the current user. + /// + /// Messages with this flag set to true are deleted for the current user but still visible to others. + public let deletedForMe: Bool + /// The reactions to the message created by any user. public let reactionScores: [MessageReactionType: Int] @@ -206,6 +211,7 @@ public struct ChatMessage { isBounced: Bool, isSilent: Bool, isShadowed: Bool, + deletedForMe: Bool, reactionScores: [MessageReactionType: Int], reactionCounts: [MessageReactionType: Int], reactionGroups: [MessageReactionType: ChatMessageReactionGroup], @@ -248,6 +254,7 @@ public struct ChatMessage { self.isBounced = isBounced self.isSilent = isSilent self.isShadowed = isShadowed + self.deletedForMe = deletedForMe self.reactionScores = reactionScores self.reactionCounts = reactionCounts self.reactionGroups = reactionGroups @@ -312,6 +319,7 @@ public struct ChatMessage { isBounced: isBounced, isSilent: isSilent, isShadowed: isShadowed, + deletedForMe: deletedForMe, reactionScores: reactionScores, reactionCounts: reactionCounts, reactionGroups: reactionGroups, @@ -427,6 +435,7 @@ public struct ChatMessage { isBounced: isBounced, isSilent: isSilent, isShadowed: isShadowed, + deletedForMe: deletedForMe, reactionScores: reactionScores, reactionCounts: reactionCounts, reactionGroups: reactionGroups, @@ -562,6 +571,7 @@ extension ChatMessage: Hashable { guard lhs.localState == rhs.localState else { return false } guard lhs.updatedAt == rhs.updatedAt else { return false } guard lhs.deletedAt == rhs.deletedAt else { return false } + guard lhs.deletedForMe == rhs.deletedForMe else { return false } guard lhs.allAttachments == rhs.allAttachments else { return false } guard lhs.poll == rhs.poll else { return false } guard lhs.author == rhs.author else { return false } diff --git a/Sources/StreamChat/Models/DraftMessage.swift b/Sources/StreamChat/Models/DraftMessage.swift index 0cec2f8615a..88a3858f799 100644 --- a/Sources/StreamChat/Models/DraftMessage.swift +++ b/Sources/StreamChat/Models/DraftMessage.swift @@ -165,5 +165,6 @@ extension ChatMessage { reminder = nil sharedLocation = nil channelRole = nil + deletedForMe = false } } diff --git a/Sources/StreamChat/Models/Payload+asModel/MessagePayload+asModel.swift b/Sources/StreamChat/Models/Payload+asModel/MessagePayload+asModel.swift index ddc43794c24..a24092dfbd2 100644 --- a/Sources/StreamChat/Models/Payload+asModel/MessagePayload+asModel.swift +++ b/Sources/StreamChat/Models/Payload+asModel/MessagePayload+asModel.swift @@ -76,6 +76,7 @@ extension MessagePayload { isBounced: moderationDetails?.action == MessageModerationAction.bounce.rawValue, isSilent: isSilent, isShadowed: isShadowed, + deletedForMe: deletedForMe ?? false, reactionScores: reactionScores, reactionCounts: reactionCounts, reactionGroups: reactionGroups.reduce(into: [:]) { acc, element in diff --git a/Sources/StreamChat/WebSocketClient/EventMiddlewares/ChannelVisibilityEventMiddleware.swift b/Sources/StreamChat/WebSocketClient/EventMiddlewares/ChannelVisibilityEventMiddleware.swift index 5fbaeeadd52..5491b6d9c55 100644 --- a/Sources/StreamChat/WebSocketClient/EventMiddlewares/ChannelVisibilityEventMiddleware.swift +++ b/Sources/StreamChat/WebSocketClient/EventMiddlewares/ChannelVisibilityEventMiddleware.swift @@ -34,7 +34,7 @@ struct ChannelVisibilityEventMiddleware: EventMiddleware { throw ClientError.ChannelDoesNotExist(cid: event.cid) } - if !event.message.isShadowed && !channelDTO.isBlocked { + if !event.message.isShadowed && event.message.campaignId == nil && !channelDTO.isBlocked { channelDTO.isHidden = false } @@ -45,7 +45,7 @@ struct ChannelVisibilityEventMiddleware: EventMiddleware { throw ClientError.ChannelDoesNotExist(cid: event.channel.cid) } - if !event.message.isShadowed && !channelDTO.isBlocked { + if !event.message.isShadowed && event.message.campaignId == nil && !channelDTO.isBlocked { channelDTO.isHidden = false } diff --git a/Sources/StreamChat/WebSocketClient/Events/EventPayload.swift b/Sources/StreamChat/WebSocketClient/Events/EventPayload.swift index 786d7bcb184..d85eafd22da 100644 --- a/Sources/StreamChat/WebSocketClient/Events/EventPayload.swift +++ b/Sources/StreamChat/WebSocketClient/Events/EventPayload.swift @@ -28,6 +28,7 @@ class EventPayload: Decodable { case banExpiredAt = "expiration" case parentId = "parent_id" case hardDelete = "hard_delete" + case deletedForMe = "deleted_for_me" case firstUnreadMessageId = "first_unread_message_id" case lastReadAt = "last_read_at" case lastReadMessageId = "last_read_message_id" @@ -62,6 +63,7 @@ class EventPayload: Decodable { let banExpiredAt: Date? let parentId: MessageId? let hardDelete: Bool + let deletedForMe: Bool? let shadow: Bool? // Mark as unread properties let firstUnreadMessageId: MessageId? @@ -115,7 +117,8 @@ class EventPayload: Decodable { aiMessage: String? = nil, draft: DraftPayload? = nil, reminder: ReminderPayload? = nil, - channelMessageCount: Int? = nil + channelMessageCount: Int? = nil, + deletedForMe: Bool? = nil ) { self.eventType = eventType self.connectionId = connectionId @@ -150,6 +153,7 @@ class EventPayload: Decodable { self.draft = draft self.reminder = reminder self.channelMessageCount = channelMessageCount + self.deletedForMe = deletedForMe } required init(from decoder: Decoder) throws { @@ -189,6 +193,7 @@ class EventPayload: Decodable { draft = try container.decodeIfPresent(DraftPayload.self, forKey: .draft) reminder = try container.decodeIfPresent(ReminderPayload.self, forKey: .reminder) channelMessageCount = try container.decodeIfPresent(Int.self, forKey: .channelMessageCount) + deletedForMe = try container.decodeIfPresent(Bool.self, forKey: .deletedForMe) } func event() throws -> Event { diff --git a/Sources/StreamChat/WebSocketClient/Events/MessageEvents.swift b/Sources/StreamChat/WebSocketClient/Events/MessageEvents.swift index fc4c0280d98..9936b4ef867 100644 --- a/Sources/StreamChat/WebSocketClient/Events/MessageEvents.swift +++ b/Sources/StreamChat/WebSocketClient/Events/MessageEvents.swift @@ -134,6 +134,9 @@ public struct MessageDeletedEvent: ChannelSpecificEvent { /// A Boolean value indicating whether it is an hard delete or not. public let isHardDelete: Bool + + /// A Boolean value indicating whether the message was deleted only for the current user. + public let deletedForMe: Bool } class MessageDeletedEventDTO: EventDTO { @@ -143,6 +146,7 @@ class MessageDeletedEventDTO: EventDTO { let createdAt: Date let payload: EventPayload let hardDelete: Bool + let deletedForMe: Bool? init(from response: EventPayload) throws { user = try? response.value(at: \.user) @@ -151,6 +155,7 @@ class MessageDeletedEventDTO: EventDTO { createdAt = try response.value(at: \.createdAt) payload = response hardDelete = response.hardDelete + deletedForMe = response.deletedForMe } func toDomainEvent(session: DatabaseSession) -> Event? { @@ -174,7 +179,8 @@ class MessageDeletedEventDTO: EventDTO { channel: channelDTO.asModel(), message: message, createdAt: createdAt, - isHardDelete: hardDelete + isHardDelete: hardDelete, + deletedForMe: deletedForMe ?? false ) } } diff --git a/Sources/StreamChat/Workers/ManualEventHandler.swift b/Sources/StreamChat/Workers/ManualEventHandler.swift index f99cfcc2cdd..28b661a9764 100644 --- a/Sources/StreamChat/Workers/ManualEventHandler.swift +++ b/Sources/StreamChat/Workers/ManualEventHandler.swift @@ -156,7 +156,8 @@ class ManualEventHandler { channel: channel, message: message, createdAt: createdAt, - isHardDelete: payload.hardDelete + isHardDelete: payload.hardDelete, + deletedForMe: payload.deletedForMe ?? false ) } diff --git a/Sources/StreamChat/Workers/MessageUpdater.swift b/Sources/StreamChat/Workers/MessageUpdater.swift index 3af9a115cd9..75b15790f3e 100644 --- a/Sources/StreamChat/Workers/MessageUpdater.swift +++ b/Sources/StreamChat/Workers/MessageUpdater.swift @@ -42,8 +42,9 @@ class MessageUpdater: Worker { /// - Parameters: /// - messageId: The message identifier. /// - hard: A Boolean value to determine if the message will be delete permanently on the backend. + /// - deleteForMe: A Boolean value to determine if the message should be deleted only for the current user. /// - completion: The completion. Will be called with an error if smth goes wrong, otherwise - will be called with `nil`. - func deleteMessage(messageId: MessageId, hard: Bool, completion: ((Error?) -> Void)? = nil) { + func deleteMessage(messageId: MessageId, hard: Bool, deleteForMe: Bool? = nil, completion: ((Error?) -> Void)? = nil) { var shouldDeleteOnBackend = true database.write({ session in @@ -80,7 +81,13 @@ class MessageUpdater: Worker { return } - apiClient?.request(endpoint: .deleteMessage(messageId: messageId, hard: hard)) { result in + apiClient?.request( + endpoint: .deleteMessage( + messageId: messageId, + hard: hard, + deleteForMe: deleteForMe + ) + ) { result in switch result { case let .success(response): repository?.saveSuccessfullyDeletedMessage(message: response.message, completion: completion) @@ -1239,9 +1246,9 @@ extension MessageUpdater { } } - func deleteMessage(messageId: MessageId, hard: Bool) async throws { + func deleteMessage(messageId: MessageId, hard: Bool, deleteForMe: Bool? = nil) async throws { try await withCheckedThrowingContinuation { continuation in - deleteMessage(messageId: messageId, hard: hard) { error in + deleteMessage(messageId: messageId, hard: hard, deleteForMe: deleteForMe) { error in continuation.resume(with: error) } } diff --git a/Sources/StreamChatUI/ChatMessageList/Attachments/Poll/PollResultsVC/PollResultsVoteListVC/PollResultsVoteListVC.swift b/Sources/StreamChatUI/ChatMessageList/Attachments/Poll/PollResultsVC/PollResultsVoteListVC/PollResultsVoteListVC.swift index d8363acf187..d0c88b2bde2 100644 --- a/Sources/StreamChatUI/ChatMessageList/Attachments/Poll/PollResultsVC/PollResultsVoteListVC/PollResultsVoteListVC.swift +++ b/Sources/StreamChatUI/ChatMessageList/Attachments/Poll/PollResultsVC/PollResultsVoteListVC/PollResultsVoteListVC.swift @@ -139,15 +139,6 @@ open class PollResultsVoteListVC: return view } - // MARK: - PollVoteListControllerDelegate - - public func controller(_ controller: PollVoteListController, didChangeVotes changes: [ListChange]) { - var snapshot = NSDiffableDataSourceSnapshot() - snapshot.appendSections([option]) - snapshot.appendItems(Array(controller.votes)) - dataSource.apply(snapshot, animatingDifferences: true) - } - // MARK: - Actions /// Loads the next page of votes. @@ -166,4 +157,20 @@ open class PollResultsVoteListVC: open func didFinishLoadingMoreVotes(with error: Error?) { isPaginatingVotes = false } + + // MARK: - PollVoteListControllerDelegate + + public func controller(_ controller: PollVoteListController, didChangeVotes changes: [ListChange]) { + var snapshot = NSDiffableDataSourceSnapshot() + snapshot.appendSections([option]) + snapshot.appendItems(Array(controller.votes)) + dataSource.apply(snapshot, animatingDifferences: true) + } + + public func controller(_ controller: PollVoteListController, didUpdatePoll poll: Poll) { + self.poll = poll + var newSnapshot = dataSource.snapshot() + newSnapshot.reloadSections([option]) + dataSource.apply(newSnapshot, animatingDifferences: true) + } } diff --git a/Sources/StreamChatUI/Info.plist b/Sources/StreamChatUI/Info.plist index a1240892fe5..cfcbcd45dc8 100644 --- a/Sources/StreamChatUI/Info.plist +++ b/Sources/StreamChatUI/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 4.90.0 + 4.91.0 CFBundleVersion $(CURRENT_PROJECT_VERSION) diff --git a/Sources/StreamChatUI/Utils/ChatMessage+Extensions.swift b/Sources/StreamChatUI/Utils/ChatMessage+Extensions.swift index 29467d53c48..9ff3315bcc1 100644 --- a/Sources/StreamChatUI/Utils/ChatMessage+Extensions.swift +++ b/Sources/StreamChatUI/Utils/ChatMessage+Extensions.swift @@ -65,7 +65,7 @@ public extension ChatMessage { /// A boolean value that says if the message is deleted. var isDeleted: Bool { - deletedAt != nil + deletedAt != nil || deletedForMe == true } /// A boolean value that determines whether the text message should be rendered as large emojis diff --git a/Stream.playground/Contents.swift b/Stream.playground/Contents.swift index d51d83ffb30..7336e7887b7 100644 --- a/Stream.playground/Contents.swift +++ b/Stream.playground/Contents.swift @@ -1,7 +1,11 @@ +// +// Copyright © 2025 Stream.io Inc. All rights reserved. +// + import PlaygroundSupport -import UIKit import StreamChat import StreamChatUI +import UIKit PlaygroundPage.current.needsIndefiniteExecution = true /*: @@ -9,7 +13,7 @@ PlaygroundPage.current.needsIndefiniteExecution = true Playground used for testing the StreamChat and StreamChatUI components of the SDK. ⚠️ Please, feel free to change the playground and test different components, but do not commit the changes. -*/ + */ let apiKeyString = "8br4watad788" LogConfig.level = .info diff --git a/StreamChat-XCFramework.podspec b/StreamChat-XCFramework.podspec index 2888b1ac125..0c2587fc4a1 100644 --- a/StreamChat-XCFramework.podspec +++ b/StreamChat-XCFramework.podspec @@ -1,25 +1,25 @@ Pod::Spec.new do |spec| - spec.name = "StreamChat-XCFramework" - spec.version = "4.90.0" - spec.summary = "StreamChat iOS Client" - spec.description = "stream-chat-swift is the official Swift client for Stream Chat, a service for building chat applications." + spec.name = 'StreamChat-XCFramework' + spec.version = '4.91.0' + spec.summary = 'StreamChat iOS Client' + spec.description = 'stream-chat-swift is the official Swift client for Stream Chat, a service for building chat applications.' - spec.homepage = "https://getstream.io/chat/" - spec.license = { :type => "BSD-3", :file => "LICENSE" } - spec.author = { "getstream.io" => "support@getstream.io" } - spec.social_media_url = "https://getstream.io" + spec.homepage = 'https://getstream.io/chat/' + spec.license = { type: 'BSD-3', file: 'LICENSE' } + spec.author = { 'getstream.io' => 'support@getstream.io' } + spec.social_media_url = 'https://getstream.io' spec.swift_version = '5.7' - spec.ios.deployment_target = '13.0' + spec.ios.deployment_target = '13.0' spec.requires_arc = true - spec.framework = "Foundation" - spec.ios.framework = "UIKit" + spec.framework = 'Foundation' + spec.ios.framework = 'UIKit' - spec.module_name = "StreamChat" - spec.source = { :http => "https://github.com/GetStream/stream-chat-swift/releases/download/#{spec.version}/#{spec.module_name}.zip" } + spec.module_name = 'StreamChat' + spec.source = { http: "https://github.com/GetStream/stream-chat-swift/releases/download/#{spec.version}/#{spec.module_name}.zip" } spec.vendored_frameworks = "#{spec.module_name}.xcframework" spec.preserve_paths = "#{spec.module_name}.xcframework/*" - spec.cocoapods_version = ">= 1.11.0" + spec.cocoapods_version = '>= 1.11.0' end diff --git a/StreamChat.podspec b/StreamChat.podspec index 93395f470f1..a5e49846675 100644 --- a/StreamChat.podspec +++ b/StreamChat.podspec @@ -1,24 +1,24 @@ Pod::Spec.new do |spec| - spec.name = "StreamChat" - spec.version = "4.90.0" - spec.summary = "StreamChat iOS Chat Client" - spec.description = "stream-chat-swift is the official Swift client for Stream Chat, a service for building chat applications." + spec.name = 'StreamChat' + spec.version = '4.91.0' + spec.summary = 'StreamChat iOS Chat Client' + spec.description = 'stream-chat-swift is the official Swift client for Stream Chat, a service for building chat applications.' - spec.homepage = "https://getstream.io/chat/" - spec.license = { :type => "BSD-3", :file => "LICENSE" } - spec.author = { "getstream.io" => "support@getstream.io" } - spec.social_media_url = "https://getstream.io" + spec.homepage = 'https://getstream.io/chat/' + spec.license = { type: 'BSD-3', file: 'LICENSE' } + spec.author = { 'getstream.io' => 'support@getstream.io' } + spec.social_media_url = 'https://getstream.io' spec.swift_version = '5.7' spec.ios.deployment_target = '13.0' spec.osx.deployment_target = '11.0' spec.requires_arc = true - spec.framework = "Foundation" - spec.ios.framework = "UIKit" + spec.framework = 'Foundation' + spec.ios.framework = 'UIKit' - spec.module_name = "StreamChat" - spec.source = { :git => "https://github.com/GetStream/stream-chat-swift.git", :tag => "#{spec.version}" } - spec.source_files = ["Sources/StreamChat/**/*.swift"] - spec.resource_bundles = { "StreamChat" => ["Sources/StreamChat/**/*.xcdatamodeld"] } + spec.module_name = 'StreamChat' + spec.source = { git: 'https://github.com/GetStream/stream-chat-swift.git', tag: "#{spec.version}" } + spec.source_files = ['Sources/StreamChat/**/*.swift'] + spec.resource_bundles = { 'StreamChat' => ['Sources/StreamChat/**/*.xcdatamodeld'] } end diff --git a/StreamChat.xcodeproj/project.pbxproj b/StreamChat.xcodeproj/project.pbxproj index 574d8d3201c..89160b37863 100644 --- a/StreamChat.xcodeproj/project.pbxproj +++ b/StreamChat.xcodeproj/project.pbxproj @@ -1579,6 +1579,7 @@ AD6E32A52BBC502D0073831B /* ThreadQuery.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD6E32A32BBC502D0073831B /* ThreadQuery.swift */; }; AD6E32AD2BBC86950073831B /* ThreadEndpoint_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD6E32AC2BBC86950073831B /* ThreadEndpoint_Tests.swift */; }; AD6F531927175FDB00D428B4 /* ChatMessageGiphyView+GiphyBadge.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD6F531827175FDB00D428B4 /* ChatMessageGiphyView+GiphyBadge.swift */; }; + AD70CC852E956C590007AB6D /* MessageDeletedForMe.json in Resources */ = {isa = PBXBuildFile; fileRef = AD70CC842E956C4F0007AB6D /* MessageDeletedForMe.json */; }; AD70DC362ADEC0F600CFC3B7 /* MessageModerationDetailsPayload.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD70DC352ADEC0F600CFC3B7 /* MessageModerationDetailsPayload.swift */; }; AD70DC372ADEC0F600CFC3B7 /* MessageModerationDetailsPayload.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD70DC352ADEC0F600CFC3B7 /* MessageModerationDetailsPayload.swift */; }; AD70DC392ADEC3C400CFC3B7 /* MessageModerationDetailsDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD70DC382ADEC3C400CFC3B7 /* MessageModerationDetailsDTO.swift */; }; @@ -4416,6 +4417,7 @@ AD6E32A32BBC502D0073831B /* ThreadQuery.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadQuery.swift; sourceTree = ""; }; AD6E32AC2BBC86950073831B /* ThreadEndpoint_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadEndpoint_Tests.swift; sourceTree = ""; }; AD6F531827175FDB00D428B4 /* ChatMessageGiphyView+GiphyBadge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ChatMessageGiphyView+GiphyBadge.swift"; sourceTree = ""; }; + AD70CC842E956C4F0007AB6D /* MessageDeletedForMe.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = MessageDeletedForMe.json; sourceTree = ""; }; AD70DC352ADEC0F600CFC3B7 /* MessageModerationDetailsPayload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageModerationDetailsPayload.swift; sourceTree = ""; }; AD70DC382ADEC3C400CFC3B7 /* MessageModerationDetailsDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageModerationDetailsDTO.swift; sourceTree = ""; }; AD70DC3B2ADEF09C00CFC3B7 /* MessageModerationDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageModerationDetails.swift; sourceTree = ""; }; @@ -6711,6 +6713,7 @@ 8A0C3BD524C1E25400CAFD19 /* Message */ = { isa = PBXGroup; children = ( + AD70CC842E956C4F0007AB6D /* MessageDeletedForMe.json */, 8A0C3BDB24C1E5AE00CAFD19 /* MessageDeleted.json */, 846C633B26FC834800F7518B /* MessageDeleted+MissingUser.json */, ADC4AAAD2788ACFE0004BB35 /* MessageDeletedHard.json */, @@ -10605,6 +10608,7 @@ 841BAA172BD01930000C73E4 /* Poll.json in Resources */, A311B3FF27E8B9A800CFCF6D /* MessageRead.json in Resources */, E3B987EF2844DE1200C2E101 /* MemberRole.json in Resources */, + AD70CC852E956C590007AB6D /* MessageDeletedForMe.json in Resources */, A311B40427E8B9AD00CFCF6D /* NotificationInviteRejected.json in Resources */, A311B3F127E8B99800CFCF6D /* ChannelUpdated_ServerSide.json in Resources */, A311B3DE27E8B98C00CFCF6D /* ChannelMembersQuery.json in Resources */, @@ -13823,6 +13827,8 @@ INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Stream.io Inc. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_GENERATE_MAP_FILE = YES; + LD_MAP_FILE_PATH = "linkmaps/$(PRODUCT_NAME)-$(CURRENT_ARCH)-LinkMap.txt"; LD_RUNPATH_SEARCH_PATHS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = io.stream.StreamChatUI; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; @@ -13851,6 +13857,8 @@ INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Stream.io Inc. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_GENERATE_MAP_FILE = YES; + LD_MAP_FILE_PATH = "linkmaps/$(PRODUCT_NAME)-$(CURRENT_ARCH)-LinkMap.txt"; LD_RUNPATH_SEARCH_PATHS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = io.stream.StreamChatUI; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; @@ -13879,6 +13887,8 @@ INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Stream.io Inc. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_GENERATE_MAP_FILE = YES; + LD_MAP_FILE_PATH = "linkmaps/$(PRODUCT_NAME)-$(CURRENT_ARCH)-LinkMap.txt"; LD_RUNPATH_SEARCH_PATHS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = io.stream.StreamChatUI; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; @@ -14202,6 +14212,8 @@ INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Stream.io Inc. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_GENERATE_MAP_FILE = YES; + LD_MAP_FILE_PATH = "linkmaps/$(PRODUCT_NAME)-$(CURRENT_ARCH)-LinkMap.txt"; LD_RUNPATH_SEARCH_PATHS = "$(inherited)"; MACOSX_DEPLOYMENT_TARGET = 10.15; PRODUCT_BUNDLE_IDENTIFIER = io.getstream.StreamChat; @@ -14232,6 +14244,8 @@ INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Stream.io Inc. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_GENERATE_MAP_FILE = YES; + LD_MAP_FILE_PATH = "linkmaps/$(PRODUCT_NAME)-$(CURRENT_ARCH)-LinkMap.txt"; LD_RUNPATH_SEARCH_PATHS = "$(inherited)"; MACOSX_DEPLOYMENT_TARGET = 10.15; PRODUCT_BUNDLE_IDENTIFIER = io.getstream.StreamChat; @@ -14954,6 +14968,8 @@ INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Stream.io Inc. All rights reserved."; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_GENERATE_MAP_FILE = YES; + LD_MAP_FILE_PATH = "linkmaps/$(PRODUCT_NAME)-$(CURRENT_ARCH)-LinkMap.txt"; LD_RUNPATH_SEARCH_PATHS = "$(inherited)"; MACOSX_DEPLOYMENT_TARGET = 10.15; PRODUCT_BUNDLE_IDENTIFIER = io.getstream.StreamChat; diff --git a/StreamChatArtifacts.json b/StreamChatArtifacts.json index 183665602c6..97714e33be1 100644 --- a/StreamChatArtifacts.json +++ b/StreamChatArtifacts.json @@ -1 +1 @@ -{"4.7.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.7.0/StreamChat-All.zip","4.8.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.8.0/StreamChat-All.zip","4.9.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.9.0/StreamChat-All.zip","4.10.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.10.0/StreamChat-All.zip","4.10.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.10.1/StreamChat-All.zip","4.11.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.11.0/StreamChat-All.zip","4.12.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.12.0/StreamChat-All.zip","4.13.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.13.0/StreamChat-All.zip","4.13.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.13.1/StreamChat-All.zip","4.14.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.14.0/StreamChat-All.zip","4.15.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.15.0/StreamChat-All.zip","4.15.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.15.1/StreamChat-All.zip","4.16.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.16.0/StreamChat-All.zip","4.17.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.17.0/StreamChat-All.zip","4.18.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.18.0/StreamChat-All.zip","4.19.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.19.0/StreamChat-All.zip","4.20.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.20.0/StreamChat-All.zip","4.21.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.21.0/StreamChat-All.zip","4.21.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.21.1/StreamChat-All.zip","4.21.2":"https://github.com/GetStream/stream-chat-swift/releases/download/4.21.2/StreamChat-All.zip","4.22.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.22.0/StreamChat-All.zip","4.23.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.23.0/StreamChat-All.zip","4.24.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.24.0/StreamChat-All.zip","4.24.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.24.1/StreamChat-All.zip","4.25.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.25.0/StreamChat-All.zip","4.25.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.25.1/StreamChat-All.zip","4.26.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.26.0/StreamChat-All.zip","4.27.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.27.0/StreamChat-All.zip","4.27.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.27.1/StreamChat-All.zip","4.28.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.28.0/StreamChat-All.zip","4.29.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.29.0/StreamChat-All.zip","4.30.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.30.0/StreamChat-All.zip","4.31.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.31.0/StreamChat-All.zip","4.32.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.32.0/StreamChat-All.zip","4.33.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.33.0/StreamChat-All.zip","4.34.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.34.0/StreamChat-All.zip","4.35.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.35.0/StreamChat-All.zip","4.35.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.35.1/StreamChat-All.zip","4.35.2":"https://github.com/GetStream/stream-chat-swift/releases/download/4.35.2/StreamChat-All.zip","4.36.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.36.0/StreamChat-All.zip","4.37.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.37.0/StreamChat-All.zip","4.37.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.37.1/StreamChat-All.zip","4.38.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.38.0/StreamChat-All.zip","4.39.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.39.0/StreamChat-All.zip","4.40.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.40.0/StreamChat-All.zip","4.41.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.41.0/StreamChat-All.zip","4.42.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.42.0/StreamChat-All.zip","4.43.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.43.0/StreamChat-All.zip","4.44.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.44.0/StreamChat-All.zip","4.45.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.45.0/StreamChat-All.zip","4.46.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.46.0/StreamChat-All.zip","4.47.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.47.0/StreamChat-All.zip","4.47.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.47.1/StreamChat-All.zip","4.48.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.48.0/StreamChat-All.zip","4.48.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.48.1/StreamChat-All.zip","4.49.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.49.0/StreamChat-All.zip","4.50.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.50.0/StreamChat-All.zip","4.51.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.51.0/StreamChat-All.zip","4.52.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.52.0/StreamChat-All.zip","4.53.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.53.0/StreamChat-All.zip","4.54.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.54.0/StreamChat-All.zip","4.55.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.55.0/StreamChat-All.zip","4.56.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.56.0/StreamChat-All.zip","4.56.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.56.1/StreamChat-All.zip","4.57.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.57.0/StreamChat-All.zip","4.58.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.58.0/StreamChat-All.zip","4.59.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.59.0/StreamChat-All.zip","4.60.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.60.0/StreamChat-All.zip","4.61.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.61.0/StreamChat-All.zip","4.62.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.62.0/StreamChat-All.zip","4.63.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.63.0/StreamChat-All.zip","4.64.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.64.0/StreamChat-All.zip","4.65.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.65.0/StreamChat-All.zip","4.66.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.66.0/StreamChat-All.zip","4.67.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.67.0/StreamChat-All.zip","4.68.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.68.0/StreamChat-All.zip","4.69.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.69.0/StreamChat-All.zip","4.70.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.70.0/StreamChat-All.zip","4.71.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.71.0/StreamChat-All.zip","4.72.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.72.0/StreamChat-All.zip","4.73.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.73.0/StreamChat-All.zip","4.74.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.74.0/StreamChat-All.zip","4.75.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.75.0/StreamChat-All.zip","4.76.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.76.0/StreamChat-All.zip","4.77.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.77.0/StreamChat-All.zip","4.78.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.78.0/StreamChat-All.zip","4.79.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.79.0/StreamChat-All.zip","4.79.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.79.1/StreamChat-All.zip","4.80.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.80.0/StreamChat-All.zip","4.81.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.81.0/StreamChat-All.zip","4.82.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.82.0/StreamChat-All.zip","4.83.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.83.0/StreamChat-All.zip","4.84.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.84.0/StreamChat-All.zip","4.85.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.85.0/StreamChat-All.zip","4.86.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.86.0/StreamChat-All.zip","4.87.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.87.0/StreamChat-All.zip","4.88.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.88.0/StreamChat-All.zip","4.89.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.89.0/StreamChat-All.zip","4.90.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.90.0/StreamChat-All.zip"} \ No newline at end of file +{"4.7.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.7.0/StreamChat-All.zip","4.8.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.8.0/StreamChat-All.zip","4.9.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.9.0/StreamChat-All.zip","4.10.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.10.0/StreamChat-All.zip","4.10.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.10.1/StreamChat-All.zip","4.11.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.11.0/StreamChat-All.zip","4.12.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.12.0/StreamChat-All.zip","4.13.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.13.0/StreamChat-All.zip","4.13.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.13.1/StreamChat-All.zip","4.14.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.14.0/StreamChat-All.zip","4.15.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.15.0/StreamChat-All.zip","4.15.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.15.1/StreamChat-All.zip","4.16.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.16.0/StreamChat-All.zip","4.17.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.17.0/StreamChat-All.zip","4.18.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.18.0/StreamChat-All.zip","4.19.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.19.0/StreamChat-All.zip","4.20.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.20.0/StreamChat-All.zip","4.21.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.21.0/StreamChat-All.zip","4.21.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.21.1/StreamChat-All.zip","4.21.2":"https://github.com/GetStream/stream-chat-swift/releases/download/4.21.2/StreamChat-All.zip","4.22.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.22.0/StreamChat-All.zip","4.23.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.23.0/StreamChat-All.zip","4.24.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.24.0/StreamChat-All.zip","4.24.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.24.1/StreamChat-All.zip","4.25.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.25.0/StreamChat-All.zip","4.25.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.25.1/StreamChat-All.zip","4.26.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.26.0/StreamChat-All.zip","4.27.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.27.0/StreamChat-All.zip","4.27.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.27.1/StreamChat-All.zip","4.28.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.28.0/StreamChat-All.zip","4.29.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.29.0/StreamChat-All.zip","4.30.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.30.0/StreamChat-All.zip","4.31.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.31.0/StreamChat-All.zip","4.32.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.32.0/StreamChat-All.zip","4.33.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.33.0/StreamChat-All.zip","4.34.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.34.0/StreamChat-All.zip","4.35.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.35.0/StreamChat-All.zip","4.35.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.35.1/StreamChat-All.zip","4.35.2":"https://github.com/GetStream/stream-chat-swift/releases/download/4.35.2/StreamChat-All.zip","4.36.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.36.0/StreamChat-All.zip","4.37.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.37.0/StreamChat-All.zip","4.37.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.37.1/StreamChat-All.zip","4.38.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.38.0/StreamChat-All.zip","4.39.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.39.0/StreamChat-All.zip","4.40.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.40.0/StreamChat-All.zip","4.41.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.41.0/StreamChat-All.zip","4.42.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.42.0/StreamChat-All.zip","4.43.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.43.0/StreamChat-All.zip","4.44.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.44.0/StreamChat-All.zip","4.45.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.45.0/StreamChat-All.zip","4.46.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.46.0/StreamChat-All.zip","4.47.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.47.0/StreamChat-All.zip","4.47.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.47.1/StreamChat-All.zip","4.48.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.48.0/StreamChat-All.zip","4.48.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.48.1/StreamChat-All.zip","4.49.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.49.0/StreamChat-All.zip","4.50.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.50.0/StreamChat-All.zip","4.51.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.51.0/StreamChat-All.zip","4.52.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.52.0/StreamChat-All.zip","4.53.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.53.0/StreamChat-All.zip","4.54.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.54.0/StreamChat-All.zip","4.55.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.55.0/StreamChat-All.zip","4.56.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.56.0/StreamChat-All.zip","4.56.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.56.1/StreamChat-All.zip","4.57.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.57.0/StreamChat-All.zip","4.58.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.58.0/StreamChat-All.zip","4.59.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.59.0/StreamChat-All.zip","4.60.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.60.0/StreamChat-All.zip","4.61.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.61.0/StreamChat-All.zip","4.62.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.62.0/StreamChat-All.zip","4.63.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.63.0/StreamChat-All.zip","4.64.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.64.0/StreamChat-All.zip","4.65.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.65.0/StreamChat-All.zip","4.66.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.66.0/StreamChat-All.zip","4.67.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.67.0/StreamChat-All.zip","4.68.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.68.0/StreamChat-All.zip","4.69.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.69.0/StreamChat-All.zip","4.70.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.70.0/StreamChat-All.zip","4.71.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.71.0/StreamChat-All.zip","4.72.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.72.0/StreamChat-All.zip","4.73.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.73.0/StreamChat-All.zip","4.74.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.74.0/StreamChat-All.zip","4.75.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.75.0/StreamChat-All.zip","4.76.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.76.0/StreamChat-All.zip","4.77.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.77.0/StreamChat-All.zip","4.78.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.78.0/StreamChat-All.zip","4.79.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.79.0/StreamChat-All.zip","4.79.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.79.1/StreamChat-All.zip","4.80.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.80.0/StreamChat-All.zip","4.81.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.81.0/StreamChat-All.zip","4.82.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.82.0/StreamChat-All.zip","4.83.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.83.0/StreamChat-All.zip","4.84.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.84.0/StreamChat-All.zip","4.85.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.85.0/StreamChat-All.zip","4.86.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.86.0/StreamChat-All.zip","4.87.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.87.0/StreamChat-All.zip","4.88.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.88.0/StreamChat-All.zip","4.89.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.89.0/StreamChat-All.zip","4.90.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.90.0/StreamChat-All.zip","4.91.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.91.0/StreamChat-All.zip"} \ No newline at end of file diff --git a/StreamChatUI-XCFramework.podspec b/StreamChatUI-XCFramework.podspec index 840d7417eda..0b7800cc8d1 100644 --- a/StreamChatUI-XCFramework.podspec +++ b/StreamChatUI-XCFramework.podspec @@ -1,26 +1,26 @@ Pod::Spec.new do |spec| - spec.name = "StreamChatUI-XCFramework" - spec.version = "4.90.0" - spec.summary = "StreamChat UI Components" - spec.description = "StreamChatUI SDK offers flexible UI components able to display data provided by StreamChat SDK." + spec.name = 'StreamChatUI-XCFramework' + spec.version = '4.91.0' + spec.summary = 'StreamChat UI Components' + spec.description = 'StreamChatUI SDK offers flexible UI components able to display data provided by StreamChat SDK.' - spec.homepage = "https://getstream.io/chat/" - spec.license = { :type => "BSD-3", :file => "LICENSE" } - spec.author = { "getstream.io" => "support@getstream.io" } - spec.social_media_url = "https://getstream.io" + spec.homepage = 'https://getstream.io/chat/' + spec.license = { type: 'BSD-3', file: 'LICENSE' } + spec.author = { 'getstream.io' => 'support@getstream.io' } + spec.social_media_url = 'https://getstream.io' spec.swift_version = '5.7' - spec.platform = :ios, "13.0" + spec.platform = :ios, '13.0' spec.requires_arc = true - spec.framework = "Foundation", "UIKit" + spec.framework = 'Foundation', 'UIKit' - spec.module_name = "StreamChatUI" - spec.source = { :http => "https://github.com/GetStream/stream-chat-swift/releases/download//#{spec.version}/#{spec.module_name}.zip" } + spec.module_name = 'StreamChatUI' + spec.source = { http: "https://github.com/GetStream/stream-chat-swift/releases/download/#{spec.version}/#{spec.module_name}.zip" } spec.vendored_frameworks = "#{spec.module_name}.xcframework" spec.preserve_paths = "#{spec.module_name}.xcframework/*" - spec.dependency "StreamChat-XCFramework", "#{spec.version}" + spec.dependency 'StreamChat-XCFramework', "#{spec.version}" - spec.cocoapods_version = ">= 1.11.0" + spec.cocoapods_version = '>= 1.11.0' end diff --git a/StreamChatUI.podspec b/StreamChatUI.podspec index 1d772be7e3a..ed86613bee7 100644 --- a/StreamChatUI.podspec +++ b/StreamChatUI.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |spec| spec.name = "StreamChatUI" - spec.version = "4.90.0" + spec.version = "4.91.0" spec.summary = "StreamChat UI Components" spec.description = "StreamChatUI SDK offers flexible UI components able to display data provided by StreamChat SDK." diff --git a/StreamChatUITestsApp/AppDelegate.swift b/StreamChatUITestsApp/AppDelegate.swift index 2f290c3e421..97d77497432 100644 --- a/StreamChatUITestsApp/AppDelegate.swift +++ b/StreamChatUITestsApp/AppDelegate.swift @@ -2,13 +2,12 @@ // Copyright © 2025 Stream.io Inc. All rights reserved. // +import StreamChat import UIKit import UserNotifications -import StreamChat @main class AppDelegate: UIResponder, UIApplicationDelegate { - private let pushNotifications = PushNotifications() var window: UIWindow? @@ -78,13 +77,14 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } func application( - _ application: UIApplication, - didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data + _ application: UIApplication, + didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data ) { let tokenParts = deviceToken.map { data in String(format: "%02.2hhx", data) } let token = tokenParts.joined() print("Device Token: \(token)") } + func application( _ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error diff --git a/StreamChatUITestsApp/DebugMenu.swift b/StreamChatUITestsApp/DebugMenu.swift index 9ffa13a0e38..2afb83de5ab 100644 --- a/StreamChatUITestsApp/DebugMenu.swift +++ b/StreamChatUITestsApp/DebugMenu.swift @@ -3,109 +3,123 @@ // import Foundation -import UIKit import StreamChat +import UIKit final class DebugMenu { - static let shared = DebugMenu() func showMenu(in viewController: UIViewController, channelController: ChatChannelController) { - presentAlert(in: viewController, - title: "Select an action", - actions: [ - .init(title: "Add member", style: .default, handler: { [unowned self] _ in - self.presentAlert(in: viewController, - title: "Enter user id", - textFieldPlaceholder: "User ID") { id in - guard let id = id, !id.isEmpty else { - self.presentAlert( - in: viewController, - title: "User ID is not valid", - actions: [] - ) - return - } - channelController.addMembers(userIds: [id]) { [unowned self] error in + presentAlert( + in: viewController, + title: "Select an action", + actions: [ + .init(title: "Add member", style: .default, handler: { [unowned self] _ in + self.presentAlert( + in: viewController, + title: "Enter user id", + textFieldPlaceholder: "User ID" + ) { id in + guard let id = id, !id.isEmpty else { + self.presentAlert( + in: viewController, + title: "User ID is not valid", + actions: [] + ) + return + } + channelController.addMembers(userIds: [id]) { [unowned self] error in + if let error = error { + self.presentAlert( + in: viewController, + title: "Couldn't add user \(id) to channel \(String(describing: channelController.cid))", + message: "\(error)", + actions: [] + ) + } + } + } + }), + .init( + title: "Remove a member", + style: .default, + handler: { [unowned self] _ in + let actions = channelController.channel?.lastActiveMembers.map { member in + UIAlertAction(title: member.id, style: .default) { _ in + channelController.removeMembers(userIds: [member.id]) { [unowned self] error in if let error = error { self.presentAlert( in: viewController, - title: "Couldn't add user \(id) to channel \(String(describing: channelController.cid))", + title: "Couldn't remove user \(member.id) from channel \(String(describing: channelController.cid))", message: "\(error)", actions: [] ) } } } - }), - .init(title: "Remove a member", - style: .default, - handler: { [unowned self] _ in - let actions = channelController.channel?.lastActiveMembers.map { member in - UIAlertAction(title: member.id, style: .default) { _ in - channelController.removeMembers(userIds: [member.id]) { [unowned self] error in - if let error = error { - self.presentAlert( - in: viewController, - title: "Couldn't remove user \(member.id) from channel \(String(describing: channelController.cid))", - message: "\(error)", - actions: [] - ) - } - } - }} ?? [] - self.presentAlert( + } ?? [] + self.presentAlert( + in: viewController, + title: "Select a member", + actions: actions + ) + } + ), + .init( + title: "Show Members", + style: .default, + handler: { [unowned self] _ in + self.presentAlert( + in: viewController, + title: "Members", + message: channelController.channel?.lastActiveMembers.map(\.name).debugDescription, + actions: [] + ) + } + ), + .init( + title: "Truncate channel w/o message", + style: .default, + handler: { _ in + channelController.truncateChannel { [unowned self] error in + if let error = error { + self.presentAlert( in: viewController, - title: "Select a member", - actions: actions - ) - }), - .init(title: "Show Members", - style: .default, - handler: { [unowned self] _ in - self.presentAlert( + title: "Couldn't truncate channel", + message: "\(error.localizedDescription)", + actions: [] + ) + } + } + } + ), + .init( + title: "Truncate channel with message", + style: .default, + handler: { _ in + channelController.truncateChannel(systemMessage: "Channel truncated") { [unowned self] error in + if let error = error { + self.presentAlert( in: viewController, - title: "Members", - message: channelController.channel?.lastActiveMembers.map(\.name).debugDescription, + title: "Couldn't truncate channel", + message: "\(error.localizedDescription)", actions: [] - ) - }), - .init(title: "Truncate channel w/o message", - style: .default, - handler: { _ in - channelController.truncateChannel { [unowned self] error in - if let error = error { - self.presentAlert( - in: viewController, - title: "Couldn't truncate channel", - message: "\(error.localizedDescription)", - actions: [] - ) - } - } - }), - .init(title: "Truncate channel with message", - style: .default, - handler: { _ in - channelController.truncateChannel(systemMessage: "Channel truncated") { [unowned self] error in - if let error = error { - self.presentAlert( - in: viewController, - title: "Couldn't truncate channel", - message: "\(error.localizedDescription)", - actions: [] - ) - } - } - }) - ]) + ) + } + } + } + ) + ] + ) } - func presentAlert(in viewController: UIViewController, - title: String?, - message: String? = nil, - actions: [UIAlertAction], - cancelHandler: (() -> Void)? = nil) { + func presentAlert( + in viewController: UIViewController, + title: String?, + message: String? = nil, + actions: [UIAlertAction], + cancelHandler: (() -> Void)? = nil + ) { let alert = UIAlertController( title: title, message: message, @@ -149,5 +163,4 @@ final class DebugMenu { viewController.present(alert, animated: true, completion: nil) } - } diff --git a/StreamChatUITestsApp/InternetConnection/InternetConnectionMonitor_Mock.swift b/StreamChatUITestsApp/InternetConnection/InternetConnectionMonitor_Mock.swift index 1f9effdb505..b23a080da52 100644 --- a/StreamChatUITestsApp/InternetConnection/InternetConnectionMonitor_Mock.swift +++ b/StreamChatUITestsApp/InternetConnection/InternetConnectionMonitor_Mock.swift @@ -19,6 +19,5 @@ final class InternetConnectionMonitor_Mock: InternetConnectionMonitor { self.status = status delegate?.internetConnectionStatusDidChange(status: status) } - } #endif diff --git a/StreamChatUITestsApp/Settings.swift b/StreamChatUITestsApp/Settings.swift index de125088c55..c9091328c8d 100644 --- a/StreamChatUITestsApp/Settings.swift +++ b/StreamChatUITestsApp/Settings.swift @@ -18,7 +18,6 @@ struct SettingValue { } struct Settings { - // Connectivity var showsConnectivity = SettingValue(setting: .showsConnectivity, isOn: true) var setConnectivity = SettingValue(setting: .setConnectivity, isOn: false) diff --git a/StreamChatUITestsApp/StreamChat/ChannelVC.swift b/StreamChatUITestsApp/StreamChat/ChannelVC.swift index 663428a732f..e4c2feff836 100644 --- a/StreamChatUITestsApp/StreamChat/ChannelVC.swift +++ b/StreamChatUITestsApp/StreamChat/ChannelVC.swift @@ -6,7 +6,6 @@ import Foundation import StreamChatUI final class ChannelVC: ChatChannelVC { - var onViewWillAppear: ((ChannelVC) -> Void)? override func viewWillAppear(_ animated: Bool) { diff --git a/StreamChatUITestsApp/StreamChat/CustomChannelListRouter.swift b/StreamChatUITestsApp/StreamChat/CustomChannelListRouter.swift index f17a4cfb68f..d7319f36360 100644 --- a/StreamChatUITestsApp/StreamChat/CustomChannelListRouter.swift +++ b/StreamChatUITestsApp/StreamChat/CustomChannelListRouter.swift @@ -3,11 +3,10 @@ // import Foundation -import StreamChatUI import StreamChat +import StreamChatUI final class CustomChannelListRouter: ChatChannelListRouter { - var onLeave: (() -> Void)? var onChannelViewWillAppear: ((ChannelVC) -> Void)? var onChannelListViewWillAppear: ((ChannelList) -> Void)? diff --git a/StreamChatUITestsApp/StreamChat/CustomChatMessageListRouter.swift b/StreamChatUITestsApp/StreamChat/CustomChatMessageListRouter.swift index c039ec6747f..89f297b2662 100644 --- a/StreamChatUITestsApp/StreamChat/CustomChatMessageListRouter.swift +++ b/StreamChatUITestsApp/StreamChat/CustomChatMessageListRouter.swift @@ -3,11 +3,10 @@ // import Foundation -import StreamChatUI import StreamChat +import StreamChatUI final class CustomMessageListRouter: ChatMessageListRouter { - var onThreadViewWillAppear: ((ThreadVC) -> Void)? override func showThread(messageId: MessageId, cid: ChannelId, client: ChatClient) { @@ -42,5 +41,4 @@ final class CustomMessageListRouter: ChatMessageListRouter { } rootNavigationController?.show(threadVC, sender: self) } - } diff --git a/StreamChatUITestsApp/StreamChat/DemoAppCoordinator+TestApp.swift b/StreamChatUITestsApp/StreamChat/DemoAppCoordinator+TestApp.swift index d83ea0a43fe..634dd5d7c2a 100644 --- a/StreamChatUITestsApp/StreamChat/DemoAppCoordinator+TestApp.swift +++ b/StreamChatUITestsApp/StreamChat/DemoAppCoordinator+TestApp.swift @@ -9,7 +9,6 @@ import UIKit // MARK: - Navigation extension DemoAppCoordinator { - func start(cid: ChannelId? = nil, completion: @escaping (Error?) -> Void) { if let cid = cid { navigateToChannel(with: cid) @@ -32,5 +31,4 @@ extension DemoAppCoordinator { viewController.router?.showChannel(for: cid) } } - } diff --git a/StreamChatUITestsApp/StreamChat/StreamChatWrapper+TestApp.swift b/StreamChatUITestsApp/StreamChat/StreamChatWrapper+TestApp.swift index 5c171227529..f2e6c94e2fa 100644 --- a/StreamChatUITestsApp/StreamChat/StreamChatWrapper+TestApp.swift +++ b/StreamChatUITestsApp/StreamChat/StreamChatWrapper+TestApp.swift @@ -11,7 +11,6 @@ import StreamChat import StreamChatUI extension StreamChatWrapper { - func setUpChat() { // Set the log level LogConfig.level = .debug @@ -41,5 +40,4 @@ extension StreamChatWrapper { Components.default.messageActionsVC = MessageActionsVC.self Components.default.messageSwipeToReplyEnabled = true } - } diff --git a/StreamChatUITestsApp/StreamChat/StreamChatWrapperExtensions.swift b/StreamChatUITestsApp/StreamChat/StreamChatWrapperExtensions.swift index f7e652eec48..33f02047e94 100644 --- a/StreamChatUITestsApp/StreamChat/StreamChatWrapperExtensions.swift +++ b/StreamChatUITestsApp/StreamChat/StreamChatWrapperExtensions.swift @@ -4,8 +4,8 @@ import Foundation #if TESTS -@testable import StreamChat import OHHTTPStubs +@testable import StreamChat #else import StreamChat #endif @@ -13,7 +13,6 @@ import StreamChatUI import UIKit extension StreamChatWrapper { - func mockConnection(isConnected: Bool) { #if TESTS let client = StreamChatWrapper.shared.client @@ -24,9 +23,11 @@ extension StreamChatWrapper { let baseURL = StreamChatWrapper.shared.config.baseURL.restAPIBaseURL.absoluteString return request.url?.absoluteString.contains(baseURL) ?? false }, withStubResponse: { _ -> HTTPStubsResponse in - let error = NSError(domain: "NSURLErrorDomain", - code: -1009, - userInfo: nil) + let error = NSError( + domain: "NSURLErrorDomain", + code: -1009, + userInfo: nil + ) return HTTPStubsResponse(error: error) }) @@ -57,5 +58,4 @@ extension StreamChatWrapper { let channelList = ChannelList.make(with: controller) return channelList } - } diff --git a/StreamChatUITestsApp/StreamChat/ThreadVC.swift b/StreamChatUITestsApp/StreamChat/ThreadVC.swift index 248182eb566..402d4c4221d 100644 --- a/StreamChatUITestsApp/StreamChat/ThreadVC.swift +++ b/StreamChatUITestsApp/StreamChat/ThreadVC.swift @@ -3,16 +3,14 @@ // import Foundation -import UIKit import StreamChatUI +import UIKit final class ThreadVC: ChatThreadVC { - var onViewWillAppear: ((ChatThreadVC) -> Void)? override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) onViewWillAppear?(self) } - } diff --git a/StreamChatUITestsApp/StreamChat/User.swift b/StreamChatUITestsApp/StreamChat/User.swift index 5e024cab9a6..087f493330b 100644 --- a/StreamChatUITestsApp/StreamChat/User.swift +++ b/StreamChatUITestsApp/StreamChat/User.swift @@ -6,7 +6,6 @@ import Foundation import StreamChat extension UserCredentials { - static var `default`: UserCredentials { UserCredentials.luke } diff --git a/StreamChatUITestsApp/ViewController.swift b/StreamChatUITestsApp/ViewController.swift index 4d7f9bccc7c..c4082d31684 100644 --- a/StreamChatUITestsApp/ViewController.swift +++ b/StreamChatUITestsApp/ViewController.swift @@ -2,14 +2,13 @@ // Copyright © 2025 Stream.io Inc. All rights reserved. // -import UIKit import StreamChat import StreamChatUI +import UIKit var settings = Settings() final class ViewController: UIViewController { - var streamChat = StreamChatWrapper.shared var channelController: ChatChannelController? @@ -123,13 +122,11 @@ final class ViewController: UIViewController { DebugMenu.shared.showMenu(in: self, channelController: controller) } } - } // MARK: UI Components extension ViewController { - func createIsConnectedSwitchIfNeeded() -> UISwitch? { guard settings.showsConnectivity.isOn else { return nil } let sw = UISwitch() @@ -190,7 +187,6 @@ extension ViewController { item.accessibilityIdentifier = "debug" return item } - } extension StreamChatWrapper { @@ -240,7 +236,6 @@ extension StreamChatWrapper { } extension URLSession { - enum HTTPError: Error { case transportError(Error) case serverSideError(Int) diff --git a/StreamChatUITestsAppUITests/Pages/ChannelListPage.swift b/StreamChatUITestsAppUITests/Pages/ChannelListPage.swift index 6d4a98138cd..97fae52610b 100644 --- a/StreamChatUITestsAppUITests/Pages/ChannelListPage.swift +++ b/StreamChatUITestsAppUITests/Pages/ChannelListPage.swift @@ -3,11 +3,10 @@ // import Foundation -import XCTest import StreamChat +import XCTest enum ChannelListPage { - static var userAvatar: XCUIElement { app.otherElements["CurrentChatUserAvatarView"] } static var cells: XCUIElementQuery { @@ -64,5 +63,4 @@ enum ChannelListPage { return cell.images[identifier] } } - } diff --git a/StreamChatUITestsAppUITests/Pages/MessageListPage.swift b/StreamChatUITestsAppUITests/Pages/MessageListPage.swift index 6ba781e1376..a4a3c9cb680 100644 --- a/StreamChatUITestsAppUITests/Pages/MessageListPage.swift +++ b/StreamChatUITestsAppUITests/Pages/MessageListPage.swift @@ -3,14 +3,13 @@ // import Foundation -import XCTest import StreamChat @testable import StreamChatUI +import XCTest // swiftlint:disable convenience_type class MessageListPage { - static var cells: XCUIElementQuery { app.cells.matching(NSPredicate(format: "identifier LIKE 'ChatMessageCell'")) } @@ -32,7 +31,6 @@ class MessageListPage { } enum NavigationBar { - static var header: XCUIElement { app.otherElements["ChatChannelHeaderView"] } static var chatAvatar: XCUIElement { @@ -57,8 +55,8 @@ class MessageListPage { // Add member static var alert: XCUIElement { app.alerts["Select an action"] } static var addMember: XCUIElement { alert.buttons["Add member"] } - static var addMemberTextField: XCUIElement {app.textFields["debug_alert_textfield"] } - static var addMemberOKButton: XCUIElement {app.alerts["Enter user id"].buttons["OK"] } + static var addMemberTextField: XCUIElement { app.textFields["debug_alert_textfield"] } + static var addMemberOKButton: XCUIElement { app.alerts["Enter user id"].buttons["OK"] } // Remove member static var removeMember: XCUIElement { alert.buttons["Remove a member"] } @@ -148,7 +146,7 @@ class MessageListPage { } } - struct Element { + enum Element { static var reactionsView: XCUIElement { app.otherElements["ChatReactionPickerReactionsView"] } static var reply: XCUIElement { app.otherElements["InlineReplyActionItem"] } static var threadReply: XCUIElement { app.otherElements["ThreadReplyActionItem"] } diff --git a/StreamChatUITestsAppUITests/Pages/SpringBoard.swift b/StreamChatUITestsAppUITests/Pages/SpringBoard.swift index ea756894991..e94aecf420e 100644 --- a/StreamChatUITestsAppUITests/Pages/SpringBoard.swift +++ b/StreamChatUITestsAppUITests/Pages/SpringBoard.swift @@ -14,9 +14,9 @@ enum SpringBoard { static var notificationBanner: XCUIElement { app.otherElements["Notification"] - .descendants(matching: .any) - .matching(NSPredicate(format: "label CONTAINS[c] ', now,'")) - .firstMatch + .descendants(matching: .any) + .matching(NSPredicate(format: "label CONTAINS[c] ', now,'")) + .firstMatch } static var testAppIcon: XCUIElement { diff --git a/StreamChatUITestsAppUITests/Pages/ThreadPage.swift b/StreamChatUITestsAppUITests/Pages/ThreadPage.swift index 2d43957fc2a..774e4a5497b 100644 --- a/StreamChatUITestsAppUITests/Pages/ThreadPage.swift +++ b/StreamChatUITestsAppUITests/Pages/ThreadPage.swift @@ -6,17 +6,14 @@ import Foundation import XCTest class ThreadPage: MessageListPage { - static var alsoSendInChannelCheckbox: XCUIElement { app.otherElements["CheckboxControl"] } static var repliesCountLabel: XCUIElement { app.staticTexts["textLabel"] } enum NavigationBar { - static var header: XCUIElement { app.otherElements["ChatThreadHeaderView"] } static var channelName: XCUIElement { header.staticTexts.lastMatch! } } - } diff --git a/StreamChatUITestsAppUITests/Robots/UserRobot+Asserts.swift b/StreamChatUITestsAppUITests/Robots/UserRobot+Asserts.swift index eb472dd5c18..e18cd51027f 100644 --- a/StreamChatUITestsAppUITests/Robots/UserRobot+Asserts.swift +++ b/StreamChatUITestsAppUITests/Robots/UserRobot+Asserts.swift @@ -3,9 +3,9 @@ // import Foundation -import XCTest -@testable import StreamChatUI @testable import StreamChat +@testable import StreamChatUI +import XCTest let channelAttributes = ChannelListPage.Attributes.self let channelCells = ChannelListPage.cells @@ -13,26 +13,28 @@ let attributes = MessageListPage.Attributes.self let cells = MessageListPage.cells // MARK: Channel List -extension UserRobot { +extension UserRobot { @discardableResult - func channelCell(withIndex index: Int? = nil, - file: StaticString = #filePath, - line: UInt = #line) -> XCUIElement { - guard let index = index else { - return channelCells.firstMatch - } + func channelCell( + withIndex index: Int? = nil, + file: StaticString = #filePath, + line: UInt = #line + ) -> XCUIElement { + guard let index = index else { + return channelCells.firstMatch + } - let minExpectedCount = index + 1 - let cells = cells.waitCount(index) - XCTAssertGreaterThanOrEqual( - cells.count, - minExpectedCount, - "Message cell is not found at index #\(index)", - file: file, - line: line - ) - return channelCells.element(boundBy: index) + let minExpectedCount = index + 1 + let cells = cells.waitCount(index) + XCTAssertGreaterThanOrEqual( + cells.count, + minExpectedCount, + "Message cell is not found at index #\(index)", + file: file, + line: line + ) + return channelCells.element(boundBy: index) } @discardableResult @@ -45,10 +47,12 @@ extension UserRobot { let cell = channelCell(withIndex: cellIndex, file: file, line: line) let message = channelAttributes.lastMessage(in: cell) let actualText = message.waitForText(text, mustBeEqual: false).text - XCTAssertTrue(actualText.contains(text), - "'\(actualText)' does not contain '\(text)'", - file: file, - line: line) + XCTAssertTrue( + actualText.contains(text), + "'\(actualText)' does not contain '\(text)'", + file: file, + line: line + ) return self } @@ -115,10 +119,12 @@ extension UserRobot { let expectedChannel = ChannelListPage.channel(withName: "\(expectedCount)") var expectedChannelExist = expectedChannel.exists - XCTAssertFalse(expectedChannelExist, - "Expected channel should not be visible", - file: file, - line: line) + XCTAssertFalse( + expectedChannelExist, + "Expected channel should not be visible", + file: file, + line: line + ) let endTime = Date().timeIntervalSince1970 * 1000 + XCUIElement.waitTimeout * 1000 while !expectedChannelExist && endTime > Date().timeIntervalSince1970 * 1000 { @@ -126,10 +132,12 @@ extension UserRobot { expectedChannelExist = expectedChannel.exists } - XCTAssertTrue(expectedChannelExist, - "Expected channel should be visible", - file: file, - line: line) + XCTAssertTrue( + expectedChannelExist, + "Expected channel should be visible", + file: file, + line: line + ) return self } @@ -158,12 +166,14 @@ extension UserRobot { } // MARK: Message List -extension UserRobot { +extension UserRobot { @discardableResult - func messageCell(withIndex index: Int? = nil, - file: StaticString = #filePath, - line: UInt = #line) -> XCUIElement { + func messageCell( + withIndex index: Int? = nil, + file: StaticString = #filePath, + line: UInt = #line + ) -> XCUIElement { let messageCell: XCUIElement if let index = index { let minExpectedCount = index + 1 @@ -225,29 +235,37 @@ extension UserRobot { line: UInt = #line ) -> Self { let pushNotification = SpringBoard.notificationBanner.wait() - XCTAssertTrue(pushNotification.exists, - "Push notification should appear", - file: file, - line: line) + XCTAssertTrue( + pushNotification.exists, + "Push notification should appear", + file: file, + line: line + ) let pushNotificationContent = pushNotification.text - XCTAssertTrue(pushNotificationContent.contains(text), - "\(pushNotificationContent) does not contain \(text)", - file: file, - line: line) - XCTAssertTrue(pushNotificationContent.contains(sender), - "\(pushNotificationContent) does not contain \(sender)", - file: file, - line: line) + XCTAssertTrue( + pushNotificationContent.contains(text), + "\(pushNotificationContent) does not contain \(text)", + file: file, + line: line + ) + XCTAssertTrue( + pushNotificationContent.contains(sender), + "\(pushNotificationContent) does not contain \(sender)", + file: file, + line: line + ) return self } @discardableResult func assertPushNotificationDoesNotAppear(file: StaticString = #filePath, line: UInt = #line) -> Self { - XCTAssertFalse(SpringBoard.notificationBanner.exists, - "Push notification should not appear", - file: file, - line: line) + XCTAssertFalse( + SpringBoard.notificationBanner.exists, + "Push notification should not appear", + file: file, + line: line + ) return self } @@ -259,11 +277,13 @@ extension UserRobot { ) -> Self { SpringBoard.notificationBanner.wait() let appIconValue = SpringBoard.testAppIcon.value as? String - XCTAssertEqual(appIconValue?.contains("1"), - shouldBeVisible, - "Badge should be visible: \(shouldBeVisible)", - file: file, - line: line) + XCTAssertEqual( + appIconValue?.contains("1"), + shouldBeVisible, + "Badge should be visible: \(shouldBeVisible)", + file: file, + line: line + ) return self } @@ -344,6 +364,7 @@ extension UserRobot { XCTAssertEqual(text, actualText, file: file, line: line) return self } + @discardableResult func assertQuotedMessageDoesNotExist( _ text: String, @@ -410,11 +431,13 @@ extension UserRobot { ) -> Self { var btn = MessageListPage.scrollToBottomButton btn = isVisible ? btn.wait(timeout: timeout) : btn.waitForDisappearance(timeout: timeout) - XCTAssertEqual(isVisible, - btn.exists, - "Scroll to bottom button should be \(isVisible ? "visible" : "hidden")", - file: file, - line: line) + XCTAssertEqual( + isVisible, + btn.exists, + "Scroll to bottom button should be \(isVisible ? "visible" : "hidden")", + file: file, + line: line + ) return self } @@ -444,14 +467,18 @@ extension UserRobot { line: UInt = #line ) -> Self { let typingIndicatorView = MessageListPage.typingIndicator.wait(timeout: waitTimeout) - XCTAssertTrue(typingIndicatorView.exists, - "Element hidden", - file: file, - line: line) - XCTAssertTrue(typingIndicatorView.text.contains(typingUserName), - "User name is wrong", - file: file, - line: line) + XCTAssertTrue( + typingIndicatorView.exists, + "Element hidden", + file: file, + line: line + ) + XCTAssertTrue( + typingIndicatorView.text.contains(typingUserName), + "User name is wrong", + file: file, + line: line + ) return self } @@ -467,10 +494,12 @@ extension UserRobot { } @discardableResult - func assertContextMenuOptionNotAvailable(option: MessageListPage.ContextMenu, - forMessageAtIndex index: Int = 0, - file: StaticString = #filePath, - line: UInt = #line) -> Self { + func assertContextMenuOptionNotAvailable( + option: MessageListPage.ContextMenu, + forMessageAtIndex index: Int = 0, + file: StaticString = #filePath, + line: UInt = #line + ) -> Self { openContextMenu(messageCellIndex: index) XCTAssertFalse(option.element.exists, "Context menu option is visible", file: file, line: line) return self @@ -534,9 +563,11 @@ extension UserRobot { return self } - func assertComposerLimits(toNumberOfLines limit: Int, - file: StaticString = #filePath, - line: UInt = #line) { + func assertComposerLimits( + toNumberOfLines limit: Int, + file: StaticString = #filePath, + line: UInt = #line + ) { let composer = MessageListPage.Composer.inputField var composerHeight = composer.height for i in 1.. Self { let additionalSpace = " " - let userName = UserDetails.hanSoloName + let userName = UserDetails.countDookuName let expectedText = "@\(userName)\(additionalSpace)" let actualText = MessageListPage.Composer.textView.waitForText(expectedText).text XCTAssertEqual(expectedText, actualText, file: file, line: line) @@ -733,8 +768,8 @@ extension UserRobot { } // MARK: Quoted Messages -extension UserRobot { +extension UserRobot { @discardableResult func assertQuotedMessage( replyText: String = "", // empty text by default for attachment messages @@ -775,15 +810,17 @@ extension UserRobot { } // MARK: Thread Replies -extension UserRobot { +extension UserRobot { @discardableResult func assertThreadIsOpen(file: StaticString = #filePath, line: UInt = #line) -> Self { let alsoSendInChannelCheckbox = ThreadPage.alsoSendInChannelCheckbox.wait() - XCTAssertTrue(alsoSendInChannelCheckbox.exists, - "alsoSendInChannel checkbox is not visible", - file: file, - line: line) + XCTAssertTrue( + alsoSendInChannelCheckbox.exists, + "alsoSendInChannel checkbox is not visible", + file: file, + line: line + ) return self } @@ -832,27 +869,35 @@ extension UserRobot { @discardableResult func assertCooldownIsShown(file: StaticString = #filePath, line: UInt = #line) -> Self { - XCTAssertEqual(MessageListPage.Composer.placeholder.text, - L10n.Composer.Placeholder.slowMode, - file: file, - line: line) - XCTAssertTrue(MessageListPage.Composer.cooldown.wait().exists, - "Cooldown should be visible", - file: file, - line: line) + XCTAssertEqual( + MessageListPage.Composer.placeholder.text, + L10n.Composer.Placeholder.slowMode, + file: file, + line: line + ) + XCTAssertTrue( + MessageListPage.Composer.cooldown.wait().exists, + "Cooldown should be visible", + file: file, + line: line + ) return self } @discardableResult func assertCooldownIsNotShown(file: StaticString = #filePath, line: UInt = #line) -> Self { - XCTAssertNotEqual(MessageListPage.Composer.placeholder.text, - L10n.Composer.Placeholder.slowMode, - file: file, - line: line) - XCTAssertFalse(MessageListPage.Composer.cooldown.exists, - "Cooldown should not be visible", - file: file, - line: line) + XCTAssertNotEqual( + MessageListPage.Composer.placeholder.text, + L10n.Composer.Placeholder.slowMode, + file: file, + line: line + ) + XCTAssertFalse( + MessageListPage.Composer.cooldown.exists, + "Cooldown should not be visible", + file: file, + line: line + ) return self } @@ -881,10 +926,12 @@ extension UserRobot { ) -> Self { let messageCell = messageCell(withIndex: messageCellIndex, file: file, line: line) let threadReplyCountButton = attributes.threadReplyCountButton(in: messageCell).wait() - XCTAssertTrue(threadReplyCountButton.exists, - "There is no thread reply count button", - file: file, - line: line) + XCTAssertTrue( + threadReplyCountButton.exists, + "There is no thread reply count button", + file: file, + line: line + ) if replies > 0 { let expectedText = "\(replies) Thread Replies" XCTAssertEqual(expectedText, threadReplyCountButton.waitForText(expectedText).text) @@ -923,6 +970,7 @@ extension UserRobot { } // MARK: Reactions + extension UserRobot { @discardableResult func assertReaction( @@ -943,9 +991,11 @@ extension UserRobot { /// /// - Returns: Self @discardableResult - func waitForNewReaction(at messageCellIndex: Int? = nil, - file: StaticString = #filePath, - line: UInt = #line) -> Self { + func waitForNewReaction( + at messageCellIndex: Int? = nil, + file: StaticString = #filePath, + line: UInt = #line + ) -> Self { let cell = messageCell(withIndex: messageCellIndex, file: file, line: line).wait() attributes.reactionButton(in: cell).wait() return self @@ -955,7 +1005,6 @@ extension UserRobot { // MARK: Ephemeral messages extension UserRobot { - @discardableResult func assertGiphyImage( at messageCellIndex: Int? = nil, @@ -996,8 +1045,8 @@ extension UserRobot { } // MARK: Keyboard -extension UserRobot { +extension UserRobot { @discardableResult func assertKeyboard( isVisible: Bool, @@ -1006,18 +1055,20 @@ extension UserRobot { ) -> Self { let keyboard = app.keyboards.firstMatch keyboard.wait(timeout: 1.5) - XCTAssertEqual(isVisible, - keyboard.exists, - "Keyboard should be \(isVisible ? "visible" : "hidden")", - file: file, - line: line) + XCTAssertEqual( + isVisible, + keyboard.exists, + "Keyboard should be \(isVisible ? "visible" : "hidden")", + file: file, + line: line + ) return self } } // MARK: Attachments -extension UserRobot { +extension UserRobot { @discardableResult func assertImage( isPresent: Bool, @@ -1073,7 +1124,6 @@ extension UserRobot { // MARK: UserDetails extension UserRobot { - @discardableResult func assertUserDetails(_ details: [String: Any]?) -> Self { let userDetails = details?[WebSocketConnectPayload.CodingKeys.userDetails.rawValue] as? [String: Any] diff --git a/StreamChatUITestsAppUITests/Robots/UserRobot.swift b/StreamChatUITestsAppUITests/Robots/UserRobot.swift index 9675b10c1ab..931a2c526e1 100644 --- a/StreamChatUITestsAppUITests/Robots/UserRobot.swift +++ b/StreamChatUITestsAppUITests/Robots/UserRobot.swift @@ -3,12 +3,11 @@ // import Foundation -import XCTest import StreamChat +import XCTest /// Simulates user behavior final class UserRobot: Robot { - let composer = MessageListPage.Composer.self let contextMenu = MessageListPage.ContextMenu.self let debugAlert = MessageListPage.Alert.Debug.self @@ -62,7 +61,7 @@ final class UserRobot: Robot { @discardableResult public func waitForJwtToExpire() -> Self { - let sleepTime = UInt32(StreamMockServer.jwtTimeout * 1000000) + let sleepTime = UInt32(StreamMockServer.jwtTimeout * 1_000_000) usleep(sleepTime) return self } @@ -71,7 +70,6 @@ final class UserRobot: Robot { // MARK: Message List extension UserRobot { - @discardableResult func openContextMenu(messageCellIndex: Int = 0) -> Self { messageCell(withIndex: messageCellIndex).waitForHitPoint().safePress(forDuration: 1) @@ -89,11 +87,13 @@ extension UserRobot { } @discardableResult - func sendMessage(_ text: String, - at messageCellIndex: Int? = nil, - waitForAppearance: Bool = true, - file: StaticString = #filePath, - line: UInt = #line) -> Self { + func sendMessage( + _ text: String, + at messageCellIndex: Int? = nil, + waitForAppearance: Bool = true, + file: StaticString = #filePath, + line: UInt = #line + ) -> Self { server.channelsEndpointWasCalled = false typeText(text) @@ -204,16 +204,20 @@ extension UserRobot { } @discardableResult - func quoteMessage(_ text: String, - messageCellIndex: Int = 0, - waitForAppearance: Bool = true, - file: StaticString = #filePath, - line: UInt = #line) -> Self { + func quoteMessage( + _ text: String, + messageCellIndex: Int = 0, + waitForAppearance: Bool = true, + file: StaticString = #filePath, + line: UInt = #line + ) -> Self { selectOptionFromContextMenu(option: .reply, forMessageAtIndex: messageCellIndex) - sendMessage(text, - waitForAppearance: waitForAppearance, - file: file, - line: line) + sendMessage( + text, + waitForAppearance: waitForAppearance, + file: file, + line: line + ) return self } @@ -288,11 +292,13 @@ extension UserRobot { if alsoSendInChannel { threadCheckbox.wait().safeTap() } - sendMessage(text, - at: messageCellIndex, - waitForAppearance: waitForAppearance, - file: file, - line: line) + sendMessage( + text, + at: messageCellIndex, + waitForAppearance: waitForAppearance, + file: file, + line: line + ) return self } @@ -471,7 +477,7 @@ extension UserRobot { @discardableResult func mentionParticipant(manually: Bool = false) -> Self { - let text = "@\(UserDetails.hanSoloId)" + let text = "@\(UserDetails.countDookuId)" if manually { typeText(text) } else { @@ -485,7 +491,6 @@ extension UserRobot { // MARK: Debug menu extension UserRobot { - @discardableResult private func tapOnDebugMenu() -> Self { MessageListPage.NavigationBar.debugMenu.safeTap() @@ -526,7 +531,6 @@ extension UserRobot { // MARK: Connectivity extension UserRobot { - /// Toggles the visibility of the connectivity switch control. When set to `.on`, the switch control will be displayed in the navigation bar. @discardableResult func setConnectivitySwitchVisibility(to state: SwitchState) -> Self { @@ -547,7 +551,6 @@ extension UserRobot { // MARK: Config extension UserRobot { - @discardableResult func setIsLocalStorageEnabled(to state: SwitchState) -> Self { setSwitchState(Settings.isLocalStorageEnabled.element, state: state) @@ -557,5 +560,4 @@ extension UserRobot { func setStaysConnectedInBackground(to state: SwitchState) -> Self { setSwitchState(Settings.staysConnectedInBackground.element, state: state) } - } diff --git a/StreamChatUITestsAppUITests/StreamChatUITests.swift b/StreamChatUITestsAppUITests/StreamChatUITests.swift index 2433fa4c267..269b699ce4a 100644 --- a/StreamChatUITestsAppUITests/StreamChatUITests.swift +++ b/StreamChatUITestsAppUITests/StreamChatUITests.swift @@ -2,8 +2,8 @@ // Copyright © 2025 Stream.io Inc. All rights reserved. // +import Foundation @_exported import StreamChatTestMockServer @_exported import StreamSwiftTestHelpers -import Foundation final class StreamChatUITests {} diff --git a/StreamChatUITestsAppUITests/Tests/Attachments_Tests.swift b/StreamChatUITestsAppUITests/Tests/Attachments_Tests.swift index 12e00357a9e..0f4b3a85ce6 100644 --- a/StreamChatUITestsAppUITests/Tests/Attachments_Tests.swift +++ b/StreamChatUITestsAppUITests/Tests/Attachments_Tests.swift @@ -5,10 +5,11 @@ import XCTest final class Attachments_Tests: StreamTestCase { - override func setUpWithError() throws { - try XCTSkipIf(ProcessInfo().operatingSystemVersion.majorVersion >= 18, - "Attachments tests freeze the test app on iOS > 18") + try XCTSkipIf( + ProcessInfo().operatingSystemVersion.majorVersion >= 18, + "Attachments tests freeze the test app on iOS > 18" + ) try super.setUpWithError() addTags([.coreFeatures]) diff --git a/StreamChatUITestsAppUITests/Tests/Authentication_Tests.swift b/StreamChatUITestsAppUITests/Tests/Authentication_Tests.swift index 9f282694296..56836212b6c 100644 --- a/StreamChatUITestsAppUITests/Tests/Authentication_Tests.swift +++ b/StreamChatUITestsAppUITests/Tests/Authentication_Tests.swift @@ -6,7 +6,6 @@ import XCTest // Requires running a standalone Sinatra server final class Authentication_Tests: StreamTestCase { - override func setUpWithError() throws { mockServerEnabled = false app.setLaunchArguments(.jwt) diff --git a/StreamChatUITestsAppUITests/Tests/Base TestCase/StreamTestCase.swift b/StreamChatUITestsAppUITests/Tests/Base TestCase/StreamTestCase.swift index 95dff20455c..f070f383e3e 100644 --- a/StreamChatUITestsAppUITests/Tests/Base TestCase/StreamTestCase.swift +++ b/StreamChatUITestsAppUITests/Tests/Base TestCase/StreamTestCase.swift @@ -8,7 +8,6 @@ import XCTest let app = XCUIApplication() class StreamTestCase: XCTestCase { - let deviceRobot = DeviceRobot(app) var userRobot: UserRobot! var backendRobot: BackendRobot! @@ -47,7 +46,6 @@ class StreamTestCase: XCTestCase { } extension StreamTestCase { - func assertMockServer() { XCTAssertFalse(mockServerCrashed, "Mock server failed on start") } diff --git a/StreamChatUITestsAppUITests/Tests/ChannelList_Tests.swift b/StreamChatUITestsAppUITests/Tests/ChannelList_Tests.swift index 020a883f5bf..da0e43bcc9b 100644 --- a/StreamChatUITestsAppUITests/Tests/ChannelList_Tests.swift +++ b/StreamChatUITestsAppUITests/Tests/ChannelList_Tests.swift @@ -5,7 +5,6 @@ import XCTest final class ChannelList_Tests: StreamTestCase { - let message = "message" override func setUpWithError() throws { diff --git a/StreamChatUITestsAppUITests/Tests/Ephemeral_Messages_Tests.swift b/StreamChatUITestsAppUITests/Tests/Ephemeral_Messages_Tests.swift index 61ce42b1a06..c581c80b060 100644 --- a/StreamChatUITestsAppUITests/Tests/Ephemeral_Messages_Tests.swift +++ b/StreamChatUITestsAppUITests/Tests/Ephemeral_Messages_Tests.swift @@ -5,7 +5,6 @@ import XCTest final class Ephemeral_Messages_Tests: StreamTestCase { - override func setUpWithError() throws { try super.setUpWithError() assertMockServer() diff --git a/StreamChatUITestsAppUITests/Tests/Message Delivery Status/MessageDeliveryStatus+ChannelList_Tests.swift b/StreamChatUITestsAppUITests/Tests/Message Delivery Status/MessageDeliveryStatus+ChannelList_Tests.swift index f54819aad60..728dcb4b625 100644 --- a/StreamChatUITestsAppUITests/Tests/Message Delivery Status/MessageDeliveryStatus+ChannelList_Tests.swift +++ b/StreamChatUITestsAppUITests/Tests/Message Delivery Status/MessageDeliveryStatus+ChannelList_Tests.swift @@ -5,7 +5,6 @@ import XCTest final class MessageDeliveryStatus_ChannelList_Tests: StreamTestCase { - let message = "message" var failedMessage: String { "failed \(message)" } @@ -59,7 +58,6 @@ final class MessageDeliveryStatus_ChannelList_Tests: StreamTestCase { userRobot .assertMessageReadCountInChannelPreview(readBy: 0) .assertMessageDeliveryStatusInChannelPreview(.sent) - } } @@ -159,7 +157,6 @@ final class MessageDeliveryStatus_ChannelList_Tests: StreamTestCase { // MARK: Thread Reply extension MessageDeliveryStatus_ChannelList_Tests { - func test_noCheckmarkShownForMessageInPreview_whenThreadReplyIsSent() { linkToScenario(withId: 172) diff --git a/StreamChatUITestsAppUITests/Tests/Message Delivery Status/MessageDeliveryStatus_Tests.swift b/StreamChatUITestsAppUITests/Tests/Message Delivery Status/MessageDeliveryStatus_Tests.swift index 42a248099b6..b1f88384e09 100644 --- a/StreamChatUITestsAppUITests/Tests/Message Delivery Status/MessageDeliveryStatus_Tests.swift +++ b/StreamChatUITestsAppUITests/Tests/Message Delivery Status/MessageDeliveryStatus_Tests.swift @@ -5,7 +5,6 @@ import XCTest final class MessageDeliveryStatus_Tests: StreamTestCase { - let message = "message" var pendingMessage: String { "pending \(message)" } var failedMessage: String { "failed \(message)" } @@ -21,6 +20,7 @@ final class MessageDeliveryStatus_Tests: StreamTestCase { } // MARK: Message List + func test_singleCheckmarkShown_whenMessageIsSent() { linkToScenario(withId: 129) @@ -64,7 +64,6 @@ final class MessageDeliveryStatus_Tests: StreamTestCase { .login() .setConnectivity(to: .off) .openChannel() - } WHEN("user sends a new message") { userRobot.sendMessage(failedMessage, waitForAppearance: false) @@ -210,8 +209,8 @@ final class MessageDeliveryStatus_Tests: StreamTestCase { // MARK: Thread Reply extension MessageDeliveryStatus_Tests { - // MARK: Thread Previews + func test_singleCheckmarkShown_whenMessageIsSent_andPreviewedInThread() { linkToScenario(withId: 148) @@ -494,7 +493,6 @@ extension MessageDeliveryStatus_Tests { // MARK: Disabled Read Events feature extension MessageDeliveryStatus_Tests { - // MARK: Messages func test_deliveryStatusHidden_whenMessageIsSentAndReadEventsIsDisabled() { diff --git a/StreamChatUITestsAppUITests/Tests/MessageList_Tests.swift b/StreamChatUITestsAppUITests/Tests/MessageList_Tests.swift index b4a7326daa2..185bdf30b77 100644 --- a/StreamChatUITestsAppUITests/Tests/MessageList_Tests.swift +++ b/StreamChatUITestsAppUITests/Tests/MessageList_Tests.swift @@ -5,7 +5,6 @@ import XCTest final class MessageList_Tests: StreamTestCase { - override func setUpWithError() throws { try super.setUpWithError() addTags([.coreFeatures]) @@ -104,7 +103,6 @@ final class MessageList_Tests: StreamTestCase { linkToScenario(withId: 64) let message = "🚢" - let author = "Han Solo" GIVEN("user opens the channel") { userRobot.login().openChannel() @@ -113,7 +111,7 @@ final class MessageList_Tests: StreamTestCase { participantRobot.sendMessage(message) } THEN("the message is delivered") { - userRobot.assertMessageAuthor(author) + userRobot.assertMessage(message) } } @@ -342,7 +340,6 @@ final class MessageList_Tests: StreamTestCase { // MARK: Scroll to bottom extension MessageList_Tests { - func test_messageListScrollsDown_whenMessageListIsScrolledUp_andUserSendsNewMessage() throws { linkToScenario(withId: 193) @@ -529,7 +526,6 @@ extension MessageList_Tests { // MARK: Mentions extension MessageList_Tests { - func test_addingCommandHidesLeftButtons() { linkToScenario(withId: 104) @@ -588,7 +584,6 @@ extension MessageList_Tests { // MARK: Links preview extension MessageList_Tests { - func test_addMessageWithLinkToUnsplash() { linkToScenario(withId: 59) @@ -697,6 +692,7 @@ extension MessageList_Tests { } // MARK: - Thread replies + extension MessageList_Tests { func test_threadReplyAppearsInThread_whenParticipantAddsThreadReply() { linkToScenario(withId: 50) diff --git a/StreamChatUITestsAppUITests/Tests/Performance/ChannelListScrollTime.swift b/StreamChatUITestsAppUITests/Tests/Performance/ChannelListScrollTime.swift index a254b338c93..d776182ea54 100644 --- a/StreamChatUITestsAppUITests/Tests/Performance/ChannelListScrollTime.swift +++ b/StreamChatUITestsAppUITests/Tests/Performance/ChannelListScrollTime.swift @@ -6,7 +6,6 @@ import XCTest @available(iOS 15.0, *) class ChannelListScrollTime: StreamTestCase { - override func setUpWithError() throws { mockServerEnabled = false switchApiKey = true diff --git a/StreamChatUITestsAppUITests/Tests/Performance/MessageListScrollTime.swift b/StreamChatUITestsAppUITests/Tests/Performance/MessageListScrollTime.swift index ed401e53510..124bbfb4e63 100644 --- a/StreamChatUITestsAppUITests/Tests/Performance/MessageListScrollTime.swift +++ b/StreamChatUITestsAppUITests/Tests/Performance/MessageListScrollTime.swift @@ -6,7 +6,6 @@ import XCTest @available(iOS 15.0, *) class MessageListScrollTime: StreamTestCase { - func testMessageListScrollTime() { WHEN("user opens the message list") { backendRobot.generateChannels(count: 1, messagesCount: 100, withAttachments: true) diff --git a/StreamChatUITestsAppUITests/Tests/PushNotification_Tests.swift b/StreamChatUITestsAppUITests/Tests/PushNotification_Tests.swift index ce57547a6a3..c1a300684ab 100644 --- a/StreamChatUITestsAppUITests/Tests/PushNotification_Tests.swift +++ b/StreamChatUITestsAppUITests/Tests/PushNotification_Tests.swift @@ -6,13 +6,14 @@ import XCTest // Requires running a standalone Sinatra server final class PushNotification_Tests: StreamTestCase { - - let sender = "Han Solo" + let sender = UserDetails.countDookuName let message = "How are you? 🙂" override func setUpWithError() throws { - try XCTSkipIf(ProcessInfo().operatingSystemVersion.majorVersion < 14, - "Push notifications infra does not work on iOS < 14") + try XCTSkipIf( + ProcessInfo().operatingSystemVersion.majorVersion < 14, + "Push notifications infra does not work on iOS < 14" + ) try super.setUpWithError() assertMockServer() } @@ -38,7 +39,7 @@ final class PushNotification_Tests: StreamTestCase { GIVEN("user goes to channel list") { userRobot .login() - .openChannel() // this is required to let the mock server know + .openChannel() // this is required to let the mock server know .tapOnBackButton() // which channel to use for push notifications } checkHappyPath(message: message, sender: sender) @@ -69,7 +70,7 @@ final class PushNotification_Tests: StreamTestCase { version: "", messageId: "", cid: "" - ) + ) GIVEN("user goes to message list") { userRobot.login().openChannel() @@ -133,9 +134,11 @@ final class PushNotification_Tests: StreamTestCase { mockPushNotification(body: nil) WHEN("participant sends a message (push body param is nil)") { - participantRobot.wait(2).sendMessage("\(message)_0", - withPushNotification: true, - bundleIdForPushNotification: app.bundleId()) + participantRobot.wait(2).sendMessage( + "\(message)_0", + withPushNotification: true, + bundleIdForPushNotification: app.bundleId() + ) } THEN("user does not receive a push notification") { userRobot.assertPushNotificationDoesNotAppear() @@ -143,9 +146,11 @@ final class PushNotification_Tests: StreamTestCase { mockPushNotification(body: "") WHEN("participant sends a message (push body param is empty)") { - participantRobot.sendMessage("\(message)_1", - withPushNotification: true, - bundleIdForPushNotification: app.bundleId()) + participantRobot.sendMessage( + "\(message)_1", + withPushNotification: true, + bundleIdForPushNotification: app.bundleId() + ) } THEN("user does not receive a push notification") { userRobot.assertPushNotificationDoesNotAppear() @@ -153,9 +158,11 @@ final class PushNotification_Tests: StreamTestCase { mockPushNotification(body: 42) WHEN("participant sends a message (push body param contains incorrect type)") { - participantRobot.sendMessage("\(message)_2", - withPushNotification: true, - bundleIdForPushNotification: app.bundleId()) + participantRobot.sendMessage( + "\(message)_2", + withPushNotification: true, + bundleIdForPushNotification: app.bundleId() + ) } THEN("user does not receive a push notification") { userRobot.assertPushNotificationDoesNotAppear() @@ -184,9 +191,11 @@ final class PushNotification_Tests: StreamTestCase { deviceRobot.moveApplication(to: .background) } AND("participant sends a message") { - participantRobot.wait(2).sendMessage(message, - withPushNotification: true, - bundleIdForPushNotification: app.bundleId()) + participantRobot.wait(2).sendMessage( + message, + withPushNotification: true, + bundleIdForPushNotification: app.bundleId() + ) } THEN("user observes an icon badge") { userRobot.assertAppIconBadge(shouldBeVisible: true) diff --git a/StreamChatUITestsAppUITests/Tests/QuotedReply_Tests.swift b/StreamChatUITestsAppUITests/Tests/QuotedReply_Tests.swift index dfe2a96453e..d0e2d2bf33b 100644 --- a/StreamChatUITestsAppUITests/Tests/QuotedReply_Tests.swift +++ b/StreamChatUITestsAppUITests/Tests/QuotedReply_Tests.swift @@ -2,11 +2,10 @@ // Copyright © 2025 Stream.io Inc. All rights reserved. // -import XCTest @testable import StreamChatUI +import XCTest final class QuotedReply_Tests: StreamTestCase { - let messageCount = 30 let pageSize = 25 let quotedText = "1" diff --git a/StreamChatUITestsAppUITests/Tests/Reactions_Tests.swift b/StreamChatUITestsAppUITests/Tests/Reactions_Tests.swift index 7340bcb38c8..ecde8a3c6ac 100644 --- a/StreamChatUITestsAppUITests/Tests/Reactions_Tests.swift +++ b/StreamChatUITestsAppUITests/Tests/Reactions_Tests.swift @@ -5,7 +5,6 @@ import XCTest final class Reactions_Tests: StreamTestCase { - override func setUpWithError() throws { try super.setUpWithError() addTags([.coreFeatures]) diff --git a/StreamChatUITestsAppUITests/Tests/SlowMode_Tests.swift b/StreamChatUITestsAppUITests/Tests/SlowMode_Tests.swift index 0cbaf03e509..b248ec0c2f9 100644 --- a/StreamChatUITestsAppUITests/Tests/SlowMode_Tests.swift +++ b/StreamChatUITestsAppUITests/Tests/SlowMode_Tests.swift @@ -5,7 +5,6 @@ import XCTest final class SlowMode_Tests: StreamTestCase { - let cooldownDuration = 15 let message = "message" let anotherNewMessage = "Another new message" diff --git a/StreamChatUITestsAppUITests/Tests/StreamTestCase+Tags.swift b/StreamChatUITestsAppUITests/Tests/StreamTestCase+Tags.swift index 55d641b85e9..2a289319e68 100644 --- a/StreamChatUITestsAppUITests/Tests/StreamTestCase+Tags.swift +++ b/StreamChatUITestsAppUITests/Tests/StreamTestCase+Tags.swift @@ -13,6 +13,6 @@ extension StreamTestCase { } func addTags(_ tags: [Tags]) { - addTagsToScenario(tags.map{ $0.rawValue }) + addTagsToScenario(tags.map { $0.rawValue }) } } diff --git a/StreamChatUITestsAppUITests/Tests/UserDetails_Tests.swift b/StreamChatUITestsAppUITests/Tests/UserDetails_Tests.swift index 1a5780f1150..15e2b0d7fdd 100644 --- a/StreamChatUITestsAppUITests/Tests/UserDetails_Tests.swift +++ b/StreamChatUITestsAppUITests/Tests/UserDetails_Tests.swift @@ -5,7 +5,6 @@ import XCTest final class UserDetails_Tests: StreamTestCase { - override func setUpWithError() throws { try super.setUpWithError() assertMockServer() diff --git a/TestTools/StreamChatTestMockServer/Extensions/Dictionary.swift b/TestTools/StreamChatTestMockServer/Extensions/Dictionary.swift index 14d321ea48c..f589f8c743a 100644 --- a/TestTools/StreamChatTestMockServer/Extensions/Dictionary.swift +++ b/TestTools/StreamChatTestMockServer/Extensions/Dictionary.swift @@ -5,20 +5,19 @@ import Foundation public extension Dictionary { - func jsonToString(prettyPrinted: Bool = false) -> String { var options: JSONSerialization.WritingOptions = [] if prettyPrinted { - options = JSONSerialization.WritingOptions.prettyPrinted + options = JSONSerialization.WritingOptions.prettyPrinted } do { - let data = try JSONSerialization.data(withJSONObject: self, options: options) - if let string = String(data: data, encoding: String.Encoding.utf8) { - return string - } + let data = try JSONSerialization.data(withJSONObject: self, options: options) + if let string = String(data: data, encoding: String.Encoding.utf8) { + return string + } } catch { - print(error) + print(error) } return "" diff --git a/TestTools/StreamChatTestMockServer/Extensions/String.swift b/TestTools/StreamChatTestMockServer/Extensions/String.swift index 4c52e9f3f04..1e8112548e8 100644 --- a/TestTools/StreamChatTestMockServer/Extensions/String.swift +++ b/TestTools/StreamChatTestMockServer/Extensions/String.swift @@ -5,23 +5,25 @@ import XCTest public extension String { - var json: [String: Any] { - try! JSONSerialization.jsonObject(with: Data(self.utf8), - options: .mutableContainers) as! [String: Any] + try! JSONSerialization.jsonObject( + with: Data(self.utf8), + options: .mutableContainers + ) as! [String: Any] } func replace(_ target: String, to: String) -> String { - replacingOccurrences(of: target, - with: to, - options: NSString.CompareOptions.literal, - range: nil) + replacingOccurrences( + of: target, + with: to, + options: NSString.CompareOptions.literal, + range: nil + ) } var html: Self { self.isEmpty ? "" : "

\(self)

\n" } - } public extension Substring { diff --git a/TestTools/StreamChatTestMockServer/Extensions/Swifter.swift b/TestTools/StreamChatTestMockServer/Extensions/Swifter.swift index 63476de95d3..691512f838e 100644 --- a/TestTools/StreamChatTestMockServer/Extensions/Swifter.swift +++ b/TestTools/StreamChatTestMockServer/Extensions/Swifter.swift @@ -5,7 +5,7 @@ import Foundation public extension HttpServer { - func register(_ path: String, execution: @escaping ((HttpRequest) throws -> HttpResponse?)) { + func register(_ path: String, execution: @escaping ((HttpRequest) throws -> HttpResponse?)) { self[path] = { [weak self] in self?.delayServerResponseIfNeeded() diff --git a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_add_member.json b/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_add_member.json index de599c619b8..32f7c85cdcf 100644 --- a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_add_member.json +++ b/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_add_member.json @@ -1,11 +1,11 @@ { "channel": { - "id": "ec2807ff-5c60-41bc-a816-49578260471a", + "id": "ec9f758c-3f7e-46f3-9618-bc5760428e5e", "type": "messaging", - "cid": "messaging:ec2807ff-5c60-41bc-a816-49578260471a", - "last_message_at": "2025-06-15T00:25:36.86243Z", - "created_at": "2025-06-15T00:25:33.285826Z", - "updated_at": "2025-06-15T00:25:33.285826Z", + "cid": "messaging:ec9f758c-3f7e-46f3-9618-bc5760428e5e", + "last_message_at": "2025-10-15T11:22:21.804306Z", + "created_at": "2025-10-15T11:22:20.27144Z", + "updated_at": "2025-10-15T11:22:20.27144Z", "created_by": { "id": "luke_skywalker", "name": "Luke Skywalker", @@ -14,22 +14,26 @@ "role": "admin", "teams": [], "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-06-06T08:22:36.844279Z", + "updated_at": "2025-09-15T05:51:41.748915Z", "banned": false, "online": true, - "last_active": "2025-06-15T00:25:32.362084978Z", + "last_active": "2025-10-15T11:22:19.961028904Z", "blocked_user_ids": [], + "avg_response_time": 203648, + "canBeAddedToGroups": true, + "pando": "{\"speciality\":\"ios engineer\"}", + "canReceiveMessages": false, "team": "test", "type": "team", - "pando": "{\"speciality\":\"ios engineer\"}", - "birthland": "Tatooine" + "birthland": "Tatooine", + "custom_extra_data_key": true }, "frozen": false, "disabled": false, "member_count": 4, "config": { "created_at": "2021-03-01T19:26:18.406502Z", - "updated_at": "2025-05-05T18:07:33.269057Z", + "updated_at": "2025-07-28T15:20:21.098826Z", "name": "messaging", "typing_events": true, "read_events": true, @@ -43,9 +47,12 @@ "url_enrichment": true, "custom_events": true, "push_notifications": true, - "reminders": false, + "reminders": true, "mark_messages_pending": false, "polls": true, + "user_message_reminders": false, + "shared_locations": true, + "count_messages": false, "message_retention": "infinite", "max_message_length": 5000, "automod": "AI", @@ -102,6 +109,7 @@ "send-restricted-visibility-message", "send-typing-events", "set-channel-cooldown", + "share-location", "skip-slow-mode", "typing-events", "update-any-message", @@ -128,13 +136,14 @@ "updated_at": "2025-04-23T13:02:29.974824Z", "banned": false, "online": false, - "last_active": "2025-04-23T13:03:51.783496Z", + "last_active": "2025-10-01T07:12:24.084195Z", "blocked_user_ids": [], + "avg_response_time": 531, "birthland": "Serenno" }, "status": "member", - "created_at": "2025-06-15T00:25:33.304034Z", - "updated_at": "2025-06-15T00:25:33.304034Z", + "created_at": "2025-10-15T11:22:20.279452Z", + "updated_at": "2025-10-15T11:22:20.279452Z", "banned": false, "shadow_banned": false, "role": "member", @@ -142,28 +151,29 @@ "notifications_muted": false }, { - "user_id": "han_solo", + "user_id": "lando_calrissian", "user": { - "id": "han_solo", - "name": "Han Solo", - "image": "https://vignette.wikia.nocookie.net/starwars/images/e/e2/TFAHanSolo.png", - "language": "fr", - "role": "user", + "id": "lando_calrissian", + "name": "Lando Calrissian", + "image": "https://vignette.wikia.nocookie.net/starwars/images/8/8f/Lando_ROTJ.png", + "language": "", + "role": "admin", "teams": [], - "created_at": "2024-04-04T09:18:11.060737Z", - "updated_at": "2025-04-24T15:07:52.050477Z", + "created_at": "2024-04-04T19:06:08.890459Z", + "updated_at": "2025-04-16T17:23:28.189521Z", "banned": false, "online": false, - "last_active": "2025-06-10T06:55:59.491807Z", + "last_active": "2025-10-12T03:39:43.111316Z", "blocked_user_ids": [], - "birthland": "Corellia" + "avg_response_time": 1165670, + "birthland": "Socorro" }, "status": "member", - "created_at": "2025-06-15T00:25:33.304034Z", - "updated_at": "2025-06-15T00:25:33.304034Z", + "created_at": "2025-10-15T11:22:20.279452Z", + "updated_at": "2025-10-15T11:22:20.279452Z", "banned": false, "shadow_banned": false, - "role": "member", + "role": "admin", "channel_role": "channel_member", "notifications_muted": false }, @@ -177,19 +187,23 @@ "role": "admin", "teams": [], "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-06-06T08:22:36.844279Z", + "updated_at": "2025-09-15T05:51:41.748915Z", "banned": false, "online": true, - "last_active": "2025-06-15T00:25:32.362084978Z", + "last_active": "2025-10-15T11:22:19.961028904Z", "blocked_user_ids": [], - "team": "test", + "avg_response_time": 203648, + "birthland": "Tatooine", + "custom_extra_data_key": true, "type": "team", - "pando": "{\"speciality\":\"ios engineer\"}", - "birthland": "Tatooine" + "canBeAddedToGroups": true, + "canReceiveMessages": false, + "team": "test", + "pando": "{\"speciality\":\"ios engineer\"}" }, "status": "member", - "created_at": "2025-06-15T00:25:33.304034Z", - "updated_at": "2025-06-15T00:25:33.304034Z", + "created_at": "2025-10-15T11:22:20.279452Z", + "updated_at": "2025-10-15T11:22:20.279452Z", "banned": false, "shadow_banned": false, "role": "owner", @@ -202,28 +216,22 @@ "id": "leia_organa", "name": "Leia Organa", "image": "https://vignette.wikia.nocookie.net/starwars/images/f/fc/Leia_Organa_TLJ.png", - "language": "zh", + "language": "", "role": "admin", "teams": [], "created_at": "2024-04-04T09:42:00.68335Z", - "updated_at": "2025-03-28T15:21:20.061525Z", + "updated_at": "2025-10-01T16:49:27.725672Z", "banned": false, - "online": false, - "last_active": "2025-06-14T17:34:03.224367Z", + "online": true, + "last_active": "2025-10-15T11:21:40.020397141Z", "blocked_user_ids": [], - "birthland": "Polis Massa", - "private_settings": { - "readReceipts": { - "enabled": false - }, - "typingIndicators": { - "enabled": false - } - } + "avg_response_time": 638602, + "is_moderator": true, + "birthland": "Polis Massa" }, "status": "member", - "created_at": "2025-06-15T00:25:38.137522Z", - "updated_at": "2025-06-15T00:25:38.137522Z", + "created_at": "2025-10-15T11:22:22.248971Z", + "updated_at": "2025-10-15T11:22:22.248971Z", "banned": false, "shadow_banned": false, "role": "admin", @@ -231,5 +239,5 @@ "notifications_muted": false } ], - "duration": "38.76ms" + "duration": "36.22ms" } \ No newline at end of file diff --git a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_attachment.json b/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_attachment.json index 0cf07795844..21e8fc65c39 100644 --- a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_attachment.json +++ b/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_attachment.json @@ -1,4 +1,4 @@ { - "file": "https://frankfurt.stream-io-cdn.com/102399/images/84500679-c986-471d-be2a-461542a4dbaf.yoda.jpg?Key-Pair-Id=APKAIHG36VEWPDULE23Q&Policy=eyJTdGF0ZW1lbnQiOlt7IlJlc291cmNlIjoiaHR0cHM6Ly9mcmFua2Z1cnQuc3RyZWFtLWlvLWNkbi5jb20vMTAyMzk5L2ltYWdlcy84NDUwMDY3OS1jOTg2LTQ3MWQtYmUyYS00NjE1NDJhNGRiYWYueW9kYS5qcGc~Km9oPTAqb3c9MCoiLCJDb25kaXRpb24iOnsiRGF0ZUxlc3NUaGFuIjp7IkFXUzpFcG9jaFRpbWUiOjE3NTExNTY3Mzh9fX1dfQ__&Signature=N0MgP9HIGp5hCyxzq-70m0lO2Km3P73oZC-jepZlIxtv4go-DnWvgPHGKEw6bo60Ud2vV8whVkG92U1c5OuU~dpwwZj8L8q0VqMDA7T~VOVEEN4VlwlD2VPElyA2IUCISqkuTd61xO9CqR0nsmHum48zdWPZn001uNZwN5rWb1GJfJijeVPhu1rkytdygNeE1ZeZiDG0gUKt7~9H30~HS75zivkryTkKRD9yhmmeInwV45FocoDzUGsPc7Ux8xW6DaxXhUaNC6FdQp9GMLCmBNrgZjMLZsS-mm~qsl78ZR24eEzuT8mwfVh9rmAmW9xUdyXEZCUod6KpMrLiRGahIA__&oh=0&ow=0", - "duration": "124.23ms" + "file": "https://frankfurt.stream-io-cdn.com/102399/images/7a78629b-d178-4bb4-9e6d-ca28484c3130.yoda.jpg?Key-Pair-Id=APKAIHG36VEWPDULE23Q&Policy=eyJTdGF0ZW1lbnQiOlt7IlJlc291cmNlIjoiaHR0cHM6Ly9mcmFua2Z1cnQuc3RyZWFtLWlvLWNkbi5jb20vMTAyMzk5L2ltYWdlcy83YTc4NjI5Yi1kMTc4LTRiYjQtOWU2ZC1jYTI4NDg0YzMxMzAueW9kYS5qcGc~Km9oPTAqb3c9MCoiLCJDb25kaXRpb24iOnsiRGF0ZUxlc3NUaGFuIjp7IkFXUzpFcG9jaFRpbWUiOjE3NjE3MzY5NDJ9fX1dfQ__&Signature=LI6nidy~xUcwmRDDyFvHiSnpuHfDei7J~FmMdbG1m1uuXAKsyJwue7isX-1tOaKV~hvWaInq-nn4-s0-OkdgwjYw7TsRlJlgowCrGm5NSFmPdkSdOZerNGVfVJEW0zV0FgRkHXrM-K9wS~sJx6x-rDkJs9YEY4CWpwlhhI-jICVpLHaNHaUT63fDtKbKoGU3JCzPjtlI9vjoHAG~nr9EahyizFoxW5~i7K-uAC2tJj15JKeqAw4nWDmPU0qV-NxEH2CGn3Otcp8vrskVq4lqW3B4pGgwVnsopfYBuLrZlN6AuiSwTQos5s6~9iVXlFjGnGOoWbgivHjV-9aiR1nMgg__&oh=0&ow=0", + "duration": "131.42ms" } \ No newline at end of file diff --git a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_channel_creation.json b/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_channel_creation.json index 13fb1c099af..c115f5640e6 100644 --- a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_channel_creation.json +++ b/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_channel_creation.json @@ -1,10 +1,10 @@ { "channel": { - "id": "ec2807ff-5c60-41bc-a816-49578260471a", + "id": "ec9f758c-3f7e-46f3-9618-bc5760428e5e", "type": "messaging", - "cid": "messaging:ec2807ff-5c60-41bc-a816-49578260471a", - "created_at": "2025-06-15T00:25:33.285826Z", - "updated_at": "2025-06-15T00:25:33.285826Z", + "cid": "messaging:ec9f758c-3f7e-46f3-9618-bc5760428e5e", + "created_at": "2025-10-15T11:22:20.27144Z", + "updated_at": "2025-10-15T11:22:20.27144Z", "created_by": { "id": "luke_skywalker", "name": "Luke Skywalker", @@ -13,12 +13,16 @@ "role": "admin", "teams": [], "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-06-06T08:22:36.844279Z", + "updated_at": "2025-09-15T05:51:41.748915Z", "banned": false, "online": true, - "last_active": "2025-06-15T00:25:32.362084978Z", + "last_active": "2025-10-15T11:22:19.961028904Z", "blocked_user_ids": [], + "avg_response_time": 203648, + "custom_extra_data_key": true, + "canBeAddedToGroups": true, "team": "test", + "canReceiveMessages": false, "type": "team", "pando": "{\"speciality\":\"ios engineer\"}", "birthland": "Tatooine" @@ -28,7 +32,7 @@ "member_count": 3, "config": { "created_at": "2021-03-01T19:26:18.406502Z", - "updated_at": "2025-05-05T18:07:33.269057Z", + "updated_at": "2025-07-28T15:20:21.098826Z", "name": "messaging", "typing_events": true, "read_events": true, @@ -42,9 +46,12 @@ "url_enrichment": true, "custom_events": true, "push_notifications": true, - "reminders": false, + "reminders": true, "mark_messages_pending": false, "polls": true, + "user_message_reminders": false, + "shared_locations": true, + "count_messages": false, "message_retention": "infinite", "max_message_length": 5000, "automod": "AI", @@ -101,6 +108,7 @@ "send-restricted-visibility-message", "send-typing-events", "set-channel-cooldown", + "share-location", "skip-slow-mode", "typing-events", "update-any-message", @@ -130,30 +138,32 @@ "updated_at": "2025-04-23T13:02:29.974824Z", "banned": false, "online": false, - "last_active": "2025-04-23T13:03:51.783496Z", + "last_active": "2025-10-01T07:12:24.084195Z", "blocked_user_ids": [], + "avg_response_time": 531, "birthland": "Serenno" }, - "last_read": "2025-06-15T00:25:33.34121176Z", + "last_read": "2025-10-15T11:22:20.320400817Z", "unread_messages": 0 }, { "user": { - "id": "han_solo", - "name": "Han Solo", - "image": "https://vignette.wikia.nocookie.net/starwars/images/e/e2/TFAHanSolo.png", - "language": "fr", - "role": "user", + "id": "lando_calrissian", + "name": "Lando Calrissian", + "image": "https://vignette.wikia.nocookie.net/starwars/images/8/8f/Lando_ROTJ.png", + "language": "", + "role": "admin", "teams": [], - "created_at": "2024-04-04T09:18:11.060737Z", - "updated_at": "2025-04-24T15:07:52.050477Z", + "created_at": "2024-04-04T19:06:08.890459Z", + "updated_at": "2025-04-16T17:23:28.189521Z", "banned": false, "online": false, - "last_active": "2025-06-10T06:55:59.491807Z", + "last_active": "2025-10-12T03:39:43.111316Z", "blocked_user_ids": [], - "birthland": "Corellia" + "avg_response_time": 1165670, + "birthland": "Socorro" }, - "last_read": "2025-06-15T00:25:33.34121176Z", + "last_read": "2025-10-15T11:22:20.320400817Z", "unread_messages": 0 }, { @@ -165,17 +175,21 @@ "role": "admin", "teams": [], "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-06-06T08:22:36.844279Z", + "updated_at": "2025-09-15T05:51:41.748915Z", "banned": false, "online": true, - "last_active": "2025-06-15T00:25:32.362084978Z", + "last_active": "2025-10-15T11:22:19.961028904Z", "blocked_user_ids": [], + "avg_response_time": 203648, + "canReceiveMessages": false, "birthland": "Tatooine", - "team": "test", + "custom_extra_data_key": true, "type": "team", - "pando": "{\"speciality\":\"ios engineer\"}" + "canBeAddedToGroups": true, + "pando": "{\"speciality\":\"ios engineer\"}", + "team": "test" }, - "last_read": "2025-06-15T00:25:33.34121176Z", + "last_read": "2025-10-15T11:22:20.320400817Z", "unread_messages": 0 } ], @@ -193,13 +207,14 @@ "updated_at": "2025-04-23T13:02:29.974824Z", "banned": false, "online": false, - "last_active": "2025-04-23T13:03:51.783496Z", + "last_active": "2025-10-01T07:12:24.084195Z", "blocked_user_ids": [], + "avg_response_time": 531, "birthland": "Serenno" }, "status": "member", - "created_at": "2025-06-15T00:25:33.304034Z", - "updated_at": "2025-06-15T00:25:33.304034Z", + "created_at": "2025-10-15T11:22:20.279452Z", + "updated_at": "2025-10-15T11:22:20.279452Z", "banned": false, "shadow_banned": false, "role": "member", @@ -207,28 +222,29 @@ "notifications_muted": false }, { - "user_id": "han_solo", + "user_id": "lando_calrissian", "user": { - "id": "han_solo", - "name": "Han Solo", - "image": "https://vignette.wikia.nocookie.net/starwars/images/e/e2/TFAHanSolo.png", - "language": "fr", - "role": "user", + "id": "lando_calrissian", + "name": "Lando Calrissian", + "image": "https://vignette.wikia.nocookie.net/starwars/images/8/8f/Lando_ROTJ.png", + "language": "", + "role": "admin", "teams": [], - "created_at": "2024-04-04T09:18:11.060737Z", - "updated_at": "2025-04-24T15:07:52.050477Z", + "created_at": "2024-04-04T19:06:08.890459Z", + "updated_at": "2025-04-16T17:23:28.189521Z", "banned": false, "online": false, - "last_active": "2025-06-10T06:55:59.491807Z", + "last_active": "2025-10-12T03:39:43.111316Z", "blocked_user_ids": [], - "birthland": "Corellia" + "avg_response_time": 1165670, + "birthland": "Socorro" }, "status": "member", - "created_at": "2025-06-15T00:25:33.304034Z", - "updated_at": "2025-06-15T00:25:33.304034Z", + "created_at": "2025-10-15T11:22:20.279452Z", + "updated_at": "2025-10-15T11:22:20.279452Z", "banned": false, "shadow_banned": false, - "role": "member", + "role": "admin", "channel_role": "channel_member", "notifications_muted": false }, @@ -242,19 +258,23 @@ "role": "admin", "teams": [], "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-06-06T08:22:36.844279Z", + "updated_at": "2025-09-15T05:51:41.748915Z", "banned": false, "online": true, - "last_active": "2025-06-15T00:25:32.362084978Z", + "last_active": "2025-10-15T11:22:19.961028904Z", "blocked_user_ids": [], + "avg_response_time": 203648, "pando": "{\"speciality\":\"ios engineer\"}", - "birthland": "Tatooine", "team": "test", - "type": "team" + "canBeAddedToGroups": true, + "type": "team", + "canReceiveMessages": false, + "birthland": "Tatooine", + "custom_extra_data_key": true }, "status": "member", - "created_at": "2025-06-15T00:25:33.304034Z", - "updated_at": "2025-06-15T00:25:33.304034Z", + "created_at": "2025-10-15T11:22:20.279452Z", + "updated_at": "2025-10-15T11:22:20.279452Z", "banned": false, "shadow_banned": false, "role": "owner", @@ -271,19 +291,23 @@ "role": "admin", "teams": [], "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-06-06T08:22:36.844279Z", + "updated_at": "2025-09-15T05:51:41.748915Z", "banned": false, "online": true, - "last_active": "2025-06-15T00:25:32.362084978Z", + "last_active": "2025-10-15T11:22:19.961028904Z", "blocked_user_ids": [], - "team": "test", + "avg_response_time": 203648, + "canReceiveMessages": false, + "birthland": "Tatooine", "type": "team", "pando": "{\"speciality\":\"ios engineer\"}", - "birthland": "Tatooine" + "custom_extra_data_key": true, + "team": "test", + "canBeAddedToGroups": true }, "status": "member", - "created_at": "2025-06-15T00:25:33.304034Z", - "updated_at": "2025-06-15T00:25:33.304034Z", + "created_at": "2025-10-15T11:22:20.279452Z", + "updated_at": "2025-10-15T11:22:20.279452Z", "banned": false, "shadow_banned": false, "role": "owner", @@ -291,5 +315,5 @@ "notifications_muted": false }, "threads": [], - "duration": "145.45ms" + "duration": "116.90ms" } \ No newline at end of file diff --git a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_channel_removal.json b/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_channel_removal.json index f6593d1e860..6002aa23601 100644 --- a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_channel_removal.json +++ b/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_channel_removal.json @@ -1,12 +1,12 @@ { - "duration": "32.46ms", + "duration": "22.64ms", "channel": { - "id": "ec2807ff-5c60-41bc-a816-49578260471a", + "id": "ec9f758c-3f7e-46f3-9618-bc5760428e5e", "type": "messaging", - "cid": "messaging:ec2807ff-5c60-41bc-a816-49578260471a", - "created_at": "2025-06-15T00:25:33.285826Z", - "updated_at": "2025-06-15T00:25:47.043063Z", - "deleted_at": "2025-06-15T00:25:47.390053Z", + "cid": "messaging:ec9f758c-3f7e-46f3-9618-bc5760428e5e", + "created_at": "2025-10-15T11:22:20.27144Z", + "updated_at": "2025-10-15T11:22:24.61465Z", + "deleted_at": "2025-10-15T11:22:24.825752Z", "created_by": null, "frozen": false, "disabled": false, @@ -24,13 +24,14 @@ "updated_at": "2025-04-23T13:02:29.974824Z", "banned": false, "online": false, - "last_active": "2025-04-23T13:03:51.783496Z", + "last_active": "2025-10-01T07:12:24.084195Z", "blocked_user_ids": [], + "avg_response_time": 531, "birthland": "Serenno" }, "status": "member", - "created_at": "2025-06-15T00:25:33.304034Z", - "updated_at": "2025-06-15T00:25:33.304034Z", + "created_at": "2025-10-15T11:22:20.279452Z", + "updated_at": "2025-10-15T11:22:20.279452Z", "banned": false, "shadow_banned": false, "role": "member", @@ -38,28 +39,29 @@ "notifications_muted": false }, { - "user_id": "han_solo", + "user_id": "lando_calrissian", "user": { - "id": "han_solo", - "name": "Han Solo", - "image": "https://vignette.wikia.nocookie.net/starwars/images/e/e2/TFAHanSolo.png", - "language": "fr", - "role": "user", + "id": "lando_calrissian", + "name": "Lando Calrissian", + "image": "https://vignette.wikia.nocookie.net/starwars/images/8/8f/Lando_ROTJ.png", + "language": "", + "role": "admin", "teams": [], - "created_at": "2024-04-04T09:18:11.060737Z", - "updated_at": "2025-04-24T15:07:52.050477Z", + "created_at": "2024-04-04T19:06:08.890459Z", + "updated_at": "2025-04-16T17:23:28.189521Z", "banned": false, "online": false, - "last_active": "2025-06-10T06:55:59.491807Z", + "last_active": "2025-10-12T03:39:43.111316Z", "blocked_user_ids": [], - "birthland": "Corellia" + "avg_response_time": 1165670, + "birthland": "Socorro" }, "status": "member", - "created_at": "2025-06-15T00:25:33.304034Z", - "updated_at": "2025-06-15T00:25:33.304034Z", + "created_at": "2025-10-15T11:22:20.279452Z", + "updated_at": "2025-10-15T11:22:20.279452Z", "banned": false, "shadow_banned": false, - "role": "member", + "role": "admin", "channel_role": "channel_member", "notifications_muted": false }, @@ -73,19 +75,23 @@ "role": "admin", "teams": [], "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-06-06T08:22:36.844279Z", + "updated_at": "2025-09-15T05:51:41.748915Z", "banned": false, "online": true, - "last_active": "2025-06-15T00:25:47.038320949Z", + "last_active": "2025-10-15T11:22:19.961028904Z", "blocked_user_ids": [], + "avg_response_time": 203648, + "canBeAddedToGroups": true, "pando": "{\"speciality\":\"ios engineer\"}", - "birthland": "Tatooine", "team": "test", - "type": "team" + "birthland": "Tatooine", + "type": "team", + "canReceiveMessages": false, + "custom_extra_data_key": true }, "status": "member", - "created_at": "2025-06-15T00:25:33.304034Z", - "updated_at": "2025-06-15T00:25:33.304034Z", + "created_at": "2025-10-15T11:22:20.279452Z", + "updated_at": "2025-10-15T11:22:20.279452Z", "banned": false, "shadow_banned": false, "role": "owner", @@ -98,28 +104,22 @@ "id": "leia_organa", "name": "Leia Organa", "image": "https://vignette.wikia.nocookie.net/starwars/images/f/fc/Leia_Organa_TLJ.png", - "language": "zh", + "language": "", "role": "admin", "teams": [], "created_at": "2024-04-04T09:42:00.68335Z", - "updated_at": "2025-03-28T15:21:20.061525Z", + "updated_at": "2025-10-01T16:49:27.725672Z", "banned": false, - "online": false, - "last_active": "2025-06-14T17:34:03.224367Z", + "online": true, + "last_active": "2025-10-15T11:21:40.020397141Z", "blocked_user_ids": [], + "avg_response_time": 638602, "birthland": "Polis Massa", - "private_settings": { - "readReceipts": { - "enabled": false - }, - "typingIndicators": { - "enabled": false - } - } + "is_moderator": true }, "status": "member", - "created_at": "2025-06-15T00:25:38.137522Z", - "updated_at": "2025-06-15T00:25:38.137522Z", + "created_at": "2025-10-15T11:22:22.248971Z", + "updated_at": "2025-10-15T11:22:22.248971Z", "banned": false, "shadow_banned": false, "role": "admin", @@ -129,7 +129,7 @@ ], "config": { "created_at": "2021-03-01T19:26:18.406502Z", - "updated_at": "2025-05-05T18:07:33.269057Z", + "updated_at": "2025-07-28T15:20:21.098826Z", "name": "messaging", "typing_events": true, "read_events": true, @@ -143,9 +143,12 @@ "url_enrichment": true, "custom_events": true, "push_notifications": true, - "reminders": false, + "reminders": true, "mark_messages_pending": false, "polls": true, + "user_message_reminders": false, + "shared_locations": true, + "count_messages": false, "message_retention": "infinite", "max_message_length": 5000, "automod": "AI", @@ -176,7 +179,7 @@ } ] }, - "truncated_at": "2025-06-15T00:25:47.390053Z", + "truncated_at": "2025-10-15T11:22:24.825752Z", "truncated_by": { "id": "luke_skywalker", "name": "Luke Skywalker", @@ -185,15 +188,20 @@ "role": "admin", "teams": [], "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-06-06T08:22:36.844279Z", + "updated_at": "2025-09-15T05:51:41.748915Z", "banned": false, "online": true, - "last_active": "2025-06-15T00:25:32.362084978Z", + "last_active": "2025-10-15T11:22:19.961028904Z", "blocked_user_ids": [], - "team": "test", + "avg_response_time": 203648, + "canBeAddedToGroups": true, "type": "team", + "team": "test", "pando": "{\"speciality\":\"ios engineer\"}", - "birthland": "Tatooine" - } + "birthland": "Tatooine", + "custom_extra_data_key": true, + "canReceiveMessages": false + }, + "message_count": 0 } } \ No newline at end of file diff --git a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_channels.json b/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_channels.json index ee50a4becb2..9687422dcfb 100644 --- a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_channels.json +++ b/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_channels.json @@ -2,11 +2,11 @@ "channels": [ { "channel": { - "id": "ec2807ff-5c60-41bc-a816-49578260471a", + "id": "ec9f758c-3f7e-46f3-9618-bc5760428e5e", "type": "messaging", - "cid": "messaging:ec2807ff-5c60-41bc-a816-49578260471a", - "created_at": "2025-06-15T00:25:33.285826Z", - "updated_at": "2025-06-15T00:25:33.285826Z", + "cid": "messaging:ec9f758c-3f7e-46f3-9618-bc5760428e5e", + "created_at": "2025-10-15T11:22:20.27144Z", + "updated_at": "2025-10-15T11:22:20.27144Z", "created_by": { "id": "luke_skywalker", "name": "Luke Skywalker", @@ -15,22 +15,26 @@ "role": "admin", "teams": [], "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-06-06T08:22:36.844279Z", + "updated_at": "2025-09-15T05:51:41.748915Z", "banned": false, "online": true, - "last_active": "2025-06-15T00:25:32.362084978Z", + "last_active": "2025-10-15T11:22:19.961028904Z", "blocked_user_ids": [], + "avg_response_time": 203648, + "canReceiveMessages": false, + "pando": "{\"speciality\":\"ios engineer\"}", "birthland": "Tatooine", - "team": "test", + "custom_extra_data_key": true, "type": "team", - "pando": "{\"speciality\":\"ios engineer\"}" + "team": "test", + "canBeAddedToGroups": true }, "frozen": false, "disabled": false, "member_count": 3, "config": { "created_at": "2021-03-01T19:26:18.406502Z", - "updated_at": "2025-05-05T18:07:33.269057Z", + "updated_at": "2025-07-28T15:20:21.098826Z", "name": "messaging", "typing_events": true, "read_events": true, @@ -44,9 +48,12 @@ "url_enrichment": true, "custom_events": true, "push_notifications": true, - "reminders": false, + "reminders": true, "mark_messages_pending": false, "polls": true, + "user_message_reminders": false, + "shared_locations": true, + "count_messages": false, "message_retention": "infinite", "max_message_length": 5000, "automod": "AI", @@ -103,6 +110,7 @@ "send-restricted-visibility-message", "send-typing-events", "set-channel-cooldown", + "share-location", "skip-slow-mode", "typing-events", "update-any-message", @@ -132,31 +140,33 @@ "updated_at": "2025-04-23T13:02:29.974824Z", "banned": false, "online": false, - "last_active": "2025-04-23T13:03:51.783496Z", + "last_active": "2025-10-01T07:12:24.084195Z", "blocked_user_ids": [], + "avg_response_time": 531, "birthland": "Serenno" }, "unread_messages": 0, - "last_read": "2025-06-15T00:25:34Z" + "last_read": "2025-10-15T11:22:20Z" }, { "user": { - "id": "han_solo", - "name": "Han Solo", - "image": "https://vignette.wikia.nocookie.net/starwars/images/e/e2/TFAHanSolo.png", - "language": "fr", - "role": "user", + "id": "lando_calrissian", + "name": "Lando Calrissian", + "image": "https://vignette.wikia.nocookie.net/starwars/images/8/8f/Lando_ROTJ.png", + "language": "", + "role": "admin", "teams": [], - "created_at": "2024-04-04T09:18:11.060737Z", - "updated_at": "2025-04-24T15:07:52.050477Z", + "created_at": "2024-04-04T19:06:08.890459Z", + "updated_at": "2025-04-16T17:23:28.189521Z", "banned": false, "online": false, - "last_active": "2025-06-10T06:55:59.491807Z", + "last_active": "2025-10-12T03:39:43.111316Z", "blocked_user_ids": [], - "birthland": "Corellia" + "avg_response_time": 1165670, + "birthland": "Socorro" }, "unread_messages": 0, - "last_read": "2025-06-15T00:25:34Z" + "last_read": "2025-10-15T11:22:20Z" }, { "user": { @@ -167,18 +177,22 @@ "role": "admin", "teams": [], "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-06-06T08:22:36.844279Z", + "updated_at": "2025-09-15T05:51:41.748915Z", "banned": false, "online": true, - "last_active": "2025-06-15T00:25:32.362084978Z", + "last_active": "2025-10-15T11:22:19.961028904Z", "blocked_user_ids": [], - "birthland": "Tatooine", + "avg_response_time": 203648, "team": "test", + "canBeAddedToGroups": true, + "custom_extra_data_key": true, "type": "team", - "pando": "{\"speciality\":\"ios engineer\"}" + "canReceiveMessages": false, + "pando": "{\"speciality\":\"ios engineer\"}", + "birthland": "Tatooine" }, "unread_messages": 0, - "last_read": "2025-06-15T00:25:34Z" + "last_read": "2025-10-15T11:22:20Z" } ], "members": [ @@ -195,13 +209,14 @@ "updated_at": "2025-04-23T13:02:29.974824Z", "banned": false, "online": false, - "last_active": "2025-04-23T13:03:51.783496Z", + "last_active": "2025-10-01T07:12:24.084195Z", "blocked_user_ids": [], + "avg_response_time": 531, "birthland": "Serenno" }, "status": "member", - "created_at": "2025-06-15T00:25:33.304034Z", - "updated_at": "2025-06-15T00:25:33.304034Z", + "created_at": "2025-10-15T11:22:20.279452Z", + "updated_at": "2025-10-15T11:22:20.279452Z", "banned": false, "shadow_banned": false, "role": "member", @@ -209,28 +224,29 @@ "notifications_muted": false }, { - "user_id": "han_solo", + "user_id": "lando_calrissian", "user": { - "id": "han_solo", - "name": "Han Solo", - "image": "https://vignette.wikia.nocookie.net/starwars/images/e/e2/TFAHanSolo.png", - "language": "fr", - "role": "user", + "id": "lando_calrissian", + "name": "Lando Calrissian", + "image": "https://vignette.wikia.nocookie.net/starwars/images/8/8f/Lando_ROTJ.png", + "language": "", + "role": "admin", "teams": [], - "created_at": "2024-04-04T09:18:11.060737Z", - "updated_at": "2025-04-24T15:07:52.050477Z", + "created_at": "2024-04-04T19:06:08.890459Z", + "updated_at": "2025-04-16T17:23:28.189521Z", "banned": false, "online": false, - "last_active": "2025-06-10T06:55:59.491807Z", + "last_active": "2025-10-12T03:39:43.111316Z", "blocked_user_ids": [], - "birthland": "Corellia" + "avg_response_time": 1165670, + "birthland": "Socorro" }, "status": "member", - "created_at": "2025-06-15T00:25:33.304034Z", - "updated_at": "2025-06-15T00:25:33.304034Z", + "created_at": "2025-10-15T11:22:20.279452Z", + "updated_at": "2025-10-15T11:22:20.279452Z", "banned": false, "shadow_banned": false, - "role": "member", + "role": "admin", "channel_role": "channel_member", "notifications_muted": false }, @@ -244,19 +260,23 @@ "role": "admin", "teams": [], "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-06-06T08:22:36.844279Z", + "updated_at": "2025-09-15T05:51:41.748915Z", "banned": false, "online": true, - "last_active": "2025-06-15T00:25:32.362084978Z", + "last_active": "2025-10-15T11:22:19.961028904Z", "blocked_user_ids": [], - "birthland": "Tatooine", + "avg_response_time": 203648, "team": "test", + "canBeAddedToGroups": true, + "custom_extra_data_key": true, "type": "team", - "pando": "{\"speciality\":\"ios engineer\"}" + "canReceiveMessages": false, + "pando": "{\"speciality\":\"ios engineer\"}", + "birthland": "Tatooine" }, "status": "member", - "created_at": "2025-06-15T00:25:33.304034Z", - "updated_at": "2025-06-15T00:25:33.304034Z", + "created_at": "2025-10-15T11:22:20.279452Z", + "updated_at": "2025-10-15T11:22:20.279452Z", "banned": false, "shadow_banned": false, "role": "owner", @@ -273,19 +293,23 @@ "role": "admin", "teams": [], "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-06-06T08:22:36.844279Z", + "updated_at": "2025-09-15T05:51:41.748915Z", "banned": false, "online": true, - "last_active": "2025-06-15T00:25:32.362084978Z", + "last_active": "2025-10-15T11:22:19.961028904Z", "blocked_user_ids": [], - "team": "test", - "type": "team", + "avg_response_time": 203648, "pando": "{\"speciality\":\"ios engineer\"}", - "birthland": "Tatooine" + "canBeAddedToGroups": true, + "canReceiveMessages": false, + "birthland": "Tatooine", + "custom_extra_data_key": true, + "team": "test", + "type": "team" }, "status": "member", - "created_at": "2025-06-15T00:25:33.304034Z", - "updated_at": "2025-06-15T00:25:33.304034Z", + "created_at": "2025-10-15T11:22:20.279452Z", + "updated_at": "2025-10-15T11:22:20.279452Z", "banned": false, "shadow_banned": false, "channel_role": "channel_member", @@ -294,5 +318,5 @@ "threads": [] } ], - "duration": "92.12ms" + "duration": "328.53ms" } \ No newline at end of file diff --git a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_events.json b/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_events.json index 5e550a393b8..a753dbb4339 100644 --- a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_events.json +++ b/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_events.json @@ -1,8 +1,8 @@ { "event": { "type": "typing.start", - "cid": "messaging:ec2807ff-5c60-41bc-a816-49578260471a", - "channel_id": "ec2807ff-5c60-41bc-a816-49578260471a", + "cid": "messaging:ec9f758c-3f7e-46f3-9618-bc5760428e5e", + "channel_id": "ec9f758c-3f7e-46f3-9618-bc5760428e5e", "channel_type": "messaging", "user": { "id": "luke_skywalker", @@ -12,17 +12,21 @@ "role": "admin", "teams": [], "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-06-06T08:22:36.844279Z", + "updated_at": "2025-09-15T05:51:41.748915Z", "banned": false, "online": true, - "last_active": "2025-06-15T00:25:32.362084978Z", + "last_active": "2025-10-15T11:22:19.961028904Z", "blocked_user_ids": [], - "team": "test", + "avg_response_time": 203648, + "birthland": "Tatooine", "type": "team", - "pando": "{\"speciality\":\"ios engineer\"}", - "birthland": "Tatooine" + "canBeAddedToGroups": true, + "canReceiveMessages": false, + "team": "test", + "custom_extra_data_key": true, + "pando": "{\"speciality\":\"ios engineer\"}" }, - "created_at": "2025-06-15T00:25:34.999482144Z" + "created_at": "2025-10-15T11:22:21.064322094Z" }, - "duration": "6.47ms" + "duration": "7.74ms" } \ No newline at end of file diff --git a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_giphy_link.json b/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_giphy_link.json index 3d6eef9c4ba..1c9cfff1224 100644 --- a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_giphy_link.json +++ b/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_giphy_link.json @@ -1,6 +1,6 @@ { "message": { - "id": "d82c481c-541b-4da4-a704-6ec285420492", + "id": "81c8fe15-790d-44d8-860c-2e832f76cda8", "text": "https://giphy.com/gifs/test-gw3IWyGkC0rsazTi", "html": "

https://giphy.com/gifs/test-gw3IWyGkC0rsazTi

\n", "type": "regular", @@ -12,15 +12,22 @@ "role": "admin", "teams": [], "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-06-06T08:22:36.844279Z", + "updated_at": "2025-09-15T05:51:41.748915Z", "banned": false, "online": true, - "last_active": "2025-06-15T00:25:32.362084978Z", + "last_active": "2025-10-15T11:22:19.961028904Z", "blocked_user_ids": [], + "avg_response_time": 203648, + "birthland": "Tatooine", + "canReceiveMessages": false, "team": "test", "type": "team", + "canBeAddedToGroups": true, "pando": "{\"speciality\":\"ios engineer\"}", - "birthland": "Tatooine" + "custom_extra_data_key": true + }, + "member": { + "channel_role": "channel_member" }, "attachments": [ { @@ -28,9 +35,9 @@ "title": "Test Computer GIF - Find & Share on GIPHY", "title_link": "https://giphy.com/gifs/test-gw3IWyGkC0rsazTi", "text": "Discover & share this Test Computer GIF with everyone you know. GIPHY is how you search, share, discover, and create GIFs.", - "image_url": "https://media1.giphy.com/media/v1.Y2lkPTc5MGI3NjExZmdiZ3NydzJ5NDZxbWVhajAyYzNmZ3BwYWJndWM4aXJyOXQwZXBydyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/gw3IWyGkC0rsazTi/giphy.webp", - "thumb_url": "https://media1.giphy.com/media/v1.Y2lkPTc5MGI3NjExZmdiZ3NydzJ5NDZxbWVhajAyYzNmZ3BwYWJndWM4aXJyOXQwZXBydyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/gw3IWyGkC0rsazTi/giphy.webp", - "asset_url": "https://media1.giphy.com/media/v1.Y2lkPTc5MGI3NjExZmdiZ3NydzJ5NDZxbWVhajAyYzNmZ3BwYWJndWM4aXJyOXQwZXBydyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/gw3IWyGkC0rsazTi/giphy.mp4", + "image_url": "https://media3.giphy.com/media/v1.Y2lkPTc5MGI3NjExcWI3dXdqbGhpNXozMG9obXg3OTh3aXNtb3NudTlzbHBreW5iaTIyNCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/gw3IWyGkC0rsazTi/giphy.webp", + "thumb_url": "https://media3.giphy.com/media/v1.Y2lkPTc5MGI3NjExcWI3dXdqbGhpNXozMG9obXg3OTh3aXNtb3NudTlzbHBreW5iaTIyNCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/gw3IWyGkC0rsazTi/giphy.webp", + "asset_url": "https://media3.giphy.com/media/v1.Y2lkPTc5MGI3NjExcWI3dXdqbGhpNXozMG9obXg3OTh3aXNtb3NudTlzbHBreW5iaTIyNCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/gw3IWyGkC0rsazTi/giphy.mp4", "og_scrape_url": "https://giphy.com/gifs/test-gw3IWyGkC0rsazTi" } ], @@ -40,16 +47,11 @@ "reaction_scores": {}, "reply_count": 0, "deleted_reply_count": 0, - "cid": "messaging:ec2807ff-5c60-41bc-a816-49578260471a", - "created_at": "2025-06-15T00:25:46.443211Z", - "updated_at": "2025-06-15T00:25:46.443211Z", + "cid": "messaging:ec9f758c-3f7e-46f3-9618-bc5760428e5e", + "created_at": "2025-10-15T11:22:24.446739Z", + "updated_at": "2025-10-15T11:22:24.446739Z", "shadowed": false, "mentioned_users": [], - "i18n": { - "language": "en", - "en_text": "https://giphy.com/gifs/test-gw3IWyGkC0rsazTi", - "fr_text": "https://giphy.com/gifs/test-gw3IWyGkC0rsazTi" - }, "silent": false, "pinned": false, "pinned_at": null, @@ -57,5 +59,5 @@ "pin_expires": null, "restricted_visibility": [] }, - "duration": "687.06ms" + "duration": "430.22ms" } \ No newline at end of file diff --git a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_message.json b/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_message.json index 92020781f10..9e6e84033fe 100644 --- a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_message.json +++ b/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_message.json @@ -1,6 +1,6 @@ { "message": { - "id": "b9841bf2-9a49-4adf-ae93-5e07d37d7c22", + "id": "dc702391-84b6-44af-aa67-c349d2e72441", "text": "Test", "html": "

Test

\n", "type": "regular", @@ -12,15 +12,22 @@ "role": "admin", "teams": [], "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-06-06T08:22:36.844279Z", + "updated_at": "2025-09-15T05:51:41.748915Z", "banned": false, "online": true, - "last_active": "2025-06-15T00:25:32.362084978Z", + "last_active": "2025-10-15T11:22:19.961028904Z", "blocked_user_ids": [], - "birthland": "Tatooine", - "team": "test", + "avg_response_time": 203648, "type": "team", - "pando": "{\"speciality\":\"ios engineer\"}" + "pando": "{\"speciality\":\"ios engineer\"}", + "custom_extra_data_key": true, + "canReceiveMessages": false, + "team": "test", + "canBeAddedToGroups": true, + "birthland": "Tatooine" + }, + "member": { + "channel_role": "channel_member" }, "attachments": [], "latest_reactions": [], @@ -29,16 +36,11 @@ "reaction_scores": {}, "reply_count": 0, "deleted_reply_count": 0, - "cid": "messaging:ec2807ff-5c60-41bc-a816-49578260471a", - "created_at": "2025-06-15T00:25:36.86243Z", - "updated_at": "2025-06-15T00:25:36.86243Z", + "cid": "messaging:ec9f758c-3f7e-46f3-9618-bc5760428e5e", + "created_at": "2025-10-15T11:22:21.804306Z", + "updated_at": "2025-10-15T11:22:21.804306Z", "shadowed": false, "mentioned_users": [], - "i18n": { - "language": "en", - "en_text": "Test", - "fr_text": "Testez" - }, "silent": false, "pinned": false, "pinned_at": null, @@ -46,5 +48,5 @@ "pin_expires": null, "restricted_visibility": [] }, - "duration": "1352.04ms" + "duration": "647.31ms" } \ No newline at end of file diff --git a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_message_ephemeral.json b/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_message_ephemeral.json index c4e6087afca..2ba07b6ab4e 100644 --- a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_message_ephemeral.json +++ b/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_message_ephemeral.json @@ -1,6 +1,6 @@ { "message": { - "id": "82168f7f-d7e2-48a1-9273-417d03365da0", + "id": "31d2f09d-e285-4e0f-a561-6aa6adce37b0", "text": "/giphy Test", "command": "giphy", "html": "

/giphy Test

\n", @@ -13,22 +13,29 @@ "role": "admin", "teams": [], "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-06-06T08:22:36.844279Z", + "updated_at": "2025-09-15T05:51:41.748915Z", "banned": false, "online": true, - "last_active": "2025-06-15T00:25:32.362084978Z", + "last_active": "2025-10-15T11:22:19.961028904Z", "blocked_user_ids": [], + "avg_response_time": 203648, + "birthland": "Tatooine", "team": "test", - "type": "team", "pando": "{\"speciality\":\"ios engineer\"}", - "birthland": "Tatooine" + "canBeAddedToGroups": true, + "custom_extra_data_key": true, + "type": "team", + "canReceiveMessages": false + }, + "member": { + "channel_role": "channel_member" }, "attachments": [ { "type": "giphy", "title": "Test", - "title_link": "https://giphy.com/gifs/rhuber-test-EtpYMjyYJowVy", - "thumb_url": "https://media3.giphy.com/media/v1.Y2lkPWM0YjAzNjc1amdqbTV3eXl6d2dqZ3hnOXJmdGt4ZHJsZmNicDFpYjNoejJ6cWNneiZlcD12MV9naWZzX3NlYXJjaCZjdD1n/EtpYMjyYJowVy/giphy.gif", + "title_link": "https://giphy.com/gifs/anthonyo-test-delete-RiiVFKow0eymLkTnMP", + "thumb_url": "https://media2.giphy.com/media/v1.Y2lkPWM0YjAzNjc1NGxxbmtmdmU5OHFqMGR6a2cwc3B2b2s0cDZ1ZzZub25nMTdkZDJoaCZlcD12MV9naWZzX3NlYXJjaCZjdD1n/RiiVFKow0eymLkTnMP/giphy.gif", "actions": [ { "name": "image_action", @@ -54,52 +61,52 @@ ], "giphy": { "original": { - "url": "https://media3.giphy.com/media/v1.Y2lkPWM0YjAzNjc1amdqbTV3eXl6d2dqZ3hnOXJmdGt4ZHJsZmNicDFpYjNoejJ6cWNneiZlcD12MV9naWZzX3NlYXJjaCZjdD1n/EtpYMjyYJowVy/giphy.gif", - "width": "400", - "height": "167", - "size": "1569544", + "url": "https://media2.giphy.com/media/v1.Y2lkPWM0YjAzNjc1NGxxbmtmdmU5OHFqMGR6a2cwc3B2b2s0cDZ1ZzZub25nMTdkZDJoaCZlcD12MV9naWZzX3NlYXJjaCZjdD1n/RiiVFKow0eymLkTnMP/giphy.gif", + "width": "268", + "height": "350", + "size": "1865328", "frames": "49" }, "fixed_height": { - "url": "https://media3.giphy.com/media/v1.Y2lkPWM0YjAzNjc1amdqbTV3eXl6d2dqZ3hnOXJmdGt4ZHJsZmNicDFpYjNoejJ6cWNneiZlcD12MV9naWZzX3NlYXJjaCZjdD1n/EtpYMjyYJowVy/200.gif", - "width": "479", + "url": "https://media2.giphy.com/media/v1.Y2lkPWM0YjAzNjc1NGxxbmtmdmU5OHFqMGR6a2cwc3B2b2s0cDZ1ZzZub25nMTdkZDJoaCZlcD12MV9naWZzX3NlYXJjaCZjdD1n/RiiVFKow0eymLkTnMP/200.gif", + "width": "154", "height": "200", - "size": "1576548", + "size": "612190", "frames": "" }, "fixed_height_still": { - "url": "https://media3.giphy.com/media/v1.Y2lkPWM0YjAzNjc1amdqbTV3eXl6d2dqZ3hnOXJmdGt4ZHJsZmNicDFpYjNoejJ6cWNneiZlcD12MV9naWZzX3NlYXJjaCZjdD1n/EtpYMjyYJowVy/200_s.gif", - "width": "479", + "url": "https://media2.giphy.com/media/v1.Y2lkPWM0YjAzNjc1NGxxbmtmdmU5OHFqMGR6a2cwc3B2b2s0cDZ1ZzZub25nMTdkZDJoaCZlcD12MV9naWZzX3NlYXJjaCZjdD1n/RiiVFKow0eymLkTnMP/200_s.gif", + "width": "154", "height": "200", - "size": "32259", + "size": "17451", "frames": "" }, "fixed_height_downsampled": { - "url": "https://media3.giphy.com/media/v1.Y2lkPWM0YjAzNjc1amdqbTV3eXl6d2dqZ3hnOXJmdGt4ZHJsZmNicDFpYjNoejJ6cWNneiZlcD12MV9naWZzX3NlYXJjaCZjdD1n/EtpYMjyYJowVy/200_d.gif", - "width": "479", + "url": "https://media2.giphy.com/media/v1.Y2lkPWM0YjAzNjc1NGxxbmtmdmU5OHFqMGR6a2cwc3B2b2s0cDZ1ZzZub25nMTdkZDJoaCZlcD12MV9naWZzX3NlYXJjaCZjdD1n/RiiVFKow0eymLkTnMP/200_d.gif", + "width": "154", "height": "200", - "size": "212805", + "size": "75328", "frames": "" }, "fixed_width": { - "url": "https://media3.giphy.com/media/v1.Y2lkPWM0YjAzNjc1amdqbTV3eXl6d2dqZ3hnOXJmdGt4ZHJsZmNicDFpYjNoejJ6cWNneiZlcD12MV9naWZzX3NlYXJjaCZjdD1n/EtpYMjyYJowVy/200w.gif", + "url": "https://media2.giphy.com/media/v1.Y2lkPWM0YjAzNjc1NGxxbmtmdmU5OHFqMGR6a2cwc3B2b2s0cDZ1ZzZub25nMTdkZDJoaCZlcD12MV9naWZzX3NlYXJjaCZjdD1n/RiiVFKow0eymLkTnMP/200w.gif", "width": "200", - "height": "84", - "size": "331240", + "height": "262", + "size": "849838", "frames": "" }, "fixed_width_still": { - "url": "https://media3.giphy.com/media/v1.Y2lkPWM0YjAzNjc1amdqbTV3eXl6d2dqZ3hnOXJmdGt4ZHJsZmNicDFpYjNoejJ6cWNneiZlcD12MV9naWZzX3NlYXJjaCZjdD1n/EtpYMjyYJowVy/200w_s.gif", + "url": "https://media2.giphy.com/media/v1.Y2lkPWM0YjAzNjc1NGxxbmtmdmU5OHFqMGR6a2cwc3B2b2s0cDZ1ZzZub25nMTdkZDJoaCZlcD12MV9naWZzX3NlYXJjaCZjdD1n/RiiVFKow0eymLkTnMP/200w_s.gif", "width": "200", - "height": "84", - "size": "8017", + "height": "262", + "size": "16788", "frames": "" }, "fixed_width_downsampled": { - "url": "https://media3.giphy.com/media/v1.Y2lkPWM0YjAzNjc1amdqbTV3eXl6d2dqZ3hnOXJmdGt4ZHJsZmNicDFpYjNoejJ6cWNneiZlcD12MV9naWZzX3NlYXJjaCZjdD1n/EtpYMjyYJowVy/200w_d.gif", + "url": "https://media2.giphy.com/media/v1.Y2lkPWM0YjAzNjc1NGxxbmtmdmU5OHFqMGR6a2cwc3B2b2s0cDZ1ZzZub25nMTdkZDJoaCZlcD12MV9naWZzX3NlYXJjaCZjdD1n/RiiVFKow0eymLkTnMP/200w_d.gif", "width": "200", - "height": "84", - "size": "43614", + "height": "262", + "size": "102260", "frames": "" } } @@ -111,16 +118,11 @@ "reaction_scores": {}, "reply_count": 0, "deleted_reply_count": 0, - "cid": "messaging:ec2807ff-5c60-41bc-a816-49578260471a", - "created_at": "2025-06-15T00:25:39.407203Z", - "updated_at": "2025-06-15T00:25:39.407203Z", + "cid": "messaging:ec9f758c-3f7e-46f3-9618-bc5760428e5e", + "created_at": "2025-10-15T11:22:22.981577Z", + "updated_at": "2025-10-15T11:22:22.981577Z", "shadowed": false, "mentioned_users": [], - "i18n": { - "fr_text": "/Test Giphy", - "language": "en", - "en_text": "/giphy Test" - }, "silent": false, "pinned": false, "pinned_at": null, @@ -132,5 +134,5 @@ "name": "Giphy" } }, - "duration": "272.79ms" + "duration": "222.97ms" } \ No newline at end of file diff --git a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_reaction.json b/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_reaction.json index bc1bad366a4..3fac69b9319 100644 --- a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_reaction.json +++ b/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_reaction.json @@ -1,6 +1,6 @@ { "message": { - "id": "b9841bf2-9a49-4adf-ae93-5e07d37d7c22", + "id": "dc702391-84b6-44af-aa67-c349d2e72441", "text": "Test", "html": "

Test

\n", "type": "regular", @@ -12,20 +12,27 @@ "role": "admin", "teams": [], "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-06-06T08:22:36.844279Z", + "updated_at": "2025-09-15T05:51:41.748915Z", "banned": false, "online": true, - "last_active": "2025-06-15T00:25:32.362084978Z", + "last_active": "2025-10-15T11:22:19.961028904Z", "blocked_user_ids": [], - "birthland": "Tatooine", + "avg_response_time": 203648, "team": "test", - "type": "team", - "pando": "{\"speciality\":\"ios engineer\"}" + "canReceiveMessages": false, + "pando": "{\"speciality\":\"ios engineer\"}", + "canBeAddedToGroups": true, + "birthland": "Tatooine", + "custom_extra_data_key": true, + "type": "team" + }, + "member": { + "channel_role": "channel_member" }, "attachments": [], "latest_reactions": [ { - "message_id": "b9841bf2-9a49-4adf-ae93-5e07d37d7c22", + "message_id": "dc702391-84b6-44af-aa67-c349d2e72441", "user_id": "luke_skywalker", "user": { "id": "luke_skywalker", @@ -35,25 +42,29 @@ "role": "admin", "teams": [], "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-06-06T08:22:36.844279Z", + "updated_at": "2025-09-15T05:51:41.748915Z", "banned": false, "online": true, - "last_active": "2025-06-15T00:25:32.362084978Z", + "last_active": "2025-10-15T11:22:19.961028904Z", "blocked_user_ids": [], - "pando": "{\"speciality\":\"ios engineer\"}", - "birthland": "Tatooine", + "avg_response_time": 203648, + "custom_extra_data_key": true, + "canReceiveMessages": false, "team": "test", - "type": "team" + "canBeAddedToGroups": true, + "pando": "{\"speciality\":\"ios engineer\"}", + "type": "team", + "birthland": "Tatooine" }, "type": "like", "score": 1, - "created_at": "2025-06-15T00:25:37.516538Z", - "updated_at": "2025-06-15T00:25:37.516538Z" + "created_at": "2025-10-15T11:22:21.987244Z", + "updated_at": "2025-10-15T11:22:21.987244Z" } ], "own_reactions": [ { - "message_id": "b9841bf2-9a49-4adf-ae93-5e07d37d7c22", + "message_id": "dc702391-84b6-44af-aa67-c349d2e72441", "user_id": "luke_skywalker", "user": { "id": "luke_skywalker", @@ -63,20 +74,24 @@ "role": "admin", "teams": [], "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-06-06T08:22:36.844279Z", + "updated_at": "2025-09-15T05:51:41.748915Z", "banned": false, "online": true, - "last_active": "2025-06-15T00:25:32.362084978Z", + "last_active": "2025-10-15T11:22:19.961028904Z", "blocked_user_ids": [], + "avg_response_time": 203648, "birthland": "Tatooine", + "canReceiveMessages": false, "team": "test", "type": "team", - "pando": "{\"speciality\":\"ios engineer\"}" + "canBeAddedToGroups": true, + "pando": "{\"speciality\":\"ios engineer\"}", + "custom_extra_data_key": true }, "type": "like", "score": 1, - "created_at": "2025-06-15T00:25:37.516538Z", - "updated_at": "2025-06-15T00:25:37.516538Z" + "created_at": "2025-10-15T11:22:21.987244Z", + "updated_at": "2025-10-15T11:22:21.987244Z" } ], "reaction_counts": { @@ -89,22 +104,17 @@ "like": { "count": 1, "sum_scores": 1, - "first_reaction_at": "2025-06-15T00:25:37.516538Z", - "last_reaction_at": "2025-06-15T00:25:37.516538Z" + "first_reaction_at": "2025-10-15T11:22:21.987244Z", + "last_reaction_at": "2025-10-15T11:22:21.987244Z" } }, "reply_count": 0, "deleted_reply_count": 0, - "cid": "messaging:ec2807ff-5c60-41bc-a816-49578260471a", - "created_at": "2025-06-15T00:25:36.86243Z", - "updated_at": "2025-06-15T00:25:37.529233Z", + "cid": "messaging:ec9f758c-3f7e-46f3-9618-bc5760428e5e", + "created_at": "2025-10-15T11:22:21.804306Z", + "updated_at": "2025-10-15T11:22:22.001363Z", "shadowed": false, "mentioned_users": [], - "i18n": { - "en_text": "Test", - "fr_text": "Testez", - "language": "en" - }, "silent": false, "pinned": false, "pinned_at": null, @@ -113,7 +123,7 @@ "restricted_visibility": [] }, "reaction": { - "message_id": "b9841bf2-9a49-4adf-ae93-5e07d37d7c22", + "message_id": "dc702391-84b6-44af-aa67-c349d2e72441", "user_id": "luke_skywalker", "user": { "id": "luke_skywalker", @@ -123,11 +133,15 @@ "role": "admin", "teams": [], "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-06-06T08:22:36.844279Z", + "updated_at": "2025-09-15T05:51:41.748915Z", "banned": false, "online": true, - "last_active": "2025-06-15T00:25:32.362084978Z", + "last_active": "2025-10-15T11:22:19.961028904Z", "blocked_user_ids": [], + "avg_response_time": 203648, + "canBeAddedToGroups": true, + "custom_extra_data_key": true, + "canReceiveMessages": false, "team": "test", "type": "team", "pando": "{\"speciality\":\"ios engineer\"}", @@ -135,8 +149,8 @@ }, "type": "like", "score": 1, - "created_at": "2025-06-15T00:25:37.516538Z", - "updated_at": "2025-06-15T00:25:37.516538Z" + "created_at": "2025-10-15T11:22:21.987244Z", + "updated_at": "2025-10-15T11:22:21.987244Z" }, - "duration": "39.84ms" + "duration": "46.60ms" } \ No newline at end of file diff --git a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_truncate.json b/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_truncate.json index 953f6dcc36c..31786af9143 100644 --- a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_truncate.json +++ b/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_truncate.json @@ -1,12 +1,12 @@ { - "duration": "70.67ms", + "duration": "73.97ms", "channel": { - "id": "ec2807ff-5c60-41bc-a816-49578260471a", + "id": "ec9f758c-3f7e-46f3-9618-bc5760428e5e", "type": "messaging", - "cid": "messaging:ec2807ff-5c60-41bc-a816-49578260471a", + "cid": "messaging:ec9f758c-3f7e-46f3-9618-bc5760428e5e", "last_message_at": "0001-01-01T00:00:00Z", - "created_at": "2025-06-15T00:25:33.285826Z", - "updated_at": "2025-06-15T00:25:47.043063Z", + "created_at": "2025-10-15T11:22:20.27144Z", + "updated_at": "2025-10-15T11:22:24.61465Z", "created_by": { "id": "luke_skywalker", "name": "Luke Skywalker", @@ -15,15 +15,19 @@ "role": "admin", "teams": [], "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-06-06T08:22:36.844279Z", + "updated_at": "2025-09-15T05:51:41.748915Z", "banned": false, "online": true, - "last_active": "2025-06-15T00:25:32.362084978Z", + "last_active": "2025-10-15T11:22:19.961028904Z", "blocked_user_ids": [], - "team": "test", - "type": "team", + "avg_response_time": 203648, "pando": "{\"speciality\":\"ios engineer\"}", - "birthland": "Tatooine" + "canReceiveMessages": false, + "type": "team", + "canBeAddedToGroups": true, + "birthland": "Tatooine", + "custom_extra_data_key": true, + "team": "test" }, "frozen": false, "disabled": false, @@ -41,13 +45,14 @@ "updated_at": "2025-04-23T13:02:29.974824Z", "banned": false, "online": false, - "last_active": "2025-04-23T13:03:51.783496Z", + "last_active": "2025-10-01T07:12:24.084195Z", "blocked_user_ids": [], + "avg_response_time": 531, "birthland": "Serenno" }, "status": "member", - "created_at": "2025-06-15T00:25:33.304034Z", - "updated_at": "2025-06-15T00:25:33.304034Z", + "created_at": "2025-10-15T11:22:20.279452Z", + "updated_at": "2025-10-15T11:22:20.279452Z", "banned": false, "shadow_banned": false, "role": "member", @@ -55,28 +60,29 @@ "notifications_muted": false }, { - "user_id": "han_solo", + "user_id": "lando_calrissian", "user": { - "id": "han_solo", - "name": "Han Solo", - "image": "https://vignette.wikia.nocookie.net/starwars/images/e/e2/TFAHanSolo.png", - "language": "fr", - "role": "user", + "id": "lando_calrissian", + "name": "Lando Calrissian", + "image": "https://vignette.wikia.nocookie.net/starwars/images/8/8f/Lando_ROTJ.png", + "language": "", + "role": "admin", "teams": [], - "created_at": "2024-04-04T09:18:11.060737Z", - "updated_at": "2025-04-24T15:07:52.050477Z", + "created_at": "2024-04-04T19:06:08.890459Z", + "updated_at": "2025-04-16T17:23:28.189521Z", "banned": false, "online": false, - "last_active": "2025-06-10T06:55:59.491807Z", + "last_active": "2025-10-12T03:39:43.111316Z", "blocked_user_ids": [], - "birthland": "Corellia" + "avg_response_time": 1165670, + "birthland": "Socorro" }, "status": "member", - "created_at": "2025-06-15T00:25:33.304034Z", - "updated_at": "2025-06-15T00:25:33.304034Z", + "created_at": "2025-10-15T11:22:20.279452Z", + "updated_at": "2025-10-15T11:22:20.279452Z", "banned": false, "shadow_banned": false, - "role": "member", + "role": "admin", "channel_role": "channel_member", "notifications_muted": false }, @@ -90,19 +96,23 @@ "role": "admin", "teams": [], "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-06-06T08:22:36.844279Z", + "updated_at": "2025-09-15T05:51:41.748915Z", "banned": false, "online": true, - "last_active": "2025-06-15T00:25:32.362084978Z", + "last_active": "2025-10-15T11:22:19.961028904Z", "blocked_user_ids": [], + "avg_response_time": 203648, "pando": "{\"speciality\":\"ios engineer\"}", + "canReceiveMessages": false, "birthland": "Tatooine", + "custom_extra_data_key": true, "team": "test", - "type": "team" + "type": "team", + "canBeAddedToGroups": true }, "status": "member", - "created_at": "2025-06-15T00:25:33.304034Z", - "updated_at": "2025-06-15T00:25:33.304034Z", + "created_at": "2025-10-15T11:22:20.279452Z", + "updated_at": "2025-10-15T11:22:20.279452Z", "banned": false, "shadow_banned": false, "role": "owner", @@ -115,28 +125,22 @@ "id": "leia_organa", "name": "Leia Organa", "image": "https://vignette.wikia.nocookie.net/starwars/images/f/fc/Leia_Organa_TLJ.png", - "language": "zh", + "language": "", "role": "admin", "teams": [], "created_at": "2024-04-04T09:42:00.68335Z", - "updated_at": "2025-03-28T15:21:20.061525Z", + "updated_at": "2025-10-01T16:49:27.725672Z", "banned": false, - "online": false, - "last_active": "2025-06-14T17:34:03.224367Z", + "online": true, + "last_active": "2025-10-15T11:21:40.020397141Z", "blocked_user_ids": [], + "avg_response_time": 638602, "birthland": "Polis Massa", - "private_settings": { - "readReceipts": { - "enabled": false - }, - "typingIndicators": { - "enabled": false - } - } + "is_moderator": true }, "status": "member", - "created_at": "2025-06-15T00:25:38.137522Z", - "updated_at": "2025-06-15T00:25:38.137522Z", + "created_at": "2025-10-15T11:22:22.248971Z", + "updated_at": "2025-10-15T11:22:22.248971Z", "banned": false, "shadow_banned": false, "role": "admin", @@ -147,7 +151,7 @@ "member_count": 4, "config": { "created_at": "2021-03-01T19:26:18.406502Z", - "updated_at": "2025-05-05T18:07:33.269057Z", + "updated_at": "2025-07-28T15:20:21.098826Z", "name": "messaging", "typing_events": true, "read_events": true, @@ -161,9 +165,12 @@ "url_enrichment": true, "custom_events": true, "push_notifications": true, - "reminders": false, + "reminders": true, "mark_messages_pending": false, "polls": true, + "user_message_reminders": false, + "shared_locations": true, + "count_messages": false, "message_retention": "infinite", "max_message_length": 5000, "automod": "AI", @@ -194,7 +201,7 @@ } ] }, - "truncated_at": "2025-06-15T00:25:47.035983Z", + "truncated_at": "2025-10-15T11:22:24.606151Z", "truncated_by": { "id": "luke_skywalker", "name": "Luke Skywalker", @@ -203,20 +210,24 @@ "role": "admin", "teams": [], "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-06-06T08:22:36.844279Z", + "updated_at": "2025-09-15T05:51:41.748915Z", "banned": false, "online": true, - "last_active": "2025-06-15T00:25:32.362084978Z", + "last_active": "2025-10-15T11:22:19.961028904Z", "blocked_user_ids": [], + "avg_response_time": 203648, + "custom_extra_data_key": true, + "team": "test", "pando": "{\"speciality\":\"ios engineer\"}", "birthland": "Tatooine", - "team": "test", - "type": "team" + "type": "team", + "canBeAddedToGroups": true, + "canReceiveMessages": false }, "name": "Sync Mock Server" }, "message": { - "id": "d636862b-394f-48af-a9f2-6b4fa8e45d98", + "id": "5d6d174f-ca82-4bf7-97c1-b484e82339e4", "text": "Channel truncated", "html": "

Channel truncated

\n", "type": "system", @@ -228,15 +239,19 @@ "role": "admin", "teams": [], "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-06-06T08:22:36.844279Z", + "updated_at": "2025-09-15T05:51:41.748915Z", "banned": false, "online": true, - "last_active": "2025-06-15T00:25:32.362084978Z", + "last_active": "2025-10-15T11:22:19.961028904Z", "blocked_user_ids": [], - "team": "test", + "avg_response_time": 203648, + "canReceiveMessages": false, "type": "team", "pando": "{\"speciality\":\"ios engineer\"}", - "birthland": "Tatooine" + "birthland": "Tatooine", + "custom_extra_data_key": true, + "team": "test", + "canBeAddedToGroups": true }, "attachments": [], "latest_reactions": [], @@ -245,9 +260,9 @@ "reaction_scores": {}, "reply_count": 0, "deleted_reply_count": 0, - "cid": "messaging:ec2807ff-5c60-41bc-a816-49578260471a", - "created_at": "2025-06-15T00:25:47.035984Z", - "updated_at": "2025-06-15T00:25:47.035984Z", + "cid": "messaging:ec9f758c-3f7e-46f3-9618-bc5760428e5e", + "created_at": "2025-10-15T11:22:24.606152Z", + "updated_at": "2025-10-15T11:22:24.606152Z", "shadowed": false, "mentioned_users": [], "silent": false, diff --git a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_unsplash_link.json b/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_unsplash_link.json index eb925d7ccd6..da72a203357 100644 --- a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_unsplash_link.json +++ b/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_unsplash_link.json @@ -1,6 +1,6 @@ { "message": { - "id": "b3021d20-53c7-4a2d-a572-8aba1c69ab72", + "id": "b3e2003e-e090-4b17-b52c-584c7ff149a6", "text": "https://unsplash.com/photos/1_2d3MRbI9c", "html": "

https://unsplash.com/photos/1_2d3MRbI9c

\n", "type": "regular", @@ -12,15 +12,22 @@ "role": "admin", "teams": [], "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-06-06T08:22:36.844279Z", + "updated_at": "2025-09-15T05:51:41.748915Z", "banned": false, "online": true, - "last_active": "2025-06-15T00:25:32.362084978Z", + "last_active": "2025-10-15T11:22:19.961028904Z", "blocked_user_ids": [], - "team": "test", + "avg_response_time": 203648, "type": "team", + "canBeAddedToGroups": true, + "birthland": "Tatooine", + "canReceiveMessages": false, "pando": "{\"speciality\":\"ios engineer\"}", - "birthland": "Tatooine" + "custom_extra_data_key": true, + "team": "test" + }, + "member": { + "channel_role": "channel_member" }, "attachments": [ { @@ -29,8 +36,8 @@ "title": "Photo by Joao Branco on Unsplash", "title_link": "https://unsplash.com/photos/green-pine-tree-mountain-slope-scenery-1_2d3MRbI9c", "text": "Download this photo by Joao Branco on Unsplash", - "image_url": "https://images.unsplash.com/photo-1568574728383-06fca083883d?mark=https%3A%2F%2Fimages.unsplash.com%2Fopengraph%2Flogo.png&mark-w=64&mark-align=top%2Cleft&mark-pad=50&h=630&w=1200&crop=faces%2Cedges&blend-w=1&blend=000000&blend-mode=normal&blend-alpha=10&auto=format&fit=crop&q=60&ixid=M3wxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNzQ5OTQ3MTQ0fA&ixlib=rb-4.1.0", - "thumb_url": "https://images.unsplash.com/photo-1568574728383-06fca083883d?mark=https%3A%2F%2Fimages.unsplash.com%2Fopengraph%2Flogo.png&mark-w=64&mark-align=top%2Cleft&mark-pad=50&h=630&w=1200&crop=faces%2Cedges&blend-w=1&blend=000000&blend-mode=normal&blend-alpha=10&auto=format&fit=crop&q=60&ixid=M3wxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNzQ5OTQ3MTQ0fA&ixlib=rb-4.1.0", + "image_url": "https://images.unsplash.com/photo-1568574728383-06fca083883d?ixid=M3wxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNzYwNDg3Mzg5fA&ixlib=rb-4.1.0&auto=format&fit=crop&q=60&mark=https%3A%2F%2Fimages.unsplash.com%2Fopengraph%2Flogo.png&mark-w=64&mark-align=top%2Cleft&mark-pad=50&h=630&w=1200&crop=faces%2Cedges&blend-w=1&blend=000000&blend-mode=normal&blend-alpha=10", + "thumb_url": "https://images.unsplash.com/photo-1568574728383-06fca083883d?ixid=M3wxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNzYwNDg3Mzg5fA&ixlib=rb-4.1.0&auto=format&fit=crop&q=60&mark=https%3A%2F%2Fimages.unsplash.com%2Fopengraph%2Flogo.png&mark-w=64&mark-align=top%2Cleft&mark-pad=50&h=630&w=1200&crop=faces%2Cedges&blend-w=1&blend=000000&blend-mode=normal&blend-alpha=10", "og_scrape_url": "https://unsplash.com/photos/1_2d3MRbI9c" } ], @@ -40,16 +47,11 @@ "reaction_scores": {}, "reply_count": 0, "deleted_reply_count": 0, - "cid": "messaging:ec2807ff-5c60-41bc-a816-49578260471a", - "created_at": "2025-06-15T00:25:45.47899Z", - "updated_at": "2025-06-15T00:25:45.47899Z", + "cid": "messaging:ec9f758c-3f7e-46f3-9618-bc5760428e5e", + "created_at": "2025-10-15T11:22:23.80289Z", + "updated_at": "2025-10-15T11:22:23.80289Z", "shadowed": false, "mentioned_users": [], - "i18n": { - "en_text": "https://unsplash.com/photos/1_2d3MRbI9c", - "fr_text": "https://unsplash.com/photos/1_2d3MRbI9c", - "language": "en" - }, "silent": false, "pinned": false, "pinned_at": null, @@ -57,5 +59,5 @@ "pin_expires": null, "restricted_visibility": [] }, - "duration": "4355.84ms" + "duration": "274.32ms" } \ No newline at end of file diff --git a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_youtube_link.json b/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_youtube_link.json index 9427cbee840..afdcad2ecb0 100644 --- a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_youtube_link.json +++ b/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_youtube_link.json @@ -1,6 +1,6 @@ { "message": { - "id": "cc20202e-3ec1-4407-b35f-70981d74d2c4", + "id": "10f63aac-6f52-40f0-ad13-2ac8a6dd2784", "text": "https://youtube.com/watch?v=xOX7MsrbaPY", "html": "

https://youtube.com/watch?v=xOX7MsrbaPY

\n", "type": "regular", @@ -12,26 +12,31 @@ "role": "admin", "teams": [], "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-06-06T08:22:36.844279Z", + "updated_at": "2025-09-15T05:51:41.748915Z", "banned": false, "online": true, - "last_active": "2025-06-15T00:25:32.362084978Z", + "last_active": "2025-10-15T11:22:19.961028904Z", "blocked_user_ids": [], + "avg_response_time": 203648, "birthland": "Tatooine", + "custom_extra_data_key": true, + "canReceiveMessages": false, + "canBeAddedToGroups": true, "team": "test", "type": "team", "pando": "{\"speciality\":\"ios engineer\"}" }, + "member": { + "channel_role": "channel_member" + }, "attachments": [ { "type": "video", - "author_name": "YouTube", + "author_name": "Introducing MotionScape", "title": "Introducing MotionScape: Prototype SwiftUI Animation Easings", "title_link": "https://www.youtube.com/watch?v=xOX7MsrbaPY", - "text": "MotionScape is your SwiftUI animation's playground as a developer. You can see all animations and their parameters in effect with beautifully designed and handcrafted animation examples. Best of all: directly preview and export your settings as production-ready SwiftUI code that you can use in your apps as-is.\n\nSupercharge your apps with animations and get to know how to use them - with MotionScape!\n\nDownload MotionScape from the Mac AppStore: \nhttps://gstrm.io/motionscape-yt\n\nWebpage:\nhttps://getstream.github.io/motionscape-app/\n\nSwiftUI chat messaging:\nhttps://getstream.io/chat/sdk/swiftui/\n...", - "image_url": "https://i.ytimg.com/vi/xOX7MsrbaPY/mqdefault.jpg", - "thumb_url": "https://i.ytimg.com/vi/xOX7MsrbaPY/mqdefault.jpg", - "asset_url": "https://www.youtube.com/watch?v=xOX7MsrbaPY", + "text": "MotionScape is your SwiftUI animation's playground as a developer. You can see all animations and their parameters in effect with beautifully designed and handcrafted animation examples. Best of all: directly preview and export your settings as production-ready SwiftUI code that you can use in your apps as-is. Supercharge your apps with animations and get to know how to use them - with MotionScape! Download MotionScape from the Mac AppStore: https://gstrm.io/motionscape-yt Webpage: https://getstream.github.io/motionscape-app/ SwiftUI chat messaging: https://getstream.io/chat/sdk/swiftui/ Chapters: 00:00 Introducing MotionScape 00:31 Adjusting animation parameters 00:54 Supported interpolations 01:03 Previewing animation examples 01:50 Find and download MotionScape", + "asset_url": "https://www.youtube.com/embed/xOX7MsrbaPY", "og_scrape_url": "https://youtube.com/watch?v=xOX7MsrbaPY" } ], @@ -41,16 +46,11 @@ "reaction_scores": {}, "reply_count": 0, "deleted_reply_count": 0, - "cid": "messaging:ec2807ff-5c60-41bc-a816-49578260471a", - "created_at": "2025-06-15T00:25:40.546889Z", - "updated_at": "2025-06-15T00:25:40.546889Z", + "cid": "messaging:ec9f758c-3f7e-46f3-9618-bc5760428e5e", + "created_at": "2025-10-15T11:22:23.388057Z", + "updated_at": "2025-10-15T11:22:23.388057Z", "shadowed": false, "mentioned_users": [], - "i18n": { - "en_text": "https://youtube.com/watch?v=xOX7MsrbaPY", - "fr_text": "https://youtube.com/watch?v=xOX7MsrbaPY", - "language": "en" - }, "silent": false, "pinned": false, "pinned_at": null, @@ -58,5 +58,5 @@ "pin_expires": null, "restricted_visibility": [] }, - "duration": "560.98ms" + "duration": "290.31ms" } \ No newline at end of file diff --git a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/ws_events.json b/TestTools/StreamChatTestMockServer/Fixtures/JSONs/ws_events.json index 8b4a4764ef4..8a0649b4b93 100644 --- a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/ws_events.json +++ b/TestTools/StreamChatTestMockServer/Fixtures/JSONs/ws_events.json @@ -1,7 +1,7 @@ { "type": "typing.start", - "cid": "messaging:ec2807ff-5c60-41bc-a816-49578260471a", - "channel_id": "ec2807ff-5c60-41bc-a816-49578260471a", + "cid": "messaging:ec9f758c-3f7e-46f3-9618-bc5760428e5e", + "channel_id": "ec9f758c-3f7e-46f3-9618-bc5760428e5e", "channel_type": "messaging", "user": { "id": "luke_skywalker", @@ -11,11 +11,12 @@ "role": "admin", "teams": [], "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-06-06T08:22:36.844279Z", + "updated_at": "2025-09-15T05:51:41.748915Z", "banned": false, "online": true, - "last_active": "2025-06-15T00:25:32.362084978Z", + "last_active": "2025-10-15T11:22:19.961028904Z", "blocked_user_ids": [], + "avg_response_time": 203648, "shadow_banned": false, "privacy_settings": { "typing_indicators": { @@ -29,170 +30,173 @@ { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "804506291419e705e68fdc61b5f71297f30881364077b8c3f374f6938bef6cb4f9801da58770c8070108cf30ac06c060630037019523dc91d750de2293f8df7be86c8c8be6be95666859bdc62fbed488", - "created_at": "2025-06-14T16:19:23.502954Z", + "id": "80ff3d0ff101a3d4f3319e20996b2e33cbe4ff1673632911ba61471d9c7382cf10db29c41676c6a42cc7e4084f0b00b28b450da1fe52a97edb8c67d5690b0d9b600c863c34a34546a649be08e3a975cf", + "created_at": "2025-10-07T13:31:12.581472Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "6397e24030aa17262a850157abf97612eff0243e873b7d1f79c996662ecb1682", - "created_at": "2025-06-12T13:18:40.260038Z", + "id": "466ec8f052e4e43f6f429a55907217c289618204ebaf4f7ca8fe52c303b01d05", + "created_at": "2025-10-06T07:05:44.023463Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "80215dc8442845c41f30d28c05db0a8f697f727303be0fb49ca7743af6a211d3", - "created_at": "2025-06-04T02:43:01.595306Z", + "id": "80dc67313692ff3d512089cd19df179395b298d106b86cd2aedf0edfe1de40cd7caba3025823acccf9173fce2000728459e6751c4c7c96b80415a441d54c690d3f2d1635d26a8d58c0407edff8fcfe34", + "created_at": "2025-09-23T11:14:39.868685Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "8078596852ae3ecc7bb8d20eb1bde24d596115270e266be1e7164009c28c841c89120222e5594b8929bafb7b55ec47e5b1db2e1274b6e0c9a8f24ba1087832d4dc8acefc857be26848f2b074fdaa42fe", - "created_at": "2025-06-03T14:54:34.358128Z", + "id": "8013785658480684ee2a2f523bd4d52d6f2fac5e34283f4765362b4d526e5f374a46a0eeff277aee8d94ca872c4cdce2c021fef9cb1c71a4e5b28bba5481cab79c2ff7f111d4a7488c7a28de76736ba6", + "created_at": "2025-09-22T07:33:07.001628Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "8009e0a929c11c0297edc1a8c994a68fb7064cebede77d29bbd663c93390b1d22394adbdeb06a68f9b07720daa0e300b1dd951f90bb49c81275a0eaed20f24c954ceba42b8c76010b9ceaa70f19c5a24", - "created_at": "2025-06-03T12:06:41.61606Z", + "id": "8075f5f2d91f892d2a16de528dea1fae54d63f9d1c09ca18501547ec8b3880c54766e80e0eb8e10920a8545cf5a08f0048adf01c029a17b69fda5dbc8019733fdb5b4923e80d525ca96bd833f02434c5", + "created_at": "2025-09-16T07:18:39.830014Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "80b9b312b4b635f1f0e4005ba0acc30d8f022a59e5006edb24f4eb7546930dbe0de2ce85800eabc1ec1a004d0a87fc7520b4f9039718f4f8437c466eb33ab9b392a77607ce982e31e443685e2b6b90b1", - "created_at": "2025-05-30T21:14:29.53116Z", + "id": "804df9f905b9fd1f2c53e160a5f3f1db34490bea2ecb0fdb6aa99d9f407fd67b477079cc403e15b7424927276ac32b113c9820bb569b99a14d60fef0f5ae24804bea0e20be5da6d724f4c3abbf5cd0cb", + "created_at": "2025-09-15T18:13:06.007999Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "809065933d06e42d27179dfe7f24ef7c34e74c0898c184cc1de0abd791f0f9d2a1342ad140129c3a26ba7a4f86b3534358475d9964024ff373bc2c705bc2d999cbbe0402af60077fb7af6c1f73295bd7", - "created_at": "2025-05-30T18:32:15.279068Z", + "id": "8044edf2252ff9f253dada5dbf39f018f454e5bcdaf188501c6d105a4f5740c1872acb619f113596a1cb6f9071667d20e601f134aa0c83db83ce733f458b60c5c1b8712e2808abd4a840ce0347a8ea1a", + "created_at": "2025-09-15T13:32:20.775357Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "804d29de95a5a1fee45ffa38a6700d75807f844dbdcdffb6256377697ca2095c1f4a43a1823b7858af4a3dd47d96fda739110aa11072b1c0bec52446806ce65b9408b68b81343a8594bf21d3c18af6c0", - "created_at": "2025-05-30T09:18:11.558444Z", + "id": "80fee24033dcaa2b50911104447c596a7490ff86fa6593eb8f0eaca6c699c951ddad5e39d5ce95d3858d12a0169ab126dec8c4fa4f791a723304bf6c3ee415b0fe67e4dd7422e7af1dc49396442946eb", + "created_at": "2025-09-13T16:07:49.949311Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "80d9539ee4ffce5bfafbfdbbc323b743c049f08d5efbce01eeef090f9884001c24b5fde2c1e6a6b5699e2c8fea4b607d3ecb5f206668e5f04ccc1b1402bd618cb78a5c4e22bf0f1a7a762d121346d951", - "created_at": "2025-05-30T05:01:32.5559Z", + "id": "a23e236960612858446bc7b0e78152f0d03ff977da16a1beec3d4558b51c3377", + "created_at": "2025-09-11T14:55:25.842429Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "8082e4f123b0f83543d1f7c04920c76ec7d4b4db9d8f2e8ff0cc4daeef338209be72df2b35257cfdb86623c14d241cf1d1f42b78fef01e16052d13b03f75ed135360886f1900d3a0e80e97b268514c77", - "created_at": "2025-05-30T05:00:30.137993Z", + "id": "5c1967db91dfc57b32d12728b8ac7410edc325daae78215430319239bb1b9378", + "created_at": "2025-09-10T10:32:28.865373Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "43d7b696c6a06de2954e74814eb9b0e28b91096f4d1eeb852694f5921679ef46", - "created_at": "2025-05-29T08:49:30.254119Z", + "id": "8082e4f123b0f83543d1f7c04920c76ec7d4b4db9d8f2e8ff0cc4daeef338209be72df2b35257cfdb86623c14d241cf1d1f42b78fef01e16052d13b03f75ed135360886f1900d3a0e80e97b268514c77", + "created_at": "2025-09-03T04:32:38.333126Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "80aed57876c4ebe2a4bb743943b56da38392ddc22517d9e402874a28034678c5e2cbe50beb4929955801aea07c13c8d434a2cd1401c25dd5d0b7cdb9a60811c547e5f6ed6c163112f160c52149c9f76e", - "created_at": "2025-05-29T04:54:25.507462Z", + "id": "802e021f067e90a9be03d142b102c30a871ad7e16b41bf469eab29c1e2f7b480f433802b2a3e6585752c09e44ed9f71d858bb61dad135568caaaefdac4536529d3b43812f2077c95b57ff4483fd1ad26", + "created_at": "2025-09-02T16:12:13.231524Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "80ab6527485c2cf2edbc709f5c252d6eab4ce65ba260a4e3d9f9ccce1dcb4f822553145f2ff49799333363c2907bc0d1ec25eab646b66da776fead61ccac0f4cbb6085d81819ad2c3c9d365b7472856e", - "created_at": "2025-05-27T12:28:46.620215Z", + "id": "8006075492db346334382095f7c068600bfa16f8216628c938b1e9bdb18eae4288cd1567707a67b9868ae4166495d92d1eb9c20b2984a0fc3bc578013bcf05654598b343fa8d3f807e5c296e8ce61dc1", + "created_at": "2025-09-02T07:14:39.15913Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "80486ca05c15def9ffc4d915f9d0b2eeaa286450486e04c33f3e6dbc81f3a834766c3cac07a0de81ba90a20d171942443bd16043124deedcb4159af340c39361e11e3e91bf4737c87c794ea55f8338f2", - "created_at": "2025-05-23T10:49:52.417843Z", + "id": "802eefd1b58c8a23fefe5d181538f0109535ad3c5baabf31f6d7a5f898b8ef0c8386d882aa07c511cf97d28f8291f6f8c46b78d7c6639f5c70f09a69ca6a92aff4d9b007a4f4a89b3ab5b722ffb9c86c", + "created_at": "2025-09-01T11:36:07.581094Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "801415be332a8ce50194e0933d0446635ff626afc0652c4b484ea32d560e8e0ed522c270ebabffa470f042fb3478645cd7764c191cfc5d6c73dab852e10de9dfd07e09bbca340ad7bd1469c1f0926000", - "created_at": "2025-05-21T10:24:46.609564Z", + "id": "5c7d0b29710fba33189fb659b1cbc2be3491d4f3c4b399b8afefb8bc920103ec", + "created_at": "2025-08-28T11:02:17.709186Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "80c9120effde0e21706972562de59f7a5c35b8ae45c154bf071b49bd6774a4ef16aa003efb42d7ff9b7da187deaf560a5c126917e9f91f5304d5c1fa6d94019d4a72e246a903afc6a209152aed9fcc06", - "created_at": "2025-05-20T11:03:27.103857Z", + "id": "8079dec1832d067a951130384525f5de98ae20fe23d131e584c0028b69bdb60aec0a461b19e2b8886e94a479781a115c703bad0e3726f644e12a812d37bdd2b79b2021cd103688a2424bde9c08c9f2f0", + "created_at": "2025-08-27T05:23:02.591215Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "b919e95520aebf36da438b1cbafb93e6cbc165de1e40d714b26b493f3ff6411f", - "created_at": "2025-05-16T13:52:18.372021Z", + "id": "809eb957c901bdbae1f20ee0bebdf84c12b6d3a141ba32b5898e7353a7575ef53f941dba98b544fd7c1a36e10d3b88abe9b65d12b44b2fd93dc4a7a57fa45a79750d8a3106fe0d54b46cebad6ec7cbda", + "created_at": "2025-08-26T15:54:24.98832Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "806f6650799cb5424543258a92eeebbd627aac976d7a7c610dc74bde5de1f465129ad76ac92c7e32d24bbe9d0601d2b2521c00386ba6542f9e7f8f99ea31582e055631cf15112764fe8d6a1e8391c67c", - "created_at": "2025-05-16T09:11:33.233014Z", + "id": "806541d07bd58f5d897f3cb28994fd47749351fb77f948edb2b140c7c147c21bab5e5f90c6124a138f809cefce008662220436a7031c09224434282393bf76d4ba0f61866ecd5c72807279da20618b0a", + "created_at": "2025-08-22T10:41:07.519822Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "80fff6ff1cc87a8173608355c783e34925ccdf20029a013d81570a44d0c9769da833b5aa630d195e12dedadd781fec5a3df4f55dcb42035b1879a9a9bee452bbc9fc911b6104fe45e9e34a4ab3b2af22", - "created_at": "2025-05-15T22:14:07.879975Z", + "id": "808f6525eea58e6aa49bd81b12abbff5a5e9d5310c626559cc1c306f1b0b08496e27d06c64872046d39edb5c9c89f84f0f8debe6663b54ce1aff83269d13850464ae06d3f293f3d6748b0ebd9a91a2f8", + "created_at": "2025-08-19T00:56:28.078927Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "802de862e0eaf8de4920b0f6ed65e7e5daa6928401a443443713c0b45bfc993a1cf203d75e7f753c7fdffa03f7fc50a1447da3898f94f2afcf593610b1897902ece834d72c2567c9e831d0396333f9c1", - "created_at": "2025-05-13T07:49:41.024562Z", + "id": "802e7837a85219bea9972172678578d10f683c085da918b90a56a477f6692ccbb52ff7badf3ece11c1d612afed415347dac1a8acfc1dfa51385e2ea53689d43466c84f729b069f10efff86604bc7c40c", + "created_at": "2025-08-12T23:25:49.648064Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "803192da84414d63ee9ff14a2d6eb2094f5fb0aaae589db1fc031baeed386d2d7e682e56cff2417f16fe5468b91cf77cd3121a6c4758ad24e385b962125ffee6d4dd236d792177154e554b728740f0d8", - "created_at": "2025-05-12T13:27:58.032984Z", + "id": "809409e7859ebfe71a461ead24d36a911eb5cdbab7184f848b45c44b153e65ff15c4be82066a2d768dc7184ab9b3b0c1b3cb7ec1f0819f231a6d09ad64c33dbfb8d1b919d5fda0474d9f29e3cc2940b7", + "created_at": "2025-08-12T12:58:05.975459Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "80aa7286ccf76a1bc406e8e8a7e8197014afc2e436ea331cefe1587c040b55edd18a8b5c516a622b939d5bc02d6a51a75f782c86c75f8d47b3a754a632608925d9cec594bc32398ac32ccfa57499ef53", - "created_at": "2025-05-12T09:40:48.415127Z", + "id": "809065933d06e42d27179dfe7f24ef7c34e74c0898c184cc1de0abd791f0f9d2a1342ad140129c3a26ba7a4f86b3534358475d9964024ff373bc2c705bc2d999cbbe0402af60077fb7af6c1f73295bd7", + "created_at": "2025-08-11T20:11:02.29744Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "8083e22c635fefc3b8ca928aee3179830f2d3553fecd6a11ccd6dc6cb91672f035c4e76f4cb3f0332ce1e18fe4ecfa091ab247f353a35d3f903f7151bd00cff341852e862719edbd150f6a1778256e3d", - "created_at": "2025-05-07T08:38:58.92846Z", + "id": "803588facf5946ecfe386d4906321b9fe31fe4181b03bffbbfd4b733c4acd00f9046a16f7beeec102e95273d15c95c15a44a2076ba83ad001bbd77ce3810b6465a7ef92fe299da7038b636e762d6150b", + "created_at": "2025-08-08T11:31:24.887765Z", "user_id": "luke_skywalker" } ], "invisible": false, + "canReceiveMessages": false, "team": "test", - "type": "team", + "custom_extra_data_key": true, "pando": "{\"speciality\":\"ios engineer\"}", - "birthland": "Tatooine" + "birthland": "Tatooine", + "type": "team", + "canBeAddedToGroups": true }, - "created_at": "2025-06-15T00:25:34.999482144Z" + "created_at": "2025-10-15T11:22:21.064322094Z" } \ No newline at end of file diff --git a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/ws_events_channel.json b/TestTools/StreamChatTestMockServer/Fixtures/JSONs/ws_events_channel.json index a738783f135..6666d35564d 100644 --- a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/ws_events_channel.json +++ b/TestTools/StreamChatTestMockServer/Fixtures/JSONs/ws_events_channel.json @@ -1,17 +1,17 @@ { "type": "channel.updated", - "created_at": "2025-06-15T00:25:38.161298226Z", - "cid": "messaging:ec2807ff-5c60-41bc-a816-49578260471a", + "created_at": "2025-10-15T11:22:22.269497124Z", + "cid": "messaging:ec9f758c-3f7e-46f3-9618-bc5760428e5e", "channel_member_count": 4, "channel_type": "messaging", - "channel_id": "ec2807ff-5c60-41bc-a816-49578260471a", + "channel_id": "ec9f758c-3f7e-46f3-9618-bc5760428e5e", "channel": { - "id": "ec2807ff-5c60-41bc-a816-49578260471a", + "id": "ec9f758c-3f7e-46f3-9618-bc5760428e5e", "type": "messaging", - "cid": "messaging:ec2807ff-5c60-41bc-a816-49578260471a", - "last_message_at": "2025-06-15T00:25:36.86243Z", - "created_at": "2025-06-15T00:25:33.285826Z", - "updated_at": "2025-06-15T00:25:33.285826Z", + "cid": "messaging:ec9f758c-3f7e-46f3-9618-bc5760428e5e", + "last_message_at": "2025-10-15T11:22:21.804306Z", + "created_at": "2025-10-15T11:22:20.27144Z", + "updated_at": "2025-10-15T11:22:20.27144Z", "created_by": { "id": "luke_skywalker", "name": "Luke Skywalker", @@ -20,11 +20,12 @@ "role": "admin", "teams": [], "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-06-06T08:22:36.844279Z", + "updated_at": "2025-09-15T05:51:41.748915Z", "banned": false, "online": true, - "last_active": "2025-06-15T00:25:32.362084978Z", + "last_active": "2025-10-15T11:22:19.961028904Z", "blocked_user_ids": [], + "avg_response_time": 203648, "shadow_banned": false, "privacy_settings": { "typing_indicators": { @@ -38,170 +39,173 @@ { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "804506291419e705e68fdc61b5f71297f30881364077b8c3f374f6938bef6cb4f9801da58770c8070108cf30ac06c060630037019523dc91d750de2293f8df7be86c8c8be6be95666859bdc62fbed488", - "created_at": "2025-06-14T16:19:23.502954Z", + "id": "80ff3d0ff101a3d4f3319e20996b2e33cbe4ff1673632911ba61471d9c7382cf10db29c41676c6a42cc7e4084f0b00b28b450da1fe52a97edb8c67d5690b0d9b600c863c34a34546a649be08e3a975cf", + "created_at": "2025-10-07T13:31:12.581472Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "6397e24030aa17262a850157abf97612eff0243e873b7d1f79c996662ecb1682", - "created_at": "2025-06-12T13:18:40.260038Z", + "id": "466ec8f052e4e43f6f429a55907217c289618204ebaf4f7ca8fe52c303b01d05", + "created_at": "2025-10-06T07:05:44.023463Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "80215dc8442845c41f30d28c05db0a8f697f727303be0fb49ca7743af6a211d3", - "created_at": "2025-06-04T02:43:01.595306Z", + "id": "80dc67313692ff3d512089cd19df179395b298d106b86cd2aedf0edfe1de40cd7caba3025823acccf9173fce2000728459e6751c4c7c96b80415a441d54c690d3f2d1635d26a8d58c0407edff8fcfe34", + "created_at": "2025-09-23T11:14:39.868685Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "8078596852ae3ecc7bb8d20eb1bde24d596115270e266be1e7164009c28c841c89120222e5594b8929bafb7b55ec47e5b1db2e1274b6e0c9a8f24ba1087832d4dc8acefc857be26848f2b074fdaa42fe", - "created_at": "2025-06-03T14:54:34.358128Z", + "id": "8013785658480684ee2a2f523bd4d52d6f2fac5e34283f4765362b4d526e5f374a46a0eeff277aee8d94ca872c4cdce2c021fef9cb1c71a4e5b28bba5481cab79c2ff7f111d4a7488c7a28de76736ba6", + "created_at": "2025-09-22T07:33:07.001628Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "8009e0a929c11c0297edc1a8c994a68fb7064cebede77d29bbd663c93390b1d22394adbdeb06a68f9b07720daa0e300b1dd951f90bb49c81275a0eaed20f24c954ceba42b8c76010b9ceaa70f19c5a24", - "created_at": "2025-06-03T12:06:41.61606Z", + "id": "8075f5f2d91f892d2a16de528dea1fae54d63f9d1c09ca18501547ec8b3880c54766e80e0eb8e10920a8545cf5a08f0048adf01c029a17b69fda5dbc8019733fdb5b4923e80d525ca96bd833f02434c5", + "created_at": "2025-09-16T07:18:39.830014Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "80b9b312b4b635f1f0e4005ba0acc30d8f022a59e5006edb24f4eb7546930dbe0de2ce85800eabc1ec1a004d0a87fc7520b4f9039718f4f8437c466eb33ab9b392a77607ce982e31e443685e2b6b90b1", - "created_at": "2025-05-30T21:14:29.53116Z", + "id": "804df9f905b9fd1f2c53e160a5f3f1db34490bea2ecb0fdb6aa99d9f407fd67b477079cc403e15b7424927276ac32b113c9820bb569b99a14d60fef0f5ae24804bea0e20be5da6d724f4c3abbf5cd0cb", + "created_at": "2025-09-15T18:13:06.007999Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "809065933d06e42d27179dfe7f24ef7c34e74c0898c184cc1de0abd791f0f9d2a1342ad140129c3a26ba7a4f86b3534358475d9964024ff373bc2c705bc2d999cbbe0402af60077fb7af6c1f73295bd7", - "created_at": "2025-05-30T18:32:15.279068Z", + "id": "8044edf2252ff9f253dada5dbf39f018f454e5bcdaf188501c6d105a4f5740c1872acb619f113596a1cb6f9071667d20e601f134aa0c83db83ce733f458b60c5c1b8712e2808abd4a840ce0347a8ea1a", + "created_at": "2025-09-15T13:32:20.775357Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "804d29de95a5a1fee45ffa38a6700d75807f844dbdcdffb6256377697ca2095c1f4a43a1823b7858af4a3dd47d96fda739110aa11072b1c0bec52446806ce65b9408b68b81343a8594bf21d3c18af6c0", - "created_at": "2025-05-30T09:18:11.558444Z", + "id": "80fee24033dcaa2b50911104447c596a7490ff86fa6593eb8f0eaca6c699c951ddad5e39d5ce95d3858d12a0169ab126dec8c4fa4f791a723304bf6c3ee415b0fe67e4dd7422e7af1dc49396442946eb", + "created_at": "2025-09-13T16:07:49.949311Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "80d9539ee4ffce5bfafbfdbbc323b743c049f08d5efbce01eeef090f9884001c24b5fde2c1e6a6b5699e2c8fea4b607d3ecb5f206668e5f04ccc1b1402bd618cb78a5c4e22bf0f1a7a762d121346d951", - "created_at": "2025-05-30T05:01:32.5559Z", + "id": "a23e236960612858446bc7b0e78152f0d03ff977da16a1beec3d4558b51c3377", + "created_at": "2025-09-11T14:55:25.842429Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "8082e4f123b0f83543d1f7c04920c76ec7d4b4db9d8f2e8ff0cc4daeef338209be72df2b35257cfdb86623c14d241cf1d1f42b78fef01e16052d13b03f75ed135360886f1900d3a0e80e97b268514c77", - "created_at": "2025-05-30T05:00:30.137993Z", + "id": "5c1967db91dfc57b32d12728b8ac7410edc325daae78215430319239bb1b9378", + "created_at": "2025-09-10T10:32:28.865373Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "43d7b696c6a06de2954e74814eb9b0e28b91096f4d1eeb852694f5921679ef46", - "created_at": "2025-05-29T08:49:30.254119Z", + "id": "8082e4f123b0f83543d1f7c04920c76ec7d4b4db9d8f2e8ff0cc4daeef338209be72df2b35257cfdb86623c14d241cf1d1f42b78fef01e16052d13b03f75ed135360886f1900d3a0e80e97b268514c77", + "created_at": "2025-09-03T04:32:38.333126Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "80aed57876c4ebe2a4bb743943b56da38392ddc22517d9e402874a28034678c5e2cbe50beb4929955801aea07c13c8d434a2cd1401c25dd5d0b7cdb9a60811c547e5f6ed6c163112f160c52149c9f76e", - "created_at": "2025-05-29T04:54:25.507462Z", + "id": "802e021f067e90a9be03d142b102c30a871ad7e16b41bf469eab29c1e2f7b480f433802b2a3e6585752c09e44ed9f71d858bb61dad135568caaaefdac4536529d3b43812f2077c95b57ff4483fd1ad26", + "created_at": "2025-09-02T16:12:13.231524Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "80ab6527485c2cf2edbc709f5c252d6eab4ce65ba260a4e3d9f9ccce1dcb4f822553145f2ff49799333363c2907bc0d1ec25eab646b66da776fead61ccac0f4cbb6085d81819ad2c3c9d365b7472856e", - "created_at": "2025-05-27T12:28:46.620215Z", + "id": "8006075492db346334382095f7c068600bfa16f8216628c938b1e9bdb18eae4288cd1567707a67b9868ae4166495d92d1eb9c20b2984a0fc3bc578013bcf05654598b343fa8d3f807e5c296e8ce61dc1", + "created_at": "2025-09-02T07:14:39.15913Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "80486ca05c15def9ffc4d915f9d0b2eeaa286450486e04c33f3e6dbc81f3a834766c3cac07a0de81ba90a20d171942443bd16043124deedcb4159af340c39361e11e3e91bf4737c87c794ea55f8338f2", - "created_at": "2025-05-23T10:49:52.417843Z", + "id": "802eefd1b58c8a23fefe5d181538f0109535ad3c5baabf31f6d7a5f898b8ef0c8386d882aa07c511cf97d28f8291f6f8c46b78d7c6639f5c70f09a69ca6a92aff4d9b007a4f4a89b3ab5b722ffb9c86c", + "created_at": "2025-09-01T11:36:07.581094Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "801415be332a8ce50194e0933d0446635ff626afc0652c4b484ea32d560e8e0ed522c270ebabffa470f042fb3478645cd7764c191cfc5d6c73dab852e10de9dfd07e09bbca340ad7bd1469c1f0926000", - "created_at": "2025-05-21T10:24:46.609564Z", + "id": "5c7d0b29710fba33189fb659b1cbc2be3491d4f3c4b399b8afefb8bc920103ec", + "created_at": "2025-08-28T11:02:17.709186Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "80c9120effde0e21706972562de59f7a5c35b8ae45c154bf071b49bd6774a4ef16aa003efb42d7ff9b7da187deaf560a5c126917e9f91f5304d5c1fa6d94019d4a72e246a903afc6a209152aed9fcc06", - "created_at": "2025-05-20T11:03:27.103857Z", + "id": "8079dec1832d067a951130384525f5de98ae20fe23d131e584c0028b69bdb60aec0a461b19e2b8886e94a479781a115c703bad0e3726f644e12a812d37bdd2b79b2021cd103688a2424bde9c08c9f2f0", + "created_at": "2025-08-27T05:23:02.591215Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "b919e95520aebf36da438b1cbafb93e6cbc165de1e40d714b26b493f3ff6411f", - "created_at": "2025-05-16T13:52:18.372021Z", + "id": "809eb957c901bdbae1f20ee0bebdf84c12b6d3a141ba32b5898e7353a7575ef53f941dba98b544fd7c1a36e10d3b88abe9b65d12b44b2fd93dc4a7a57fa45a79750d8a3106fe0d54b46cebad6ec7cbda", + "created_at": "2025-08-26T15:54:24.98832Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "806f6650799cb5424543258a92eeebbd627aac976d7a7c610dc74bde5de1f465129ad76ac92c7e32d24bbe9d0601d2b2521c00386ba6542f9e7f8f99ea31582e055631cf15112764fe8d6a1e8391c67c", - "created_at": "2025-05-16T09:11:33.233014Z", + "id": "806541d07bd58f5d897f3cb28994fd47749351fb77f948edb2b140c7c147c21bab5e5f90c6124a138f809cefce008662220436a7031c09224434282393bf76d4ba0f61866ecd5c72807279da20618b0a", + "created_at": "2025-08-22T10:41:07.519822Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "80fff6ff1cc87a8173608355c783e34925ccdf20029a013d81570a44d0c9769da833b5aa630d195e12dedadd781fec5a3df4f55dcb42035b1879a9a9bee452bbc9fc911b6104fe45e9e34a4ab3b2af22", - "created_at": "2025-05-15T22:14:07.879975Z", + "id": "808f6525eea58e6aa49bd81b12abbff5a5e9d5310c626559cc1c306f1b0b08496e27d06c64872046d39edb5c9c89f84f0f8debe6663b54ce1aff83269d13850464ae06d3f293f3d6748b0ebd9a91a2f8", + "created_at": "2025-08-19T00:56:28.078927Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "802de862e0eaf8de4920b0f6ed65e7e5daa6928401a443443713c0b45bfc993a1cf203d75e7f753c7fdffa03f7fc50a1447da3898f94f2afcf593610b1897902ece834d72c2567c9e831d0396333f9c1", - "created_at": "2025-05-13T07:49:41.024562Z", + "id": "802e7837a85219bea9972172678578d10f683c085da918b90a56a477f6692ccbb52ff7badf3ece11c1d612afed415347dac1a8acfc1dfa51385e2ea53689d43466c84f729b069f10efff86604bc7c40c", + "created_at": "2025-08-12T23:25:49.648064Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "803192da84414d63ee9ff14a2d6eb2094f5fb0aaae589db1fc031baeed386d2d7e682e56cff2417f16fe5468b91cf77cd3121a6c4758ad24e385b962125ffee6d4dd236d792177154e554b728740f0d8", - "created_at": "2025-05-12T13:27:58.032984Z", + "id": "809409e7859ebfe71a461ead24d36a911eb5cdbab7184f848b45c44b153e65ff15c4be82066a2d768dc7184ab9b3b0c1b3cb7ec1f0819f231a6d09ad64c33dbfb8d1b919d5fda0474d9f29e3cc2940b7", + "created_at": "2025-08-12T12:58:05.975459Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "80aa7286ccf76a1bc406e8e8a7e8197014afc2e436ea331cefe1587c040b55edd18a8b5c516a622b939d5bc02d6a51a75f782c86c75f8d47b3a754a632608925d9cec594bc32398ac32ccfa57499ef53", - "created_at": "2025-05-12T09:40:48.415127Z", + "id": "809065933d06e42d27179dfe7f24ef7c34e74c0898c184cc1de0abd791f0f9d2a1342ad140129c3a26ba7a4f86b3534358475d9964024ff373bc2c705bc2d999cbbe0402af60077fb7af6c1f73295bd7", + "created_at": "2025-08-11T20:11:02.29744Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "8083e22c635fefc3b8ca928aee3179830f2d3553fecd6a11ccd6dc6cb91672f035c4e76f4cb3f0332ce1e18fe4ecfa091ab247f353a35d3f903f7151bd00cff341852e862719edbd150f6a1778256e3d", - "created_at": "2025-05-07T08:38:58.92846Z", + "id": "803588facf5946ecfe386d4906321b9fe31fe4181b03bffbbfd4b733c4acd00f9046a16f7beeec102e95273d15c95c15a44a2076ba83ad001bbd77ce3810b6465a7ef92fe299da7038b636e762d6150b", + "created_at": "2025-08-08T11:31:24.887765Z", "user_id": "luke_skywalker" } ], "invisible": false, + "custom_extra_data_key": true, + "pando": "{\"speciality\":\"ios engineer\"}", + "birthland": "Tatooine", + "canReceiveMessages": false, "team": "test", "type": "team", - "pando": "{\"speciality\":\"ios engineer\"}", - "birthland": "Tatooine" + "canBeAddedToGroups": true }, "frozen": false, "disabled": false, @@ -219,8 +223,9 @@ "updated_at": "2025-04-23T13:02:29.974824Z", "banned": false, "online": false, - "last_active": "2025-04-23T13:03:51.783496Z", + "last_active": "2025-10-01T07:12:24.084195Z", "blocked_user_ids": [], + "avg_response_time": 531, "shadow_banned": false, "privacy_settings": { "typing_indicators": { @@ -245,13 +250,6 @@ "created_at": "2025-04-13T16:47:57.568822Z", "user_id": "count_dooku" }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "80f982d49f7b7e3c3a89b9463d30130c9d7c09d37afb9f5e068fcc3c96bf6a727a2178877764bd0d91d5ad2701f0d270e69e53751be92e476de68484f7ead5704e57a56355c557de23ce045680384c2f", - "created_at": "2025-03-11T14:28:48.579734Z", - "user_id": "count_dooku" - }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", @@ -320,8 +318,8 @@ "birthland": "Serenno" }, "status": "member", - "created_at": "2025-06-15T00:25:33.304034Z", - "updated_at": "2025-06-15T00:25:33.304034Z", + "created_at": "2025-10-15T11:22:20.279452Z", + "updated_at": "2025-10-15T11:22:20.279452Z", "banned": false, "shadow_banned": false, "role": "member", @@ -329,20 +327,21 @@ "notifications_muted": false }, { - "user_id": "han_solo", + "user_id": "lando_calrissian", "user": { - "id": "han_solo", - "name": "Han Solo", - "image": "https://vignette.wikia.nocookie.net/starwars/images/e/e2/TFAHanSolo.png", - "language": "fr", - "role": "user", + "id": "lando_calrissian", + "name": "Lando Calrissian", + "image": "https://vignette.wikia.nocookie.net/starwars/images/8/8f/Lando_ROTJ.png", + "language": "", + "role": "admin", "teams": [], - "created_at": "2024-04-04T09:18:11.060737Z", - "updated_at": "2025-04-24T15:07:52.050477Z", + "created_at": "2024-04-04T19:06:08.890459Z", + "updated_at": "2025-04-16T17:23:28.189521Z", "banned": false, "online": false, - "last_active": "2025-06-10T06:55:59.491807Z", + "last_active": "2025-10-12T03:39:43.111316Z", "blocked_user_ids": [], + "avg_response_time": 1165670, "shadow_banned": false, "privacy_settings": { "typing_indicators": { @@ -356,160 +355,160 @@ { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "801f108a7521af9233f66e763339a5faff36e534ef24bce87154544ae1b4f7add0b077ce763b0449ba774b8e59e0362fee7fb1e5416081c835060aa013ea22bd04e7fd775cfbbe7b4fbff5be9dfde484", - "created_at": "2025-06-07T01:46:40.213206Z", - "user_id": "han_solo" + "id": "80fd207c06394ad12bef4ba1c29d8d47d33c15f09551da4da659bbfb3fcfa20b2a19c03c63a0face2a9713e2249c2801f296cf89a675601de1b49437cd354aa6f9e8f6c6b1c0f70fe8b47e13b9966e2d", + "created_at": "2025-07-02T17:28:07.646031Z", + "user_id": "lando_calrissian" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "80ec0dff63e1b885f1607e2989e0ca6c83885a0b8eafa968e0ff1f4458887c961f0ac756c2ee4b8c557f363d337d6029888bd0d3fe9cc4912146be6e2d1d6848d8a0f135de7de73f3ce1fa4ee9704795", - "created_at": "2025-06-03T11:44:12.612508Z", - "user_id": "han_solo" + "id": "807668fb1146ec2887451bfb492ed176318b75bac6f269373032a5279c42f48ee1d39e5a6343069fa72a1edb955848a931f1f156a5a543b878e9be2aefe45260843389eec42f640510ff8ed3b225cf03", + "created_at": "2025-06-20T06:29:12.864132Z", + "user_id": "lando_calrissian" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "80a189064a8e5bf1a5a12daba9c8309f359455a441fbec84c213b9e684b481b7a9aa75e3d1e8aada6c4cc683b262f436f5d48f9759370dbf463c9b85fc78dd5bb6a007b5751ea4eacd2a118305d90b89", - "created_at": "2025-05-26T16:48:24.572368Z", - "user_id": "han_solo" + "id": "82093d2a6d4b1a5ce7516398b350e46816da77c05f22d88c27c0d1e0cf3dd22d", + "created_at": "2025-06-08T07:57:49.274354Z", + "user_id": "lando_calrissian" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "8acda612d833bdb8c17dcfb7198db6ffd55713eda4b19515fe887a07bb9349d7", - "created_at": "2025-05-17T03:41:10.660569Z", - "user_id": "han_solo" + "id": "8660694406405a9e8c9544a9693061feebe9c8dbe43897d135a8540417224aba", + "created_at": "2025-06-03T17:01:00.396494Z", + "user_id": "lando_calrissian" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "efb15ea0c61b3fc7b399c09fe1cafee9b3462a1ba42f9b0698875122f5080f76", - "created_at": "2025-05-13T19:24:07.892191Z", - "user_id": "han_solo" + "id": "8511fb7ed1b4b274cfb28bd37fdfadd50759e268f5eed5d2a62d0dacf662d04a", + "created_at": "2025-05-28T13:10:55.29705Z", + "user_id": "lando_calrissian" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "8042be7b0c7318a366368f5dbff9af18ad371a65bbeab53203dc3bd27c9718228f1a1eed6cfd1db2367ae7a3905115ed7b369ca065f5dcc8954a8d0e64de56b4dc72657f4cc5f1a943439b071f58b5f4", - "created_at": "2025-05-09T19:51:10.947338Z", - "user_id": "han_solo" + "id": "93c24dc48e340302116e3887c5c5581b9023bdb0eb742a5a465d0fe4903c1876", + "created_at": "2025-05-26T10:15:28.710502Z", + "user_id": "lando_calrissian" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "ed3f92f410b6a8f040059d75b44914face8f0ac1e6d9bd2e02505923ccd28685", - "created_at": "2025-04-30T20:23:09.66427Z", - "user_id": "han_solo" + "id": "1d83f0fa4dd79bea75fd51324924027e81d366eaac47c55a36df8de8a870e1fb", + "created_at": "2025-05-16T20:34:31.031296Z", + "user_id": "lando_calrissian" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "0dd5c8f8d0004e27619cf9a8e260a8a532e7495864b5d8dd6aa4516fa9e8dd66", - "created_at": "2025-04-28T10:42:45.955481Z", - "user_id": "han_solo" + "id": "c26374589b9d018f36c61e2b9e6f18ca3dce5f8cb7c24392d61dd66fc7a5fd32", + "created_at": "2025-05-07T01:14:21.875477Z", + "user_id": "lando_calrissian" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "8044c176cfde058bf5cbd79fa72e14dff3bc931ad295db6a25d413c339e78f3e256ce84a9413b53a3d5bedb1581c54fc27d1a3626c5aa59dddf3967aa4c09f0e795e74e341f224bfb8dfafb05ccfe8ba", - "created_at": "2025-04-11T10:50:47.782896Z", - "user_id": "han_solo" + "id": "8006d309839f95442e1e5a75eb6ca08c663a1b7b9162046961fe84fd902929f59ec854b39a430949ecc95225525599fe9162eac6eeddbe91fe96dfab75a66614b0b4d722f1ca941b5c918a44eface3bc", + "created_at": "2025-05-04T22:06:32.060655Z", + "user_id": "lando_calrissian" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "8312675d1f9738030286c5ec019d1b195ba7fc1722036b6f93befb0564fbe4a1", - "created_at": "2025-03-31T10:45:01.90956Z", - "user_id": "han_solo" + "id": "36d3efac60c179fff596cc47e81c22beedaa4ebe367b34f59bd0c583b9046c74", + "created_at": "2025-04-29T22:02:15.838796Z", + "user_id": "lando_calrissian" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "200b284b4c9ea0ddfb21da49745e2ff52d407e0a7dd4e8f78a5c8f72ebdc297b", - "created_at": "2025-03-18T04:30:58.970837Z", - "user_id": "han_solo" + "id": "6519624008d60453a633556a08c0af4f6744a47d655d05eb2c8c51e475c6bd46", + "created_at": "2025-04-26T20:21:18.692769Z", + "user_id": "lando_calrissian" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "8009715c5d17766a2ab457f9381189a6065761d20e50656707592528c960f1ec370b2bafc7936b550474aaef247cea3673a175dac38aeb26f08eca6fecb13e6b5e70e0367adc604bb574c8d35a4b7647", - "created_at": "2025-03-10T16:16:04.223903Z", - "user_id": "han_solo" + "id": "e3ce7c58999ac779aed5460b367a140423498e4b28ee8b5c56a4ad8523f3e00c", + "created_at": "2025-04-15T09:30:42.778492Z", + "user_id": "lando_calrissian" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "f62bff12b2c8c4faf8463c6ae5c675a8149ca3aaa2776b7753a9e8653927da4e", - "created_at": "2025-03-08T21:27:08.451268Z", - "user_id": "han_solo" + "id": "89b56798cdf220d4a5de4990dd8a34b5c312950927d0409097a2073a07de25e5", + "created_at": "2025-04-11T22:24:27.745853Z", + "user_id": "lando_calrissian" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "496c1a822944cac2201c1fe59fb245ae22025b00b6649756fd719ba513681c5c", - "created_at": "2025-03-06T09:09:35.570586Z", - "user_id": "han_solo" + "id": "c57e77977466c71fff0a972536a436f6ba4425fcd426b75cee5b6be6c0326712", + "created_at": "2025-04-02T18:55:46.532785Z", + "user_id": "lando_calrissian" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "80c1d91b2f37c3a1c13accec2c31982402cf8ea90ae8925d2514ff83bca53a51e76acf361c4bed27548dc0730b59774c74ae2c47dfefd8b7cefa32b00a1604ba22b8e41f1e17a44e4319ed3fb66253f5", - "created_at": "2025-02-27T10:35:26.432802Z", - "user_id": "han_solo" + "id": "2522a047f4d128f29ea3c25e4ec6795bcaa248efc879a09d33d015be1ca2afa8", + "created_at": "2025-04-01T09:45:35.569944Z", + "user_id": "lando_calrissian" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "327aea75615b5163a87779ea32e241e360a049e418bc4e42993a996d3d6cf94c", - "created_at": "2025-02-04T13:16:37.534389Z", - "user_id": "han_solo" + "id": "97de66a3fc9266046b0ee3c5ee6ef26fd5f5d3c818a1ddff9aab504da2e6d6fc", + "created_at": "2025-03-17T23:03:11.287394Z", + "user_id": "lando_calrissian" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "afb6c2ecd6fafc6d2c9fa30a1204fd15a2192ddeea2503dece31a5aee361e7c7", - "created_at": "2025-01-28T22:01:52.754315Z", - "user_id": "han_solo" + "id": "026710ef5c80ecff242b6908cf638d810ea7f682622d7acad7a06776411e601b", + "created_at": "2025-03-15T07:07:21.347976Z", + "user_id": "lando_calrissian" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "5cf71106a7f5759fcfbe0e0eab99fc423e7bf61b17519d69369cd55bd5b0896a", - "created_at": "2025-01-28T13:21:44.725776Z", - "user_id": "han_solo" + "id": "8039cd4c893aac2f2af7c668002c75b0cbe6155b25a5a2cbbd94068f907d758de31d93d25b5347d9f75f168baac9b27b842143bbbcfc4c49bb8b5413fcd5d08bb0334536f7f94e438b0a1591d4c33b06", + "created_at": "2025-03-12T12:47:27.304069Z", + "user_id": "lando_calrissian" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "ba5c9a15da015e60009b8b18a022796ff3f1443e32c5bc6eb6582f37c83a279e", - "created_at": "2025-01-24T12:28:25.593919Z", - "user_id": "han_solo" + "id": "4ac20de2cda85c8c31a50b416b5049b3b7b31e5184e0f0371a811529d20b3ad0", + "created_at": "2025-02-10T19:25:02.942617Z", + "user_id": "lando_calrissian" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "4db7f6b8a1741a63ab34225fe973658f7a4bf5e51980009ea861b936ec98303d", - "created_at": "2025-01-19T20:33:43.240521Z", - "user_id": "han_solo" + "id": "6de10cbebd4518ae2a917f075be6754784684c3b95dda0c5579315c101c22dcd", + "created_at": "2025-02-07T00:15:22.100131Z", + "user_id": "lando_calrissian" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "c0fd9f37a0a660d02f19b26df4c29c9519115010b6fcc6e3c8c98bd9c40f0782", - "created_at": "2025-01-16T02:08:16.720431Z", - "user_id": "han_solo" + "id": "7d1c4b24c6143de096973964dd34016b60a96acc74944dfa2997170c5368f65d", + "created_at": "2025-01-31T17:35:26.948409Z", + "user_id": "lando_calrissian" } ], "invisible": false, - "birthland": "Corellia" + "birthland": "Socorro" }, "status": "member", - "created_at": "2025-06-15T00:25:33.304034Z", - "updated_at": "2025-06-15T00:25:33.304034Z", + "created_at": "2025-10-15T11:22:20.279452Z", + "updated_at": "2025-10-15T11:22:20.279452Z", "banned": false, "shadow_banned": false, - "role": "member", + "role": "admin", "channel_role": "channel_member", "notifications_muted": false }, @@ -523,11 +522,12 @@ "role": "admin", "teams": [], "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-06-06T08:22:36.844279Z", + "updated_at": "2025-09-15T05:51:41.748915Z", "banned": false, "online": true, - "last_active": "2025-06-15T00:25:32.362084978Z", + "last_active": "2025-10-15T11:22:19.961028904Z", "blocked_user_ids": [], + "avg_response_time": 203648, "shadow_banned": false, "privacy_settings": { "typing_indicators": { @@ -541,174 +541,177 @@ { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "804506291419e705e68fdc61b5f71297f30881364077b8c3f374f6938bef6cb4f9801da58770c8070108cf30ac06c060630037019523dc91d750de2293f8df7be86c8c8be6be95666859bdc62fbed488", - "created_at": "2025-06-14T16:19:23.502954Z", + "id": "80ff3d0ff101a3d4f3319e20996b2e33cbe4ff1673632911ba61471d9c7382cf10db29c41676c6a42cc7e4084f0b00b28b450da1fe52a97edb8c67d5690b0d9b600c863c34a34546a649be08e3a975cf", + "created_at": "2025-10-07T13:31:12.581472Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "6397e24030aa17262a850157abf97612eff0243e873b7d1f79c996662ecb1682", - "created_at": "2025-06-12T13:18:40.260038Z", + "id": "466ec8f052e4e43f6f429a55907217c289618204ebaf4f7ca8fe52c303b01d05", + "created_at": "2025-10-06T07:05:44.023463Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "80215dc8442845c41f30d28c05db0a8f697f727303be0fb49ca7743af6a211d3", - "created_at": "2025-06-04T02:43:01.595306Z", + "id": "80dc67313692ff3d512089cd19df179395b298d106b86cd2aedf0edfe1de40cd7caba3025823acccf9173fce2000728459e6751c4c7c96b80415a441d54c690d3f2d1635d26a8d58c0407edff8fcfe34", + "created_at": "2025-09-23T11:14:39.868685Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "8078596852ae3ecc7bb8d20eb1bde24d596115270e266be1e7164009c28c841c89120222e5594b8929bafb7b55ec47e5b1db2e1274b6e0c9a8f24ba1087832d4dc8acefc857be26848f2b074fdaa42fe", - "created_at": "2025-06-03T14:54:34.358128Z", + "id": "8013785658480684ee2a2f523bd4d52d6f2fac5e34283f4765362b4d526e5f374a46a0eeff277aee8d94ca872c4cdce2c021fef9cb1c71a4e5b28bba5481cab79c2ff7f111d4a7488c7a28de76736ba6", + "created_at": "2025-09-22T07:33:07.001628Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "8009e0a929c11c0297edc1a8c994a68fb7064cebede77d29bbd663c93390b1d22394adbdeb06a68f9b07720daa0e300b1dd951f90bb49c81275a0eaed20f24c954ceba42b8c76010b9ceaa70f19c5a24", - "created_at": "2025-06-03T12:06:41.61606Z", + "id": "8075f5f2d91f892d2a16de528dea1fae54d63f9d1c09ca18501547ec8b3880c54766e80e0eb8e10920a8545cf5a08f0048adf01c029a17b69fda5dbc8019733fdb5b4923e80d525ca96bd833f02434c5", + "created_at": "2025-09-16T07:18:39.830014Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "80b9b312b4b635f1f0e4005ba0acc30d8f022a59e5006edb24f4eb7546930dbe0de2ce85800eabc1ec1a004d0a87fc7520b4f9039718f4f8437c466eb33ab9b392a77607ce982e31e443685e2b6b90b1", - "created_at": "2025-05-30T21:14:29.53116Z", + "id": "804df9f905b9fd1f2c53e160a5f3f1db34490bea2ecb0fdb6aa99d9f407fd67b477079cc403e15b7424927276ac32b113c9820bb569b99a14d60fef0f5ae24804bea0e20be5da6d724f4c3abbf5cd0cb", + "created_at": "2025-09-15T18:13:06.007999Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "809065933d06e42d27179dfe7f24ef7c34e74c0898c184cc1de0abd791f0f9d2a1342ad140129c3a26ba7a4f86b3534358475d9964024ff373bc2c705bc2d999cbbe0402af60077fb7af6c1f73295bd7", - "created_at": "2025-05-30T18:32:15.279068Z", + "id": "8044edf2252ff9f253dada5dbf39f018f454e5bcdaf188501c6d105a4f5740c1872acb619f113596a1cb6f9071667d20e601f134aa0c83db83ce733f458b60c5c1b8712e2808abd4a840ce0347a8ea1a", + "created_at": "2025-09-15T13:32:20.775357Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "804d29de95a5a1fee45ffa38a6700d75807f844dbdcdffb6256377697ca2095c1f4a43a1823b7858af4a3dd47d96fda739110aa11072b1c0bec52446806ce65b9408b68b81343a8594bf21d3c18af6c0", - "created_at": "2025-05-30T09:18:11.558444Z", + "id": "80fee24033dcaa2b50911104447c596a7490ff86fa6593eb8f0eaca6c699c951ddad5e39d5ce95d3858d12a0169ab126dec8c4fa4f791a723304bf6c3ee415b0fe67e4dd7422e7af1dc49396442946eb", + "created_at": "2025-09-13T16:07:49.949311Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "80d9539ee4ffce5bfafbfdbbc323b743c049f08d5efbce01eeef090f9884001c24b5fde2c1e6a6b5699e2c8fea4b607d3ecb5f206668e5f04ccc1b1402bd618cb78a5c4e22bf0f1a7a762d121346d951", - "created_at": "2025-05-30T05:01:32.5559Z", + "id": "a23e236960612858446bc7b0e78152f0d03ff977da16a1beec3d4558b51c3377", + "created_at": "2025-09-11T14:55:25.842429Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "8082e4f123b0f83543d1f7c04920c76ec7d4b4db9d8f2e8ff0cc4daeef338209be72df2b35257cfdb86623c14d241cf1d1f42b78fef01e16052d13b03f75ed135360886f1900d3a0e80e97b268514c77", - "created_at": "2025-05-30T05:00:30.137993Z", + "id": "5c1967db91dfc57b32d12728b8ac7410edc325daae78215430319239bb1b9378", + "created_at": "2025-09-10T10:32:28.865373Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "43d7b696c6a06de2954e74814eb9b0e28b91096f4d1eeb852694f5921679ef46", - "created_at": "2025-05-29T08:49:30.254119Z", + "id": "8082e4f123b0f83543d1f7c04920c76ec7d4b4db9d8f2e8ff0cc4daeef338209be72df2b35257cfdb86623c14d241cf1d1f42b78fef01e16052d13b03f75ed135360886f1900d3a0e80e97b268514c77", + "created_at": "2025-09-03T04:32:38.333126Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "80aed57876c4ebe2a4bb743943b56da38392ddc22517d9e402874a28034678c5e2cbe50beb4929955801aea07c13c8d434a2cd1401c25dd5d0b7cdb9a60811c547e5f6ed6c163112f160c52149c9f76e", - "created_at": "2025-05-29T04:54:25.507462Z", + "id": "802e021f067e90a9be03d142b102c30a871ad7e16b41bf469eab29c1e2f7b480f433802b2a3e6585752c09e44ed9f71d858bb61dad135568caaaefdac4536529d3b43812f2077c95b57ff4483fd1ad26", + "created_at": "2025-09-02T16:12:13.231524Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "80ab6527485c2cf2edbc709f5c252d6eab4ce65ba260a4e3d9f9ccce1dcb4f822553145f2ff49799333363c2907bc0d1ec25eab646b66da776fead61ccac0f4cbb6085d81819ad2c3c9d365b7472856e", - "created_at": "2025-05-27T12:28:46.620215Z", + "id": "8006075492db346334382095f7c068600bfa16f8216628c938b1e9bdb18eae4288cd1567707a67b9868ae4166495d92d1eb9c20b2984a0fc3bc578013bcf05654598b343fa8d3f807e5c296e8ce61dc1", + "created_at": "2025-09-02T07:14:39.15913Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "80486ca05c15def9ffc4d915f9d0b2eeaa286450486e04c33f3e6dbc81f3a834766c3cac07a0de81ba90a20d171942443bd16043124deedcb4159af340c39361e11e3e91bf4737c87c794ea55f8338f2", - "created_at": "2025-05-23T10:49:52.417843Z", + "id": "802eefd1b58c8a23fefe5d181538f0109535ad3c5baabf31f6d7a5f898b8ef0c8386d882aa07c511cf97d28f8291f6f8c46b78d7c6639f5c70f09a69ca6a92aff4d9b007a4f4a89b3ab5b722ffb9c86c", + "created_at": "2025-09-01T11:36:07.581094Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "801415be332a8ce50194e0933d0446635ff626afc0652c4b484ea32d560e8e0ed522c270ebabffa470f042fb3478645cd7764c191cfc5d6c73dab852e10de9dfd07e09bbca340ad7bd1469c1f0926000", - "created_at": "2025-05-21T10:24:46.609564Z", + "id": "5c7d0b29710fba33189fb659b1cbc2be3491d4f3c4b399b8afefb8bc920103ec", + "created_at": "2025-08-28T11:02:17.709186Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "80c9120effde0e21706972562de59f7a5c35b8ae45c154bf071b49bd6774a4ef16aa003efb42d7ff9b7da187deaf560a5c126917e9f91f5304d5c1fa6d94019d4a72e246a903afc6a209152aed9fcc06", - "created_at": "2025-05-20T11:03:27.103857Z", + "id": "8079dec1832d067a951130384525f5de98ae20fe23d131e584c0028b69bdb60aec0a461b19e2b8886e94a479781a115c703bad0e3726f644e12a812d37bdd2b79b2021cd103688a2424bde9c08c9f2f0", + "created_at": "2025-08-27T05:23:02.591215Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "b919e95520aebf36da438b1cbafb93e6cbc165de1e40d714b26b493f3ff6411f", - "created_at": "2025-05-16T13:52:18.372021Z", + "id": "809eb957c901bdbae1f20ee0bebdf84c12b6d3a141ba32b5898e7353a7575ef53f941dba98b544fd7c1a36e10d3b88abe9b65d12b44b2fd93dc4a7a57fa45a79750d8a3106fe0d54b46cebad6ec7cbda", + "created_at": "2025-08-26T15:54:24.98832Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "806f6650799cb5424543258a92eeebbd627aac976d7a7c610dc74bde5de1f465129ad76ac92c7e32d24bbe9d0601d2b2521c00386ba6542f9e7f8f99ea31582e055631cf15112764fe8d6a1e8391c67c", - "created_at": "2025-05-16T09:11:33.233014Z", + "id": "806541d07bd58f5d897f3cb28994fd47749351fb77f948edb2b140c7c147c21bab5e5f90c6124a138f809cefce008662220436a7031c09224434282393bf76d4ba0f61866ecd5c72807279da20618b0a", + "created_at": "2025-08-22T10:41:07.519822Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "80fff6ff1cc87a8173608355c783e34925ccdf20029a013d81570a44d0c9769da833b5aa630d195e12dedadd781fec5a3df4f55dcb42035b1879a9a9bee452bbc9fc911b6104fe45e9e34a4ab3b2af22", - "created_at": "2025-05-15T22:14:07.879975Z", + "id": "808f6525eea58e6aa49bd81b12abbff5a5e9d5310c626559cc1c306f1b0b08496e27d06c64872046d39edb5c9c89f84f0f8debe6663b54ce1aff83269d13850464ae06d3f293f3d6748b0ebd9a91a2f8", + "created_at": "2025-08-19T00:56:28.078927Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "802de862e0eaf8de4920b0f6ed65e7e5daa6928401a443443713c0b45bfc993a1cf203d75e7f753c7fdffa03f7fc50a1447da3898f94f2afcf593610b1897902ece834d72c2567c9e831d0396333f9c1", - "created_at": "2025-05-13T07:49:41.024562Z", + "id": "802e7837a85219bea9972172678578d10f683c085da918b90a56a477f6692ccbb52ff7badf3ece11c1d612afed415347dac1a8acfc1dfa51385e2ea53689d43466c84f729b069f10efff86604bc7c40c", + "created_at": "2025-08-12T23:25:49.648064Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "803192da84414d63ee9ff14a2d6eb2094f5fb0aaae589db1fc031baeed386d2d7e682e56cff2417f16fe5468b91cf77cd3121a6c4758ad24e385b962125ffee6d4dd236d792177154e554b728740f0d8", - "created_at": "2025-05-12T13:27:58.032984Z", + "id": "809409e7859ebfe71a461ead24d36a911eb5cdbab7184f848b45c44b153e65ff15c4be82066a2d768dc7184ab9b3b0c1b3cb7ec1f0819f231a6d09ad64c33dbfb8d1b919d5fda0474d9f29e3cc2940b7", + "created_at": "2025-08-12T12:58:05.975459Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "80aa7286ccf76a1bc406e8e8a7e8197014afc2e436ea331cefe1587c040b55edd18a8b5c516a622b939d5bc02d6a51a75f782c86c75f8d47b3a754a632608925d9cec594bc32398ac32ccfa57499ef53", - "created_at": "2025-05-12T09:40:48.415127Z", + "id": "809065933d06e42d27179dfe7f24ef7c34e74c0898c184cc1de0abd791f0f9d2a1342ad140129c3a26ba7a4f86b3534358475d9964024ff373bc2c705bc2d999cbbe0402af60077fb7af6c1f73295bd7", + "created_at": "2025-08-11T20:11:02.29744Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "8083e22c635fefc3b8ca928aee3179830f2d3553fecd6a11ccd6dc6cb91672f035c4e76f4cb3f0332ce1e18fe4ecfa091ab247f353a35d3f903f7151bd00cff341852e862719edbd150f6a1778256e3d", - "created_at": "2025-05-07T08:38:58.92846Z", + "id": "803588facf5946ecfe386d4906321b9fe31fe4181b03bffbbfd4b733c4acd00f9046a16f7beeec102e95273d15c95c15a44a2076ba83ad001bbd77ce3810b6465a7ef92fe299da7038b636e762d6150b", + "created_at": "2025-08-08T11:31:24.887765Z", "user_id": "luke_skywalker" } ], "invisible": false, + "team": "test", + "canBeAddedToGroups": true, "pando": "{\"speciality\":\"ios engineer\"}", "birthland": "Tatooine", - "team": "test", - "type": "team" + "type": "team", + "canReceiveMessages": false, + "custom_extra_data_key": true }, "status": "member", - "created_at": "2025-06-15T00:25:33.304034Z", - "updated_at": "2025-06-15T00:25:33.304034Z", + "created_at": "2025-10-15T11:22:20.279452Z", + "updated_at": "2025-10-15T11:22:20.279452Z", "banned": false, "shadow_banned": false, "role": "owner", @@ -721,15 +724,16 @@ "id": "leia_organa", "name": "Leia Organa", "image": "https://vignette.wikia.nocookie.net/starwars/images/f/fc/Leia_Organa_TLJ.png", - "language": "zh", + "language": "", "role": "admin", "teams": [], "created_at": "2024-04-04T09:42:00.68335Z", - "updated_at": "2025-03-28T15:21:20.061525Z", + "updated_at": "2025-10-01T16:49:27.725672Z", "banned": false, - "online": false, - "last_active": "2025-06-14T17:34:03.224367Z", + "online": true, + "last_active": "2025-10-15T11:21:40.020397141Z", "blocked_user_ids": [], + "avg_response_time": 638602, "shadow_banned": false, "privacy_settings": { "typing_indicators": { @@ -743,193 +747,165 @@ { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "80562365fd4558a3f35d7242fe7f75f55d343e3c08793327b95c2a66bed62dd580e03ea87ee2670e045188cc002ce0c474cc2be00aac2e41417566303513d01c2f9b08fbe20a875423706773c938a276", - "created_at": "2025-06-11T13:32:58.048525Z", - "user_id": "leia_organa" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "15266a2cdf77614a3d9c88c714022dd0e946e537a8a61c33e6813e26480ed518", - "created_at": "2025-06-11T13:31:05.651333Z", + "id": "801ffb7f463b104d50a2cbdad23120a4b2ea77d3c98954b7929bc19f4fd0f38ca988fbd91577879dedfc503001f515fbf11ccf204b755c0e062df31750ebb8c463da8a3afb6a4e6dc028e96623f74a35", + "created_at": "2025-10-11T12:30:03.355048Z", "user_id": "leia_organa" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", "id": "14216b28af8556468ae8dbf08a5ea602f9deb9c2eb115d9b6aacf7f7bde74ade", - "created_at": "2025-06-04T10:55:53.753151Z", + "created_at": "2025-10-10T16:00:49.838202Z", "user_id": "leia_organa" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "80c1245d997bc62f7e6aec8a72f6493c876cf29a1d97aac74801c882c04cb2074b646c4ad7ecd661810cb22af8cc7049b71d1920a560d77da872be268e7917d3225078dfd13ea5b80328bfe55be159e9", - "created_at": "2025-05-13T03:34:54.823631Z", + "id": "ff2dc6e49c2663ff99b3a5e2a1dc8bf779a263c851f2dad3dc06270145fd1bbd", + "created_at": "2025-09-29T15:50:48.713725Z", "user_id": "leia_organa" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "807146c48063da5e10a127367fa5b54103c9b61e0f95576a485d6289681490a92bef0a3d59c4ca33fcd7eef7ce339463603221f39d7ef5109aebd80cd647942efc31e8a5d2e4c03f7e2af6d0e3b0db4b", - "created_at": "2025-05-02T18:24:07.641188Z", + "id": "80fc30257c03c5a273df381a64d9e168a94ca4cc5c44d82f6e9b27f94f6f5de710a08d7baa9a83fe41a72366c446eaba3c148273ef7b66f15c6e6ed21e20e068c45df62d18ab7680e91a076bb325fce7", + "created_at": "2025-09-08T17:54:15.751885Z", "user_id": "leia_organa" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "5dd2129704b206673f40fcacc51498af12466d202fb7b86fbcd1def5e021743b", - "created_at": "2025-04-29T18:11:54.744459Z", + "id": "8038e9ddae90ad612073cccda59c8e715193e375185a4c96cc374b12bf3b74d8bdcc4016ad13c5e48544a4606c82e874809a13322acdd03b4a4e9fd4ccfc5704aa183fd1cc12481b3a9d83e630e8adfe", + "created_at": "2025-07-29T15:46:00.608151Z", "user_id": "leia_organa" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "80416524327191703aa5704a6788f5b85b6b38cb8de437675da7883c1a61b4b479fc20426200dd366c45b972948046a258d95d3331d4fc694e17c491ba671b209792b4df54e58907cdca208e58ffbd66", - "created_at": "2025-04-25T04:57:05.059816Z", + "id": "806c802cfe521f2980a80135de1e2c18dff2d634ab770bf6b645014a5da1f01047ff66c9933c83a8c5a464f5eb00e4d34a8bc02addc27f93aa24dd7f56c92e272e15d9f6d9beca620d08c93f80e01fe3", + "created_at": "2025-07-27T10:22:24.684004Z", "user_id": "leia_organa" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "de52baf21c547d06ce71f54f5ef8a47693c9b9f194dd6e82e1c855803eaacc4d", - "created_at": "2025-04-24T16:27:51.309408Z", + "id": "8043c6d0aeff8f1223f3c516e40e520c378e2ede462f06d215b630367d81f7b103181bb31f74ab23e37fe1f8686480f54eb31b2c560cac5ee18daa5558981c7f25465733563afc8050ecb7035be93ea7", + "created_at": "2025-07-04T15:29:30.008558Z", "user_id": "leia_organa" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "8babc49a1d5e8943a25291fd4c2606f6d8f38470693daf7628a9a9c8617ea313", - "created_at": "2025-04-15T19:38:32.316517Z", + "id": "80b164164bc6eedba4a8c4d606d1e92b965facaa019aa89d81eaf7ed2579c4128e3ad7dff7a26dbba507ef75f79260d8acd86a26ce53a48efb83216045e8d9ed0eccd716fcd1807e381169a3f68f5f2e", + "created_at": "2025-07-04T13:17:05.176311Z", "user_id": "leia_organa" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "80390cd4b5322ce878abb0ea8c6f2f554d21f2233767b61b4eb3a916e0be7325bc225528ffd97e3503ac6a56c5a62b747150b0d44abbeb6fb8c8e8b2dbf7fbe4291f3dbcac257ab869a6046876db1289", - "created_at": "2025-04-07T13:06:09.56915Z", + "id": "80c3ed5bdc76358518c9ef3707c00a4046848575a3ecc6f5f0fc9e67cf5823dd17e3784464aa6fc2a9640ec7e4f6eb6b7be3a4a9858857cda94fce3f6b3af907c1a68c19052302a4d07c5eeacbad1d0d", + "created_at": "2025-06-23T12:39:52.733646Z", "user_id": "leia_organa" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "f71e38ca56a6625256d487331369e2b950219fd0d55e8d7cb82fbd88966fee65", - "created_at": "2025-04-06T04:27:36.245492Z", + "id": "7b7bf6129032c5c3c333f2f80d8939d914587a11aaa30953af5e63d89d9defa1", + "created_at": "2025-06-18T03:58:29.477347Z", "user_id": "leia_organa" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "809855cbb448059e832d627031ae978b9bb37f323b1ad0b3b0b879e4437cdc20", - "created_at": "2025-04-04T23:27:03.99699Z", - "user_id": "leia_organa" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "92045b8b37f75e4c36ed74665f68ebf4d932c19acd2a02d0c3604230a44a9537", - "created_at": "2025-03-27T01:06:37.823489Z", - "user_id": "leia_organa" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "80ef23e592c74d32dc863239a026b8b815ee54cc794d69ca44f61defe7dbd01085bd66d5da7b1a1c5b7d9790790459d3ba6e27c9393db0fb0c7b2920526a976066df8fd4193e5df2d2f5620dfe432cf9", - "created_at": "2025-02-25T13:16:00.896262Z", + "id": "807146c48063da5e10a127367fa5b54103c9b61e0f95576a485d6289681490a92bef0a3d59c4ca33fcd7eef7ce339463603221f39d7ef5109aebd80cd647942efc31e8a5d2e4c03f7e2af6d0e3b0db4b", + "created_at": "2025-05-02T18:24:07.641188Z", "user_id": "leia_organa" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "80f13fec122f492c20a9dc11bb7003a1e870fb75de24890568c0fb5e3b7365331618227d89162b7322e9b68b20a0b02a42d6b8bf9923091d34492a1375dc781026cd9845b6b561a0d284cbc77b5bdc39", - "created_at": "2025-02-20T11:33:28.513905Z", + "id": "5dd2129704b206673f40fcacc51498af12466d202fb7b86fbcd1def5e021743b", + "created_at": "2025-04-29T18:11:54.744459Z", "user_id": "leia_organa" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "80c9f99c1d02347ba3599ab752e55308681244fe97fcbc95eb0e630bcd97cb3ee927e8b15d5085938b4b18246f2e9ba2de3f357a6ca9127b1971df14742725841f0fd8537233b5e43a07a24422411816", - "created_at": "2025-02-06T05:12:00.670052Z", + "id": "80416524327191703aa5704a6788f5b85b6b38cb8de437675da7883c1a61b4b479fc20426200dd366c45b972948046a258d95d3331d4fc694e17c491ba671b209792b4df54e58907cdca208e58ffbd66", + "created_at": "2025-04-25T04:57:05.059816Z", "user_id": "leia_organa" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "8099cf56e010eb7a7ec2202eacffbc22c0d84b2c3007b9b45403f4f99fd42b670d6552521aea4c14263fd0ed0d034f2e3f25aa44145a4054ed7d067e25e2f221582d30de66f750e62e45e2c0fa73ed74", - "created_at": "2025-02-05T14:25:05.737649Z", + "id": "de52baf21c547d06ce71f54f5ef8a47693c9b9f194dd6e82e1c855803eaacc4d", + "created_at": "2025-04-24T16:27:51.309408Z", "user_id": "leia_organa" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "003ddc257b04f7d989eb91bf70fd2e19aaef86bd7419d16993cba3e74f6e2ba4", - "created_at": "2025-02-01T23:38:43.460976Z", + "id": "8babc49a1d5e8943a25291fd4c2606f6d8f38470693daf7628a9a9c8617ea313", + "created_at": "2025-04-15T19:38:32.316517Z", "user_id": "leia_organa" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "06f2e16cacf2bc6c1917d945e3aa875d2c0faa6cd1a379f7c2c28b9b61269bd2", - "created_at": "2025-01-25T23:50:24.74497Z", + "id": "80390cd4b5322ce878abb0ea8c6f2f554d21f2233767b61b4eb3a916e0be7325bc225528ffd97e3503ac6a56c5a62b747150b0d44abbeb6fb8c8e8b2dbf7fbe4291f3dbcac257ab869a6046876db1289", + "created_at": "2025-04-07T13:06:09.56915Z", "user_id": "leia_organa" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "80902ac37a00a72af2fcebb5fa7db31603af4ed96681d8c356409dc391ce5d0a", - "created_at": "2025-01-24T05:28:13.061448Z", + "id": "f71e38ca56a6625256d487331369e2b950219fd0d55e8d7cb82fbd88966fee65", + "created_at": "2025-04-06T04:27:36.245492Z", "user_id": "leia_organa" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "80d2480357faab5bfb5328e24089a3d3a9c51d7baf593089a8cff3779238135973c3e7a2fa015b857a9e022da1f30d3a1fd081243aa8f92e527428e9d63fbef4f0bbcb28016f756e1aeaaf60d8923ce4", - "created_at": "2025-01-17T16:26:56.459178Z", + "id": "809855cbb448059e832d627031ae978b9bb37f323b1ad0b3b0b879e4437cdc20", + "created_at": "2025-04-04T23:27:03.99699Z", "user_id": "leia_organa" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "80c28b1312d1e9123e63a3451441c59c17742260f08a627897c26520178e55ddac30fb3cfb30725e2f595983f333edea82b5377b3032c45b7483c1b0876f5ba7b46461edb563bd30d26e7adbe8a3b615", - "created_at": "2025-01-15T15:38:17.46356Z", + "id": "92045b8b37f75e4c36ed74665f68ebf4d932c19acd2a02d0c3604230a44a9537", + "created_at": "2025-03-27T01:06:37.823489Z", "user_id": "leia_organa" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "c72625f7d8d3cea767f3a3343c6308a66b191532e2f01b5fc36e7ba1ea5ad1bb", - "created_at": "2025-01-02T17:25:19.278525Z", + "id": "80f13fec122f492c20a9dc11bb7003a1e870fb75de24890568c0fb5e3b7365331618227d89162b7322e9b68b20a0b02a42d6b8bf9923091d34492a1375dc781026cd9845b6b561a0d284cbc77b5bdc39", + "created_at": "2025-02-20T11:33:28.513905Z", "user_id": "leia_organa" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "fd0aaea9b4ba4c8e23a6aefa1fba3a500fd617d9cf8dcfc4740837e030d595ea", - "created_at": "2024-12-27T21:25:50.077757Z", + "id": "80c9f99c1d02347ba3599ab752e55308681244fe97fcbc95eb0e630bcd97cb3ee927e8b15d5085938b4b18246f2e9ba2de3f357a6ca9127b1971df14742725841f0fd8537233b5e43a07a24422411816", + "created_at": "2025-02-06T05:12:00.670052Z", "user_id": "leia_organa" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "7806d403ee3740e98fc519a123a9450dbd9d90b13229bff5dbe7a1d5825d2fdf", - "created_at": "2024-12-27T17:32:18.338986Z", + "id": "8099cf56e010eb7a7ec2202eacffbc22c0d84b2c3007b9b45403f4f99fd42b670d6552521aea4c14263fd0ed0d034f2e3f25aa44145a4054ed7d067e25e2f221582d30de66f750e62e45e2c0fa73ed74", + "created_at": "2025-02-05T14:25:05.737649Z", "user_id": "leia_organa" } ], "invisible": false, - "birthland": "Polis Massa", - "private_settings": { - "readReceipts": { - "enabled": false - }, - "typingIndicators": { - "enabled": false - } - } + "is_moderator": true, + "birthland": "Polis Massa" }, "status": "member", - "created_at": "2025-06-15T00:25:38.137522Z", - "updated_at": "2025-06-15T00:25:38.137522Z", + "created_at": "2025-10-15T11:22:22.248971Z", + "updated_at": "2025-10-15T11:22:22.248971Z", "banned": false, "shadow_banned": false, "role": "admin", @@ -940,7 +916,7 @@ "member_count": 4, "config": { "created_at": "2021-03-01T19:26:18.406502Z", - "updated_at": "2025-05-05T18:07:33.269057Z", + "updated_at": "2025-07-28T15:20:21.098826Z", "name": "messaging", "typing_events": true, "read_events": true, @@ -954,9 +930,12 @@ "url_enrichment": true, "custom_events": true, "push_notifications": true, - "reminders": false, + "reminders": true, "mark_messages_pending": false, "polls": true, + "user_message_reminders": false, + "shared_locations": true, + "count_messages": false, "message_retention": "infinite", "max_message_length": 5000, "automod": "AI", @@ -997,22 +976,26 @@ "role": "admin", "teams": [], "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-06-06T08:22:36.844279Z", + "updated_at": "2025-09-15T05:51:41.748915Z", "banned": false, "online": true, - "last_active": "2025-06-15T00:25:32.362084978Z", + "last_active": "2025-10-15T11:22:19.961028904Z", "blocked_user_ids": [], + "avg_response_time": 203648, + "canBeAddedToGroups": true, + "canReceiveMessages": false, + "pando": "{\"speciality\":\"ios engineer\"}", + "birthland": "Tatooine", + "custom_extra_data_key": true, + "team": "test", + "type": "team", "privacy_settings": { "read_receipts": { - "enabled": false + "enabled": true }, "typing_indicators": { - "enabled": false + "enabled": true } - }, - "team": "test", - "type": "team", - "pando": "{\"speciality\":\"ios engineer\"}", - "birthland": "Tatooine" + } } } \ No newline at end of file diff --git a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/ws_events_member.json b/TestTools/StreamChatTestMockServer/Fixtures/JSONs/ws_events_member.json index 097b0aee5b8..ffb7cd3c4d6 100644 --- a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/ws_events_member.json +++ b/TestTools/StreamChatTestMockServer/Fixtures/JSONs/ws_events_member.json @@ -1,7 +1,7 @@ { "type": "member.added", - "cid": "messaging:ec2807ff-5c60-41bc-a816-49578260471a", - "channel_id": "ec2807ff-5c60-41bc-a816-49578260471a", + "cid": "messaging:ec9f758c-3f7e-46f3-9618-bc5760428e5e", + "channel_id": "ec9f758c-3f7e-46f3-9618-bc5760428e5e", "channel_type": "messaging", "member": { "user_id": "leia_organa", @@ -10,27 +10,20 @@ "role": "admin", "teams_role": null, "created_at": "2024-04-04T09:42:00.68335Z", - "updated_at": "2025-03-28T15:21:20.061525Z", - "last_active": "2025-06-14T17:34:03.224367Z", - "last_engaged_at": "2025-06-14T17:34:05.446982Z", + "updated_at": "2025-10-01T16:49:27.725672Z", + "last_active": "2025-10-15T11:19:09.931581Z", + "last_engaged_at": "2025-10-15T00:02:20.390107Z", "banned": false, - "online": false, - "language": "zh", - "birthland": "Polis Massa", - "private_settings": { - "readReceipts": { - "enabled": false - }, - "typingIndicators": { - "enabled": false - } - }, + "online": true, + "avg_response_time": 638602, "name": "Leia Organa", - "image": "https://vignette.wikia.nocookie.net/starwars/images/f/fc/Leia_Organa_TLJ.png" + "image": "https://vignette.wikia.nocookie.net/starwars/images/f/fc/Leia_Organa_TLJ.png", + "birthland": "Polis Massa", + "is_moderator": true }, "status": "member", - "created_at": "2025-06-15T00:25:38.137522Z", - "updated_at": "2025-06-15T00:25:38.137522Z", + "created_at": "2025-10-15T11:22:22.248971Z", + "updated_at": "2025-10-15T11:22:22.248971Z", "banned": false, "shadow_banned": false, "is_global_banned": false, @@ -45,24 +38,17 @@ "role": "admin", "teams_role": null, "created_at": "2024-04-04T09:42:00.68335Z", - "updated_at": "2025-03-28T15:21:20.061525Z", - "last_active": "2025-06-14T17:34:03.224367Z", - "last_engaged_at": "2025-06-14T17:34:05.446982Z", + "updated_at": "2025-10-01T16:49:27.725672Z", + "last_active": "2025-10-15T11:19:09.931581Z", + "last_engaged_at": "2025-10-15T00:02:20.390107Z", "banned": false, - "online": false, - "language": "zh", + "online": true, + "avg_response_time": 638602, + "name": "Leia Organa", "image": "https://vignette.wikia.nocookie.net/starwars/images/f/fc/Leia_Organa_TLJ.png", "birthland": "Polis Massa", - "private_settings": { - "readReceipts": { - "enabled": false - }, - "typingIndicators": { - "enabled": false - } - }, - "name": "Leia Organa" + "is_moderator": true }, - "channel_last_message_at": "2025-06-15T00:25:36.86243Z", - "created_at": "2025-06-15T00:25:38.150649795Z" + "channel_last_message_at": "2025-10-15T11:22:21.804306Z", + "created_at": "2025-10-15T11:22:22.257574084Z" } \ No newline at end of file diff --git a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/ws_health_check.json b/TestTools/StreamChatTestMockServer/Fixtures/JSONs/ws_health_check.json index 9d5093a6f0a..86e1e1209f9 100644 --- a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/ws_health_check.json +++ b/TestTools/StreamChatTestMockServer/Fixtures/JSONs/ws_health_check.json @@ -1,5 +1,5 @@ { - "connection_id": "684a957c-0a15-3975-0200-000000000f8a", + "connection_id": "68e67b7d-0a15-3975-0200-0000000082cb", "me": { "id": "luke_skywalker", "name": "Luke Skywalker", @@ -8,10 +8,11 @@ "role": "admin", "teams": [], "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-06-06T08:22:36.844279Z", + "updated_at": "2025-09-15T05:51:41.748915Z", "banned": false, "online": true, - "last_active": "2025-06-15T00:25:32.362084978Z", + "last_active": "2025-10-15T11:22:19.961028904Z", + "avg_response_time": 203648, "privacy_settings": { "typing_indicators": { "enabled": true @@ -24,16 +25,19 @@ "invisible": false, "mutes": [], "channel_mutes": [], - "unread_count": 0, - "total_unread_count": 0, - "unread_channels": 0, + "unread_count": 9, + "total_unread_count": 9, + "unread_channels": 1, "unread_threads": 0, - "type": "team", + "canBeAddedToGroups": true, "pando": "{\"speciality\":\"ios engineer\"}", + "custom_extra_data_key": true, + "canReceiveMessages": false, "birthland": "Tatooine", - "team": "test" + "team": "test", + "type": "team" }, "cid": "*", "type": "health.check", - "created_at": "2025-06-15T00:25:32.398620669Z" + "created_at": "2025-10-15T11:22:20.040248985Z" } \ No newline at end of file diff --git a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/ws_message.json b/TestTools/StreamChatTestMockServer/Fixtures/JSONs/ws_message.json index d97de06a1b6..c34b88ed7d9 100644 --- a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/ws_message.json +++ b/TestTools/StreamChatTestMockServer/Fixtures/JSONs/ws_message.json @@ -1,16 +1,16 @@ { "type": "message.new", - "created_at": "2025-06-15T00:25:36.923178296Z", - "cid": "messaging:ec2807ff-5c60-41bc-a816-49578260471a", + "created_at": "2025-10-15T11:22:21.838194472Z", + "cid": "messaging:ec9f758c-3f7e-46f3-9618-bc5760428e5e", "channel_member_count": 3, "channel_custom": { "name": "Sync Mock Server" }, "channel_type": "messaging", - "channel_id": "ec2807ff-5c60-41bc-a816-49578260471a", - "message_id": "b9841bf2-9a49-4adf-ae93-5e07d37d7c22", + "channel_id": "ec9f758c-3f7e-46f3-9618-bc5760428e5e", + "message_id": "dc702391-84b6-44af-aa67-c349d2e72441", "message": { - "id": "b9841bf2-9a49-4adf-ae93-5e07d37d7c22", + "id": "dc702391-84b6-44af-aa67-c349d2e72441", "text": "Test", "html": "

Test

\n", "type": "regular", @@ -22,180 +22,187 @@ "role": "admin", "teams": [], "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-06-06T08:22:36.844279Z", + "updated_at": "2025-09-15T05:51:41.748915Z", "banned": false, "online": true, - "last_active": "2025-06-15T00:25:32.362084978Z", + "last_active": "2025-10-15T11:22:19.961028904Z", "blocked_user_ids": [], + "avg_response_time": 203648, "shadow_banned": false, "devices": [ { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "804506291419e705e68fdc61b5f71297f30881364077b8c3f374f6938bef6cb4f9801da58770c8070108cf30ac06c060630037019523dc91d750de2293f8df7be86c8c8be6be95666859bdc62fbed488", - "created_at": "2025-06-14T16:19:23.502954Z", + "id": "80ff3d0ff101a3d4f3319e20996b2e33cbe4ff1673632911ba61471d9c7382cf10db29c41676c6a42cc7e4084f0b00b28b450da1fe52a97edb8c67d5690b0d9b600c863c34a34546a649be08e3a975cf", + "created_at": "2025-10-07T13:31:12.581472Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "6397e24030aa17262a850157abf97612eff0243e873b7d1f79c996662ecb1682", - "created_at": "2025-06-12T13:18:40.260038Z", + "id": "466ec8f052e4e43f6f429a55907217c289618204ebaf4f7ca8fe52c303b01d05", + "created_at": "2025-10-06T07:05:44.023463Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "80215dc8442845c41f30d28c05db0a8f697f727303be0fb49ca7743af6a211d3", - "created_at": "2025-06-04T02:43:01.595306Z", + "id": "80dc67313692ff3d512089cd19df179395b298d106b86cd2aedf0edfe1de40cd7caba3025823acccf9173fce2000728459e6751c4c7c96b80415a441d54c690d3f2d1635d26a8d58c0407edff8fcfe34", + "created_at": "2025-09-23T11:14:39.868685Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "8078596852ae3ecc7bb8d20eb1bde24d596115270e266be1e7164009c28c841c89120222e5594b8929bafb7b55ec47e5b1db2e1274b6e0c9a8f24ba1087832d4dc8acefc857be26848f2b074fdaa42fe", - "created_at": "2025-06-03T14:54:34.358128Z", + "id": "8013785658480684ee2a2f523bd4d52d6f2fac5e34283f4765362b4d526e5f374a46a0eeff277aee8d94ca872c4cdce2c021fef9cb1c71a4e5b28bba5481cab79c2ff7f111d4a7488c7a28de76736ba6", + "created_at": "2025-09-22T07:33:07.001628Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "8009e0a929c11c0297edc1a8c994a68fb7064cebede77d29bbd663c93390b1d22394adbdeb06a68f9b07720daa0e300b1dd951f90bb49c81275a0eaed20f24c954ceba42b8c76010b9ceaa70f19c5a24", - "created_at": "2025-06-03T12:06:41.61606Z", + "id": "8075f5f2d91f892d2a16de528dea1fae54d63f9d1c09ca18501547ec8b3880c54766e80e0eb8e10920a8545cf5a08f0048adf01c029a17b69fda5dbc8019733fdb5b4923e80d525ca96bd833f02434c5", + "created_at": "2025-09-16T07:18:39.830014Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "80b9b312b4b635f1f0e4005ba0acc30d8f022a59e5006edb24f4eb7546930dbe0de2ce85800eabc1ec1a004d0a87fc7520b4f9039718f4f8437c466eb33ab9b392a77607ce982e31e443685e2b6b90b1", - "created_at": "2025-05-30T21:14:29.53116Z", + "id": "804df9f905b9fd1f2c53e160a5f3f1db34490bea2ecb0fdb6aa99d9f407fd67b477079cc403e15b7424927276ac32b113c9820bb569b99a14d60fef0f5ae24804bea0e20be5da6d724f4c3abbf5cd0cb", + "created_at": "2025-09-15T18:13:06.007999Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "809065933d06e42d27179dfe7f24ef7c34e74c0898c184cc1de0abd791f0f9d2a1342ad140129c3a26ba7a4f86b3534358475d9964024ff373bc2c705bc2d999cbbe0402af60077fb7af6c1f73295bd7", - "created_at": "2025-05-30T18:32:15.279068Z", + "id": "8044edf2252ff9f253dada5dbf39f018f454e5bcdaf188501c6d105a4f5740c1872acb619f113596a1cb6f9071667d20e601f134aa0c83db83ce733f458b60c5c1b8712e2808abd4a840ce0347a8ea1a", + "created_at": "2025-09-15T13:32:20.775357Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "804d29de95a5a1fee45ffa38a6700d75807f844dbdcdffb6256377697ca2095c1f4a43a1823b7858af4a3dd47d96fda739110aa11072b1c0bec52446806ce65b9408b68b81343a8594bf21d3c18af6c0", - "created_at": "2025-05-30T09:18:11.558444Z", + "id": "80fee24033dcaa2b50911104447c596a7490ff86fa6593eb8f0eaca6c699c951ddad5e39d5ce95d3858d12a0169ab126dec8c4fa4f791a723304bf6c3ee415b0fe67e4dd7422e7af1dc49396442946eb", + "created_at": "2025-09-13T16:07:49.949311Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "80d9539ee4ffce5bfafbfdbbc323b743c049f08d5efbce01eeef090f9884001c24b5fde2c1e6a6b5699e2c8fea4b607d3ecb5f206668e5f04ccc1b1402bd618cb78a5c4e22bf0f1a7a762d121346d951", - "created_at": "2025-05-30T05:01:32.5559Z", + "id": "a23e236960612858446bc7b0e78152f0d03ff977da16a1beec3d4558b51c3377", + "created_at": "2025-09-11T14:55:25.842429Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "8082e4f123b0f83543d1f7c04920c76ec7d4b4db9d8f2e8ff0cc4daeef338209be72df2b35257cfdb86623c14d241cf1d1f42b78fef01e16052d13b03f75ed135360886f1900d3a0e80e97b268514c77", - "created_at": "2025-05-30T05:00:30.137993Z", + "id": "5c1967db91dfc57b32d12728b8ac7410edc325daae78215430319239bb1b9378", + "created_at": "2025-09-10T10:32:28.865373Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "43d7b696c6a06de2954e74814eb9b0e28b91096f4d1eeb852694f5921679ef46", - "created_at": "2025-05-29T08:49:30.254119Z", + "id": "8082e4f123b0f83543d1f7c04920c76ec7d4b4db9d8f2e8ff0cc4daeef338209be72df2b35257cfdb86623c14d241cf1d1f42b78fef01e16052d13b03f75ed135360886f1900d3a0e80e97b268514c77", + "created_at": "2025-09-03T04:32:38.333126Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "80aed57876c4ebe2a4bb743943b56da38392ddc22517d9e402874a28034678c5e2cbe50beb4929955801aea07c13c8d434a2cd1401c25dd5d0b7cdb9a60811c547e5f6ed6c163112f160c52149c9f76e", - "created_at": "2025-05-29T04:54:25.507462Z", + "id": "802e021f067e90a9be03d142b102c30a871ad7e16b41bf469eab29c1e2f7b480f433802b2a3e6585752c09e44ed9f71d858bb61dad135568caaaefdac4536529d3b43812f2077c95b57ff4483fd1ad26", + "created_at": "2025-09-02T16:12:13.231524Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "80ab6527485c2cf2edbc709f5c252d6eab4ce65ba260a4e3d9f9ccce1dcb4f822553145f2ff49799333363c2907bc0d1ec25eab646b66da776fead61ccac0f4cbb6085d81819ad2c3c9d365b7472856e", - "created_at": "2025-05-27T12:28:46.620215Z", + "id": "8006075492db346334382095f7c068600bfa16f8216628c938b1e9bdb18eae4288cd1567707a67b9868ae4166495d92d1eb9c20b2984a0fc3bc578013bcf05654598b343fa8d3f807e5c296e8ce61dc1", + "created_at": "2025-09-02T07:14:39.15913Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "80486ca05c15def9ffc4d915f9d0b2eeaa286450486e04c33f3e6dbc81f3a834766c3cac07a0de81ba90a20d171942443bd16043124deedcb4159af340c39361e11e3e91bf4737c87c794ea55f8338f2", - "created_at": "2025-05-23T10:49:52.417843Z", + "id": "802eefd1b58c8a23fefe5d181538f0109535ad3c5baabf31f6d7a5f898b8ef0c8386d882aa07c511cf97d28f8291f6f8c46b78d7c6639f5c70f09a69ca6a92aff4d9b007a4f4a89b3ab5b722ffb9c86c", + "created_at": "2025-09-01T11:36:07.581094Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "801415be332a8ce50194e0933d0446635ff626afc0652c4b484ea32d560e8e0ed522c270ebabffa470f042fb3478645cd7764c191cfc5d6c73dab852e10de9dfd07e09bbca340ad7bd1469c1f0926000", - "created_at": "2025-05-21T10:24:46.609564Z", + "id": "5c7d0b29710fba33189fb659b1cbc2be3491d4f3c4b399b8afefb8bc920103ec", + "created_at": "2025-08-28T11:02:17.709186Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "80c9120effde0e21706972562de59f7a5c35b8ae45c154bf071b49bd6774a4ef16aa003efb42d7ff9b7da187deaf560a5c126917e9f91f5304d5c1fa6d94019d4a72e246a903afc6a209152aed9fcc06", - "created_at": "2025-05-20T11:03:27.103857Z", + "id": "8079dec1832d067a951130384525f5de98ae20fe23d131e584c0028b69bdb60aec0a461b19e2b8886e94a479781a115c703bad0e3726f644e12a812d37bdd2b79b2021cd103688a2424bde9c08c9f2f0", + "created_at": "2025-08-27T05:23:02.591215Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "b919e95520aebf36da438b1cbafb93e6cbc165de1e40d714b26b493f3ff6411f", - "created_at": "2025-05-16T13:52:18.372021Z", + "id": "809eb957c901bdbae1f20ee0bebdf84c12b6d3a141ba32b5898e7353a7575ef53f941dba98b544fd7c1a36e10d3b88abe9b65d12b44b2fd93dc4a7a57fa45a79750d8a3106fe0d54b46cebad6ec7cbda", + "created_at": "2025-08-26T15:54:24.98832Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "806f6650799cb5424543258a92eeebbd627aac976d7a7c610dc74bde5de1f465129ad76ac92c7e32d24bbe9d0601d2b2521c00386ba6542f9e7f8f99ea31582e055631cf15112764fe8d6a1e8391c67c", - "created_at": "2025-05-16T09:11:33.233014Z", + "id": "806541d07bd58f5d897f3cb28994fd47749351fb77f948edb2b140c7c147c21bab5e5f90c6124a138f809cefce008662220436a7031c09224434282393bf76d4ba0f61866ecd5c72807279da20618b0a", + "created_at": "2025-08-22T10:41:07.519822Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "80fff6ff1cc87a8173608355c783e34925ccdf20029a013d81570a44d0c9769da833b5aa630d195e12dedadd781fec5a3df4f55dcb42035b1879a9a9bee452bbc9fc911b6104fe45e9e34a4ab3b2af22", - "created_at": "2025-05-15T22:14:07.879975Z", + "id": "808f6525eea58e6aa49bd81b12abbff5a5e9d5310c626559cc1c306f1b0b08496e27d06c64872046d39edb5c9c89f84f0f8debe6663b54ce1aff83269d13850464ae06d3f293f3d6748b0ebd9a91a2f8", + "created_at": "2025-08-19T00:56:28.078927Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "802de862e0eaf8de4920b0f6ed65e7e5daa6928401a443443713c0b45bfc993a1cf203d75e7f753c7fdffa03f7fc50a1447da3898f94f2afcf593610b1897902ece834d72c2567c9e831d0396333f9c1", - "created_at": "2025-05-13T07:49:41.024562Z", + "id": "802e7837a85219bea9972172678578d10f683c085da918b90a56a477f6692ccbb52ff7badf3ece11c1d612afed415347dac1a8acfc1dfa51385e2ea53689d43466c84f729b069f10efff86604bc7c40c", + "created_at": "2025-08-12T23:25:49.648064Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "803192da84414d63ee9ff14a2d6eb2094f5fb0aaae589db1fc031baeed386d2d7e682e56cff2417f16fe5468b91cf77cd3121a6c4758ad24e385b962125ffee6d4dd236d792177154e554b728740f0d8", - "created_at": "2025-05-12T13:27:58.032984Z", + "id": "809409e7859ebfe71a461ead24d36a911eb5cdbab7184f848b45c44b153e65ff15c4be82066a2d768dc7184ab9b3b0c1b3cb7ec1f0819f231a6d09ad64c33dbfb8d1b919d5fda0474d9f29e3cc2940b7", + "created_at": "2025-08-12T12:58:05.975459Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "80aa7286ccf76a1bc406e8e8a7e8197014afc2e436ea331cefe1587c040b55edd18a8b5c516a622b939d5bc02d6a51a75f782c86c75f8d47b3a754a632608925d9cec594bc32398ac32ccfa57499ef53", - "created_at": "2025-05-12T09:40:48.415127Z", + "id": "809065933d06e42d27179dfe7f24ef7c34e74c0898c184cc1de0abd791f0f9d2a1342ad140129c3a26ba7a4f86b3534358475d9964024ff373bc2c705bc2d999cbbe0402af60077fb7af6c1f73295bd7", + "created_at": "2025-08-11T20:11:02.29744Z", "user_id": "luke_skywalker" }, { "push_provider": "apn", "push_provider_name": "APN-Configuration", - "id": "8083e22c635fefc3b8ca928aee3179830f2d3553fecd6a11ccd6dc6cb91672f035c4e76f4cb3f0332ce1e18fe4ecfa091ab247f353a35d3f903f7151bd00cff341852e862719edbd150f6a1778256e3d", - "created_at": "2025-05-07T08:38:58.92846Z", + "id": "803588facf5946ecfe386d4906321b9fe31fe4181b03bffbbfd4b733c4acd00f9046a16f7beeec102e95273d15c95c15a44a2076ba83ad001bbd77ce3810b6465a7ef92fe299da7038b636e762d6150b", + "created_at": "2025-08-08T11:31:24.887765Z", "user_id": "luke_skywalker" } ], "invisible": false, + "pando": "{\"speciality\":\"ios engineer\"}", "team": "test", + "canBeAddedToGroups": true, "type": "team", - "pando": "{\"speciality\":\"ios engineer\"}", - "birthland": "Tatooine" + "canReceiveMessages": false, + "birthland": "Tatooine", + "custom_extra_data_key": true + }, + "member": { + "channel_role": "channel_member" }, "attachments": [], "latest_reactions": [], @@ -204,16 +211,11 @@ "reaction_scores": {}, "reply_count": 0, "deleted_reply_count": 0, - "cid": "messaging:ec2807ff-5c60-41bc-a816-49578260471a", - "created_at": "2025-06-15T00:25:36.86243Z", - "updated_at": "2025-06-15T00:25:36.86243Z", + "cid": "messaging:ec9f758c-3f7e-46f3-9618-bc5760428e5e", + "created_at": "2025-10-15T11:22:21.804306Z", + "updated_at": "2025-10-15T11:22:21.804306Z", "shadowed": false, "mentioned_users": [], - "i18n": { - "en_text": "Test", - "fr_text": "Testez", - "language": "en" - }, "silent": false, "pinned": false, "pinned_at": null, @@ -229,15 +231,17 @@ "role": "admin", "teams": [], "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-06-06T08:22:36.844279Z", + "updated_at": "2025-09-15T05:51:41.748915Z", "banned": false, "online": true, - "last_active": "2025-06-15T00:25:32.362084978Z", + "last_active": "2025-10-15T11:22:19.961028904Z", "blocked_user_ids": [], + "avg_response_time": 203648, + "custom_extra_data_key": true, + "pando": "{\"speciality\":\"ios engineer\"}", "team": "test", "type": "team", - "pando": "{\"speciality\":\"ios engineer\"}", - "birthland": "Tatooine", + "canReceiveMessages": false, "privacy_settings": { "read_receipts": { "enabled": false @@ -245,10 +249,12 @@ "typing_indicators": { "enabled": false } - } + }, + "canBeAddedToGroups": true, + "birthland": "Tatooine" }, "watcher_count": 1, - "unread_count": 0, - "total_unread_count": 0, - "unread_channels": 0 + "unread_count": 9, + "total_unread_count": 9, + "unread_channels": 1 } \ No newline at end of file diff --git a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/ws_reaction.json b/TestTools/StreamChatTestMockServer/Fixtures/JSONs/ws_reaction.json index 92d03e644c3..f50cd514d28 100644 --- a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/ws_reaction.json +++ b/TestTools/StreamChatTestMockServer/Fixtures/JSONs/ws_reaction.json @@ -1,10 +1,10 @@ { "type": "reaction.new", - "cid": "messaging:ec2807ff-5c60-41bc-a816-49578260471a", - "channel_id": "ec2807ff-5c60-41bc-a816-49578260471a", + "cid": "messaging:ec9f758c-3f7e-46f3-9618-bc5760428e5e", + "channel_id": "ec9f758c-3f7e-46f3-9618-bc5760428e5e", "channel_type": "messaging", "message": { - "id": "b9841bf2-9a49-4adf-ae93-5e07d37d7c22", + "id": "dc702391-84b6-44af-aa67-c349d2e72441", "text": "Test", "html": "

Test

\n", "type": "regular", @@ -13,47 +13,55 @@ "role": "admin", "teams_role": null, "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-06-06T08:22:36.844279Z", - "last_active": "2025-06-15T00:25:32.362084978Z", - "last_engaged_at": "2025-06-14T02:15:22.646364Z", + "updated_at": "2025-09-15T05:51:41.748915Z", + "last_active": "2025-10-15T11:22:19.961028904Z", + "last_engaged_at": "2025-10-15T00:03:45.658694Z", "banned": false, "online": true, "language": "en", - "name": "Luke Skywalker", - "team": "test", + "avg_response_time": 203648, + "canBeAddedToGroups": true, + "canReceiveMessages": false, + "pando": "{\"speciality\":\"ios engineer\"}", + "birthland": "Tatooine", + "custom_extra_data_key": true, "type": "team", "image": "https://vignette.wikia.nocookie.net/starwars/images/2/20/LukeTLJ.jpg", - "pando": "{\"speciality\":\"ios engineer\"}", - "birthland": "Tatooine" + "name": "Luke Skywalker", + "team": "test" }, "restricted_visibility": [], "attachments": [], "latest_reactions": [ { - "message_id": "b9841bf2-9a49-4adf-ae93-5e07d37d7c22", + "message_id": "dc702391-84b6-44af-aa67-c349d2e72441", "user_id": "luke_skywalker", "user": { "id": "luke_skywalker", "role": "admin", "teams_role": null, "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-06-06T08:22:36.844279Z", - "last_active": "2025-06-15T00:25:32.362084978Z", - "last_engaged_at": "2025-06-14T02:15:22.646364Z", + "updated_at": "2025-09-15T05:51:41.748915Z", + "last_active": "2025-10-15T11:22:19.961028904Z", + "last_engaged_at": "2025-10-15T00:03:45.658694Z", "banned": false, "online": true, "language": "en", - "birthland": "Tatooine", + "avg_response_time": 203648, + "canReceiveMessages": false, "name": "Luke Skywalker", "team": "test", "type": "team", - "image": "https://vignette.wikia.nocookie.net/starwars/images/2/20/LukeTLJ.jpg", - "pando": "{\"speciality\":\"ios engineer\"}" + "canBeAddedToGroups": true, + "pando": "{\"speciality\":\"ios engineer\"}", + "birthland": "Tatooine", + "custom_extra_data_key": true, + "image": "https://vignette.wikia.nocookie.net/starwars/images/2/20/LukeTLJ.jpg" }, "type": "like", "score": 1, - "created_at": "2025-06-15T00:25:37.516538Z", - "updated_at": "2025-06-15T00:25:37.516538Z" + "created_at": "2025-10-15T11:22:21.987244Z", + "updated_at": "2025-10-15T11:22:21.987244Z" } ], "own_reactions": [], @@ -67,72 +75,110 @@ "like": { "count": 1, "sum_scores": 1, - "first_reaction_at": "2025-06-15T00:25:37.516538Z", - "last_reaction_at": "2025-06-15T00:25:37.516538Z" + "first_reaction_at": "2025-10-15T11:22:21.987244Z", + "last_reaction_at": "2025-10-15T11:22:21.987244Z" } }, "reply_count": 0, "deleted_reply_count": 0, - "cid": "messaging:ec2807ff-5c60-41bc-a816-49578260471a", - "created_at": "2025-06-15T00:25:36.86243Z", - "updated_at": "2025-06-15T00:25:37.529233Z", + "cid": "messaging:ec9f758c-3f7e-46f3-9618-bc5760428e5e", + "created_at": "2025-10-15T11:22:21.804306Z", + "updated_at": "2025-10-15T11:22:22.001363Z", "shadowed": false, "mentioned_users": [], - "i18n": { - "en_text": "Test", - "fr_text": "Testez", - "language": "en" - }, "silent": false, "pinned": false, "pinned_at": null, "pinned_by": null, - "pin_expires": null + "pin_expires": null, + "member": { + "user_id": "luke_skywalker", + "user": { + "id": "luke_skywalker", + "role": "admin", + "teams_role": null, + "created_at": "2024-04-04T09:26:11.805899Z", + "updated_at": "2025-09-15T05:51:41.748915Z", + "last_active": "2025-10-15T11:22:19.961028904Z", + "last_engaged_at": "2025-10-15T00:03:45.658694Z", + "banned": false, + "online": true, + "language": "en", + "avg_response_time": 203648, + "custom_extra_data_key": true, + "team": "test", + "image": "https://vignette.wikia.nocookie.net/starwars/images/2/20/LukeTLJ.jpg", + "name": "Luke Skywalker", + "birthland": "Tatooine", + "type": "team", + "canBeAddedToGroups": true, + "canReceiveMessages": false, + "pando": "{\"speciality\":\"ios engineer\"}" + }, + "status": "member", + "created_at": "2025-10-15T11:22:20.279452Z", + "updated_at": "2025-10-15T11:22:20.279452Z", + "banned": false, + "shadow_banned": false, + "is_global_banned": false, + "archived_at": null, + "pinned_at": null, + "channel_role": "channel_member", + "notifications_muted": false + } }, "reaction": { - "message_id": "b9841bf2-9a49-4adf-ae93-5e07d37d7c22", + "message_id": "dc702391-84b6-44af-aa67-c349d2e72441", "user_id": "luke_skywalker", "user": { "id": "luke_skywalker", "role": "admin", "teams_role": null, "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-06-06T08:22:36.844279Z", - "last_active": "2025-06-15T00:25:32.362084978Z", - "last_engaged_at": "2025-06-14T02:15:22.646364Z", + "updated_at": "2025-09-15T05:51:41.748915Z", + "last_active": "2025-10-15T11:22:19.961028904Z", + "last_engaged_at": "2025-10-15T00:03:45.658694Z", "banned": false, "online": true, "language": "en", + "avg_response_time": 203648, + "canBeAddedToGroups": true, "pando": "{\"speciality\":\"ios engineer\"}", "birthland": "Tatooine", + "custom_extra_data_key": true, + "image": "https://vignette.wikia.nocookie.net/starwars/images/2/20/LukeTLJ.jpg", + "canReceiveMessages": false, "name": "Luke Skywalker", "team": "test", - "type": "team", - "image": "https://vignette.wikia.nocookie.net/starwars/images/2/20/LukeTLJ.jpg" + "type": "team" }, "type": "like", "score": 1, - "created_at": "2025-06-15T00:25:37.516538Z", - "updated_at": "2025-06-15T00:25:37.516538Z" + "created_at": "2025-10-15T11:22:21.987244Z", + "updated_at": "2025-10-15T11:22:21.987244Z" }, "user": { "id": "luke_skywalker", "role": "admin", "teams_role": null, "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-06-06T08:22:36.844279Z", - "last_active": "2025-06-15T00:25:32.362084978Z", - "last_engaged_at": "2025-06-14T02:15:22.646364Z", + "updated_at": "2025-09-15T05:51:41.748915Z", + "last_active": "2025-10-15T11:22:19.961028904Z", + "last_engaged_at": "2025-10-15T00:03:45.658694Z", "banned": false, "online": true, "language": "en", + "avg_response_time": 203648, + "image": "https://vignette.wikia.nocookie.net/starwars/images/2/20/LukeTLJ.jpg", + "canReceiveMessages": false, "name": "Luke Skywalker", "team": "test", "type": "team", - "image": "https://vignette.wikia.nocookie.net/starwars/images/2/20/LukeTLJ.jpg", + "canBeAddedToGroups": true, "pando": "{\"speciality\":\"ios engineer\"}", - "birthland": "Tatooine" + "birthland": "Tatooine", + "custom_extra_data_key": true }, - "channel_last_message_at": "2025-06-15T00:25:36.86243Z", - "created_at": "2025-06-15T00:25:37.540065556Z" + "channel_last_message_at": "2025-10-15T11:22:21.804306Z", + "created_at": "2025-10-15T11:22:22.01374502Z" } \ No newline at end of file diff --git a/TestTools/StreamChatTestMockServer/MockServer/AttachmentResponses.swift b/TestTools/StreamChatTestMockServer/MockServer/AttachmentResponses.swift index 5cc5b6cffa1..09b17ba9cee 100644 --- a/TestTools/StreamChatTestMockServer/MockServer/AttachmentResponses.swift +++ b/TestTools/StreamChatTestMockServer/MockServer/AttachmentResponses.swift @@ -6,12 +6,11 @@ import XCTest public extension StreamMockServer { - func configureAttachmentEndpoints() { - server.register(MockEndpoint.image) { [weak self] request in + server.register(MockEndpoint.image) { [weak self] _ in self?.attachmentCreation(fileUrl: Attachments.image) } - server.register(MockEndpoint.file) { [weak self] request in + server.register(MockEndpoint.file) { [weak self] _ in self?.attachmentCreation(fileUrl: Attachments.file) } } @@ -21,5 +20,4 @@ public extension StreamMockServer { json[JSONKey.file] = fileUrl return .ok(.json(json)) } - } diff --git a/TestTools/StreamChatTestMockServer/MockServer/ChannelConfig.swift b/TestTools/StreamChatTestMockServer/MockServer/ChannelConfig.swift index eafe115e86c..b8189626c2d 100644 --- a/TestTools/StreamChatTestMockServer/MockServer/ChannelConfig.swift +++ b/TestTools/StreamChatTestMockServer/MockServer/ChannelConfig.swift @@ -10,7 +10,6 @@ import XCTest // MARK: - Config public struct ChannelConfigs { - struct Cooldown { var isEnabled = false var duration: Int = 3 @@ -37,9 +36,11 @@ public struct ChannelConfigs { channel[JSONKey.channel] = innerChannel } - mutating func updateConfig(config: ChannelConfig_Mock, - forChannelWithId id: String, - server: StreamMockServer) { + mutating func updateConfig( + config: ChannelConfig_Mock, + forChannelWithId id: String, + server: StreamMockServer + ) { var json = server.channelList guard var channels = json[JSONKey.channels] as? [[String: Any]], @@ -57,8 +58,10 @@ public struct ChannelConfigs { server.channelList = json } - mutating func config(forChannelId id: String, - server: StreamMockServer) -> ChannelConfig_Mock? { + mutating func config( + forChannelId id: String, + server: StreamMockServer + ) -> ChannelConfig_Mock? { if let config = configs[id] { return config } let config = loadConfig(forChannelId: id, server: server) @@ -66,8 +69,10 @@ public struct ChannelConfigs { return config } - private func loadConfig(forChannelId id: String, - server: StreamMockServer) -> ChannelConfig_Mock? { + private func loadConfig( + forChannelId id: String, + server: StreamMockServer + ) -> ChannelConfig_Mock? { guard let channel = server.channel(withId: id), let innerChannel = channel[JSONKey.channel] as? [String: Any], @@ -78,7 +83,6 @@ public struct ChannelConfigs { return try? ChannelConfig_Mock(configJson) } - } public struct ChannelConfig_Mock: Codable { @@ -100,16 +104,16 @@ public struct ChannelConfig_Mock: Codable { case typingEvents = "typing_events" case readEvents = "read_events" case connectEvents = "connect_events" - case search = "search" - case reactions = "reactions" - case replies = "replies" - case quotes = "quotes" - case mutes = "mutes" - case uploads = "uploads" + case search + case reactions + case replies + case quotes + case mutes + case uploads case urlEnrichment = "url_enrichment" case customEvents = "custom_events" case pushNotifications = "push_notifications" - case reminders = "reminders" + case reminders } public func update(json: inout [String: Any]) { diff --git a/TestTools/StreamChatTestMockServer/MockServer/ChannelResponses.swift b/TestTools/StreamChatTestMockServer/MockServer/ChannelResponses.swift index 6c06f313f98..0fad3d4aa1c 100644 --- a/TestTools/StreamChatTestMockServer/MockServer/ChannelResponses.swift +++ b/TestTools/StreamChatTestMockServer/MockServer/ChannelResponses.swift @@ -10,7 +10,6 @@ public let channelPayloadKey = ChannelPayload.CodingKeys.self var autogeneratedMessagesCounter = 0 public extension StreamMockServer { - private enum ChannelRequestType { case addMembers([String]) case removeMembers([String]) @@ -100,13 +99,13 @@ public extension StreamMockServer { func waitForChannelQueryUpdate(timeout: Double = StreamMockServer.waitTimeout) { let endTime = Date().timeIntervalSince1970 * 1000 + timeout * 1000 while !channelQueryEndpointWasCalled - && endTime > Date().timeIntervalSince1970 * 1000 {} + && endTime > Date().timeIntervalSince1970 * 1000 {} } func waitForChannelsUpdate(timeout: Double = StreamMockServer.waitTimeout) { let endTime = Date().timeIntervalSince1970 * 1000 + timeout * 1000 while !channelsEndpointWasCalled - && endTime > Date().timeIntervalSince1970 * 1000 {} + && endTime > Date().timeIntervalSince1970 * 1000 {} } private func updateChannel(withId id: String) { @@ -198,6 +197,7 @@ public extension StreamMockServer { } // MARK: Channel Members + private func handleChannelRequest(_ request: HttpRequest) -> HttpResponse? { guard let type = ChannelRequestType.type(from: request.body) else { print("Unhandled request: \(request)") @@ -212,9 +212,11 @@ public extension StreamMockServer { .ok(.json([JSONKey.events: []])) } - private func updateChannelMembers(_ request: HttpRequest, - ids: [String], - eventType: EventType) -> HttpResponse { + private func updateChannelMembers( + _ request: HttpRequest, + ids: [String], + eventType: EventType + ) -> HttpResponse { guard let id = request.params[EndpointQuery.channelId] else { @@ -365,12 +367,12 @@ public extension StreamMockServer { replyCount: Int? = 0, withAttachments: Bool = false, overallMessagesCount: Int = 1 - ) -> [String : Any]? { + ) -> [String: Any]? { let timeInterval = TimeInterval(index + channelIndex * 1000 - 123_456_789) let timestamp = TestData.stringTimestamp(Date(timeIntervalSinceNow: timeInterval)) let messageText = text == nil ? String(index) : text var message = mockMessage( - TestData.toJson(.message)[JSONKey.message] as? [String : Any], + TestData.toJson(.message)[JSONKey.message] as? [String: Any], channelId: channelId, messageId: id, text: messageText, @@ -400,12 +402,12 @@ public extension StreamMockServer { type = .file file[AttachmentCodingKeys.assetURL.rawValue] = Attachments.file file[AttachmentFile.CodingKeys.mimeType.rawValue] = "application/pdf" - file[AttachmentFile.CodingKeys.size.rawValue] = 123456 + file[AttachmentFile.CodingKeys.size.rawValue] = 123_456 case overallMessagesCount - 7, overallMessagesCount - 15: type = .video file[AttachmentCodingKeys.assetURL.rawValue] = Attachments.video file[AttachmentFile.CodingKeys.mimeType.rawValue] = "video/mp4" - file[AttachmentFile.CodingKeys.size.rawValue] = 123456 + file[AttachmentFile.CodingKeys.size.rawValue] = 123_456 default: break } diff --git a/TestTools/StreamChatTestMockServer/MockServer/DeviceRemoteControl.swift b/TestTools/StreamChatTestMockServer/MockServer/DeviceRemoteControl.swift index 64e627b0b64..3665d4a9a26 100644 --- a/TestTools/StreamChatTestMockServer/MockServer/DeviceRemoteControl.swift +++ b/TestTools/StreamChatTestMockServer/MockServer/DeviceRemoteControl.swift @@ -5,7 +5,6 @@ import Foundation public extension StreamMockServer { - func pushNotification( senderName: String, text: String, diff --git a/TestTools/StreamChatTestMockServer/MockServer/EventResponses.swift b/TestTools/StreamChatTestMockServer/MockServer/EventResponses.swift index ce72bd8abd0..1106cb426a1 100644 --- a/TestTools/StreamChatTestMockServer/MockServer/EventResponses.swift +++ b/TestTools/StreamChatTestMockServer/MockServer/EventResponses.swift @@ -8,7 +8,6 @@ import XCTest public let eventKey = EventPayload.CodingKeys.self public extension StreamMockServer { - func configureEventEndpoints() { server.register(MockEndpoint.event) { [weak self] request in let channelId = try XCTUnwrap(request.params[EndpointQuery.channelId]) diff --git a/TestTools/StreamChatTestMockServer/MockServer/MembersResponse.swift b/TestTools/StreamChatTestMockServer/MockServer/MembersResponse.swift index a404d2d69bf..ef35ab704c3 100644 --- a/TestTools/StreamChatTestMockServer/MockServer/MembersResponse.swift +++ b/TestTools/StreamChatTestMockServer/MockServer/MembersResponse.swift @@ -2,9 +2,7 @@ // Copyright © 2025 Stream.io Inc. All rights reserved. // - public extension StreamMockServer { - func configureMembersEndpoints() { server.register(MockEndpoint.members) { [weak self] request in return self?.mockMembersQuery(request) diff --git a/TestTools/StreamChatTestMockServer/MockServer/MessageList.swift b/TestTools/StreamChatTestMockServer/MockServer/MessageList.swift index d6a85a2de62..b0a0df16e6c 100644 --- a/TestTools/StreamChatTestMockServer/MockServer/MessageList.swift +++ b/TestTools/StreamChatTestMockServer/MockServer/MessageList.swift @@ -6,7 +6,6 @@ import XCTest public extension StreamMockServer { - func saveMessage(_ message: [String: Any]?) { guard let newMessage = message else { return } @@ -142,20 +141,24 @@ public extension StreamMockServer { return newMessageList.first } - func waitForWebsocketMessage(withText text: String, - timeout: Double = StreamMockServer.waitTimeout) { + func waitForWebsocketMessage( + withText text: String, + timeout: Double = StreamMockServer.waitTimeout + ) { let endTime = Date().timeIntervalSince1970 * 1000 + timeout * 1000 while latestWebsocketMessage != text - && endTime > Date().timeIntervalSince1970 * 1000 { + && endTime > Date().timeIntervalSince1970 * 1000 { print("Waiting for websocket message with text: '\(text)'") } } - func waitForHttpMessage(withText text: String, - timeout: Double = StreamMockServer.waitTimeout) { + func waitForHttpMessage( + withText text: String, + timeout: Double = StreamMockServer.waitTimeout + ) { let endTime = Date().timeIntervalSince1970 * 1000 + timeout * 1000 while latestHttpMessage != text - && endTime > Date().timeIntervalSince1970 * 1000 { + && endTime > Date().timeIntervalSince1970 * 1000 { print("Waiting for http message with text: '\(text)'") } } diff --git a/TestTools/StreamChatTestMockServer/MockServer/MessageResponses.swift b/TestTools/StreamChatTestMockServer/MockServer/MessageResponses.swift index d0943d89afc..379812f1aaa 100644 --- a/TestTools/StreamChatTestMockServer/MockServer/MessageResponses.swift +++ b/TestTools/StreamChatTestMockServer/MockServer/MessageResponses.swift @@ -9,7 +9,6 @@ public let messageKey = MessagePayloadsCodingKeys.self public let paginationKey = PaginationParameter.CodingKeys.self public extension StreamMockServer { - func configureMessagingEndpoints() { server.register(MockEndpoint.message) { [weak self] request in let channelId = try XCTUnwrap(request.params[EndpointQuery.channelId]) @@ -26,15 +25,19 @@ public extension StreamMockServer { let messageId = json[AttachmentActionRequestBody.CodingKeys.messageId.rawValue] as? String let channelId = json[AttachmentActionRequestBody.CodingKeys.channelId.rawValue] as? String let formData = json[AttachmentActionRequestBody.CodingKeys.data.rawValue] as? [String: Any] - return self?.ephemeralMessageCreation(messageId: try XCTUnwrap(messageId), - channelId: try XCTUnwrap(channelId), - formData: try XCTUnwrap(formData)) + return self?.ephemeralMessageCreation( + messageId: try XCTUnwrap(messageId), + channelId: try XCTUnwrap(channelId), + formData: try XCTUnwrap(formData) + ) } } - private func trackMessage(_ text: String, - messageType: MessageType, - eventType: EventType) { + private func trackMessage( + _ text: String, + messageType: MessageType, + eventType: EventType + ) { if eventType == .messageNew && messageType != .ephemeral { latestHttpMessage = text } @@ -81,10 +84,10 @@ public extension StreamMockServer { mockedMessage?[messageKey.text.rawValue] = text mockedMessage?[messageKey.html.rawValue] = text.html - if [Links.youtube, Links.unsplash].contains(where: {text.contains($0)}) { + if [Links.youtube, Links.unsplash].contains(where: { text.contains($0) }) { let jsonWithLink = text.contains(Links.youtube) ? MockFile.youtube : MockFile.unsplash let json = TestData.toJson(jsonWithLink)[JSONKey.message] as? [String: Any] - let linkAttachments = json?[messageKey.attachments.rawValue] + let linkAttachments = json?[messageKey.attachments.rawValue] var updatedAttachments = attachments as? [[String: Any]] ?? [] updatedAttachments += linkAttachments as? [[String: Any]] ?? [] mockedMessage?[messageKey.attachments.rawValue] = updatedAttachments @@ -201,10 +204,12 @@ public extension StreamMockServer { messageType = .reply } if messageText.starts(with: "/") && messageType != .ephemeral { - return messageInvalidCommand(message, - command: String(messageText.dropFirst(1)), - channelId: channelId, - parentId: parentId) + return messageInvalidCommand( + message, + command: String(messageText.dropFirst(1)), + channelId: channelId, + parentId: parentId + ) } else if messageType != .ephemeral && !forbiddenWords.isDisjoint(with: messageTextComponents) { return errorMessageHttpResponse( from: message, @@ -407,7 +412,6 @@ public extension StreamMockServer { let attachments = message?[messageKey.attachments.rawValue] ?? responseMessage?[messageKey.attachments.rawValue] - let mockedMessage = mockMessage( responseMessage, messageType: messageType, @@ -448,7 +452,7 @@ public extension StreamMockServer { messageType: MessageType, eventType: EventType, channelReply: Bool? = false - ){ + ) { if let parentId = httpMessage?[messageKey.parentId.rawValue] as? String { let parentMessage = findMessageById(parentId) websocketMessage( diff --git a/TestTools/StreamChatTestMockServer/MockServer/MockServerAttributes.swift b/TestTools/StreamChatTestMockServer/MockServer/MockServerAttributes.swift index 6f99d191ea5..8252b7e0f8c 100644 --- a/TestTools/StreamChatTestMockServer/MockServer/MockServerAttributes.swift +++ b/TestTools/StreamChatTestMockServer/MockServer/MockServerAttributes.swift @@ -2,8 +2,8 @@ // Copyright © 2025 Stream.io Inc. All rights reserved. // -@testable import StreamChat import Foundation +@testable import StreamChat public enum Message { public static func message(withInvalidCommand command: String) -> String { @@ -26,6 +26,7 @@ public enum MockFile: String { case httpAttachment = "http_attachment" case httpTruncate = "http_truncate" + case wsMessage = "ws_message" case wsChatEvent = "ws_events" case wsChannelEvent = "ws_events_channel" case wsMemberEvent = "ws_events_member" @@ -123,7 +124,6 @@ public enum APNSKey { } public enum UserDetails { - public static var users: [[String: String]] { [ hanSolo, @@ -153,6 +153,8 @@ public enum UserDetails { userKey.imageURL.rawValue: lukeSkywalkerImageURL ] + public static let countDookuId = "count_dooku" + public static let countDookuName = "Count Dooku" public static let countDooku = [ userKey.id.rawValue: "count_dooku", userKey.name.rawValue: "Count Dooku", @@ -160,12 +162,15 @@ public enum UserDetails { ] public static let leiaOrganaId = "leia_organa" + public static let leiaOrganaName = "Leia Organa" public static let leiaOrgana = [ userKey.id.rawValue: leiaOrganaId, userKey.name.rawValue: "Leia Organa", userKey.imageURL.rawValue: "https://vignette.wikia.nocookie.net/starwars/images/f/fc/Leia_Organa_TLJ.png" ] + public static let landoCalrissianId = "lando_calrissian" + public static let landoCalrissianName = "Lando Calrissian" public static let landoCalrissian = [ userKey.id.rawValue: "lando_calrissian", userKey.name.rawValue: "Lando Calrissian", diff --git a/TestTools/StreamChatTestMockServer/MockServer/ReactionResponses.swift b/TestTools/StreamChatTestMockServer/MockServer/ReactionResponses.swift index 64ca10b2b22..a8b3029707f 100644 --- a/TestTools/StreamChatTestMockServer/MockServer/ReactionResponses.swift +++ b/TestTools/StreamChatTestMockServer/MockServer/ReactionResponses.swift @@ -8,7 +8,6 @@ import XCTest public let reactionKey = MessageReactionPayload.CodingKeys.self public extension StreamMockServer { - func configureReactionEndpoints() { server.register(MockEndpoint.reaction) { [weak self] request in let messageId = try XCTUnwrap(request.params[EndpointQuery.messageId]) diff --git a/TestTools/StreamChatTestMockServer/MockServer/StreamMockServer.swift b/TestTools/StreamChatTestMockServer/MockServer/StreamMockServer.swift index 142cea4edd0..97d426bc03c 100644 --- a/TestTools/StreamChatTestMockServer/MockServer/StreamMockServer.swift +++ b/TestTools/StreamChatTestMockServer/MockServer/StreamMockServer.swift @@ -2,12 +2,11 @@ // Copyright © 2025 Stream.io Inc. All rights reserved. // -@testable import StreamChat import Foundation +@testable import StreamChat import XCTest public final class StreamMockServer { - // Delays all HTTP responses by given time interval, 0 by default public static var httpResponseDelay: TimeInterval = 0.0 // Waits for all HTTP and Websocket responses during given time interval, 10 by default @@ -106,7 +105,6 @@ extension StreamMockServer { // MARK: Config public extension StreamMockServer { - func config(forChannelId id: String) -> ChannelConfig_Mock? { channelConfigs.config(forChannelId: id, server: self) } @@ -121,7 +119,6 @@ public extension StreamMockServer { } public extension StreamMockServer { - func setCooldown(enabled: Bool, duration: Int, inChannelWithId id: String) { channelConfigs.setCooldown(enabled: enabled, duration: duration) diff --git a/TestTools/StreamChatTestMockServer/MockServer/User.swift b/TestTools/StreamChatTestMockServer/MockServer/User.swift index 683f91bab2a..cfa431140a9 100644 --- a/TestTools/StreamChatTestMockServer/MockServer/User.swift +++ b/TestTools/StreamChatTestMockServer/MockServer/User.swift @@ -7,7 +7,6 @@ public let userKey = UserPayloadsCodingKeys.self public extension StreamMockServer { - func setUpUser( source: [String: Any]?, details: [String: String] = [:] diff --git a/TestTools/StreamChatTestMockServer/MockServer/WebsocketResponses.swift b/TestTools/StreamChatTestMockServer/MockServer/WebsocketResponses.swift index 79244a38dab..9fef7cff03a 100644 --- a/TestTools/StreamChatTestMockServer/MockServer/WebsocketResponses.swift +++ b/TestTools/StreamChatTestMockServer/MockServer/WebsocketResponses.swift @@ -2,11 +2,10 @@ // Copyright © 2025 Stream.io Inc. All rights reserved. // -@testable import StreamChat import Foundation +@testable import StreamChat public extension StreamMockServer { - /// Sends an event over a websocket connection /// /// - Parameters: @@ -62,7 +61,7 @@ public extension StreamMockServer { ) -> Self { guard let messageId = messageId else { return self } - let mockFile = messageType == .ephemeral ? MockFile.ephemeralMessage : MockFile.message + let mockFile = messageType == .ephemeral ? MockFile.ephemeralMessage : MockFile.wsMessage var json = TestData.getMockResponse(fromFile: mockFile).json var mockedMessage: [String: Any]? @@ -198,7 +197,6 @@ public extension StreamMockServer { // MARK: Channel Members public extension StreamMockServer { - /// Adds new members to channel /// /// - Parameters: diff --git a/TestTools/StreamChatTestMockServer/Robots/BackendRobot.swift b/TestTools/StreamChatTestMockServer/Robots/BackendRobot.swift index eba53f70ac9..074cebedb13 100644 --- a/TestTools/StreamChatTestMockServer/Robots/BackendRobot.swift +++ b/TestTools/StreamChatTestMockServer/Robots/BackendRobot.swift @@ -6,7 +6,6 @@ import Foundation import XCTest public class BackendRobot { - private var server: StreamMockServer public init(_ server: StreamMockServer) { @@ -50,7 +49,7 @@ public class BackendRobot { UserDetails.countDooku ], withAttachments: Bool = false - ) -> Self { + ) -> Self { var json = server.channelList guard let sampleChannel = (json[JSONKey.channels] as? [[String: Any]])?.first else { return self } diff --git a/TestTools/StreamChatTestMockServer/Robots/ParticipantRobot.swift b/TestTools/StreamChatTestMockServer/Robots/ParticipantRobot.swift index cc18329c41d..9d78a3cab71 100644 --- a/TestTools/StreamChatTestMockServer/Robots/ParticipantRobot.swift +++ b/TestTools/StreamChatTestMockServer/Robots/ParticipantRobot.swift @@ -6,10 +6,9 @@ import XCTest public class ParticipantRobot { - private var server: StreamMockServer private var threadParentId: String? - private var user: [String: String] = UserDetails.hanSolo + private var user: [String: String] = UserDetails.countDooku public init(_ server: StreamMockServer) { self.server = server @@ -66,7 +65,7 @@ public class ParticipantRobot { // Sleep in seconds @discardableResult public func wait(_ duration: TimeInterval) -> Self { - let sleepTime = UInt32(duration * 1000000) + let sleepTime = UInt32(duration * 1_000_000) usleep(sleepTime) return self } @@ -97,14 +96,16 @@ public class ParticipantRobot { } @discardableResult - public func sendMessage(_ text: String, - withPushNotification: Bool = false, - bundleIdForPushNotification: String = "", - waitForAppearance: Bool = true, - waitForChannelQuery: Bool = true, - waitBeforeSending: TimeInterval = 0, - file: StaticString = #filePath, - line: UInt = #line) -> Self { + public func sendMessage( + _ text: String, + withPushNotification: Bool = false, + bundleIdForPushNotification: String = "", + waitForAppearance: Bool = true, + waitForChannelQuery: Bool = true, + waitBeforeSending: TimeInterval = 0, + file: StaticString = #filePath, + line: UInt = #line + ) -> Self { if waitBeforeSending > 0 { wait(waitBeforeSending) } @@ -379,17 +380,19 @@ public class ParticipantRobot { } @discardableResult - public func uploadAttachment(type: AttachmentType, - count: Int = 1, - asReplyToFirstMessage: Bool = false, - asReplyToLastMessage: Bool = false, - inThread: Bool = false, - alsoInChannel: Bool = false, - waitForAppearance: Bool = true, - waitForChannelQuery: Bool = true, - waitBeforeSending: TimeInterval = 0, - file: StaticString = #filePath, - line: UInt = #line) -> Self { + public func uploadAttachment( + type: AttachmentType, + count: Int = 1, + asReplyToFirstMessage: Bool = false, + asReplyToLastMessage: Bool = false, + inThread: Bool = false, + alsoInChannel: Bool = false, + waitForAppearance: Bool = true, + waitForChannelQuery: Bool = true, + waitBeforeSending: TimeInterval = 0, + file: StaticString = #filePath, + line: UInt = #line + ) -> Self { if waitBeforeSending > 0 { wait(waitBeforeSending) } @@ -433,7 +436,7 @@ public class ParticipantRobot { } if type != .image { - file[AttachmentFile.CodingKeys.size.rawValue] = 123456 + file[AttachmentFile.CodingKeys.size.rawValue] = 123_456 } for i in 1...count { diff --git a/TestTools/StreamChatTestMockServer/StreamChatTestMockServer.swift b/TestTools/StreamChatTestMockServer/StreamChatTestMockServer.swift index a24caf8f6c6..b3bf38fb3f2 100644 --- a/TestTools/StreamChatTestMockServer/StreamChatTestMockServer.swift +++ b/TestTools/StreamChatTestMockServer/StreamChatTestMockServer.swift @@ -5,7 +5,6 @@ import Foundation extension Bundle { - private final class StreamChatTestMockServer {} static let bundleName = "StreamChat_StreamChatTestMockServer" diff --git a/TestTools/StreamChatTestMockServer/Swifter/DemoServer.swift b/TestTools/StreamChatTestMockServer/Swifter/DemoServer.swift index 495821473d0..2e811e8f9a0 100644 --- a/TestTools/StreamChatTestMockServer/Swifter/DemoServer.swift +++ b/TestTools/StreamChatTestMockServer/Swifter/DemoServer.swift @@ -1,15 +1,11 @@ // -// DemoServer.swift -// Swifter -// -// Copyright (c) 2014-2016 Damian Kołakowski. All rights reserved. +// Copyright © 2025 Stream.io Inc. All rights reserved. // import Foundation // swiftlint:disable function_body_length public func demoServer(_ publicDir: String) -> HttpServer { - print(publicDir) let server = HttpServer() @@ -125,7 +121,6 @@ public func demoServer(_ publicDir: String) -> HttpServer { } } } - } javascript { src = "http://cdn.staticfile.org/twitter-bootstrap/3.3.0/js/bootstrap.min.js" diff --git a/TestTools/StreamChatTestMockServer/Swifter/Errno.swift b/TestTools/StreamChatTestMockServer/Swifter/Errno.swift index 3657b95ee1c..c36d7414020 100644 --- a/TestTools/StreamChatTestMockServer/Swifter/Errno.swift +++ b/TestTools/StreamChatTestMockServer/Swifter/Errno.swift @@ -1,14 +1,10 @@ // -// Errno.swift -// Swifter -// -// Copyright © 2016 Damian Kołakowski. All rights reserved. +// Copyright © 2025 Stream.io Inc. All rights reserved. // import Foundation public class Errno { - public class func description() -> String { // https://forums.developer.apple.com/thread/113919 return String(cString: strerror(errno)) diff --git a/TestTools/StreamChatTestMockServer/Swifter/Files.swift b/TestTools/StreamChatTestMockServer/Swifter/Files.swift index a45e2c5b48e..e47152285b1 100644 --- a/TestTools/StreamChatTestMockServer/Swifter/Files.swift +++ b/TestTools/StreamChatTestMockServer/Swifter/Files.swift @@ -1,8 +1,5 @@ // -// HttpHandlers+Files.swift -// Swifter -// -// Copyright (c) 2014-2016 Damian Kołakowski. All rights reserved. +// Copyright © 2025 Stream.io Inc. All rights reserved. // import Foundation @@ -14,7 +11,7 @@ public func shareFile(_ path: String) -> ((HttpRequest) -> HttpResponse) { var responseHeader: [String: String] = ["Content-Type": mimeType] if let attr = try? FileManager.default.attributesOfItem(atPath: path), - let fileSize = attr[FileAttributeKey.size] as? UInt64 { + let fileSize = attr[FileAttributeKey.size] as? UInt64 { responseHeader["Content-Length"] = String(fileSize) } return .raw(200, "OK", responseHeader, { writer in @@ -48,7 +45,7 @@ public func shareFilesFromDirectory(_ directoryPath: String, defaults: [String] var responseHeader: [String: String] = ["Content-Type": mimeType] if let attr = try? FileManager.default.attributesOfItem(atPath: filePath), - let fileSize = attr[FileAttributeKey.size] as? UInt64 { + let fileSize = attr[FileAttributeKey.size] as? UInt64 { responseHeader["Content-Length"] = String(fileSize) } @@ -73,7 +70,7 @@ public func directoryBrowser(_ dir: String) -> ((HttpRequest) -> HttpResponse) { } if try filePath.directory() { var files = try filePath.files() - files.sort(by: {$0.lowercased() < $1.lowercased()}) + files.sort(by: { $0.lowercased() < $1.lowercased() }) return scopes { html { body { @@ -89,7 +86,7 @@ public func directoryBrowser(_ dir: String) -> ((HttpRequest) -> HttpResponse) { } } } - }(request) + }(request) } else { guard let file = try? filePath.openForReading() else { return .notFound() diff --git a/TestTools/StreamChatTestMockServer/Swifter/HttpParser.swift b/TestTools/StreamChatTestMockServer/Swifter/HttpParser.swift index 5c1abfa9e24..31a5d0fa83a 100644 --- a/TestTools/StreamChatTestMockServer/Swifter/HttpParser.swift +++ b/TestTools/StreamChatTestMockServer/Swifter/HttpParser.swift @@ -1,8 +1,5 @@ // -// HttpParser.swift -// Swifter -// -// Copyright (c) 2014-2016 Damian Kołakowski. All rights reserved. +// Copyright © 2025 Stream.io Inc. All rights reserved. // import Foundation @@ -13,8 +10,7 @@ enum HttpParserError: Error, Equatable { } public class HttpParser { - - public init() { } + public init() {} public func readHttpRequest(_ socket: Socket) throws -> HttpRequest { let statusLine = try socket.readLine() @@ -38,7 +34,7 @@ public class HttpParser { request.body = try readBody(socket, size: contentLengthValue) } return request - } + } private func readBody(_ socket: Socket, size: Int) throws -> [UInt8] { return try socket.read(length: size) @@ -57,7 +53,7 @@ public class HttpParser { func supportsKeepAlive(_ headers: [String: String]) -> Bool { if let value = headers["connection"] { - return "keep-alive" == value.trimmingCharacters(in: .whitespaces) + return value.trimmingCharacters(in: .whitespaces) == "keep-alive" } return false } diff --git a/TestTools/StreamChatTestMockServer/Swifter/HttpRequest.swift b/TestTools/StreamChatTestMockServer/Swifter/HttpRequest.swift index 00d0962f30b..be11678cd8a 100644 --- a/TestTools/StreamChatTestMockServer/Swifter/HttpRequest.swift +++ b/TestTools/StreamChatTestMockServer/Swifter/HttpRequest.swift @@ -1,14 +1,10 @@ // -// HttpRequest.swift -// Swifter -// -// Copyright (c) 2014-2016 Damian Kołakowski. All rights reserved. +// Copyright © 2025 Stream.io Inc. All rights reserved. // import Foundation public class HttpRequest { - public var path: String = "" public var queryParams: [(String, String)] = [] public var method: String = "" @@ -23,7 +19,7 @@ public class HttpRequest { guard let headerValue = headers[headerName] else { return false } - return headerValue.components(separatedBy: ",").filter({ $0.trimmingCharacters(in: .whitespaces).lowercased() == token }).count > 0 + return !headerValue.components(separatedBy: ",").filter({ $0.trimmingCharacters(in: .whitespaces).lowercased() == token }).isEmpty } public func parseUrlencodedForm() -> [(String, String)] { @@ -41,15 +37,16 @@ public class HttpRequest { return utf8String.components(separatedBy: "&").map { param -> (String, String) in let tokens = param.components(separatedBy: "=") if let name = tokens.first?.removingPercentEncoding, let value = tokens.last?.removingPercentEncoding, tokens.count == 2 { - return (name.replacingOccurrences(of: "+", with: " "), - value.replacingOccurrences(of: "+", with: " ")) + return ( + name.replacingOccurrences(of: "+", with: " "), + value.replacingOccurrences(of: "+", with: " ") + ) } return ("", "") } } public struct MultiPart { - public let headers: [String: String] public let body: [UInt8] @@ -74,7 +71,7 @@ public class HttpRequest { } return results }) - }.first + }.first } } @@ -93,7 +90,7 @@ public class HttpRequest { boundary = tokens.last } }) - if let boundary = boundary, boundary.utf8.count > 0 { + if let boundary = boundary, !boundary.utf8.isEmpty { return parseMultiPartFormData(body, boundary: "--\(boundary)") } return [] @@ -114,7 +111,7 @@ public class HttpRequest { return nil } } else { - let /* ignore */ _ = nextUTF8MultiPartLine(&generator) + /* ignore */ _ = nextUTF8MultiPartLine(&generator) } var headers = [String: String]() while let line = nextUTF8MultiPartLine(&generator), !line.isEmpty { @@ -151,13 +148,13 @@ public class HttpRequest { let boundaryArray = [UInt8](boundary.utf8) var matchOffset = 0 while let x = generator.next() { - matchOffset = ( x == boundaryArray[matchOffset] ? matchOffset + 1 : 0 ) + matchOffset = (x == boundaryArray[matchOffset] ? matchOffset + 1 : 0) body.append(x) if matchOffset == boundaryArray.count { #if swift(>=4.2) - body.removeSubrange(body.count-matchOffset ..< body.count) + body.removeSubrange(body.count - matchOffset..(body.count-matchOffset ..< body.count)) + body.removeSubrange(CountableRange(body.count - matchOffset.. Void) case ok(HttpResponseBody, [String: String] = [:]), created, accepted case movedPermanently(String) case movedTemporarily(String) case badRequest(HttpResponseBody?), unauthorized(HttpResponseBody?), forbidden(HttpResponseBody?), notFound(HttpResponseBody? = nil), notAcceptable(HttpResponseBody?), tooManyRequests(HttpResponseBody?), internalServerError(HttpResponseBody?) - case raw(Int, String, [String: String]?, ((HttpResponseBodyWriter) throws -> Void)? ) + case raw(Int, String, [String: String]?, ((HttpResponseBodyWriter) throws -> Void)?) public var statusCode: Int { switch self { - case .switchProtocols : return 101 - case .ok : return 200 - case .created : return 201 - case .accepted : return 202 - case .movedPermanently : return 301 - case .movedTemporarily : return 307 - case .badRequest : return 400 - case .unauthorized : return 401 - case .forbidden : return 403 - case .notFound : return 404 - case .notAcceptable : return 406 - case .tooManyRequests : return 429 - case .internalServerError : return 500 - case .raw(let code, _, _, _) : return code + case .switchProtocols: return 101 + case .ok: return 200 + case .created: return 201 + case .accepted: return 202 + case .movedPermanently: return 301 + case .movedTemporarily: return 307 + case .badRequest: return 400 + case .unauthorized: return 401 + case .forbidden: return 403 + case .notFound: return 404 + case .notAcceptable: return 406 + case .tooManyRequests: return 429 + case .internalServerError: return 500 + case .raw(let code, _, _, _): return code } } public var reasonPhrase: String { switch self { - case .switchProtocols : return "Switching Protocols" - case .ok : return "OK" - case .created : return "Created" - case .accepted : return "Accepted" - case .movedPermanently : return "Moved Permanently" - case .movedTemporarily : return "Moved Temporarily" - case .badRequest : return "Bad Request" - case .unauthorized : return "Unauthorized" - case .forbidden : return "Forbidden" - case .notFound : return "Not Found" - case .notAcceptable : return "Not Acceptable" - case .tooManyRequests : return "Too Many Requests" - case .internalServerError : return "Internal Server Error" - case .raw(_, let phrase, _, _) : return phrase + case .switchProtocols: return "Switching Protocols" + case .ok: return "OK" + case .created: return "Created" + case .accepted: return "Accepted" + case .movedPermanently: return "Moved Permanently" + case .movedTemporarily: return "Moved Temporarily" + case .badRequest: return "Bad Request" + case .unauthorized: return "Unauthorized" + case .forbidden: return "Forbidden" + case .notFound: return "Not Found" + case .notAcceptable: return "Not Acceptable" + case .tooManyRequests: return "Too Many Requests" + case .internalServerError: return "Internal Server Error" + case .raw(_, let phrase, _, _): return phrase } } @@ -140,7 +135,7 @@ public enum HttpResponse { case .html, .htmlBody: headers["Content-Type"] = "text/html" case .text: headers["Content-Type"] = "text/plain" case .data(_, let contentType): headers["Content-Type"] = contentType - default:break + default: break } case .movedPermanently(let location): headers["Location"] = location @@ -152,38 +147,38 @@ public enum HttpResponse { headers.updateValue(value, forKey: key) } } - default:break + default: break } return headers } func content() -> (length: Int, write: ((HttpResponseBodyWriter) throws -> Void)?) { switch self { - case .ok(let body, _) : return body.content() - case .badRequest(let body), .unauthorized(let body), .forbidden(let body), .notFound(let body), .tooManyRequests(let body), .internalServerError(let body) : return body?.content() ?? (-1, nil) - case .raw(_, _, _, let writer) : return (-1, writer) - default : return (-1, nil) + case .ok(let body, _): return body.content() + case .badRequest(let body), .unauthorized(let body), .forbidden(let body), .notFound(let body), .tooManyRequests(let body), .internalServerError(let body): return body?.content() ?? (-1, nil) + case .raw(_, _, _, let writer): return (-1, writer) + default: return (-1, nil) } } func socketSession() -> ((Socket) -> Void)? { switch self { - case .switchProtocols(_, let handler) : return handler + case .switchProtocols(_, let handler): return handler default: return nil } } } /** - Makes it possible to compare handler responses with '==', but - ignores any associated values. This should generally be what - you want. E.g.: - - let resp = handler(updatedRequest) - if resp == .NotFound { - print("Client requested not found: \(request.url)") - } -*/ + Makes it possible to compare handler responses with '==', but + ignores any associated values. This should generally be what + you want. E.g.: + + let resp = handler(updatedRequest) + if resp == .NotFound { + print("Client requested not found: \(request.url)") + } + */ func == (inLeft: HttpResponse, inRight: HttpResponse) -> Bool { return inLeft.statusCode == inRight.statusCode diff --git a/TestTools/StreamChatTestMockServer/Swifter/HttpRouter.swift b/TestTools/StreamChatTestMockServer/Swifter/HttpRouter.swift index 1429627e47a..c487e8a2f88 100644 --- a/TestTools/StreamChatTestMockServer/Swifter/HttpRouter.swift +++ b/TestTools/StreamChatTestMockServer/Swifter/HttpRouter.swift @@ -1,18 +1,13 @@ // -// HttpRouter.swift -// Swifter -// -// Copyright (c) 2014-2016 Damian Kołakowski. All rights reserved. +// Copyright © 2025 Stream.io Inc. All rights reserved. // import Foundation open class HttpRouter { - public init() {} private class Node { - /// The children nodes that form the route var nodes = [String: Node]() @@ -59,7 +54,6 @@ open class HttpRouter { } public func route(_ method: String?, path: String) -> ([String: String], (HttpRequest) -> HttpResponse)? { - return queue.sync { if let method = method { let pathSegments = (method + "/" + stripQuery(path)).split("/") @@ -82,7 +76,6 @@ open class HttpRouter { } private func inflate(_ node: inout Node, generator: inout IndexingIterator<[String]>) -> Node { - var currentNode = node while let pathSegment = generator.next() { @@ -99,7 +92,6 @@ open class HttpRouter { } private func findHandler(_ node: inout Node, params: inout [String: String], generator: inout IndexingIterator<[String]>) -> ((HttpRequest) -> HttpResponse)? { - var matchedRoutes = [Node]() let pattern = generator.map { $0 } let numberOfElements = pattern.count @@ -119,9 +111,7 @@ open class HttpRouter { /// - index: The index of current position in the generator /// - count: The number of elements if the route to match private func findHandler(_ node: inout Node, params: inout [String: String], pattern: [String], matchedNodes: inout [Node], index: Int, count: Int) { - if index < count, let pathToken = pattern[index].removingPercentEncoding { - var currentIndex = index + 1 let variableNodes = node.nodes.filter { $0.0.first == ":" } if let variableNode = variableNodes.first { @@ -129,7 +119,7 @@ open class HttpRouter { // if it's the last element of the pattern and it's a variable, stop the search and // append a tail as a value for the variable. let tail = pattern[currentIndex.. 0 { + if !tail.isEmpty { params[variableNode.0] = pathToken + "/" + tail } else { params[variableNode.0] = pathToken @@ -184,9 +174,7 @@ open class HttpRouter { } extension String { - func split(_ separator: Character) -> [String] { return self.split { $0 == separator }.map(String.init) } - } diff --git a/TestTools/StreamChatTestMockServer/Swifter/HttpServer.swift b/TestTools/StreamChatTestMockServer/Swifter/HttpServer.swift index b8d56790a9d..4fe625b1ae2 100644 --- a/TestTools/StreamChatTestMockServer/Swifter/HttpServer.swift +++ b/TestTools/StreamChatTestMockServer/Swifter/HttpServer.swift @@ -1,16 +1,11 @@ // -// HttpServer.swift -// Swifter -// -// Copyright (c) 2014-2016 Damian Kołakowski. All rights reserved. +// Copyright © 2025 Stream.io Inc. All rights reserved. // import Foundation open class HttpServer: HttpServerIO { - public static let VERSION: String = { - #if os(Linux) return "1.5.0" #else @@ -22,20 +17,20 @@ open class HttpServer: HttpServerIO { private let router = HttpRouter() - public override init() { + override public init() { self.DELETE = MethodRoute(method: "DELETE", router: router) - self.PATCH = MethodRoute(method: "PATCH", router: router) - self.HEAD = MethodRoute(method: "HEAD", router: router) - self.POST = MethodRoute(method: "POST", router: router) - self.GET = MethodRoute(method: "GET", router: router) - self.PUT = MethodRoute(method: "PUT", router: router) + self.PATCH = MethodRoute(method: "PATCH", router: router) + self.HEAD = MethodRoute(method: "HEAD", router: router) + self.POST = MethodRoute(method: "POST", router: router) + self.GET = MethodRoute(method: "GET", router: router) + self.PUT = MethodRoute(method: "PUT", router: router) self.delete = MethodRoute(method: "DELETE", router: router) - self.patch = MethodRoute(method: "PATCH", router: router) - self.head = MethodRoute(method: "HEAD", router: router) - self.post = MethodRoute(method: "POST", router: router) - self.get = MethodRoute(method: "GET", router: router) - self.put = MethodRoute(method: "PUT", router: router) + self.patch = MethodRoute(method: "PATCH", router: router) + self.head = MethodRoute(method: "HEAD", router: router) + self.post = MethodRoute(method: "POST", router: router) + self.get = MethodRoute(method: "GET", router: router) + self.put = MethodRoute(method: "PUT", router: router) } public var DELETE, PATCH, HEAD, POST, GET, PUT: MethodRoute diff --git a/TestTools/StreamChatTestMockServer/Swifter/HttpServerIO.swift b/TestTools/StreamChatTestMockServer/Swifter/HttpServerIO.swift index 65c6e95a3e1..3611bb713fd 100644 --- a/TestTools/StreamChatTestMockServer/Swifter/HttpServerIO.swift +++ b/TestTools/StreamChatTestMockServer/Swifter/HttpServerIO.swift @@ -1,19 +1,15 @@ // -// HttpServer.swift -// Swifter -// -// Copyright (c) 2014-2016 Damian Kołakowski. All rights reserved. +// Copyright © 2025 Stream.io Inc. All rights reserved. // -import Foundation import Dispatch +import Foundation public protocol HttpServerIODelegate: AnyObject { func socketConnectionReceived(_ socket: Socket) } open class HttpServerIO { - public weak var delegate: HttpServerIODelegate? private var socket = Socket(socketFileDescriptor: -1) @@ -142,7 +138,6 @@ open class HttpServerIO { } private struct InnerWriteContext: HttpResponseBodyWriter { - let socket: Socket func write(_ file: String.File) throws { diff --git a/TestTools/StreamChatTestMockServer/Swifter/MimeTypes.swift b/TestTools/StreamChatTestMockServer/Swifter/MimeTypes.swift index 204a262c792..1eb217b1ec2 100644 --- a/TestTools/StreamChatTestMockServer/Swifter/MimeTypes.swift +++ b/TestTools/StreamChatTestMockServer/Swifter/MimeTypes.swift @@ -1,8 +1,5 @@ // -// MimeTypes.swift -// Swifter -// -// Created by Daniel Große on 16.02.18. +// Copyright © 2025 Stream.io Inc. All rights reserved. // import Foundation diff --git a/TestTools/StreamChatTestMockServer/Swifter/Process.swift b/TestTools/StreamChatTestMockServer/Swifter/Process.swift index f6c214a83af..fdbae80f263 100644 --- a/TestTools/StreamChatTestMockServer/Swifter/Process.swift +++ b/TestTools/StreamChatTestMockServer/Swifter/Process.swift @@ -1,25 +1,21 @@ // -// Process -// Swifter -// -// Copyright (c) 2014-2016 Damian Kołakowski. All rights reserved. +// Copyright © 2025 Stream.io Inc. All rights reserved. // import Foundation public class Process { - public static var pid: Int { return Int(getpid()) } public static var tid: UInt64 { #if os(Linux) - return UInt64(pthread_self()) + return UInt64(pthread_self()) #else - var tid: __uint64_t = 0 - pthread_threadid_np(nil, &tid) - return UInt64(tid) + var tid: __uint64_t = 0 + pthread_threadid_np(nil, &tid) + return UInt64(tid) #endif } diff --git a/TestTools/StreamChatTestMockServer/Swifter/Scopes.swift b/TestTools/StreamChatTestMockServer/Swifter/Scopes.swift index 731920a7aeb..882d79ba9ae 100644 --- a/TestTools/StreamChatTestMockServer/Swifter/Scopes.swift +++ b/TestTools/StreamChatTestMockServer/Swifter/Scopes.swift @@ -1,8 +1,5 @@ // -// HttpHandlers+Scopes.swift -// Swifter -// -// Copyright © 2014-2016 Damian Kołakowski. All rights reserved. +// Copyright © 2025 Stream.io Inc. All rights reserved. // // swiftlint:disable file_length @@ -13,7 +10,7 @@ public func scopes(_ scope: @escaping Closure) -> ((HttpRequest) -> HttpResponse scopesBuffer[Process.tid] = "" scope() return .raw(200, "OK", ["Content-Type": "text/html"], { - try? $0.write([UInt8](("" + (scopesBuffer[Process.tid] ?? "")).utf8)) + try? $0.write([UInt8](("" + (scopesBuffer[Process.tid] ?? "")).utf8)) }) } } @@ -337,7 +334,6 @@ var scopesBuffer = [UInt64: String]() // swiftlint:disable cyclomatic_complexity function_body_length private func evaluate(_ node: String, _ attrs: [String: String?] = [:], _ closure: Closure) { - // Push the attributes. let stackid = idd @@ -752,7 +748,7 @@ private func evaluate(_ node: String, _ attrs: [String: String?] = [:], _ closur if let inner = inner { scopesBuffer[Process.tid] = output + ">" + (inner) + "" } else { - let current = scopesBuffer[Process.tid] ?? "" + let current = scopesBuffer[Process.tid] ?? "" scopesBuffer[Process.tid] = output + ">" + current + "" } diff --git a/TestTools/StreamChatTestMockServer/Swifter/Socket+File.swift b/TestTools/StreamChatTestMockServer/Swifter/Socket+File.swift index e0a9e82ad24..928f4db289f 100644 --- a/TestTools/StreamChatTestMockServer/Swifter/Socket+File.swift +++ b/TestTools/StreamChatTestMockServer/Swifter/Socket+File.swift @@ -1,50 +1,46 @@ // -// Socket+File.swift -// Swifter -// -// Created by Damian Kolakowski on 13/07/16. +// Copyright © 2025 Stream.io Inc. All rights reserved. // import Foundation -#if os(iOS) || os(tvOS) || os (Linux) +#if os(iOS) || os(tvOS) || os(Linux) // swiftlint:disable type_name function_parameter_count - struct sf_hdtr { } +struct sf_hdtr {} - private func sendfileImpl(_ source: UnsafeMutablePointer, _ target: Int32, _: off_t, _: UnsafeMutablePointer, _: UnsafeMutablePointer, _: Int32) -> Int32 { - var buffer = [UInt8](repeating: 0, count: 1024) - while true { - let readResult = fread(&buffer, 1, buffer.count, source) - guard readResult > 0 else { - return Int32(readResult) +private func sendfileImpl(_ source: UnsafeMutablePointer, _ target: Int32, _: off_t, _: UnsafeMutablePointer, _: UnsafeMutablePointer, _: Int32) -> Int32 { + var buffer = [UInt8](repeating: 0, count: 1024) + while true { + let readResult = fread(&buffer, 1, buffer.count, source) + guard readResult > 0 else { + return Int32(readResult) + } + var writeCounter = 0 + while writeCounter < readResult { + let writeResult = buffer.withUnsafeBytes { (ptr) -> Int in + let start = ptr.baseAddress! + writeCounter + let len = readResult - writeCounter + #if os(Linux) + return send(target, start, len, Int32(MSG_NOSIGNAL)) + #else + return write(target, start, len) + #endif } - var writeCounter = 0 - while writeCounter < readResult { - let writeResult = buffer.withUnsafeBytes { (ptr) -> Int in - let start = ptr.baseAddress! + writeCounter - let len = readResult - writeCounter - #if os(Linux) - return send(target, start, len, Int32(MSG_NOSIGNAL)) - #else - return write(target, start, len) - #endif - } - guard writeResult > 0 else { - return Int32(writeResult) - } - writeCounter += writeResult + guard writeResult > 0 else { + return Int32(writeResult) } + writeCounter += writeResult } } +} #endif extension Socket { - public func writeFile(_ file: String.File) throws { var offset: off_t = 0 var sf: sf_hdtr = sf_hdtr() - #if os(iOS) || os(tvOS) || os (Linux) + #if os(iOS) || os(tvOS) || os(Linux) let result = sendfileImpl(file.pointer, self.socketFileDescriptor, 0, &offset, &sf, 0) #else let result = sendfile(fileno(file.pointer), self.socketFileDescriptor, 0, &offset, &sf, 0) diff --git a/TestTools/StreamChatTestMockServer/Swifter/Socket+Server.swift b/TestTools/StreamChatTestMockServer/Swifter/Socket+Server.swift index 11076a1e8b7..7dbeadf8c3f 100644 --- a/TestTools/StreamChatTestMockServer/Swifter/Socket+Server.swift +++ b/TestTools/StreamChatTestMockServer/Swifter/Socket+Server.swift @@ -1,25 +1,20 @@ // -// Socket+Server.swift -// Swifter -// -// Created by Damian Kolakowski on 13/07/16. +// Copyright © 2025 Stream.io Inc. All rights reserved. // import Foundation extension Socket { - // swiftlint:disable function_body_length /// - Parameters: /// - listenAddress: String representation of the address the socket should accept /// connections from. It should be in IPv4 format if forceIPv4 == true, /// otherwise - in IPv6. public class func tcpSocketForListen(_ port: in_port_t, _ forceIPv4: Bool = false, _ maxPendingConnection: Int32 = SOMAXCONN, _ listenAddress: String? = nil) throws -> Socket { - #if os(Linux) - let socketFileDescriptor = socket(forceIPv4 ? AF_INET : AF_INET6, Int32(SOCK_STREAM.rawValue), 0) + let socketFileDescriptor = socket(forceIPv4 ? AF_INET : AF_INET6, Int32(SOCK_STREAM.rawValue), 0) #else - let socketFileDescriptor = socket(forceIPv4 ? AF_INET : AF_INET6, SOCK_STREAM, 0) + let socketFileDescriptor = socket(forceIPv4 ? AF_INET : AF_INET6, SOCK_STREAM, 0) #endif if socketFileDescriptor == -1 { @@ -41,21 +36,23 @@ extension Socket { sin_family: sa_family_t(AF_INET), sin_port: port.bigEndian, sin_addr: in_addr(s_addr: in_addr_t(0)), - sin_zero: (0, 0, 0, 0, 0, 0, 0, 0)) + sin_zero: (0, 0, 0, 0, 0, 0, 0, 0) + ) #else var addr = sockaddr_in( sin_len: UInt8(MemoryLayout.stride), sin_family: UInt8(AF_INET), sin_port: port.bigEndian, sin_addr: in_addr(s_addr: in_addr_t(0)), - sin_zero: (0, 0, 0, 0, 0, 0, 0, 0)) + sin_zero: (0, 0, 0, 0, 0, 0, 0, 0) + ) #endif if let address = listenAddress { - if address.withCString({ cstring in inet_pton(AF_INET, cstring, &addr.sin_addr) }) == 1 { - // print("\(address) is converted to \(addr.sin_addr).") - } else { - // print("\(address) is not converted.") - } + if address.withCString({ cstring in inet_pton(AF_INET, cstring, &addr.sin_addr) }) == 1 { + // print("\(address) is converted to \(addr.sin_addr).") + } else { + // print("\(address) is not converted.") + } } bindResult = withUnsafePointer(to: &addr) { bind(socketFileDescriptor, UnsafePointer(OpaquePointer($0)), socklen_t(MemoryLayout.size)) @@ -67,7 +64,8 @@ extension Socket { sin6_port: port.bigEndian, sin6_flowinfo: 0, sin6_addr: in6addr_any, - sin6_scope_id: 0) + sin6_scope_id: 0 + ) #else var addr = sockaddr_in6( sin6_len: UInt8(MemoryLayout.stride), @@ -75,14 +73,15 @@ extension Socket { sin6_port: port.bigEndian, sin6_flowinfo: 0, sin6_addr: in6addr_any, - sin6_scope_id: 0) + sin6_scope_id: 0 + ) #endif if let address = listenAddress { - if address.withCString({ cstring in inet_pton(AF_INET6, cstring, &addr.sin6_addr) }) == 1 { - //print("\(address) is converted to \(addr.sin6_addr).") - } else { - //print("\(address) is not converted.") - } + if address.withCString({ cstring in inet_pton(AF_INET6, cstring, &addr.sin6_addr) }) == 1 { + // print("\(address) is converted to \(addr.sin6_addr).") + } else { + // print("\(address) is not converted.") + } } bindResult = withUnsafePointer(to: &addr) { bind(socketFileDescriptor, UnsafePointer(OpaquePointer($0)), socklen_t(MemoryLayout.size)) diff --git a/TestTools/StreamChatTestMockServer/Swifter/Socket.swift b/TestTools/StreamChatTestMockServer/Swifter/Socket.swift index 1a9887e9f6d..4daeda24ecf 100644 --- a/TestTools/StreamChatTestMockServer/Swifter/Socket.swift +++ b/TestTools/StreamChatTestMockServer/Swifter/Socket.swift @@ -1,8 +1,5 @@ // -// Socket.swift -// Swifter -// -// Copyright (c) 2014-2016 Damian Kołakowski. All rights reserved. +// Copyright © 2025 Stream.io Inc. All rights reserved. // import Foundation @@ -23,7 +20,6 @@ public enum SocketError: Error { // swiftlint: disable identifier_name open class Socket: Hashable, Equatable { - let socketFileDescriptor: Int32 private var shutdown = false @@ -56,9 +52,9 @@ open class Socket: Hashable, Equatable { } let sin_port = pointer.pointee.sin_port #if os(Linux) - return ntohs(sin_port) + return ntohs(sin_port) #else - return Int(OSHostByteOrder()) != OSLittleEndian ? sin_port.littleEndian : sin_port.bigEndian + return Int(OSHostByteOrder()) != OSLittleEndian ? sin_port.littleEndian : sin_port.bigEndian #endif } } @@ -94,14 +90,14 @@ open class Socket: Hashable, Equatable { public func writeData(_ data: Data) throws { #if compiler(>=5.0) - try data.withUnsafeBytes { (body: UnsafeRawBufferPointer) -> Void in - if let baseAddress = body.baseAddress, body.count > 0 { + try data.withUnsafeBytes { (body: UnsafeRawBufferPointer) in + if let baseAddress = body.baseAddress, !body.isEmpty { let pointer = baseAddress.assumingMemoryBound(to: UInt8.self) try self.writeBuffer(pointer, length: data.count) } } #else - try data.withUnsafeBytes { (pointer: UnsafePointer) -> Void in + try data.withUnsafeBytes { (pointer: UnsafePointer) in try self.writeBuffer(pointer, length: data.count) } #endif @@ -111,9 +107,9 @@ open class Socket: Hashable, Equatable { var sent = 0 while sent < length { #if os(Linux) - let result = send(self.socketFileDescriptor, pointer + sent, Int(length - sent), Int32(MSG_NOSIGNAL)) + let result = send(self.socketFileDescriptor, pointer + sent, Int(length - sent), Int32(MSG_NOSIGNAL)) #else - let result = write(self.socketFileDescriptor, pointer + sent, Int(length - sent)) + let result = write(self.socketFileDescriptor, pointer + sent, Int(length - sent)) #endif if result <= 0 { throw SocketError.writeFailed(Errno.description()) @@ -132,10 +128,10 @@ open class Socket: Hashable, Equatable { var byte: UInt8 = 0 #if os(Linux) - let count = Glibc.read(self.socketFileDescriptor as Int32, &byte, 1) - #else - let count = Darwin.read(self.socketFileDescriptor as Int32, &byte, 1) - #endif + let count = Glibc.read(self.socketFileDescriptor as Int32, &byte, 1) + #else + let count = Darwin.read(self.socketFileDescriptor as Int32, &byte, 1) + #endif guard count > 0 else { throw SocketError.recvFailed(Errno.description()) @@ -172,9 +168,9 @@ open class Socket: Hashable, Equatable { #if os(Linux) let bytesRead = Glibc.read(self.socketFileDescriptor as Int32, baseAddress + offset, readLength) - #else - let bytesRead = Darwin.read(self.socketFileDescriptor as Int32, baseAddress + offset, readLength) - #endif + #else + let bytesRead = Darwin.read(self.socketFileDescriptor as Int32, baseAddress + offset, readLength) + #endif guard bytesRead > 0 else { throw SocketError.recvFailed(Errno.description()) @@ -213,20 +209,20 @@ open class Socket: Hashable, Equatable { public class func setNoSigPipe(_ socket: Int32) { #if os(Linux) - // There is no SO_NOSIGPIPE in Linux (nor some other systems). You can instead use the MSG_NOSIGNAL flag when calling send(), - // or use signal(SIGPIPE, SIG_IGN) to make your entire application ignore SIGPIPE. + // There is no SO_NOSIGPIPE in Linux (nor some other systems). You can instead use the MSG_NOSIGNAL flag when calling send(), + // or use signal(SIGPIPE, SIG_IGN) to make your entire application ignore SIGPIPE. #else - // Prevents crashes when blocking calls are pending and the app is paused ( via Home button ). - var no_sig_pipe: Int32 = 1 - setsockopt(socket, SOL_SOCKET, SO_NOSIGPIPE, &no_sig_pipe, socklen_t(MemoryLayout.size)) + // Prevents crashes when blocking calls are pending and the app is paused ( via Home button ). + var no_sig_pipe: Int32 = 1 + setsockopt(socket, SOL_SOCKET, SO_NOSIGPIPE, &no_sig_pipe, socklen_t(MemoryLayout.size)) #endif } public class func close(_ socket: Int32) { #if os(Linux) - _ = Glibc.close(socket) + _ = Glibc.close(socket) #else - _ = Darwin.close(socket) + _ = Darwin.close(socket) #endif } } diff --git a/TestTools/StreamChatTestMockServer/Swifter/String+BASE64.swift b/TestTools/StreamChatTestMockServer/Swifter/String+BASE64.swift index 15988452215..f5d52fff1ba 100644 --- a/TestTools/StreamChatTestMockServer/Swifter/String+BASE64.swift +++ b/TestTools/StreamChatTestMockServer/Swifter/String+BASE64.swift @@ -1,14 +1,10 @@ // -// String+BASE64.swift -// Swifter -// -// Copyright © 2016 Damian Kołakowski. All rights reserved. +// Copyright © 2025 Stream.io Inc. All rights reserved. // import Foundation extension String { - public static func toBase64(_ data: [UInt8]) -> String { return Data(data).base64EncodedString() } diff --git a/TestTools/StreamChatTestMockServer/Swifter/String+File.swift b/TestTools/StreamChatTestMockServer/Swifter/String+File.swift index 15fb7fb7458..62a4f14d9e9 100644 --- a/TestTools/StreamChatTestMockServer/Swifter/String+File.swift +++ b/TestTools/StreamChatTestMockServer/Swifter/String+File.swift @@ -1,20 +1,15 @@ // -// String+File.swift -// Swifter -// -// Copyright © 2016 Damian Kołakowski. All rights reserved. +// Copyright © 2025 Stream.io Inc. All rights reserved. // import Foundation extension String { - public enum FileError: Error { case error(Int32) } public class File { - let pointer: UnsafeMutablePointer public init(_ pointer: UnsafeMutablePointer) { @@ -114,15 +109,15 @@ extension String { var name = ent.pointee.d_name let fileName = withUnsafePointer(to: &name) { (ptr) -> String? in #if os(Linux) - return String(validatingUTF8: ptr.withMemoryRebound(to: CChar.self, capacity: Int(ent.pointee.d_reclen), { (ptrc) -> [CChar] in + return String(validatingUTF8: ptr.withMemoryRebound(to: CChar.self, capacity: Int(ent.pointee.d_reclen), { (ptrc) -> [CChar] in return [CChar](UnsafeBufferPointer(start: ptrc, count: 256)) - })) + })) #else - var buffer = ptr.withMemoryRebound(to: CChar.self, capacity: Int(ent.pointee.d_reclen), { (ptrc) -> [CChar] in - return [CChar](UnsafeBufferPointer(start: ptrc, count: Int(ent.pointee.d_namlen))) - }) - buffer.append(0) - return String(validatingUTF8: buffer) + var buffer = ptr.withMemoryRebound(to: CChar.self, capacity: Int(ent.pointee.d_reclen), { (ptrc) -> [CChar] in + return [CChar](UnsafeBufferPointer(start: ptrc, count: Int(ent.pointee.d_namlen))) + }) + buffer.append(0) + return String(validatingUTF8: buffer) #endif } if let fileName = fileName { diff --git a/TestTools/StreamChatTestMockServer/Swifter/String+Misc.swift b/TestTools/StreamChatTestMockServer/Swifter/String+Misc.swift index 592683c786f..8ce505db8b9 100644 --- a/TestTools/StreamChatTestMockServer/Swifter/String+Misc.swift +++ b/TestTools/StreamChatTestMockServer/Swifter/String+Misc.swift @@ -1,14 +1,10 @@ // -// String+Misc.swift -// Swifter -// -// Copyright (c) 2014-2016 Damian Kołakowski. All rights reserved. +// Copyright © 2025 Stream.io Inc. All rights reserved. // import Foundation extension String { - public func unquote() -> String { var scalars = self.unicodeScalars if scalars.first == "\"" && scalars.last == "\"" && scalars.count >= 2 { @@ -21,7 +17,6 @@ extension String { } extension UnicodeScalar { - public func asWhitespace() -> UInt8? { if self.value >= 9 && self.value <= 13 { return UInt8(self.value) diff --git a/TestTools/StreamChatTestMockServer/Swifter/String+SHA1.swift b/TestTools/StreamChatTestMockServer/Swifter/String+SHA1.swift index b7400bb2f86..870dbdd0583 100644 --- a/TestTools/StreamChatTestMockServer/Swifter/String+SHA1.swift +++ b/TestTools/StreamChatTestMockServer/Swifter/String+SHA1.swift @@ -1,26 +1,21 @@ // -// String+SHA1.swift -// Swifter -// -// Copyright 2014-2016 Damian Kołakowski. All rights reserved. +// Copyright © 2025 Stream.io Inc. All rights reserved. // import Foundation // swiftlint:disable identifier_name function_body_length -public struct SHA1 { - +public enum SHA1 { public static func hash(_ input: [UInt8]) -> [UInt8] { - // Alghorithm from: https://en.wikipedia.org/wiki/SHA-1 var message = input var h0 = UInt32(littleEndian: 0x67452301) - var h1 = UInt32(littleEndian: 0xEFCDAB89) - var h2 = UInt32(littleEndian: 0x98BADCFE) + var h1 = UInt32(littleEndian: 0xefcdab89) + var h2 = UInt32(littleEndian: 0x98badcfe) var h3 = UInt32(littleEndian: 0x10325476) - var h4 = UInt32(littleEndian: 0xC3D2E1F0) + var h4 = UInt32(littleEndian: 0xc3d2e1f0) // ml = message length in bits (always a multiple of the number of bits in a character). @@ -32,7 +27,7 @@ public struct SHA1 { // append 0 ≤ k < 512 bits '0', such that the resulting message length in bits is congruent to −64 ≡ 448 (mod 512) - let padBytesCount = ( message.count + 8 ) % 64 + let padBytesCount = (message.count + 8) % 64 message.append(contentsOf: [UInt8](repeating: 0, count: 64 - padBytesCount)) @@ -45,21 +40,21 @@ public struct SHA1 { // Process the message in successive 512-bit chunks ( 64 bytes chunks ): - for chunkStart in 0..(OpaquePointer($0.baseAddress! + (index*4))).pointee}) + let value = chunk.withUnsafeBufferPointer({ UnsafePointer(OpaquePointer($0.baseAddress! + (index * 4))).pointee }) words.append(value.bigEndian) } // Extend the sixteen 32-bit words into eighty 32-bit words: for index in 16...79 { - let value: UInt32 = ((words[index-3]) ^ (words[index-8]) ^ (words[index-14]) ^ (words[index-16])) + let value: UInt32 = ((words[index - 3]) ^ (words[index - 8]) ^ (words[index - 14]) ^ (words[index - 16])) words.append(rotateLeft(value, 1)) } @@ -77,19 +72,19 @@ public struct SHA1 { switch i { case 0...19: f = (b & c) | ((~b) & d) - k = 0x5A827999 + k = 0x5a827999 case 20...39: f = b ^ c ^ d - k = 0x6ED9EBA1 + k = 0x6ed9eba1 case 40...59: f = (b & c) | (b & d) | (c & d) - k = 0x8F1BBCDC + k = 0x8f1bbcdc case 60...79: f = b ^ c ^ d - k = 0xCA62C1D6 + k = 0xca62c1d6 default: break } - let temp = (rotateLeft(a, 5) &+ f &+ e &+ k &+ words[i]) & 0xFFFFFFFF + let temp = (rotateLeft(a, 5) &+ f &+ e &+ k &+ words[i]) & 0xffffffff e = d d = c c = rotateLeft(b, 30) @@ -99,11 +94,11 @@ public struct SHA1 { // Add this chunk's hash to result so far: - h0 = ( h0 &+ a ) & 0xFFFFFFFF - h1 = ( h1 &+ b ) & 0xFFFFFFFF - h2 = ( h2 &+ c ) & 0xFFFFFFFF - h3 = ( h3 &+ d ) & 0xFFFFFFFF - h4 = ( h4 &+ e ) & 0xFFFFFFFF + h0 = (h0 &+ a) & 0xffffffff + h1 = (h1 &+ b) & 0xffffffff + h2 = (h2 &+ c) & 0xffffffff + h3 = (h3 &+ d) & 0xffffffff + h4 = (h4 &+ e) & 0xffffffff } // Produce the final hash value (big-endian) as a 160 bit number: @@ -121,12 +116,11 @@ public struct SHA1 { } private static func rotateLeft(_ v: UInt32, _ n: UInt32) -> UInt32 { - return ((v << n) & 0xFFFFFFFF) | (v >> (32 - n)) + return ((v << n) & 0xffffffff) | (v >> (32 - n)) } } extension String { - public func sha1() -> [UInt8] { return SHA1.hash([UInt8](self.utf8)) } diff --git a/TestTools/StreamChatTestMockServer/Swifter/WebSockets.swift b/TestTools/StreamChatTestMockServer/Swifter/WebSockets.swift index 177813adee1..cca8e8ee213 100644 --- a/TestTools/StreamChatTestMockServer/Swifter/WebSockets.swift +++ b/TestTools/StreamChatTestMockServer/Swifter/WebSockets.swift @@ -1,16 +1,15 @@ // -// HttpHandlers+WebSockets.swift -// Swifter -// -// Copyright © 2014-2016 Damian Kołakowski. All rights reserved. +// Copyright © 2025 Stream.io Inc. All rights reserved. // import Foundation @available(*, deprecated, message: "Use websocket(text:binary:pong:connected:disconnected:) instead.") -public func websocket(_ text: @escaping (WebSocketSession, String) -> Void, - _ binary: @escaping (WebSocketSession, [UInt8]) -> Void, - _ pong: @escaping (WebSocketSession, [UInt8]) -> Void) -> ((HttpRequest) -> HttpResponse) { +public func websocket( + _ text: @escaping (WebSocketSession, String) -> Void, + _ binary: @escaping (WebSocketSession, [UInt8]) -> Void, + _ pong: @escaping (WebSocketSession, [UInt8]) -> Void +) -> ((HttpRequest) -> HttpResponse) { return websocket(text: text, binary: binary, pong: pong) } @@ -20,7 +19,8 @@ public func websocket( binary: ((WebSocketSession, [UInt8]) -> Void)? = nil, pong: ((WebSocketSession, [UInt8]) -> Void)? = nil, connected: ((WebSocketSession) -> Void)? = nil, - disconnected: ((WebSocketSession) -> Void)? = nil) -> ((HttpRequest) -> HttpResponse) { + disconnected: ((WebSocketSession) -> Void)? = nil +) -> ((HttpRequest) -> HttpResponse) { return { request in guard request.hasTokenForHeader("upgrade", token: "websocket") else { return .badRequest(.text("Invalid value of 'Upgrade' header: \(request.headers["upgrade"] ?? "unknown")")) @@ -39,7 +39,7 @@ public func websocket( func handleTextPayload(_ frame: WebSocketSession.Frame) throws { if let handleText = text { if frame.fin { - if payload.count > 0 { + if !payload.isEmpty { throw WebSocketSession.WsError.protocolError("Continuing fragmented frame cannot have an operation code.") } var textFramePayload = frame.payload.map { Int8(bitPattern: $0) } @@ -59,7 +59,7 @@ public func websocket( func handleBinaryPayload(_ frame: WebSocketSession.Frame) throws { if let handleBinary = binary { if frame.fin { - if payload.count > 0 { + if !payload.isEmpty { throw WebSocketSession.WsError.protocolError("Continuing fragmented frame cannot have an operation code.") } handleBinary(session, frame.payload) @@ -101,7 +101,7 @@ public func websocket( } case .pong: if let handlePong = pong { - handlePong(session, frame.payload) + handlePong(session, frame.payload) } } } @@ -117,7 +117,7 @@ public func websocket( do { try read() - } catch let error { + } catch { switch error { case WebSocketSession.Control.close: // Normal close @@ -146,9 +146,8 @@ public func websocket( } public class WebSocketSession: Hashable, Equatable { - public enum WsError: Error { case unknownOpCode(String), unMaskedFrame(String), protocolError(String), invalidUTF8(String) } - public enum OpCode: UInt8 { case `continue` = 0x00, close = 0x08, ping = 0x09, pong = 0x0A, text = 0x01, binary = 0x02 } + public enum OpCode: UInt8 { case `continue` = 0x00, close = 0x08, ping = 0x09, pong = 0x0a, text = 0x01, binary = 0x02 } public enum Control: Error { case close } public class Frame { @@ -206,19 +205,19 @@ public class WebSocketSession: Hashable, Equatable { case 0...125: encodedBytes.append(encodedLngth | UInt8(len)) case 126...UInt64(UINT16_MAX): - encodedBytes.append(encodedLngth | 0x7E) - encodedBytes.append(UInt8(len >> 8 & 0xFF)) - encodedBytes.append(UInt8(len >> 0 & 0xFF)) + encodedBytes.append(encodedLngth | 0x7e) + encodedBytes.append(UInt8(len >> 8 & 0xff)) + encodedBytes.append(UInt8(len >> 0 & 0xff)) default: - encodedBytes.append(encodedLngth | 0x7F) - encodedBytes.append(UInt8(len >> 56 & 0xFF)) - encodedBytes.append(UInt8(len >> 48 & 0xFF)) - encodedBytes.append(UInt8(len >> 40 & 0xFF)) - encodedBytes.append(UInt8(len >> 32 & 0xFF)) - encodedBytes.append(UInt8(len >> 24 & 0xFF)) - encodedBytes.append(UInt8(len >> 16 & 0xFF)) - encodedBytes.append(UInt8(len >> 08 & 0xFF)) - encodedBytes.append(UInt8(len >> 00 & 0xFF)) + encodedBytes.append(encodedLngth | 0x7f) + encodedBytes.append(UInt8(len >> 56 & 0xff)) + encodedBytes.append(UInt8(len >> 48 & 0xff)) + encodedBytes.append(UInt8(len >> 40 & 0xff)) + encodedBytes.append(UInt8(len >> 32 & 0xff)) + encodedBytes.append(UInt8(len >> 24 & 0xff)) + encodedBytes.append(UInt8(len >> 16 & 0xff)) + encodedBytes.append(UInt8(len >> 08 & 0xff)) + encodedBytes.append(UInt8(len >> 00 & 0xff)) } return encodedBytes } @@ -232,10 +231,10 @@ public class WebSocketSession: Hashable, Equatable { frm.rsv2 = fst & 0x20 frm.rsv3 = fst & 0x10 guard frm.rsv1 == 0 && frm.rsv2 == 0 && frm.rsv3 == 0 - else { + else { throw WsError.protocolError("Reserved frame bit has not been negociated.") } - let opc = fst & 0x0F + let opc = fst & 0x0f guard let opcode = OpCode(rawValue: opc) else { // "If an unknown opcode is received, the receiving endpoint MUST _Fail the WebSocket Connection_." // http://tools.ietf.org/html/rfc6455#section-5.2 ( Page 29 ) @@ -259,12 +258,12 @@ public class WebSocketSession: Hashable, Equatable { // http://tools.ietf.org/html/rfc6455#section-5.1 throw WsError.unMaskedFrame("A client must mask all frames that it sends to the server.") } - var len = UInt64(sec & 0x7F) - if len == 0x7E { + var len = UInt64(sec & 0x7f) + if len == 0x7e { let b0 = UInt64(try socket.read()) << 8 let b1 = UInt64(try socket.read()) len = UInt64(littleEndian: b0 | b1) - } else if len == 0x7F { + } else if len == 0x7f { let b0 = UInt64(try socket.read()) << 54 let b1 = UInt64(try socket.read()) << 48 let b2 = UInt64(try socket.read()) << 40 diff --git a/TestTools/StreamChatTestMockServer/Utilities/LaunchArgument.swift b/TestTools/StreamChatTestMockServer/Utilities/LaunchArgument.swift index 0a1663fc23a..f7f25352499 100644 --- a/TestTools/StreamChatTestMockServer/Utilities/LaunchArgument.swift +++ b/TestTools/StreamChatTestMockServer/Utilities/LaunchArgument.swift @@ -12,7 +12,6 @@ public enum MockServerConfiguration { } public enum EnvironmentVariable: String { - // This changes the base url to localhost with assigned port. // Two conditions need to be met in order to leverage the web socket server in LLC. // 1. App runs in Debug build configuration diff --git a/TestTools/StreamChatTestMockServer/Utilities/TestData.swift b/TestTools/StreamChatTestMockServer/Utilities/TestData.swift index 9081d9f4163..f43dc4a1d00 100644 --- a/TestTools/StreamChatTestMockServer/Utilities/TestData.swift +++ b/TestTools/StreamChatTestMockServer/Utilities/TestData.swift @@ -6,7 +6,6 @@ import XCTest public enum TestData { - public static var uniqueId: String { UUID().uuidString } public static var currentDate: String { @@ -22,7 +21,7 @@ public enum TestData { } public static var waitingEndTime: TimeInterval { - currentTimeInterval + 10_000 + currentTimeInterval + 10000 } public static func getMockResponse(fromFile file: MockFile) -> String { diff --git a/TestTools/StreamChatTestTools/Assertions/AssertAsync.swift b/TestTools/StreamChatTestTools/Assertions/AssertAsync.swift index 895be3bc4ff..b47e1828578 100644 --- a/TestTools/StreamChatTestTools/Assertions/AssertAsync.swift +++ b/TestTools/StreamChatTestTools/Assertions/AssertAsync.swift @@ -358,7 +358,6 @@ public extension AssertAsync { ) { _ = withoutActuallyEscaping(expression) { expression in withoutActuallyEscaping(message) { message in - AssertAsync { Assert.willBeEqual( expression(), @@ -392,7 +391,6 @@ public extension AssertAsync { ) { _ = withoutActuallyEscaping(expression) { expression in withoutActuallyEscaping(message) { message in - AssertAsync { Assert.willBeEqual( expression(), @@ -429,7 +427,6 @@ public extension AssertAsync { _ = withoutActuallyEscaping(expression1) { expression1 in withoutActuallyEscaping(expression2) { expression2 in withoutActuallyEscaping(message) { message in - AssertAsync { Assert.willBeEqual( expression1(), @@ -464,7 +461,6 @@ public extension AssertAsync { ) { _ = withoutActuallyEscaping(expression) { expression in withoutActuallyEscaping(message) { message in - AssertAsync { Assert.willBeTrue( expression() == nil, @@ -526,7 +522,6 @@ public extension AssertAsync { _ = withoutActuallyEscaping(expression1) { expression1 in withoutActuallyEscaping(expression2) { expression2 in withoutActuallyEscaping(message) { message in - AssertAsync { Assert.staysEqual( expression1(), diff --git a/TestTools/StreamChatTestTools/Difference/Difference.swift b/TestTools/StreamChatTestTools/Difference/Difference.swift index 9027b03f3b2..a57739b3ae0 100644 --- a/TestTools/StreamChatTestTools/Difference/Difference.swift +++ b/TestTools/StreamChatTestTools/Difference/Difference.swift @@ -1,9 +1,5 @@ // -// Difference.swift -// Difference -// -// Created by Krzysztof Zablocki on 18.10.2017 -// Copyright © 2017 Krzysztof Zablocki. All rights reserved. +// Copyright © 2025 Stream.io Inc. All rights reserved. // import Foundation @@ -66,7 +62,7 @@ private struct Differ { let expectedMirror = Mirror(reflecting: expected) let receivedMirror = Mirror(reflecting: received) - guard expectedMirror.children.count != 0, receivedMirror.children.count != 0 else { + guard !expectedMirror.children.isEmpty, !receivedMirror.children.isEmpty else { let receivedDump = String(dumping: received) if receivedDump != String(dumping: expected) { return handleChildless(expected, expectedMirror, received, receivedMirror, level) @@ -96,8 +92,8 @@ private struct Differ { (.enum?, .enum?) where hasDiffNumOfChildren: return [generateDifferentCountBlock(expected, expectedMirror, received, receivedMirror, level)] case (.dictionary?, .dictionary?): - if let expectedDict = expected as? Dictionary, - let receivedDict = received as? Dictionary { + if let expectedDict = expected as? [AnyHashable: Any], + let receivedDict = received as? [AnyHashable: Any] { var resultLines: [Line] = [] let missingKeys = Set(expectedDict.keys).subtracting(receivedDict.keys) let extraKeys = Set(receivedDict.keys).subtracting(expectedDict.keys) @@ -108,14 +104,14 @@ private struct Differ { resultLines.append(Line(contents: "Key \(key.description):", indentationLevel: level, canBeOrdered: true, children: results)) } } - if (!missingKeys.isEmpty) { + if !missingKeys.isEmpty { var missingKeyPairs: [Line] = [] missingKeys.forEach { key in missingKeyPairs.append(Line(contents: "\(key.description): \(String(describing: expectedDict[key]))", indentationLevel: level + 1, canBeOrdered: true)) } resultLines.append(Line(contents: "\(nameLabels.missing) key pairs:", indentationLevel: level, canBeOrdered: false, children: missingKeyPairs)) } - if (!extraKeys.isEmpty) { + if !extraKeys.isEmpty { var extraKeyPairs: [Line] = [] extraKeys.forEach { key in extraKeyPairs.append(Line(contents: "\(key.description): \(String(describing: receivedDict[key]))", indentationLevel: level + 1, canBeOrdered: true)) @@ -126,7 +122,7 @@ private struct Differ { } case (.set?, .set?): if let expectedSet = expected as? Set, - let receivedSet = received as? Set { + let receivedSet = received as? Set { let missing = expectedSet.subtracting(receivedSet) .map { unique in Line(contents: "\(nameLabels.missing): \(unique.description)", indentationLevel: level, canBeOrdered: true) @@ -155,7 +151,8 @@ private struct Differ { let results = diffLines(lhs.value, rhs.value, level: level + 1) if !results.isEmpty { - let line = Line(contents: childName, + let line = Line( + contents: childName, indentationLevel: level, canBeOrdered: true, children: results @@ -182,12 +179,12 @@ private struct Differ { let receivedPrintable: String let expectedPrintable: String // Received mirror has a different number of arguments to expected - if receivedMirror.children.count == 0, expectedMirror.children.count != 0 { + if receivedMirror.children.isEmpty, !expectedMirror.children.isEmpty { // Print whole description of received, as it's only a label if childless receivedPrintable = String(dumping: received) // Get the label from the expected, to prevent printing long list of arguments expectedPrintable = enumLabelFromFirstChild(expectedMirror) ?? String(describing: expected) - } else if expectedMirror.children.count == 0, receivedMirror.children.count != 0 { + } else if expectedMirror.children.isEmpty, !receivedMirror.children.isEmpty { receivedPrintable = enumLabelFromFirstChild(receivedMirror) ?? String(describing: received) expectedPrintable = String(dumping: expected) } else { @@ -311,7 +308,7 @@ public struct Line { guard lhs.canBeOrdered && rhs.canBeOrdered else { return false } return lhs.contents < rhs.contents } - .map { $0.generateContents(indentationType: indentationType)} + .map { $0.generateContents(indentationType: indentationType) } .joined() return "\(indentationString)\(contents)\n" + childrenContents } @@ -321,7 +318,7 @@ public struct Line { } } -fileprivate extension String { +private extension String { init(dumping object: T) { self.init() dump(object, to: &self) @@ -344,7 +341,7 @@ private func enumLabelFromFirstChild(_ mirror: Mirror) -> String? { } } -fileprivate extension Mirror { +private extension Mirror { func displayStyleDescriptor(index: Int) -> String { switch self.displayStyle { case .enum: return "Enum " @@ -437,7 +434,6 @@ public func dumpDiff( ).forEach { print($0) } } - /// Prints list of differences between 2 objects /// /// - Parameters: diff --git a/TestTools/StreamChatTestTools/Extensions/FileManager+Extensions.swift b/TestTools/StreamChatTestTools/Extensions/FileManager+Extensions.swift index 6aa9052cb38..20d81e04558 100644 --- a/TestTools/StreamChatTestTools/Extensions/FileManager+Extensions.swift +++ b/TestTools/StreamChatTestTools/Extensions/FileManager+Extensions.swift @@ -13,7 +13,7 @@ public extension FileManager { ) guard let urls else { return } let urlsToDelete = urls.filter({ $0.lastPathComponent == URL.temporaryFileName }) - guard urlsToDelete.count > 0 else { return } + guard !urlsToDelete.isEmpty else { return } urlsToDelete.forEach { url in try? FileManager.default.removeItem(at: url) } diff --git a/TestTools/StreamChatTestTools/Extensions/Unique/ChannelId+Unique.swift b/TestTools/StreamChatTestTools/Extensions/Unique/ChannelId+Unique.swift index 411cc34dd34..d23290e6400 100644 --- a/TestTools/StreamChatTestTools/Extensions/Unique/ChannelId+Unique.swift +++ b/TestTools/StreamChatTestTools/Extensions/Unique/ChannelId+Unique.swift @@ -5,7 +5,7 @@ @testable import StreamChat extension ChannelId { - static public var unique: ChannelId { + public static var unique: ChannelId { ChannelId( type: .custom(String.unique.lowercased().replacingOccurrences(of: "-", with: "_")), id: .unique diff --git a/TestTools/StreamChatTestTools/Extensions/Unique/ChatMessage+Unique.swift b/TestTools/StreamChatTestTools/Extensions/Unique/ChatMessage+Unique.swift index 7c6c4aaecd2..f638dd89322 100644 --- a/TestTools/StreamChatTestTools/Extensions/Unique/ChatMessage+Unique.swift +++ b/TestTools/StreamChatTestTools/Extensions/Unique/ChatMessage+Unique.swift @@ -26,12 +26,14 @@ extension ChatMessage { isBounced: false, isSilent: false, isShadowed: false, + deletedForMe: false, reactionScores: ["like": 1], reactionCounts: ["like": 1], reactionGroups: [ "like": .init( type: "like", - sumScores: 1, count: 1, + sumScores: 1, + count: 1, firstReactionAt: .unique, lastReactionAt: .unique ) diff --git a/TestTools/StreamChatTestTools/Extensions/Unique/ChatUser+Unique.swift b/TestTools/StreamChatTestTools/Extensions/Unique/ChatUser+Unique.swift index e8d3cf589bf..1a3aaf684a3 100644 --- a/TestTools/StreamChatTestTools/Extensions/Unique/ChatUser+Unique.swift +++ b/TestTools/StreamChatTestTools/Extensions/Unique/ChatUser+Unique.swift @@ -6,7 +6,7 @@ import Foundation @testable import StreamChat extension ChatUser { - static public var unique: ChatUser { + public static var unique: ChatUser { .mock( id: .unique, isOnline: true, diff --git a/TestTools/StreamChatTestTools/Extensions/Unique/Poll+Unique.swift b/TestTools/StreamChatTestTools/Extensions/Unique/Poll+Unique.swift index e640b8adec4..2529464f023 100644 --- a/TestTools/StreamChatTestTools/Extensions/Unique/Poll+Unique.swift +++ b/TestTools/StreamChatTestTools/Extensions/Unique/Poll+Unique.swift @@ -27,7 +27,7 @@ extension Poll { latestAnswers: [], options: [], latestVotesByOption: [], - latestVotes: [], + latestVotes: [], ownVotes: [] ) } diff --git a/TestTools/StreamChatTestTools/Extensions/XCTest+Helpers.swift b/TestTools/StreamChatTestTools/Extensions/XCTest+Helpers.swift index d47e8bbfb52..91c3f1de55e 100644 --- a/TestTools/StreamChatTestTools/Extensions/XCTest+Helpers.swift +++ b/TestTools/StreamChatTestTools/Extensions/XCTest+Helpers.swift @@ -6,10 +6,12 @@ import Foundation import XCTest /// Asserts diff between the expected and received values -public func XCTAssertEqual(_ expected: T, - _ received: T, - file: StaticString = #filePath, - line: UInt = #line) { +public func XCTAssertEqual( + _ expected: T, + _ received: T, + file: StaticString = #filePath, + line: UInt = #line +) { if TestRunnerEnvironment.isCI { // Use built-in `XCTAssertEqual` when running on the CI to get CI-friendly logs. XCTAssertEqual(received, expected, "", file: file, line: line) @@ -29,66 +31,80 @@ public func XCTAssertEqual(_ expected: T, /// /// Usage: /// XCTAssertEqual(error, .fileSizeTooLarge(messageId: messageId)) -public func XCTAssertEqual(_ error1: T, - _ error: T, - _ message: @autoclosure () -> String = "", - file: StaticString = #filePath, - line: UInt = #line, - usesDifference: Bool = true) { - XCTAssertEqual(error1.stringReflection, - error.stringReflection, - diffMessage(error1, received: error, message: message()), - file: file, - line: line) +public func XCTAssertEqual( + _ error1: T, + _ error: T, + _ message: @autoclosure () -> String = "", + file: StaticString = #filePath, + line: UInt = #line, + usesDifference: Bool = true +) { + XCTAssertEqual( + error1.stringReflection, + error.stringReflection, + diffMessage(error1, received: error, message: message()), + file: file, + line: line + ) } /// Asserts when two (optional) errors are not equal. /// /// Usage: /// XCTAssertEqual(some?.error, .fileSizeTooLarge(messageId: messageId)) -public func XCTAssertEqual(_ error1: T?, - _ error: T, - _ message: @autoclosure () -> String = "", - file: StaticString = #filePath, - line: UInt = #line) { - XCTAssertEqual(error1?.stringReflection, - error.stringReflection, - diffMessage(error1, received: error, message: message()), - file: file, - line: line) +public func XCTAssertEqual( + _ error1: T?, + _ error: T, + _ message: @autoclosure () -> String = "", + file: StaticString = #filePath, + line: UInt = #line +) { + XCTAssertEqual( + error1?.stringReflection, + error.stringReflection, + diffMessage(error1, received: error, message: message()), + file: file, + line: line + ) } /// Asserts when two given errors are not equal /// /// Usage: /// XCTAssertEqual(error, .fileSizeTooLarge(messageId: messageId)) -public func XCTAssertEqual(_ error1: T, - _ error: T, - _ message: @autoclosure () -> String = "", - file: StaticString = #filePath, - line: UInt = #line) where T: Equatable { +public func XCTAssertEqual( + _ error1: T, + _ error: T, + _ message: @autoclosure () -> String = "", + file: StaticString = #filePath, + line: UInt = #line +) where T: Equatable { if error == error1 { /// This covers the case, when `Equatable` conformance of the `Error` was overriden by the custom implementation XCTAssertTrue(true, message()) return } - XCTAssertEqual(error1.stringReflection, - error.stringReflection, - diffMessage(error1, received: error, message: message()), - file: file, - line: line) + XCTAssertEqual( + error1.stringReflection, + error.stringReflection, + diffMessage(error1, received: error, message: message()), + file: file, + line: line + ) } /// Asserts when two (optional) errors are not equal. /// /// Usage: /// XCTAssertEqual(some?.error, .fileSizeTooLarge(messageId: messageId)) -public func XCTAssertEqual(_ error1: T?, - _ error: T, - _ message: @autoclosure () -> String = "", - file: StaticString = #filePath, - line: UInt = #line) where T: Equatable { +public func XCTAssertEqual( + _ error1: T?, + _ error: T, + _ message: @autoclosure () -> String = "", + file: StaticString = #filePath, + line: UInt = #line +) where T: Equatable { if let equalError = error1, equalError == error { /// This covers the case, when `Equatable` conformance of the `Error` was overriden by the custom implementation @@ -96,11 +112,13 @@ public func XCTAssertEqual(_ error1: T?, return } - XCTAssertEqual(error1?.stringReflection, - error.stringReflection, - diffMessage(error1, received: error, message: message()), - file: file, - line: line) + XCTAssertEqual( + error1?.stringReflection, + error.stringReflection, + diffMessage(error1, received: error, message: message()), + file: file, + line: line + ) } // MARK: Throws @@ -111,16 +129,20 @@ public func XCTAssertEqual(_ error1: T?, /// - expression: An expression that can throw /// - message: An description message for failure /// - errorHandler: An error handler to access the error with concrete type -public func XCTAssertThrowsError(_ expression: @autoclosure () throws -> T, - _ message: String, - file: StaticString = #filePath, - line: UInt = #line, - _ errorHandler: (U) -> Void) { +public func XCTAssertThrowsError( + _ expression: @autoclosure () throws -> T, + _ message: String, + file: StaticString = #filePath, + line: UInt = #line, + _ errorHandler: (U) -> Void +) { XCTAssertThrowsError(try expression(), message, file: file, line: line) { (error) in guard let typedError = error as? U else { - XCTFail("Error: \(error) doesnt match with given error type: \(U.self)", - file: file, - line: line) + XCTFail( + "Error: \(error) doesnt match with given error type: \(U.self)", + file: file, + line: line + ) return } errorHandler(typedError) @@ -128,17 +150,21 @@ public func XCTAssertThrowsError(_ expression: @autoclosure () thro } /// Asserts when thrown error type doesnt match given type -public func XCTAssertThrowsError(ofType: U.Type, - _ expression: @autoclosure () throws -> T, - _ message: String, - file: StaticString = #filePath, - line: UInt = #line, - _ errorHandler: (U) -> Void) { - XCTAssertThrowsError(try expression(), - message, - file: file, - line: line, - errorHandler) +public func XCTAssertThrowsError( + ofType: U.Type, + _ expression: @autoclosure () throws -> T, + _ message: String, + file: StaticString = #filePath, + line: UInt = #line, + _ errorHandler: (U) -> Void +) { + XCTAssertThrowsError( + try expression(), + message, + file: file, + line: line, + errorHandler + ) } /// Asserts when given throwing expression doesnt throw the expected error @@ -149,17 +175,21 @@ public func XCTAssertThrowsError(ofType: U.Type, /// /// Usage: /// XCTAssertThrowsError(try PathUpdate(with: data), ParsingError.failedToParseJSON) -public func XCTAssertThrowsError(_ expression: @autoclosure () throws -> T, - _ error: Error, - _ message: @autoclosure () -> String = "", - file: StaticString = #filePath, - line: UInt = #line) { +public func XCTAssertThrowsError( + _ expression: @autoclosure () throws -> T, + _ error: Error, + _ message: @autoclosure () -> String = "", + file: StaticString = #filePath, + line: UInt = #line +) { XCTAssertThrowsError(try expression(), message()) { (thrownError) in - XCTAssertEqual(thrownError, - error, - diffMessage(thrownError, received: error, message: message()), - file: file, - line: line) + XCTAssertEqual( + thrownError, + error, + diffMessage(thrownError, received: error, message: message()), + file: file, + line: line + ) } } @@ -173,15 +203,19 @@ public func XCTAssertThrowsError(_ expression: @autoclosure () throws -> T, /// /// Usage: /// XCTAssertEqual(result, success: "Success") -public func XCTAssertEqual(_ result: Result, - success value: Value, - _ message: @autoclosure () -> String = "", - file: StaticString = #filePath, - line: UInt = #line) { - let resultValue = XCTAssertResultSuccess(result, - message(), - file: file, - line: line) +public func XCTAssertEqual( + _ result: Result, + success value: Value, + _ message: @autoclosure () -> String = "", + file: StaticString = #filePath, + line: UInt = #line +) { + let resultValue = XCTAssertResultSuccess( + result, + message(), + file: file, + line: line + ) XCTAssertEqual(resultValue, value, diffMessage(resultValue, received: value, message: message()), file: file, line: line) } @@ -193,21 +227,27 @@ public func XCTAssertEqual(_ result: Result(_ result: Result, - failure error: ErrorType, - _ message: @autoclosure () -> String = "", - file: StaticString = #filePath, - line: UInt = #line) { +public func XCTAssertEqual( + _ result: Result, + failure error: ErrorType, + _ message: @autoclosure () -> String = "", + file: StaticString = #filePath, + line: UInt = #line +) { let errorMessage = message() - XCTAssertResultFailure(result, - errorMessage, - file: file, - line: line) { failureError in - XCTAssertEqual(failureError, - error, - diffMessage(failureError, received: error, message: errorMessage), - file: file, - line: line) + XCTAssertResultFailure( + result, + errorMessage, + file: file, + line: line + ) { failureError in + XCTAssertEqual( + failureError, + error, + diffMessage(failureError, received: error, message: errorMessage), + file: file, + line: line + ) } } @@ -219,30 +259,38 @@ public func XCTAssertEqual(_ result: Result(_ result: Result, - failure error: ErrorType, - _ message: @autoclosure () -> String = "", - file: StaticString = #filePath, - line: UInt = #line) where ErrorType: Equatable { +public func XCTAssertEqual( + _ result: Result, + failure error: ErrorType, + _ message: @autoclosure () -> String = "", + file: StaticString = #filePath, + line: UInt = #line +) where ErrorType: Equatable { let errorMessage = message() - XCTAssertResultFailure(result, - errorMessage, - file: file, - line: line) { failureError in - XCTAssertEqual(failureError, - error, - diffMessage(failureError, received: error, message: errorMessage), - file: file, - line: line) + XCTAssertResultFailure( + result, + errorMessage, + file: file, + line: line + ) { failureError in + XCTAssertEqual( + failureError, + error, + diffMessage(failureError, received: error, message: errorMessage), + file: file, + line: line + ) } } /// Asserts when given result `Result` is failure @discardableResult -public func XCTAssertResultSuccess(_ result: Result, - _ message: @autoclosure () -> String = "Expectation failed for result", - file: StaticString = #filePath, - line: UInt = #line) -> Value? { +public func XCTAssertResultSuccess( + _ result: Result, + _ message: @autoclosure () -> String = "Expectation failed for result", + file: StaticString = #filePath, + line: UInt = #line +) -> Value? { switch result { case .success(let value): return value @@ -262,17 +310,21 @@ public func XCTAssertResultSuccess(_ result: Result, /// XCTAssertResultFailure(result) { (error) in /// XCTAssertEqual(error, .fileSizeTooLarge(messageId: messageId)) /// } -public func XCTAssertResultFailure(_ result: Result, - _ message: @autoclosure () -> String = "", - file: StaticString = #filePath, - line: UInt = #line, - errorHandler: ((ErrorType) -> Void)? = nil) { - XCTAssertResultFailure(result, - ofErrorType: ErrorType.self, - message(), - file: file, - line: line, - errorHandler: errorHandler) +public func XCTAssertResultFailure( + _ result: Result, + _ message: @autoclosure () -> String = "", + file: StaticString = #filePath, + line: UInt = #line, + errorHandler: ((ErrorType) -> Void)? = nil +) { + XCTAssertResultFailure( + result, + ofErrorType: ErrorType.self, + message(), + file: file, + line: line, + errorHandler: errorHandler + ) } /// Asserts when given result `Result` has succeeded or the result doesnt match the given `errorType` @@ -286,28 +338,34 @@ public func XCTAssertResultFailure(_ result: Result(_ result: Result, - ofErrorType errorType: ErrorType.Type? = nil, - _ message: @autoclosure () -> String = "", - file: StaticString = #filePath, - line: UInt = #line, - errorHandler: ((ErrorType) -> Void)? = nil) { +public func XCTAssertResultFailure( + _ result: Result, + ofErrorType errorType: ErrorType.Type? = nil, + _ message: @autoclosure () -> String = "", + file: StaticString = #filePath, + line: UInt = #line, + errorHandler: ((ErrorType) -> Void)? = nil +) { switch result { case .success: - XCTFail("Result was not Failure", - file: file, - line: line) + XCTFail( + "Result was not Failure", + file: file, + line: line + ) case .failure(let failureError): guard let errorType = errorType, let errorHandler = errorHandler else { - return + return } guard let error = failureError as? ErrorType else { - XCTFail("Result error: \(failureError) doesnt match with given error type: \(errorType)", - file: file, - line: line) + XCTFail( + "Result error: \(failureError) doesnt match with given error type: \(errorType)", + file: file, + line: line + ) return } @@ -377,11 +435,14 @@ public extension Error { // MARK: Diff message -func diffMessage(_ expected: Value, - received: Value, - message: @autoclosure () -> String = "") -> String { - [message(), - "Found difference for", - diff(expected, received).joined(separator: ", ") +func diffMessage( + _ expected: Value, + received: Value, + message: @autoclosure () -> String = "" +) -> String { + [ + message(), + "Found difference for", + diff(expected, received).joined(separator: ", ") ].joined(separator: "\n") } diff --git a/TestTools/StreamChatTestTools/Extensions/XCTestCase+iOS13.swift b/TestTools/StreamChatTestTools/Extensions/XCTestCase+iOS13.swift index e0c87163eaf..b94b9823358 100644 --- a/TestTools/StreamChatTestTools/Extensions/XCTestCase+iOS13.swift +++ b/TestTools/StreamChatTestTools/Extensions/XCTestCase+iOS13.swift @@ -10,8 +10,4 @@ import XCTest /// If you make you tests class a subclass of this class, it will still be visible to Xcode but the test won't be executed. /// /// Stack overflow: https://stackoverflow.com/questions/59645536/available-attribute-does-not-work-with-xctest-classes-or-methods -open class iOS13TestCase: XCTestCase { - override public func invokeTest() { - return super.invokeTest() - } -} +open class iOS13TestCase: XCTestCase {} diff --git a/TestTools/StreamChatTestTools/Fixtures/JSONs/Events/Message/MessageDeletedForMe.json b/TestTools/StreamChatTestTools/Fixtures/JSONs/Events/Message/MessageDeletedForMe.json new file mode 100644 index 00000000000..6e83105c4d3 --- /dev/null +++ b/TestTools/StreamChatTestTools/Fixtures/JSONs/Events/Message/MessageDeletedForMe.json @@ -0,0 +1,80 @@ +{ + "user" : { + "id" : "broken-waterfall-5", + "banned" : false, + "extraData" : { + "name" : "Tester" + }, + "totalUnreadCount" : 0, + "unread_channels" : 0, + "last_active" : "2020-07-17T13:38:43.540206Z", + "created_at" : "2019-12-12T15:33:46.488935Z", + "invisible" : false, + "unreadChannels" : 0, + "unread_count" : 0, + "image" : "https:\/\/api.adorable.io\/avatars\/285\/broken-waterfall-5.png", + "updated_at" : "2020-07-17T13:42:01.565446Z", + "role" : "user", + "total_unread_count" : 0, + "online" : true, + "name" : "broken-waterfall-5" + }, + "channel_type" : "messaging", + "channel_id" : "general", + "created_at" : "2020-07-17T13:49:48.158327128Z", + "message" : { + "id" : "1ff9f6d0-df70-4703-aef0-379f95ad7366", + "reaction_counts" : null, + "silent" : false, + "created_at" : "2020-07-17T13:42:21.531523Z", + "reaction_scores" : { + + }, + "type" : "deleted", + "latest_reactions" : [ + + ], + "deleted_at" : "2020-07-17T13:49:48.149605Z", + "text" : "Hello", + "attachments" : [ + + ], + "own_reactions" : [ + + ], + "updated_at" : "2020-07-17T13:46:10.534328Z", + "reply_count" : 0, + "user" : { + "id" : "broken-waterfall-5", + "banned" : false, + "unread_channels" : 0, + "totalUnreadCount" : 0, + "extraData" : { + "name" : "Tester" + }, + "last_active" : "2020-07-17T13:38:43.540206Z", + "created_at" : "2019-12-12T15:33:46.488935Z", + "invisible" : false, + "unreadChannels" : 0, + "unread_count" : 0, + "image" : "https:\/\/api.adorable.io\/avatars\/285\/broken-waterfall-5.png", + "updated_at" : "2020-07-17T13:42:01.565446Z", + "role" : "user", + "total_unread_count" : 0, + "online" : true, + "name" : "broken-waterfall-5" + }, + "mentioned_users" : [ + + ], + "html" : "

Hello<\/p>\n", + "pinned": false, + "pinned_at": null, + "pin_expires": null, + "pinned_by": null + }, + "type" : "message.deleted", + "deleted_for_me": true, + "cid" : "messaging:general" +} + diff --git a/TestTools/StreamChatTestTools/Fixtures/JSONs/Message.json b/TestTools/StreamChatTestTools/Fixtures/JSONs/Message.json index 970418528a9..4c8fae1c08b 100644 --- a/TestTools/StreamChatTestTools/Fixtures/JSONs/Message.json +++ b/TestTools/StreamChatTestTools/Fixtures/JSONs/Message.json @@ -6,6 +6,7 @@ }, "silent" : true, "shadowed": true, + "deleted_for_me": true, "created_at" : "2020-07-16T15:39:03.010717Z", "deleted_at" : "2020-07-16T15:55:03.010717Z", "reaction_scores" : { diff --git a/TestTools/StreamChatTestTools/Mocks/Models + Extensions/Attachments/AttachmentUploadingState_Mock.swift b/TestTools/StreamChatTestTools/Mocks/Models + Extensions/Attachments/AttachmentUploadingState_Mock.swift index 320480e6b46..b68e16ac4f5 100644 --- a/TestTools/StreamChatTestTools/Mocks/Models + Extensions/Attachments/AttachmentUploadingState_Mock.swift +++ b/TestTools/StreamChatTestTools/Mocks/Models + Extensions/Attachments/AttachmentUploadingState_Mock.swift @@ -2,8 +2,8 @@ // Copyright © 2025 Stream.io Inc. All rights reserved. // -@testable import StreamChat import Foundation +@testable import StreamChat public extension AttachmentUploadingState { /// Creates a new `AttachmentUploadingState` object from the provided data. diff --git a/TestTools/StreamChatTestTools/Mocks/Models + Extensions/Attachments/ChatMessageAudioAttachment_Mock.swift b/TestTools/StreamChatTestTools/Mocks/Models + Extensions/Attachments/ChatMessageAudioAttachment_Mock.swift index 330c90eb8d8..1cd3d31f389 100644 --- a/TestTools/StreamChatTestTools/Mocks/Models + Extensions/Attachments/ChatMessageAudioAttachment_Mock.swift +++ b/TestTools/StreamChatTestTools/Mocks/Models + Extensions/Attachments/ChatMessageAudioAttachment_Mock.swift @@ -2,8 +2,8 @@ // Copyright © 2025 Stream.io Inc. All rights reserved. // -@testable import StreamChat import Foundation +@testable import StreamChat public extension ChatMessageAudioAttachment { static func mock( diff --git a/TestTools/StreamChatTestTools/Mocks/Models + Extensions/Attachments/ChatMessageFileAttachment_Mock.swift b/TestTools/StreamChatTestTools/Mocks/Models + Extensions/Attachments/ChatMessageFileAttachment_Mock.swift index 4915f40303a..09669b7a7dd 100644 --- a/TestTools/StreamChatTestTools/Mocks/Models + Extensions/Attachments/ChatMessageFileAttachment_Mock.swift +++ b/TestTools/StreamChatTestTools/Mocks/Models + Extensions/Attachments/ChatMessageFileAttachment_Mock.swift @@ -2,8 +2,8 @@ // Copyright © 2025 Stream.io Inc. All rights reserved. // -@testable import StreamChat import Foundation +@testable import StreamChat public extension ChatMessageFileAttachment { /// Creates a new `ChatMessageFileAttachment` object from the provided data. diff --git a/TestTools/StreamChatTestTools/Mocks/Models + Extensions/Attachments/ChatMessageImageAttachment_Mock.swift b/TestTools/StreamChatTestTools/Mocks/Models + Extensions/Attachments/ChatMessageImageAttachment_Mock.swift index 753ae4270cc..e61d5b29d7c 100644 --- a/TestTools/StreamChatTestTools/Mocks/Models + Extensions/Attachments/ChatMessageImageAttachment_Mock.swift +++ b/TestTools/StreamChatTestTools/Mocks/Models + Extensions/Attachments/ChatMessageImageAttachment_Mock.swift @@ -2,8 +2,8 @@ // Copyright © 2025 Stream.io Inc. All rights reserved. // -@testable import StreamChat import Foundation +@testable import StreamChat extension ChatMessageImageAttachment { /// Creates a new `ChatMessageImageAttachment` object from the provided data. diff --git a/TestTools/StreamChatTestTools/Mocks/Models + Extensions/Attachments/ChatMessageLinkAttachment_Mock.swift b/TestTools/StreamChatTestTools/Mocks/Models + Extensions/Attachments/ChatMessageLinkAttachment_Mock.swift index 8989f1afd2f..dc5f51779ab 100644 --- a/TestTools/StreamChatTestTools/Mocks/Models + Extensions/Attachments/ChatMessageLinkAttachment_Mock.swift +++ b/TestTools/StreamChatTestTools/Mocks/Models + Extensions/Attachments/ChatMessageLinkAttachment_Mock.swift @@ -2,8 +2,8 @@ // Copyright © 2025 Stream.io Inc. All rights reserved. // -@testable import StreamChat import Foundation +@testable import StreamChat extension ChatMessageLinkAttachment { /// Creates a new `ChatMessageLinkAttachment` object from the provided data. diff --git a/TestTools/StreamChatTestTools/Mocks/Models + Extensions/Attachments/ChatMessageVideoAttachment_Mock.swift b/TestTools/StreamChatTestTools/Mocks/Models + Extensions/Attachments/ChatMessageVideoAttachment_Mock.swift index bdc63d3b73c..22868c35285 100644 --- a/TestTools/StreamChatTestTools/Mocks/Models + Extensions/Attachments/ChatMessageVideoAttachment_Mock.swift +++ b/TestTools/StreamChatTestTools/Mocks/Models + Extensions/Attachments/ChatMessageVideoAttachment_Mock.swift @@ -2,8 +2,8 @@ // Copyright © 2025 Stream.io Inc. All rights reserved. // -@testable import StreamChat import Foundation +@testable import StreamChat public extension ChatMessageVideoAttachment { static func mock( diff --git a/TestTools/StreamChatTestTools/Mocks/Models + Extensions/Attachments/ChatMessageVoiceRecordingAttachment_Mock.swift b/TestTools/StreamChatTestTools/Mocks/Models + Extensions/Attachments/ChatMessageVoiceRecordingAttachment_Mock.swift index 40c9b02c5f7..6a1c6a40797 100644 --- a/TestTools/StreamChatTestTools/Mocks/Models + Extensions/Attachments/ChatMessageVoiceRecordingAttachment_Mock.swift +++ b/TestTools/StreamChatTestTools/Mocks/Models + Extensions/Attachments/ChatMessageVoiceRecordingAttachment_Mock.swift @@ -2,8 +2,8 @@ // Copyright © 2025 Stream.io Inc. All rights reserved. // -@testable import StreamChat import Foundation +@testable import StreamChat public extension ChatMessageVoiceRecordingAttachment { /// Creates a new `ChatMessageVoiceRecordingAttachment` object from the provided data. diff --git a/TestTools/StreamChatTestTools/Mocks/Models + Extensions/Attachments/StreamAttachment_Mock.swift b/TestTools/StreamChatTestTools/Mocks/Models + Extensions/Attachments/StreamAttachment_Mock.swift index 43b03abbebb..9c8d3fa9141 100644 --- a/TestTools/StreamChatTestTools/Mocks/Models + Extensions/Attachments/StreamAttachment_Mock.swift +++ b/TestTools/StreamChatTestTools/Mocks/Models + Extensions/Attachments/StreamAttachment_Mock.swift @@ -2,8 +2,8 @@ // Copyright © 2025 Stream.io Inc. All rights reserved. // -@testable import StreamChat import Foundation +@testable import StreamChat public extension StreamAttachment { /// Creates a new `ChatMessageFileAttachment` object from the provided data. diff --git a/TestTools/StreamChatTestTools/Mocks/Models + Extensions/ChatMessage_Mock.swift b/TestTools/StreamChatTestTools/Mocks/Models + Extensions/ChatMessage_Mock.swift index 32660e49536..cc0429bad18 100644 --- a/TestTools/StreamChatTestTools/Mocks/Models + Extensions/ChatMessage_Mock.swift +++ b/TestTools/StreamChatTestTools/Mocks/Models + Extensions/ChatMessage_Mock.swift @@ -2,8 +2,8 @@ // Copyright © 2025 Stream.io Inc. All rights reserved. // -import Foundation import CoreData.NSManagedObjectContext +import Foundation @testable import StreamChat public extension ChatMessage { @@ -53,7 +53,8 @@ public extension ChatMessage { draftReply: DraftMessage? = nil, reminder: MessageReminderInfo? = nil, sharedLocation: SharedLocation? = nil, - channelRole: MemberRole? = nil + channelRole: MemberRole? = nil, + deletedForMe: Bool = false ) -> Self { .init( id: id, @@ -74,6 +75,7 @@ public extension ChatMessage { isBounced: isBounced, isSilent: isSilent, isShadowed: isShadowed, + deletedForMe: deletedForMe, reactionScores: reactionScores, reactionCounts: reactionCounts, reactionGroups: reactionGroups, diff --git a/TestTools/StreamChatTestTools/Mocks/Models + Extensions/MessageReminder_Mock.swift b/TestTools/StreamChatTestTools/Mocks/Models + Extensions/MessageReminder_Mock.swift index e2ec5bfe7cc..da9789be567 100644 --- a/TestTools/StreamChatTestTools/Mocks/Models + Extensions/MessageReminder_Mock.swift +++ b/TestTools/StreamChatTestTools/Mocks/Models + Extensions/MessageReminder_Mock.swift @@ -23,4 +23,4 @@ public extension MessageReminder { updatedAt: updatedAt ) } -} +} diff --git a/TestTools/StreamChatTestTools/Mocks/Models + Extensions/Poll_Mock.swift b/TestTools/StreamChatTestTools/Mocks/Models + Extensions/Poll_Mock.swift index 5f3a2efc85b..d6ddc85df8e 100644 --- a/TestTools/StreamChatTestTools/Mocks/Models + Extensions/Poll_Mock.swift +++ b/TestTools/StreamChatTestTools/Mocks/Models + Extensions/Poll_Mock.swift @@ -17,8 +17,8 @@ extension Poll { name: String = .unique, updatedAt: Date? = nil, voteCount: Int = 0, - extraData: [String : RawJSON] = [:], - voteCountsByOption: [String : Int]? = nil, + extraData: [String: RawJSON] = [:], + voteCountsByOption: [String: Int]? = nil, isClosed: Bool = false, maxVotesAllowed: Int? = nil, votingVisibility: VotingVisibility? = nil, @@ -48,12 +48,13 @@ extension Poll { createdBy: createdBy, latestAnswers: latestAnswers, options: options, - latestVotesByOption: latestVotesByOption, + latestVotesByOption: latestVotesByOption, latestVotes: latestVotes, ownVotes: ownVotes ) } } + extension PollVote { static func mock( id: String = .unique, diff --git a/TestTools/StreamChatTestTools/Mocks/StreamChat/ConnectionRepository_Mock.swift b/TestTools/StreamChatTestTools/Mocks/StreamChat/ConnectionRepository_Mock.swift index 3c885d7a93c..1cc1ea9271e 100644 --- a/TestTools/StreamChatTestTools/Mocks/StreamChat/ConnectionRepository_Mock.swift +++ b/TestTools/StreamChatTestTools/Mocks/StreamChat/ConnectionRepository_Mock.swift @@ -2,8 +2,8 @@ // Copyright © 2025 Stream.io Inc. All rights reserved. // -@testable import StreamChat import Foundation +@testable import StreamChat /// Mock implementation of `ChatClientUpdater` final class ConnectionRepository_Mock: ConnectionRepository, Spy { @@ -34,19 +34,23 @@ final class ConnectionRepository_Mock: ConnectionRepository, Spy { var provideConnectionIdResult: Result? convenience init() { - self.init(isClientInActiveMode: true, - syncRepository: SyncRepository_Mock(), - webSocketClient: WebSocketClient_Mock(), - apiClient: APIClient_Spy(), - timerType: DefaultTimer.self) + self.init( + isClientInActiveMode: true, + syncRepository: SyncRepository_Mock(), + webSocketClient: WebSocketClient_Mock(), + apiClient: APIClient_Spy(), + timerType: DefaultTimer.self + ) } convenience init(client: ChatClient) { - self.init(isClientInActiveMode: client.config.isClientInActiveMode, - syncRepository: client.syncRepository, - webSocketClient: client.webSocketClient, - apiClient: client.apiClient, - timerType: DefaultTimer.self) + self.init( + isClientInActiveMode: client.config.isClientInActiveMode, + syncRepository: client.syncRepository, + webSocketClient: client.webSocketClient, + apiClient: client.apiClient, + timerType: DefaultTimer.self + ) } override init(isClientInActiveMode: Bool, syncRepository: SyncRepository, webSocketClient: WebSocketClient?, apiClient: APIClient, timerType: StreamChat.Timer.Type) { diff --git a/TestTools/StreamChatTestTools/Mocks/StreamChat/Controllers/ChannelListController_Mock.swift b/TestTools/StreamChatTestTools/Mocks/StreamChat/Controllers/ChannelListController_Mock.swift index 83cf63fff40..41474d01b7b 100644 --- a/TestTools/StreamChatTestTools/Mocks/StreamChat/Controllers/ChannelListController_Mock.swift +++ b/TestTools/StreamChatTestTools/Mocks/StreamChat/Controllers/ChannelListController_Mock.swift @@ -27,6 +27,6 @@ final class ChannelListController_Mock: ChatChannelListController { override func synchronize(_ completion: ((Error?) -> Void)? = nil) { synchronize_called = true - synchronizeCallCount += 1 + synchronizeCallCount += 1 } } diff --git a/TestTools/StreamChatTestTools/Mocks/StreamChat/Controllers/ChatChannelController_Mock.swift b/TestTools/StreamChatTestTools/Mocks/StreamChat/Controllers/ChatChannelController_Mock.swift index f7b0d5b7b1c..adc26ff0c2b 100644 --- a/TestTools/StreamChatTestTools/Mocks/StreamChat/Controllers/ChatChannelController_Mock.swift +++ b/TestTools/StreamChatTestTools/Mocks/StreamChat/Controllers/ChatChannelController_Mock.swift @@ -6,7 +6,6 @@ import Foundation @testable import StreamChat class ChatChannelController_Mock: ChatChannelController { - var mockCid: ChannelId? override var cid: ChannelId? { mockCid ?? super.cid @@ -67,7 +66,7 @@ class ChatChannelController_Mock: ChatChannelController { skipEnrichUrl: Bool = false, restrictedVisibility: [UserId] = [], location: NewLocationInfo? = nil, - extraData: [String : RawJSON] = [:], + extraData: [String: RawJSON] = [:], completion: ((Result) -> Void)? = nil ) { createNewMessageCallCount += 1 @@ -146,7 +145,7 @@ class ChatChannelController_Mock: ChatChannelController { mentionedUserIds: [UserId] = [], quotedMessageId: MessageId? = nil, command: Command? = nil, - extraData: [String : RawJSON] = [:], + extraData: [String: RawJSON] = [:], completion: ((Result) -> Void)? = nil ) { updateDraftMessage_text = text diff --git a/TestTools/StreamChatTestTools/Mocks/StreamChat/Controllers/ChatMessageController_Mock.swift b/TestTools/StreamChatTestTools/Mocks/StreamChat/Controllers/ChatMessageController_Mock.swift index 7603a7ff9fd..70ae6b3be53 100644 --- a/TestTools/StreamChatTestTools/Mocks/StreamChat/Controllers/ChatMessageController_Mock.swift +++ b/TestTools/StreamChatTestTools/Mocks/StreamChat/Controllers/ChatMessageController_Mock.swift @@ -56,7 +56,6 @@ class ChatMessageController_Mock: ChatMessageController { synchronize_completion = completion } - var loadPageAroundReplyId_callCount = 0 var loadPageAroundReplyId_completion: ((Error?) -> Void)? override func loadPageAroundReplyId( diff --git a/TestTools/StreamChatTestTools/Mocks/StreamChat/Controllers/ChatUserSearchController_Mock.swift b/TestTools/StreamChatTestTools/Mocks/StreamChat/Controllers/ChatUserSearchController_Mock.swift index dcd5a3d5913..458478a5bb6 100644 --- a/TestTools/StreamChatTestTools/Mocks/StreamChat/Controllers/ChatUserSearchController_Mock.swift +++ b/TestTools/StreamChatTestTools/Mocks/StreamChat/Controllers/ChatUserSearchController_Mock.swift @@ -6,7 +6,6 @@ import Foundation @testable import StreamChat class ChatUserSearchController_Mock: ChatUserSearchController { - var searchCallCount = 0 static func mock(client: ChatClient? = nil) -> ChatUserSearchController_Mock { diff --git a/TestTools/StreamChatTestTools/Mocks/StreamChat/Controllers/PollController_Mock.swift b/TestTools/StreamChatTestTools/Mocks/StreamChat/Controllers/PollController_Mock.swift index b2bc05c2510..db44ff39825 100644 --- a/TestTools/StreamChatTestTools/Mocks/StreamChat/Controllers/PollController_Mock.swift +++ b/TestTools/StreamChatTestTools/Mocks/StreamChat/Controllers/PollController_Mock.swift @@ -63,7 +63,7 @@ final class PollController_Mock: PollController { override func suggestPollOption( text: String, position: Int? = nil, - extraData: [String : RawJSON]? = nil, + extraData: [String: RawJSON]? = nil, completion: ((Error?) -> Void)? = nil ) { suggestPollOption_called = true diff --git a/TestTools/StreamChatTestTools/Mocks/StreamChat/Controllers/PollVoteListController_Mock.swift b/TestTools/StreamChatTestTools/Mocks/StreamChat/Controllers/PollVoteListController_Mock.swift index 69e67689596..0b75b69c4bf 100644 --- a/TestTools/StreamChatTestTools/Mocks/StreamChat/Controllers/PollVoteListController_Mock.swift +++ b/TestTools/StreamChatTestTools/Mocks/StreamChat/Controllers/PollVoteListController_Mock.swift @@ -15,6 +15,11 @@ final class PollVoteListController_Mock: PollVoteListController { override var votes: LazyCachedMapCollection { votes_simulated } + + var poll_simulated: Poll? + override var poll: Poll? { + poll_simulated + } var state_simulated: DataController.State? override var state: DataController.State { diff --git a/TestTools/StreamChatTestTools/Mocks/StreamChat/Repositories/AuthenticationRepository_Mock.swift b/TestTools/StreamChatTestTools/Mocks/StreamChat/Repositories/AuthenticationRepository_Mock.swift index bf7faf98ffa..14f5c7394fa 100644 --- a/TestTools/StreamChatTestTools/Mocks/StreamChat/Repositories/AuthenticationRepository_Mock.swift +++ b/TestTools/StreamChatTestTools/Mocks/StreamChat/Repositories/AuthenticationRepository_Mock.swift @@ -38,16 +38,20 @@ class AuthenticationRepository_Mock: AuthenticationRepository, Spy { return mockedToken } - override init(apiClient: APIClient, - databaseContainer: DatabaseContainer, - connectionRepository: ConnectionRepository, - tokenExpirationRetryStrategy: RetryStrategy = DefaultRetryStrategy(), - timerType: StreamChat.Timer.Type = DefaultTimer.self) { - super.init(apiClient: apiClient, - databaseContainer: databaseContainer, - connectionRepository: connectionRepository, - tokenExpirationRetryStrategy: tokenExpirationRetryStrategy, - timerType: timerType) + override init( + apiClient: APIClient, + databaseContainer: DatabaseContainer, + connectionRepository: ConnectionRepository, + tokenExpirationRetryStrategy: RetryStrategy = DefaultRetryStrategy(), + timerType: StreamChat.Timer.Type = DefaultTimer.self + ) { + super.init( + apiClient: apiClient, + databaseContainer: databaseContainer, + connectionRepository: connectionRepository, + tokenExpirationRetryStrategy: tokenExpirationRetryStrategy, + timerType: timerType + ) } override func fetchCurrentUser() { diff --git a/TestTools/StreamChatTestTools/Mocks/StreamChat/Repositories/ChannelRepository_Mock.swift b/TestTools/StreamChatTestTools/Mocks/StreamChat/Repositories/ChannelRepository_Mock.swift index f2838726c64..5e96cd21314 100644 --- a/TestTools/StreamChatTestTools/Mocks/StreamChat/Repositories/ChannelRepository_Mock.swift +++ b/TestTools/StreamChatTestTools/Mocks/StreamChat/Repositories/ChannelRepository_Mock.swift @@ -1,13 +1,9 @@ // -// ChannelRepository_Mock.swift -// StreamChatTestTools -// -// Created by Pol Quintana on 1/3/23. -// Copyright © 2025 Stream.io Inc. All rights reserved. +// Copyright © 2025 Stream.io Inc. All rights reserved. // -@testable import StreamChat import Foundation +@testable import StreamChat class ChannelRepository_Mock: ChannelRepository, Spy { let spyState = SpyState() diff --git a/TestTools/StreamChatTestTools/Mocks/StreamChat/Repositories/DraftMessagesRepository_Mock.swift b/TestTools/StreamChatTestTools/Mocks/StreamChat/Repositories/DraftMessagesRepository_Mock.swift index ef00a1b8693..c22244d326a 100644 --- a/TestTools/StreamChatTestTools/Mocks/StreamChat/Repositories/DraftMessagesRepository_Mock.swift +++ b/TestTools/StreamChatTestTools/Mocks/StreamChat/Repositories/DraftMessagesRepository_Mock.swift @@ -101,4 +101,4 @@ final class DraftMessagesRepository_Mock: DraftMessagesRepository { deleteDraft_calledWith = (cid: cid, threadId: threadId) deleteDraft_completion = completion } -} +} diff --git a/TestTools/StreamChatTestTools/Mocks/StreamChat/Repositories/OfflineRequestsRepository_Mock.swift b/TestTools/StreamChatTestTools/Mocks/StreamChat/Repositories/OfflineRequestsRepository_Mock.swift index 900e9453cf3..fbf8ca4566a 100644 --- a/TestTools/StreamChatTestTools/Mocks/StreamChat/Repositories/OfflineRequestsRepository_Mock.swift +++ b/TestTools/StreamChatTestTools/Mocks/StreamChat/Repositories/OfflineRequestsRepository_Mock.swift @@ -11,9 +11,11 @@ final class OfflineRequestsRepository_Mock: OfflineRequestsRepository, Spy { convenience init() { let apiClient = APIClient_Spy() let database = DatabaseContainer_Spy() - self.init(messageRepository: MessageRepository_Mock(database: database, apiClient: apiClient), - database: database, - apiClient: apiClient) + self.init( + messageRepository: MessageRepository_Mock(database: database, apiClient: apiClient), + database: database, + apiClient: apiClient + ) } override init( diff --git a/TestTools/StreamChatTestTools/Mocks/StreamChat/Repositories/PollsRepository_Mock.swift b/TestTools/StreamChatTestTools/Mocks/StreamChat/Repositories/PollsRepository_Mock.swift index 22ce49b941e..a4dec6a0905 100644 --- a/TestTools/StreamChatTestTools/Mocks/StreamChat/Repositories/PollsRepository_Mock.swift +++ b/TestTools/StreamChatTestTools/Mocks/StreamChat/Repositories/PollsRepository_Mock.swift @@ -15,6 +15,9 @@ final class PollsRepository_Mock: PollsRepository, Spy { var recordedFunctions: [String] = [] var spyState: SpyState = .init() + + // Mock for link method + var link: ((PollVote, PollVoteListQuery) -> Void)? override func queryPollVotes( query: PollVoteListQuery, @@ -53,7 +56,7 @@ final class PollsRepository_Mock: PollsRepository, Spy { pollId: String, text: String, position: Int? = nil, - custom: [String : RawJSON]? = nil, + custom: [String: RawJSON]? = nil, completion: ((Error?) -> Void)? = nil ) { suggestPollOption_completion = completion @@ -65,4 +68,8 @@ final class PollsRepository_Mock: PollsRepository, Spy { ) { deletePoll_completion = completion } + + override func link(pollVote: PollVote, to query: PollVoteListQuery) { + link?(pollVote, query) + } } diff --git a/TestTools/StreamChatTestTools/Mocks/StreamChat/Repositories/RemindersRepository_Mock.swift b/TestTools/StreamChatTestTools/Mocks/StreamChat/Repositories/RemindersRepository_Mock.swift index b0eedd0a9fe..6c12022b411 100644 --- a/TestTools/StreamChatTestTools/Mocks/StreamChat/Repositories/RemindersRepository_Mock.swift +++ b/TestTools/StreamChatTestTools/Mocks/StreamChat/Repositories/RemindersRepository_Mock.swift @@ -110,8 +110,8 @@ final class RemindersRepository_Mock: RemindersRepository { } override func deleteReminder( - messageId: MessageId, - cid: ChannelId, + messageId: MessageId, + cid: ChannelId, completion: @escaping ((Error?) -> Void) ) { deleteReminder_messageId = messageId @@ -120,4 +120,4 @@ final class RemindersRepository_Mock: RemindersRepository { completion(deleteReminder_error) } -} +} diff --git a/TestTools/StreamChatTestTools/Mocks/StreamChat/Repositories/SyncRepository_Mock.swift b/TestTools/StreamChatTestTools/Mocks/StreamChat/Repositories/SyncRepository_Mock.swift index 3e054623255..6c5ec400c2c 100644 --- a/TestTools/StreamChatTestTools/Mocks/StreamChat/Repositories/SyncRepository_Mock.swift +++ b/TestTools/StreamChatTestTools/Mocks/StreamChat/Repositories/SyncRepository_Mock.swift @@ -16,12 +16,14 @@ final class SyncRepository_Mock: SyncRepository, Spy { convenience init() { let apiClient = APIClient_Spy() let database = DatabaseContainer_Spy() - self.init(config: .init(apiKeyString: ""), - offlineRequestsRepository: OfflineRequestsRepository_Mock(), - eventNotificationCenter: EventNotificationCenter_Mock(database: database), - database: database, - apiClient: apiClient, - channelListUpdater: ChannelListUpdater_Spy(database: database, apiClient: apiClient)) + self.init( + config: .init(apiKeyString: ""), + offlineRequestsRepository: OfflineRequestsRepository_Mock(), + eventNotificationCenter: EventNotificationCenter_Mock(database: database), + database: database, + apiClient: apiClient, + channelListUpdater: ChannelListUpdater_Spy(database: database, apiClient: apiClient) + ) } override init( diff --git a/TestTools/StreamChatTestTools/Mocks/StreamChat/State/ChannelList_Mock.swift b/TestTools/StreamChatTestTools/Mocks/StreamChat/State/ChannelList_Mock.swift index 092c7fabb0a..83b604b3228 100644 --- a/TestTools/StreamChatTestTools/Mocks/StreamChat/State/ChannelList_Mock.swift +++ b/TestTools/StreamChatTestTools/Mocks/StreamChat/State/ChannelList_Mock.swift @@ -6,7 +6,6 @@ import Foundation @testable import StreamChat public class ChannelList_Mock: ChannelList { - public static func mock( query: ChannelListQuery? = nil, client: ChatClient? = nil @@ -36,7 +35,7 @@ public class ChannelList_Mock: ChannelList { } public var loadNextChannelsIsCalled = false - public override func loadMoreChannels(limit: Int? = nil) async throws -> [ChatChannel] { + override public func loadMoreChannels(limit: Int? = nil) async throws -> [ChatChannel] { loadNextChannelsIsCalled = true return await MainActor.run { Array(state.channels) diff --git a/TestTools/StreamChatTestTools/Mocks/StreamChat/State/Chat_Mock.swift b/TestTools/StreamChatTestTools/Mocks/StreamChat/State/Chat_Mock.swift index 4eb034f924a..69f36a5585f 100644 --- a/TestTools/StreamChatTestTools/Mocks/StreamChat/State/Chat_Mock.swift +++ b/TestTools/StreamChatTestTools/Mocks/StreamChat/State/Chat_Mock.swift @@ -49,13 +49,13 @@ public class Chat_Mock: Chat, Spy { } var createNewMessageCallCount = 0 - public override func sendMessage( + override public func sendMessage( with text: String, attachments: [AnyAttachmentPayload] = [], quote quotedMessageId: MessageId? = nil, mentions: [UserId] = [], pinning: MessagePinning? = nil, - extraData: [String : RawJSON] = [:], + extraData: [String: RawJSON] = [:], silent: Bool = false, skipPushNotification: Bool = false, skipEnrichURL: Bool = false, @@ -67,11 +67,11 @@ public class Chat_Mock: Chat, Spy { } public var loadPageAroundMessageIdCallCount = 0 - public override func loadMessages(around messageId: MessageId, limit: Int? = nil) async throws { + override public func loadMessages(around messageId: MessageId, limit: Int? = nil) async throws { loadPageAroundMessageIdCallCount += 1 } - public override func watch() async throws { + override public func watch() async throws { record() } } diff --git a/TestTools/StreamChatTestTools/Mocks/StreamChat/State/MessageSearch_Mock.swift b/TestTools/StreamChatTestTools/Mocks/StreamChat/State/MessageSearch_Mock.swift index 12f9ce93c6d..a3b08cfe47c 100644 --- a/TestTools/StreamChatTestTools/Mocks/StreamChat/State/MessageSearch_Mock.swift +++ b/TestTools/StreamChatTestTools/Mocks/StreamChat/State/MessageSearch_Mock.swift @@ -21,13 +21,13 @@ public class MessageSearch_Mock: MessageSearch { } var loadNextMessagesCallCount = 0 - public override func loadMoreMessages(limit: Int? = nil) async throws -> [ChatMessage] { + override public func loadMoreMessages(limit: Int? = nil) async throws -> [ChatMessage] { loadNextMessagesCallCount += 1 return await Array(state.messages) } var searchCallCount = 0 - public override func search(query: MessageSearchQuery) async throws -> [ChatMessage] { + override public func search(query: MessageSearchQuery) async throws -> [ChatMessage] { searchCallCount += 1 return await MainActor.run { state.messages = messages @@ -35,7 +35,7 @@ public class MessageSearch_Mock: MessageSearch { } } - public override func search(text: String) async throws -> [ChatMessage] { + override public func search(text: String) async throws -> [ChatMessage] { searchCallCount += 1 return await MainActor.run { state.messages = messages diff --git a/TestTools/StreamChatTestTools/Mocks/StreamChat/State/UserSearch_Mock.swift b/TestTools/StreamChatTestTools/Mocks/StreamChat/State/UserSearch_Mock.swift index 7717582dfa3..5ec72e3d636 100644 --- a/TestTools/StreamChatTestTools/Mocks/StreamChat/State/UserSearch_Mock.swift +++ b/TestTools/StreamChatTestTools/Mocks/StreamChat/State/UserSearch_Mock.swift @@ -6,7 +6,6 @@ import Foundation @testable import StreamChat public class UserSearch_Mock: UserSearch { - var searchCallCount = 0 public static func mock(client: ChatClient? = nil) -> UserSearch_Mock { @@ -17,7 +16,7 @@ public class UserSearch_Mock: UserSearch { self.state.users = StreamCollection(users) } - public override func loadMoreUsers(limit: Int? = nil) async throws -> [ChatUser] { + override public func loadMoreUsers(limit: Int? = nil) async throws -> [ChatUser] { await Array(state.users) } diff --git a/TestTools/StreamChatTestTools/Mocks/StreamChat/Utils/MessagesPaginationStateHandler_Mock.swift b/TestTools/StreamChatTestTools/Mocks/StreamChat/Utils/MessagesPaginationStateHandler_Mock.swift index 63687b18906..872dc0c5eb2 100644 --- a/TestTools/StreamChatTestTools/Mocks/StreamChat/Utils/MessagesPaginationStateHandler_Mock.swift +++ b/TestTools/StreamChatTestTools/Mocks/StreamChat/Utils/MessagesPaginationStateHandler_Mock.swift @@ -1,16 +1,11 @@ // -// MessagesPaginationStateHandler_Mock.swift -// StreamChatTestTools -// -// Created by Nuno Vieira on 05/05/2023. -// Copyright © 2025 Stream.io Inc. All rights reserved. +// Copyright © 2025 Stream.io Inc. All rights reserved. // import Foundation @testable import StreamChat final class MessagesPaginationStateHandler_Mock: MessagesPaginationStateHandling { - var mockState: MessagesPaginationState = .initial var beginCallCount = 0 @@ -32,5 +27,4 @@ final class MessagesPaginationStateHandler_Mock: MessagesPaginationStateHandling endCallCount += 1 endCalledWith = (pagination, result) } - } diff --git a/TestTools/StreamChatTestTools/Mocks/StreamChat/VoiceRecording/MockAVPlayer.swift b/TestTools/StreamChatTestTools/Mocks/StreamChat/VoiceRecording/MockAVPlayer.swift index a4fccce1a77..a4b85e191af 100644 --- a/TestTools/StreamChatTestTools/Mocks/StreamChat/VoiceRecording/MockAVPlayer.swift +++ b/TestTools/StreamChatTestTools/Mocks/StreamChat/VoiceRecording/MockAVPlayer.swift @@ -20,7 +20,7 @@ public class MockAVPlayer: AVPlayer { public private(set) var seekWasCalledWithToleranceAfter: CMTime? public var holdSeekCompletion = false - public override var rate: Float { + override public var rate: Float { didSet { rateWasUpdatedTo = rate mockPlayerObserver?.addPeriodicTimeObserverWasCalledWithBlock?() diff --git a/TestTools/StreamChatTestTools/Mocks/StreamChat/VoiceRecording/MockAVURLAsset.swift b/TestTools/StreamChatTestTools/Mocks/StreamChat/VoiceRecording/MockAVURLAsset.swift index 5c130885191..a7e1c2f0c37 100644 --- a/TestTools/StreamChatTestTools/Mocks/StreamChat/VoiceRecording/MockAVURLAsset.swift +++ b/TestTools/StreamChatTestTools/Mocks/StreamChat/VoiceRecording/MockAVURLAsset.swift @@ -6,7 +6,6 @@ import AVFoundation @dynamicMemberLookup public final class MockAVURLAsset: AVURLAsset, Spy, Stub, @unchecked Sendable { - public let spyState = SpyState() public var stubbedProperties: [String: Any] = [:] @@ -15,11 +14,11 @@ public final class MockAVURLAsset: AVURLAsset, Spy, Stub, @unchecked Sendable { public private(set) var loadValuesAsynchronouslyWasCalledWithKeys: [String]? - public override var duration: CMTime { + override public var duration: CMTime { get { self[dynamicMember: \.duration] } } - public override func statusOfValue( + override public func statusOfValue( forKey key: String, error outError: NSErrorPointer ) -> AVKeyValueStatus { @@ -28,7 +27,7 @@ public final class MockAVURLAsset: AVURLAsset, Spy, Stub, @unchecked Sendable { return statusOfValueResultMap[key] ?? super.statusOfValue(forKey: key, error: outError) } - public override func loadValuesAsynchronously( + override public func loadValuesAsynchronously( forKeys keys: [String], completionHandler handler: (() -> Void)? = nil ) { diff --git a/TestTools/StreamChatTestTools/Mocks/StreamChat/VoiceRecording/MockAudioPlayer.swift b/TestTools/StreamChatTestTools/Mocks/StreamChat/VoiceRecording/MockAudioPlayer.swift index 780d1328710..74f6b35b0aa 100644 --- a/TestTools/StreamChatTestTools/Mocks/StreamChat/VoiceRecording/MockAudioPlayer.swift +++ b/TestTools/StreamChatTestTools/Mocks/StreamChat/VoiceRecording/MockAudioPlayer.swift @@ -6,7 +6,6 @@ import Foundation import StreamChat public class MockAudioPlayer: AudioPlaying { - public private(set) var subscribeWasCalledWithSubscriber: AudioPlayingDelegate? public private(set) var loadAssetWasCalledWithURL: URL? diff --git a/TestTools/StreamChatTestTools/Mocks/StreamChat/VoiceRecording/MockAudioRecorder.swift b/TestTools/StreamChatTestTools/Mocks/StreamChat/VoiceRecording/MockAudioRecorder.swift index c0df7217ac0..518bced1de9 100644 --- a/TestTools/StreamChatTestTools/Mocks/StreamChat/VoiceRecording/MockAudioRecorder.swift +++ b/TestTools/StreamChatTestTools/Mocks/StreamChat/VoiceRecording/MockAudioRecorder.swift @@ -6,7 +6,6 @@ import Foundation import StreamChat public final class MockAudioRecorder: AudioRecording { - public private(set) var subscribeWasCalledWithSubscriber: AudioRecordingDelegate? public private(set) var beginRecordingWasCalledWithCompletionHandler: (() -> Void)? public private(set) var pauseRecordingWasCalled: Bool = false diff --git a/TestTools/StreamChatTestTools/Mocks/StreamChat/VoiceRecording/MockAudioRecordingDelegate.swift b/TestTools/StreamChatTestTools/Mocks/StreamChat/VoiceRecording/MockAudioRecordingDelegate.swift index 609973e3b98..a3dc7a87f86 100644 --- a/TestTools/StreamChatTestTools/Mocks/StreamChat/VoiceRecording/MockAudioRecordingDelegate.swift +++ b/TestTools/StreamChatTestTools/Mocks/StreamChat/VoiceRecording/MockAudioRecordingDelegate.swift @@ -6,7 +6,6 @@ import Foundation import StreamChat public final class MockAudioRecordingDelegate: AudioRecordingDelegate { - public private(set) var didUpdateContextWasCalledWithAudioRecorder: AudioRecording? public private(set) var didUpdateContextWasCalledWithContext: AudioRecordingContext? diff --git a/TestTools/StreamChatTestTools/Mocks/StreamChat/VoiceRecording/MockAudioSessionConfigurator.swift b/TestTools/StreamChatTestTools/Mocks/StreamChat/VoiceRecording/MockAudioSessionConfigurator.swift index 8eac292f2df..92b3e66fecc 100644 --- a/TestTools/StreamChatTestTools/Mocks/StreamChat/VoiceRecording/MockAudioSessionConfigurator.swift +++ b/TestTools/StreamChatTestTools/Mocks/StreamChat/VoiceRecording/MockAudioSessionConfigurator.swift @@ -6,15 +6,17 @@ import Foundation @testable import StreamChat public final class MockAudioSessionConfigurator: Stub, Spy, AudioSessionConfiguring { - // MARK: - Stub & Spy requirements - public var stubbedProperties: [String : Any] = [:] + + public var stubbedProperties: [String: Any] = [:] public let spyState = SpyState() // MARK: - Recorded function parameters + public private(set) var requestRecordPermissionCompletionHandler: ((Bool) -> Void)? // MARK: - Flow Control properties + public var activateRecordingSessionThrowsError: Error? public var deactivateRecordingSessionThrowsError: Error? public var activatePlaybackSessionThrowsError: Error? diff --git a/TestTools/StreamChatTestTools/Mocks/StreamChat/WebSocketClient/WebSocketClient_Mock.swift b/TestTools/StreamChatTestTools/Mocks/StreamChat/WebSocketClient/WebSocketClient_Mock.swift index 71a7106d852..5d0bdb50779 100644 --- a/TestTools/StreamChatTestTools/Mocks/StreamChat/WebSocketClient/WebSocketClient_Mock.swift +++ b/TestTools/StreamChatTestTools/Mocks/StreamChat/WebSocketClient/WebSocketClient_Mock.swift @@ -21,7 +21,6 @@ final class WebSocketClient_Mock: WebSocketClient { var disconnect_called: Bool { disconnect_calledCounter > 0 } var disconnect_completion: (() -> Void)? - var mockedConnectionState: WebSocketConnectionState? override var connectionState: WebSocketConnectionState { @@ -56,11 +55,13 @@ final class WebSocketClient_Mock: WebSocketClient { init_eventNotificationCenter = eventNotificationCenter init_environment = environment - super.init(sessionConfiguration: sessionConfiguration, - requestEncoder: requestEncoder, - eventDecoder: eventDecoder, - eventNotificationCenter: eventNotificationCenter, - environment: environment) + super.init( + sessionConfiguration: sessionConfiguration, + requestEncoder: requestEncoder, + eventDecoder: eventDecoder, + eventNotificationCenter: eventNotificationCenter, + environment: environment + ) } override func connect() { diff --git a/TestTools/StreamChatTestTools/Mocks/StreamChat/WebSocketClient/WebSocketEngine_Mock.swift b/TestTools/StreamChatTestTools/Mocks/StreamChat/WebSocketClient/WebSocketEngine_Mock.swift index 92d55cef070..014ff81a753 100644 --- a/TestTools/StreamChatTestTools/Mocks/StreamChat/WebSocketClient/WebSocketEngine_Mock.swift +++ b/TestTools/StreamChatTestTools/Mocks/StreamChat/WebSocketClient/WebSocketEngine_Mock.swift @@ -92,7 +92,7 @@ extension Dictionary { "online": true, "name": "Steep Moon", "test": 1 - ] as [String : Any], + ] as [String: Any], "type": "health.check", "connection_id": connectionId ] diff --git a/TestTools/StreamChatTestTools/Mocks/StreamChat/Workers/ManualEventHandler_Mock.swift b/TestTools/StreamChatTestTools/Mocks/StreamChat/Workers/ManualEventHandler_Mock.swift index ff371bdd01f..e31593737c6 100644 --- a/TestTools/StreamChatTestTools/Mocks/StreamChat/Workers/ManualEventHandler_Mock.swift +++ b/TestTools/StreamChatTestTools/Mocks/StreamChat/Workers/ManualEventHandler_Mock.swift @@ -42,4 +42,4 @@ final class ManualEventHandler_Mock: ManualEventHandler { handleCalledWith.append(event) return handleReturnValue } -} \ No newline at end of file +} diff --git a/TestTools/StreamChatTestTools/Mocks/StreamChat/Workers/MessageUpdater_Mock.swift b/TestTools/StreamChatTestTools/Mocks/StreamChat/Workers/MessageUpdater_Mock.swift index 2cfa90b621b..a4c2d576e38 100644 --- a/TestTools/StreamChatTestTools/Mocks/StreamChat/Workers/MessageUpdater_Mock.swift +++ b/TestTools/StreamChatTestTools/Mocks/StreamChat/Workers/MessageUpdater_Mock.swift @@ -15,6 +15,7 @@ final class MessageUpdater_Mock: MessageUpdater { @Atomic var deleteMessage_completion: ((Error?) -> Void)? @Atomic var deleteMessage_completion_result: Result? @Atomic var deleteMessage_hard: Bool? + @Atomic var deleteMessage_deleteForMe: Bool? @Atomic var downloadAttachment_attachmentId: AttachmentId? @Atomic var downloadAttachment_completion_result: Result? @@ -286,9 +287,10 @@ final class MessageUpdater_Mock: MessageUpdater { getMessage_completion = completion } - override func deleteMessage(messageId: MessageId, hard: Bool, completion: ((Error?) -> Void)? = nil) { + override func deleteMessage(messageId: MessageId, hard: Bool, deleteForMe: Bool? = nil, completion: ((Error?) -> Void)? = nil) { deleteMessage_messageId = messageId deleteMessage_hard = hard + deleteMessage_deleteForMe = deleteForMe deleteMessage_completion = completion deleteMessage_completion_result?.invoke(with: completion) } diff --git a/TestTools/StreamChatTestTools/Mocks/StreamChat/Workers/UserUpdater_Mock.swift b/TestTools/StreamChatTestTools/Mocks/StreamChat/Workers/UserUpdater_Mock.swift index eee1979db2f..b75dad18b03 100644 --- a/TestTools/StreamChatTestTools/Mocks/StreamChat/Workers/UserUpdater_Mock.swift +++ b/TestTools/StreamChatTestTools/Mocks/StreamChat/Workers/UserUpdater_Mock.swift @@ -52,7 +52,7 @@ final class UserUpdater_Mock: UserUpdater { loadUser_completion_result?.invoke(with: completion) } - override func flagUser(_ flag: Bool, with userId: UserId, reason: String?, extraData: [String : RawJSON]?, completion: (((any Error)?) -> Void)? = nil) { + override func flagUser(_ flag: Bool, with userId: UserId, reason: String?, extraData: [String: RawJSON]?, completion: (((any Error)?) -> Void)? = nil) { flagUser_flag = flag flagUser_userId = userId flagUser_reason = reason diff --git a/TestTools/StreamChatTestTools/SpyPattern/Spy/APIClient_Spy.swift b/TestTools/StreamChatTestTools/SpyPattern/Spy/APIClient_Spy.swift index 9e8f1825026..5ee6e1389ef 100644 --- a/TestTools/StreamChatTestTools/SpyPattern/Spy/APIClient_Spy.swift +++ b/TestTools/StreamChatTestTools/SpyPattern/Spy/APIClient_Spy.swift @@ -11,6 +11,7 @@ final class APIClient_Spy: APIClient, Spy { enum Signature { static let flushRequestsQueue = "flushRequestsQueue()" } + let spyState = SpyState() /// The last endpoint `request` function was called with. @@ -158,7 +159,7 @@ final class APIClient_Spy: APIClient, Spy { override func unmanagedRequest( endpoint: Endpoint, completion: @escaping (Result) -> Void - ) where Response : Decodable { + ) where Response: Decodable { unmanagedRequest_endpoint = AnyEndpoint(endpoint) unmanagedRequest_completion = completion _unmanagedRequest_allRecordedCalls.mutate { $0.append((unmanagedRequest_endpoint!, unmanagedRequest_completion!)) } @@ -184,7 +185,6 @@ final class APIClient_Spy: APIClient, Spy { progress: ((Double) -> Void)?, completion: @escaping (Result) -> Void ) { - uploadFile_attachment = attachment uploadFile_progress = progress uploadFile_completion = completion diff --git a/TestTools/StreamChatTestTools/SpyPattern/Spy/AttachmentUploader_Spy.swift b/TestTools/StreamChatTestTools/SpyPattern/Spy/AttachmentUploader_Spy.swift index b1516ef48cb..d44cc5bbf03 100644 --- a/TestTools/StreamChatTestTools/SpyPattern/Spy/AttachmentUploader_Spy.swift +++ b/TestTools/StreamChatTestTools/SpyPattern/Spy/AttachmentUploader_Spy.swift @@ -42,7 +42,7 @@ final class AttachmentUploader_Spy: AttachmentUploader, Spy { if let uploadAttachmentResult = uploadAttachmentResult { DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { - completion(uploadAttachmentResult.map { UploadedFile(fileURL: $0.remoteURL )}) + completion(uploadAttachmentResult.map { UploadedFile(fileURL: $0.remoteURL) }) } } } diff --git a/TestTools/StreamChatTestTools/SpyPattern/Spy/CDNClient_Spy.swift b/TestTools/StreamChatTestTools/SpyPattern/Spy/CDNClient_Spy.swift index ce4481073ea..42ad7a75cb2 100644 --- a/TestTools/StreamChatTestTools/SpyPattern/Spy/CDNClient_Spy.swift +++ b/TestTools/StreamChatTestTools/SpyPattern/Spy/CDNClient_Spy.swift @@ -2,8 +2,8 @@ // Copyright © 2025 Stream.io Inc. All rights reserved. // -import StreamChat import Foundation +import StreamChat final class CDNClient_Spy: CDNClient, Spy { let spyState = SpyState() diff --git a/TestTools/StreamChatTestTools/SpyPattern/Spy/DatabaseContainer_Spy.swift b/TestTools/StreamChatTestTools/SpyPattern/Spy/DatabaseContainer_Spy.swift index c82d548a8dd..f0347c787ac 100644 --- a/TestTools/StreamChatTestTools/SpyPattern/Spy/DatabaseContainer_Spy.swift +++ b/TestTools/StreamChatTestTools/SpyPattern/Spy/DatabaseContainer_Spy.swift @@ -336,7 +336,8 @@ extension DatabaseContainer { channelDTO: channelDTO, syncOwnReactions: true, skipDraftUpdate: true, - cache: nil) + cache: nil + ) messageDTO.localMessageState = localState messageDTO.reactionCounts = reactionCounts.mapKeys(\.rawValue) diff --git a/TestTools/StreamChatTestTools/StreamChatTestTools.swift b/TestTools/StreamChatTestTools/StreamChatTestTools.swift index 51cdc546f12..d5202f6e5b9 100644 --- a/TestTools/StreamChatTestTools/StreamChatTestTools.swift +++ b/TestTools/StreamChatTestTools/StreamChatTestTools.swift @@ -15,7 +15,6 @@ public let defaultTimeoutForInversedExpecations: TimeInterval = TestRunnerEnviro public let evaluationPeriod: TimeInterval = 0.00001 extension Bundle { - private final class StreamChatTestTools {} static let bundleName = "StreamChat_StreamChatTestTools" diff --git a/TestTools/StreamChatTestTools/TestData/AnyEndpoint.swift b/TestTools/StreamChatTestTools/TestData/AnyEndpoint.swift index 9ae53f34c8b..9851d09eda0 100644 --- a/TestTools/StreamChatTestTools/TestData/AnyEndpoint.swift +++ b/TestTools/StreamChatTestTools/TestData/AnyEndpoint.swift @@ -3,8 +3,8 @@ // import Foundation -import XCTest @testable import StreamChat +import XCTest public struct AnyEndpoint: Equatable { public let path: EndpointPath diff --git a/TestTools/StreamChatTestTools/TestData/DummyData/MessageAttachmentPayload.swift b/TestTools/StreamChatTestTools/TestData/DummyData/MessageAttachmentPayload.swift index 9131cedf86f..486abfe9f85 100644 --- a/TestTools/StreamChatTestTools/TestData/DummyData/MessageAttachmentPayload.swift +++ b/TestTools/StreamChatTestTools/TestData/DummyData/MessageAttachmentPayload.swift @@ -1,5 +1,5 @@ // -// Copyright 2025 Stream.io Inc. All rights reserved. +// Copyright © 2025 Stream.io Inc. All rights reserved. // import Foundation diff --git a/TestTools/StreamChatTestTools/TestData/DummyData/MessagePayload.swift b/TestTools/StreamChatTestTools/TestData/DummyData/MessagePayload.swift index 9f2f525da5d..920e458f98c 100644 --- a/TestTools/StreamChatTestTools/TestData/DummyData/MessagePayload.swift +++ b/TestTools/StreamChatTestTools/TestData/DummyData/MessagePayload.swift @@ -53,7 +53,9 @@ extension MessagePayload { poll: PollPayload? = nil, draft: DraftPayload? = nil, sharedLocation: SharedLocationPayload? = nil, - member: MemberInfoPayload? = nil + member: MemberInfoPayload? = nil, + deletedForMe: Bool? = nil, + campaignId: String? = nil ) -> MessagePayload { .init( id: messageId, @@ -97,7 +99,9 @@ extension MessagePayload { poll: poll, draft: draft, location: sharedLocation, - member: member + member: member, + deletedForMe: deletedForMe, + campaignId: campaignId ) } diff --git a/TestTools/StreamChatTestTools/TestData/DummyData/UploadedAttachment.swift b/TestTools/StreamChatTestTools/TestData/DummyData/UploadedAttachment.swift index bc24636241f..2657c17d976 100644 --- a/TestTools/StreamChatTestTools/TestData/DummyData/UploadedAttachment.swift +++ b/TestTools/StreamChatTestTools/TestData/DummyData/UploadedAttachment.swift @@ -1,7 +1,4 @@ // -// UploadedAttachment.swift -// StreamChat -// // Copyright © 2025 Stream.io Inc. All rights reserved. // diff --git a/Tests/StreamChatTests/APIClient/Endpoints/MessageEndpoints_Tests.swift b/Tests/StreamChatTests/APIClient/Endpoints/MessageEndpoints_Tests.swift index fa845cf9b01..549f4eb6dce 100644 --- a/Tests/StreamChatTests/APIClient/Endpoints/MessageEndpoints_Tests.swift +++ b/Tests/StreamChatTests/APIClient/Endpoints/MessageEndpoints_Tests.swift @@ -68,6 +68,71 @@ final class MessageEndpoints_Tests: XCTestCase { XCTAssertEqual("messages/\(messageId)", endpoint.path.value) } + func test_deleteMessage_whenDeleteForMeEnabled_buildsCorrectly() { + let messageId: MessageId = .unique + + let expectedEndpoint = Endpoint( + path: .message(messageId), + method: .delete, + queryItems: nil, + requiresConnectionId: false, + body: [ + "hard": false, + "delete_for_me": true + ] + ) + + // Build endpoint + let endpoint: Endpoint = .deleteMessage(messageId: messageId, hard: false, deleteForMe: true) + + // Assert endpoint is built correctly + XCTAssertEqual(AnyEndpoint(expectedEndpoint), AnyEndpoint(endpoint)) + XCTAssertEqual("messages/\(messageId)", endpoint.path.value) + } + + func test_deleteMessage_whenDeleteForMeDisabled_buildsCorrectly() { + let messageId: MessageId = .unique + + let expectedEndpoint = Endpoint( + path: .message(messageId), + method: .delete, + queryItems: nil, + requiresConnectionId: false, + body: [ + "hard": false, + "delete_for_me": false + ] + ) + + // Build endpoint + let endpoint: Endpoint = .deleteMessage(messageId: messageId, hard: false, deleteForMe: false) + + // Assert endpoint is built correctly + XCTAssertEqual(AnyEndpoint(expectedEndpoint), AnyEndpoint(endpoint)) + XCTAssertEqual("messages/\(messageId)", endpoint.path.value) + } + + func test_deleteMessage_whenDeleteForMeIsNil_buildsCorrectly() { + let messageId: MessageId = .unique + + let expectedEndpoint = Endpoint( + path: .message(messageId), + method: .delete, + queryItems: nil, + requiresConnectionId: false, + body: [ + "hard": false + ] + ) + + // Build endpoint + let endpoint: Endpoint = .deleteMessage(messageId: messageId, hard: false, deleteForMe: nil) + + // Assert endpoint is built correctly + XCTAssertEqual(AnyEndpoint(expectedEndpoint), AnyEndpoint(endpoint)) + XCTAssertEqual("messages/\(messageId)", endpoint.path.value) + } + func test_editMessage_buildsCorrectly() { let payload = MessageRequestBody( id: .unique, diff --git a/Tests/StreamChatTests/APIClient/Endpoints/Payloads/MessagePayloads_Tests.swift b/Tests/StreamChatTests/APIClient/Endpoints/Payloads/MessagePayloads_Tests.swift index ee427fcfd33..df5a76e4ba7 100644 --- a/Tests/StreamChatTests/APIClient/Endpoints/Payloads/MessagePayloads_Tests.swift +++ b/Tests/StreamChatTests/APIClient/Endpoints/Payloads/MessagePayloads_Tests.swift @@ -63,6 +63,7 @@ final class MessagePayload_Tests: XCTestCase { XCTAssertEqual(payload.moderation?.blocklistMatched, "profanity_2021_01") XCTAssertEqual(payload.moderation?.semanticFilterMatched, "bad_phrases") XCTAssertEqual(payload.moderation?.platformCircumvented, false) + XCTAssertEqual(payload.deletedForMe, true) XCTAssertEqual(payload.member?.channelRole, .moderator) } @@ -336,6 +337,44 @@ final class MessagePayload_Tests: XCTestCase { XCTAssertNil(chatMessage.reminder) XCTAssertNil(chatMessage.sharedLocation) } + + // MARK: - deletedForMe Tests + + func test_messagePayload_asModel_deletedForMe_whenTrue() { + let payload = MessagePayload.dummy(deletedForMe: true) + + let message = payload.asModel( + cid: ChannelId(type: .messaging, id: "test"), + currentUserId: "test-user", + channelReads: [] + ) + + XCTAssertEqual(message.deletedForMe, true) + } + + func test_messagePayload_asModel_deletedForMe_whenFalse() { + let payload = MessagePayload.dummy(deletedForMe: false) + + let message = payload.asModel( + cid: ChannelId(type: .messaging, id: "test"), + currentUserId: "test-user", + channelReads: [] + ) + + XCTAssertEqual(message.deletedForMe, false) + } + + func test_messagePayload_asModel_deletedForMe_whenNil_defaultsToFalse() { + let payload = MessagePayload.dummy(deletedForMe: nil) + + let message = payload.asModel( + cid: ChannelId(type: .messaging, id: "test"), + currentUserId: "test-user", + channelReads: [] + ) + + XCTAssertEqual(message.deletedForMe, false) + } } final class MessageRequestBody_Tests: XCTestCase { diff --git a/Tests/StreamChatTests/ChatClient_Tests.swift b/Tests/StreamChatTests/ChatClient_Tests.swift index 1c15855a8e0..e29e729c4f3 100644 --- a/Tests/StreamChatTests/ChatClient_Tests.swift +++ b/Tests/StreamChatTests/ChatClient_Tests.swift @@ -340,7 +340,7 @@ final class ChatClient_Tests: XCTestCase { XCTAssertEqual(testEnv.apiClient?.request_endpoint?.method, .delete) } - func test_logout_whenNoCurrentDevice_doesNotRemoveDevice() throws { + func test_logout_whenNoCurrentDevice_doesNotRemoveDevice_shouldClearToken() throws { // GIVEN let client = ChatClient( config: inMemoryStorageConfig, @@ -363,6 +363,7 @@ final class ChatClient_Tests: XCTestCase { // THEN XCTAssertCall(ConnectionRepository_Mock.Signature.disconnect, on: testEnv.connectionRepository!) + XCTAssertCall(AuthenticationRepository_Mock.Signature.logOut, on: testEnv.authenticationRepository!) XCTAssertNil(testEnv.apiClient?.request_endpoint?.path) XCTAssertNil(testEnv.apiClient?.request_endpoint?.method) } diff --git a/Tests/StreamChatTests/Controllers/ChannelController/LivestreamChannelController_Tests.swift b/Tests/StreamChatTests/Controllers/ChannelController/LivestreamChannelController_Tests.swift index 7568c73229c..8ab728e3823 100644 --- a/Tests/StreamChatTests/Controllers/ChannelController/LivestreamChannelController_Tests.swift +++ b/Tests/StreamChatTests/Controllers/ChannelController/LivestreamChannelController_Tests.swift @@ -1342,7 +1342,8 @@ extension LivestreamChannelController_Tests { channel: .mock(cid: controller.cid!), message: messageToDelete, createdAt: .unique, - isHardDelete: true + isHardDelete: true, + deletedForMe: false ) // When @@ -1380,7 +1381,8 @@ extension LivestreamChannelController_Tests { channel: .mock(cid: controller.cid!), message: deletedMessage, createdAt: .unique, - isHardDelete: false + isHardDelete: false, + deletedForMe: false ) // When diff --git a/Tests/StreamChatTests/Controllers/MessageController/MessageController_Tests.swift b/Tests/StreamChatTests/Controllers/MessageController/MessageController_Tests.swift index b5c23edbc21..66efb80f4d0 100644 --- a/Tests/StreamChatTests/Controllers/MessageController/MessageController_Tests.swift +++ b/Tests/StreamChatTests/Controllers/MessageController/MessageController_Tests.swift @@ -913,6 +913,59 @@ final class MessageController_Tests: XCTestCase { XCTAssertEqual(env.messageUpdater.deleteMessage_hard, true) } + func test_deleteMessageForMe_propagatesError() { + // Simulate `deleteMessageForMe` call and catch the completion + var completionError: Error? + controller.deleteMessageForMe { [callbackQueueID] in + AssertTestQueue(withId: callbackQueueID) + completionError = $0 + } + + // Simulate network response with the error + let networkError = TestError() + env.messageUpdater.deleteMessage_completion?(networkError) + + // Assert error is propagated + AssertAsync.willBeEqual(completionError as? TestError, networkError) + } + + func test_deleteMessageForMe_propagatesNilError() { + // Simulate `deleteMessageForMe` call and catch the completion + var completionCalled = false + controller.deleteMessageForMe { [callbackQueueID] in + AssertTestQueue(withId: callbackQueueID) + XCTAssertNil($0) + completionCalled = true + } + + // Keep a weak ref so we can check if it's actually deallocated + weak var weakController = controller + + // (Try to) deallocate the controller + // by not keeping any references to it + controller = nil + + // Simulate successful network response + env.messageUpdater.deleteMessage_completion?(nil) + // Release reference of completion so we can deallocate stuff + env.messageUpdater.deleteMessage_completion = nil + + // Assert completion is called + AssertAsync.willBeTrue(completionCalled) + // `weakController` should be deallocated too + AssertAsync.canBeReleased(&weakController) + } + + func test_deleteMessageForMe_callsMessageUpdater_withCorrectValues() { + // Simulate `deleteMessageForMe` call + controller.deleteMessageForMe() + + // Assert messageUpdater is called with correct `messageId`, hard: false, and deleteForMe: true + XCTAssertEqual(env.messageUpdater.deleteMessage_messageId, controller.messageId) + XCTAssertEqual(env.messageUpdater.deleteMessage_hard, false) + XCTAssertEqual(env.messageUpdater.deleteMessage_deleteForMe, true) + } + // MARK: - Edit message func test_editMessage_propagatesError() { diff --git a/Tests/StreamChatTests/Controllers/PollsControllers/PollVoteListController+Combine_Tests.swift b/Tests/StreamChatTests/Controllers/PollsControllers/PollVoteListController+Combine_Tests.swift index 6810450646e..40600ff4f3e 100644 --- a/Tests/StreamChatTests/Controllers/PollsControllers/PollVoteListController+Combine_Tests.swift +++ b/Tests/StreamChatTests/Controllers/PollsControllers/PollVoteListController+Combine_Tests.swift @@ -79,4 +79,26 @@ final class PollVoteListController_Combine_Tests: iOS13TestCase { XCTAssertEqual(recording.output, .init(arrayLiteral: [.insert(vote, index: .init())])) } + + func test_pollPublisher() { + // Setup Recording publishers + var recording = Record.Recording() + + // Setup the chain + voteListController + .pollPublisher + .sink(receiveValue: { recording.receive($0) }) + .store(in: &cancellables) + + // Keep only the weak reference to the controller. The existing publisher should keep it alive. + weak var controller: PollVoteListController? = voteListController + voteListController = nil + + let poll: Poll = .unique + controller?.delegateCallback { + $0.controller(controller!, didUpdatePoll: poll) + } + + XCTAssertEqual(recording.output, [poll]) + } } diff --git a/Tests/StreamChatTests/Controllers/PollsControllers/PollVoteListController+SwiftUI_Tests.swift b/Tests/StreamChatTests/Controllers/PollsControllers/PollVoteListController+SwiftUI_Tests.swift index f29bc3626c0..cd0459207c7 100644 --- a/Tests/StreamChatTests/Controllers/PollsControllers/PollVoteListController+SwiftUI_Tests.swift +++ b/Tests/StreamChatTests/Controllers/PollsControllers/PollVoteListController+SwiftUI_Tests.swift @@ -66,4 +66,25 @@ final class PollVoteListController_SwiftUI_Tests: iOS13TestCase { AssertAsync.willBeEqual(observableObject.state, newState) } + + func test_observableObject_initialPollValue() { + let observableObject = voteListController.observableObject + + // Initially poll should be nil + XCTAssertNil(observableObject.poll) + } + + func test_observableObject_reactsToDelegateUpdatePollCallback() { + let observableObject = voteListController.observableObject + + // Simulate poll update + let poll: Poll = .unique + voteListController.poll_simulated = poll + + voteListController.delegateCallback { + $0.controller(self.voteListController, didUpdatePoll: poll) + } + + AssertAsync.willBeEqual(observableObject.poll, poll) + } } diff --git a/Tests/StreamChatTests/Controllers/PollsControllers/PollVoteListController_Tests.swift b/Tests/StreamChatTests/Controllers/PollsControllers/PollVoteListController_Tests.swift index ba45a32507d..9422eadadfd 100644 --- a/Tests/StreamChatTests/Controllers/PollsControllers/PollVoteListController_Tests.swift +++ b/Tests/StreamChatTests/Controllers/PollsControllers/PollVoteListController_Tests.swift @@ -208,4 +208,388 @@ final class PollVoteListController_Tests: XCTestCase { wait(for: [exp], timeout: defaultTimeout) XCTAssertTrue(controller.hasLoadedAllVotes) } + + // MARK: - EventsControllerDelegate Tests + + func test_eventsController_didReceiveEvent_PollVoteCastedEvent_withAnswerVote() { + // Create a vote list controller for answers (optionId = nil) + let answerQuery = PollVoteListQuery(pollId: pollId, optionId: nil) + let answerController = PollVoteListController( + query: answerQuery, + client: client, + environment: env + ) + + // Create an answer vote (isAnswer = true) + let answerVote = PollVote.mock( + pollId: pollId, + optionId: nil, + isAnswer: true, + answerText: "Test answer" + ) + + let poll = Poll.unique + let event = PollVoteCastedEvent(vote: answerVote, poll: poll, createdAt: Date()) + + // Track link method calls + var linkCallCount = 0 + var linkedVote: PollVote? + var linkedQuery: PollVoteListQuery? + + // Mock the link method to track calls + let originalLink = client.mockPollsRepository.link + client.mockPollsRepository.link = { pollVote, query in + linkCallCount += 1 + linkedVote = pollVote + linkedQuery = query + } + + // Simulate receiving the event + answerController.eventsController(EventsController(notificationCenter: client.eventNotificationCenter), didReceiveEvent: event) + + // Verify the vote was linked + XCTAssertEqual(linkCallCount, 1) + XCTAssertEqual(linkedVote?.id, answerVote.id) + XCTAssertEqual(linkedQuery?.pollId, answerQuery.pollId) + XCTAssertEqual(linkedQuery?.optionId, answerQuery.optionId) + + // Restore original method + client.mockPollsRepository.link = originalLink + } + + func test_eventsController_didReceiveEvent_PollVoteCastedEvent_withRegularVote() { + // Create a vote list controller for a specific option + let regularQuery = PollVoteListQuery(pollId: pollId, optionId: optionId) + let regularController = PollVoteListController( + query: regularQuery, + client: client, + environment: env + ) + + // Create a regular vote (isAnswer = false) + let regularVote = PollVote.mock( + pollId: pollId, + optionId: optionId, + isAnswer: false + ) + + let poll = Poll.unique + let event = PollVoteCastedEvent(vote: regularVote, poll: poll, createdAt: Date()) + + // Track link method calls + var linkCallCount = 0 + var linkedVote: PollVote? + var linkedQuery: PollVoteListQuery? + + // Mock the link method to track calls + let originalLink = client.mockPollsRepository.link + client.mockPollsRepository.link = { pollVote, query in + linkCallCount += 1 + linkedVote = pollVote + linkedQuery = query + } + + // Simulate receiving the event + regularController.eventsController(EventsController(notificationCenter: client.eventNotificationCenter), didReceiveEvent: event) + + // Verify the vote was linked + XCTAssertEqual(linkCallCount, 1) + XCTAssertEqual(linkedVote?.id, regularVote.id) + XCTAssertEqual(linkedQuery?.pollId, regularQuery.pollId) + XCTAssertEqual(linkedQuery?.optionId, regularQuery.optionId) + + // Restore original method + client.mockPollsRepository.link = originalLink + } + + func test_eventsController_didReceiveEvent_PollVoteChangedEvent_withAnswerVote() { + // Create a vote list controller for answers (optionId = nil) + let answerQuery = PollVoteListQuery(pollId: pollId, optionId: nil) + let answerController = PollVoteListController( + query: answerQuery, + client: client, + environment: env + ) + + // Create an answer vote (isAnswer = true) + let answerVote = PollVote.mock( + pollId: pollId, + optionId: nil, + isAnswer: true, + answerText: "Updated answer" + ) + + let poll = Poll.unique + let event = PollVoteChangedEvent(vote: answerVote, poll: poll, createdAt: Date()) + + // Track link method calls + var linkCallCount = 0 + var linkedVote: PollVote? + var linkedQuery: PollVoteListQuery? + + // Mock the link method to track calls + let originalLink = client.mockPollsRepository.link + client.mockPollsRepository.link = { pollVote, query in + linkCallCount += 1 + linkedVote = pollVote + linkedQuery = query + } + + // Simulate receiving the event + answerController.eventsController(EventsController(notificationCenter: client.eventNotificationCenter), didReceiveEvent: event) + + // Verify the vote was linked + XCTAssertEqual(linkCallCount, 1) + XCTAssertEqual(linkedVote?.id, answerVote.id) + XCTAssertEqual(linkedQuery?.pollId, answerQuery.pollId) + XCTAssertEqual(linkedQuery?.optionId, answerQuery.optionId) + + // Restore original method + client.mockPollsRepository.link = originalLink + } + + func test_eventsController_didReceiveEvent_PollVoteChangedEvent_withRegularVote() { + // Create a vote list controller for a specific option + let regularQuery = PollVoteListQuery(pollId: pollId, optionId: optionId) + let regularController = PollVoteListController( + query: regularQuery, + client: client, + environment: env + ) + + // Create a regular vote (isAnswer = false) + let regularVote = PollVote.mock( + pollId: pollId, + optionId: optionId, + isAnswer: false + ) + + let poll = Poll.unique + let event = PollVoteChangedEvent(vote: regularVote, poll: poll, createdAt: Date()) + + // Track link method calls + var linkCallCount = 0 + var linkedVote: PollVote? + var linkedQuery: PollVoteListQuery? + + // Mock the link method to track calls + let originalLink = client.mockPollsRepository.link + client.mockPollsRepository.link = { pollVote, query in + linkCallCount += 1 + linkedVote = pollVote + linkedQuery = query + } + + // Simulate receiving the event + regularController.eventsController(EventsController(notificationCenter: client.eventNotificationCenter), didReceiveEvent: event) + + // Verify the vote was linked + XCTAssertEqual(linkCallCount, 1) + XCTAssertEqual(linkedVote?.id, regularVote.id) + XCTAssertEqual(linkedQuery?.pollId, regularQuery.pollId) + XCTAssertEqual(linkedQuery?.optionId, regularQuery.optionId) + + // Restore original method + client.mockPollsRepository.link = originalLink + } + + func test_eventsController_didReceiveEvent_ignoresVotesWithDifferentPollId() { + // Create a vote list controller + let controller = PollVoteListController( + query: query, + client: client, + environment: env + ) + + // Create a vote with different poll ID + let differentPollId = String.unique + let vote = PollVote.mock( + pollId: differentPollId, + optionId: optionId, + isAnswer: false + ) + + let poll = Poll.unique + let event = PollVoteCastedEvent(vote: vote, poll: poll, createdAt: Date()) + + // Track link method calls + var linkCallCount = 0 + + // Mock the link method to track calls + let originalLink = client.mockPollsRepository.link + client.mockPollsRepository.link = { _, _ in + linkCallCount += 1 + } + + // Simulate receiving the event + controller.eventsController(EventsController(notificationCenter: client.eventNotificationCenter), didReceiveEvent: event) + + // Verify the vote was NOT linked due to different poll ID + XCTAssertEqual(linkCallCount, 0) + + // Restore original method + client.mockPollsRepository.link = originalLink + } + + func test_eventsController_didReceiveEvent_ignoresAnswerVotesWhenOptionIdIsSet() { + // Create a vote list controller for a specific option + let regularQuery = PollVoteListController( + query: query, + client: client, + environment: env + ) + + // Create an answer vote (isAnswer = true) but controller is for specific option + let answerVote = PollVote.mock( + pollId: pollId, + optionId: nil, + isAnswer: true, + answerText: "Test answer" + ) + + let poll = Poll.unique + let event = PollVoteCastedEvent(vote: answerVote, poll: poll, createdAt: Date()) + + // Track link method calls + var linkCallCount = 0 + + // Mock the link method to track calls + let originalLink = client.mockPollsRepository.link + client.mockPollsRepository.link = { _, _ in + linkCallCount += 1 + } + + // Simulate receiving the event + regularQuery.eventsController(EventsController(notificationCenter: client.eventNotificationCenter), didReceiveEvent: event) + + // Verify the vote was NOT linked because answer votes should only be linked when optionId is nil + XCTAssertEqual(linkCallCount, 0) + + // Restore original method + client.mockPollsRepository.link = originalLink + } + + func test_eventsController_didReceiveEvent_ignoresRegularVotesWhenOptionIdDoesNotMatch() { + // Create a vote list controller for a specific option + let controller = PollVoteListController( + query: query, + client: client, + environment: env + ) + + // Create a regular vote with different option ID + let differentOptionId = String.unique + let vote = PollVote.mock( + pollId: pollId, + optionId: differentOptionId, + isAnswer: false + ) + + let poll = Poll.unique + let event = PollVoteCastedEvent(vote: vote, poll: poll, createdAt: Date()) + + // Track link method calls + var linkCallCount = 0 + + // Mock the link method to track calls + let originalLink = client.mockPollsRepository.link + client.mockPollsRepository.link = { _, _ in + linkCallCount += 1 + } + + // Simulate receiving the event + controller.eventsController(EventsController(notificationCenter: client.eventNotificationCenter), didReceiveEvent: event) + + // Verify the vote was NOT linked because option IDs don't match + XCTAssertEqual(linkCallCount, 0) + + // Restore original method + client.mockPollsRepository.link = originalLink + } + + // MARK: - Poll Observer Tests + + func test_pollProperty_returnsPollFromObserver() { + // Create a poll in the database + let user = UserPayload.dummy(userId: currentUserId) + let poll = dummyPollPayload(id: pollId, user: user) + + try! client.databaseContainer.writeSynchronously { session in + try session.savePoll(payload: poll, cache: nil) + } + + // Synchronize to start observers + controller.synchronize() + + // Verify poll is returned + XCTAssertNotNil(controller.poll) + XCTAssertEqual(controller.poll?.id, pollId) + } + + func test_pollProperty_returnsNilWhenNoPollExists() { + // Don't create any poll in database + controller.synchronize() + + // Verify poll is nil + XCTAssertNil(controller.poll) + } + + func test_pollObserver_notifiesDelegateOnPollUpdate() { + // Create initial poll + let user = UserPayload.dummy(userId: currentUserId) + let initialPoll = dummyPollPayload(id: pollId, user: user) + + try! client.databaseContainer.writeSynchronously { session in + try session.savePoll(payload: initialPoll, cache: nil) + } + + // Set up delegate + let delegate = TestDelegate() + controller.delegate = delegate + + // Wait for expection + let exp = expectation(description: "didUpdatePoll called") + exp.expectedFulfillmentCount = 2 + delegate.didUpdatePollCompletion = { + exp.fulfill() + } + + // Synchronize to start observers + controller.synchronize() + + // Update poll in database + let updatedPoll = dummyPollPayload( + id: pollId, + name: "Updated Poll Name", + user: user + ) + + try! client.databaseContainer.writeSynchronously { session in + try session.savePoll(payload: updatedPoll, cache: nil) + } + + // Verify delegate was notified + waitForExpectations(timeout: defaultTimeout) + XCTAssertEqual(delegate.didUpdatePollCalled, true) + XCTAssertEqual(delegate.updatedPoll?.id, pollId) + XCTAssertEqual(delegate.updatedPoll?.name, "Updated Poll Name") + } +} + +// MARK: - Test Helper + +private class TestDelegate: PollVoteListControllerDelegate { + var didUpdatePollCalled = false + var updatedPoll: Poll? + var didUpdatePollCompletion: (() -> Void)? + + func controller(_ controller: PollVoteListController, didUpdatePoll poll: Poll) { + didUpdatePollCalled = true + updatedPoll = poll + didUpdatePollCompletion?() + } + + func controller(_ controller: PollVoteListController, didChangeVotes changes: [ListChange]) { + // Not used in these tests + } } diff --git a/Tests/StreamChatTests/Database/DTOs/MessageDTO_Tests.swift b/Tests/StreamChatTests/Database/DTOs/MessageDTO_Tests.swift index 86a06327fb9..6444c94a8c2 100644 --- a/Tests/StreamChatTests/Database/DTOs/MessageDTO_Tests.swift +++ b/Tests/StreamChatTests/Database/DTOs/MessageDTO_Tests.swift @@ -5130,4 +5130,75 @@ final class MessageDTO_Tests: XCTestCase { let message = try XCTUnwrap(database.viewContext.message(id: messagePayload.id)?.asModel()) XCTAssertNil(message.draftReply) } + + // MARK: - deletedForMe Tests + + func test_saveMessage_savesDeletedForMe_whenTrue() throws { + // GIVEN + let cid: ChannelId = .unique + let messagePayload = MessagePayload.dummy( + messageId: .unique, + deletedForMe: true + ) + + // WHEN + try database.writeSynchronously { session in + try session.saveCurrentUser(payload: .dummy(userId: .unique, role: .admin)) + try session.saveChannel(payload: .dummy(channel: .dummy(cid: cid))) + try session.saveMessage(payload: messagePayload, for: cid, syncOwnReactions: false, cache: nil) + } + + // THEN + let messageDTO = try XCTUnwrap(database.viewContext.message(id: messagePayload.id)) + XCTAssertEqual(messageDTO.deletedForMe, true) + + let message = try XCTUnwrap(messageDTO.asModel()) + XCTAssertEqual(message.deletedForMe, true) + } + + func test_saveMessage_savesDeletedForMe_whenFalse() throws { + // GIVEN + let cid: ChannelId = .unique + let messagePayload = MessagePayload.dummy( + messageId: .unique, + deletedForMe: false + ) + + // WHEN + try database.writeSynchronously { session in + try session.saveCurrentUser(payload: .dummy(userId: .unique, role: .admin)) + try session.saveChannel(payload: .dummy(channel: .dummy(cid: cid))) + try session.saveMessage(payload: messagePayload, for: cid, syncOwnReactions: false, cache: nil) + } + + // THEN + let messageDTO = try XCTUnwrap(database.viewContext.message(id: messagePayload.id)) + XCTAssertEqual(messageDTO.deletedForMe, false) + + let message = try XCTUnwrap(messageDTO.asModel()) + XCTAssertEqual(message.deletedForMe, false) + } + + func test_saveMessage_savesDeletedForMe_whenNil_defaultsToFalse() throws { + // GIVEN + let cid: ChannelId = .unique + let messagePayload = MessagePayload.dummy( + messageId: .unique, + deletedForMe: nil + ) + + // WHEN + try database.writeSynchronously { session in + try session.saveCurrentUser(payload: .dummy(userId: .unique, role: .admin)) + try session.saveChannel(payload: .dummy(channel: .dummy(cid: cid))) + try session.saveMessage(payload: messagePayload, for: cid, syncOwnReactions: false, cache: nil) + } + + // THEN + let messageDTO = try XCTUnwrap(database.viewContext.message(id: messagePayload.id)) + XCTAssertEqual(messageDTO.deletedForMe, false) + + let message = try XCTUnwrap(messageDTO.asModel()) + XCTAssertEqual(message.deletedForMe, false) + } } diff --git a/Tests/StreamChatTests/WebSocketClient/EventMiddlewares/ChannelVisibilityEventMiddleware_Tests.swift b/Tests/StreamChatTests/WebSocketClient/EventMiddlewares/ChannelVisibilityEventMiddleware_Tests.swift index 7c754fda91c..186548d6d65 100644 --- a/Tests/StreamChatTests/WebSocketClient/EventMiddlewares/ChannelVisibilityEventMiddleware_Tests.swift +++ b/Tests/StreamChatTests/WebSocketClient/EventMiddlewares/ChannelVisibilityEventMiddleware_Tests.swift @@ -236,7 +236,36 @@ final class ChannelVisibilityEventMiddleware_Tests: XCTestCase { // Assert the `isHidden` value is still true XCTAssertTrue(channelDTO.isHidden) } + + func test_messageNewEvent_whenCampaignMessage_doesNotResetIsHidden() throws { + let cid: ChannelId = .unique + + // Create the event + let event = try MessageNewEventDTO( + from: .init( + eventType: .messageNew, + cid: cid, + user: .dummy(userId: .unique), + message: .dummy(messageId: .unique, authorUserId: .unique, campaignId: "campaign_123"), + createdAt: .unique + ) as EventPayload + ) + + // Create a channel in the DB with `isHidden` set to true + try database.writeSynchronously { session in + let dto = try session.saveChannel(payload: XCTestCase().dummyPayload(with: cid)) + dto.isHidden = true + } + + // Simulate incoming event + _ = middleware.handle(event: event, session: database.viewContext) + let channelDTO = try XCTUnwrap(database.viewContext.channel(cid: cid)) + + // Assert the `isHidden` value is still true + XCTAssertTrue(channelDTO.isHidden) + } + func test_notificationMessageNewEvent_resetsHiddenAtValue() throws { let cid: ChannelId = .unique @@ -296,4 +325,34 @@ final class ChannelVisibilityEventMiddleware_Tests: XCTestCase { // Assert the `isHidden` value is still true XCTAssertTrue(channelDTO.isHidden) } + + func test_notificationMessageNewEvent_whenCampaignMessage_doesNotResetIsHidden() throws { + let cid: ChannelId = .unique + + // Create the event + let event = try NotificationMessageNewEventDTO( + from: .init( + eventType: .notificationMessageNew, + cid: cid, + user: .dummy(userId: .unique), + channel: .dummy(cid: cid), + message: .dummy(messageId: .unique, authorUserId: .unique, campaignId: "campaign_123"), + createdAt: .unique + ) + ) + + // Create a channel in the DB with `isHidden` set to true + try database.writeSynchronously { session in + let dto = try session.saveChannel(payload: XCTestCase().dummyPayload(with: cid)) + dto.isHidden = true + } + + // Simulate incoming event + _ = middleware.handle(event: event, session: database.viewContext) + + let channelDTO = try XCTUnwrap(database.viewContext.channel(cid: cid)) + + // Assert the `isHidden` value is still true + XCTAssertTrue(channelDTO.isHidden) + } } diff --git a/Tests/StreamChatTests/WebSocketClient/Events/MessageEvents_Tests.swift b/Tests/StreamChatTests/WebSocketClient/Events/MessageEvents_Tests.swift index 43320e69a41..5eb6f86c621 100644 --- a/Tests/StreamChatTests/WebSocketClient/Events/MessageEvents_Tests.swift +++ b/Tests/StreamChatTests/WebSocketClient/Events/MessageEvents_Tests.swift @@ -82,6 +82,18 @@ final class MessageEvents_Tests: XCTestCase { XCTAssertEqual(event?.hardDelete, true) } + func test_messageDeletedEvent_whenDeletedForMe_deletedForMeIsTrue() throws { + let json = XCTestCase.mockData(fromJSONFile: "MessageDeletedForMe") + let event = try eventDecoder.decode(from: json) as? MessageDeletedEventDTO + XCTAssertEqual(event?.deletedForMe, true) + } + + func test_messageDeletedEvent_whenNotDeletedForMe_deletedForMeIsNil() throws { + let json = XCTestCase.mockData(fromJSONFile: "MessageDeleted") + let event = try eventDecoder.decode(from: json) as? MessageDeletedEventDTO + XCTAssertEqual(event?.deletedForMe, nil) + } + func test_messageDeletedEvent_toDomainEvent() throws { let json = XCTestCase.mockData(fromJSONFile: "MessageDeleted") let event = try eventDecoder.decode(from: json) as? MessageDeletedEventDTO diff --git a/Tests/StreamChatTests/Workers/MessageUpdater_Tests.swift b/Tests/StreamChatTests/Workers/MessageUpdater_Tests.swift index 8291fabacc1..7089d93b2d3 100644 --- a/Tests/StreamChatTests/Workers/MessageUpdater_Tests.swift +++ b/Tests/StreamChatTests/Workers/MessageUpdater_Tests.swift @@ -851,6 +851,48 @@ final class MessageUpdater_Tests: XCTestCase { XCTAssertEqual(channelDTO.previewMessage?.id, firstPreviewMessage.id) } + func test_deleteMessage_withDeleteForMe_sendsCorrectAPICall() throws { + let messageId: MessageId = .unique + + // Create current user in the database + try database.createCurrentUser() + + // Simulate `deleteMessage(messageId:, hard:, deleteForMe:)` call + messageUpdater.deleteMessage(messageId: messageId, hard: false, deleteForMe: true) + + // Assert correct endpoint is called + let expectedEndpoint: Endpoint = .deleteMessage(messageId: messageId, hard: false, deleteForMe: true) + AssertAsync.willBeEqual(apiClient.request_endpoint, AnyEndpoint(expectedEndpoint)) + } + + func test_deleteMessage_withDeleteForMeFalse_sendsCorrectAPICall() throws { + let messageId: MessageId = .unique + + // Create current user in the database + try database.createCurrentUser() + + // Simulate `deleteMessage(messageId:, hard:, deleteForMe:)` call + messageUpdater.deleteMessage(messageId: messageId, hard: false, deleteForMe: false) + + // Assert correct endpoint is called + let expectedEndpoint: Endpoint = .deleteMessage(messageId: messageId, hard: false, deleteForMe: false) + AssertAsync.willBeEqual(apiClient.request_endpoint, AnyEndpoint(expectedEndpoint)) + } + + func test_deleteMessage_withDeleteForMeNil_sendsCorrectAPICall() throws { + let messageId: MessageId = .unique + + // Create current user in the database + try database.createCurrentUser() + + // Simulate `deleteMessage(messageId:, hard:, deleteForMe:)` call + messageUpdater.deleteMessage(messageId: messageId, hard: false, deleteForMe: nil) + + // Assert correct endpoint is called + let expectedEndpoint: Endpoint = .deleteMessage(messageId: messageId, hard: false, deleteForMe: nil) + AssertAsync.willBeEqual(apiClient.request_endpoint, AnyEndpoint(expectedEndpoint)) + } + // MARK: Get message func test_getMessage_shouldForwardSuccess() { diff --git a/Tests/StreamChatUITests/ChatMessage_Tests.swift b/Tests/StreamChatUITests/ChatMessage_Tests.swift index 8072dc8130a..af4ac8fb728 100644 --- a/Tests/StreamChatUITests/ChatMessage_Tests.swift +++ b/Tests/StreamChatUITests/ChatMessage_Tests.swift @@ -429,6 +429,19 @@ final class ChatMessage_Tests: XCTestCase { XCTAssertTrue(deletedMessage.isDeleted) } + func test_isDeleted_whenMessageIsDeletedForMe_returnsTrue() { + let deletedMessage: ChatMessage = .mock( + id: .unique, + cid: .unique, + text: .unique, + author: .mock(id: .unique), + deletedAt: nil, + deletedForMe: true + ) + + XCTAssertTrue(deletedMessage.isDeleted) + } + // MARK: - shouldRenderAsSystemMessage func test_shouldRenderAsSystemMessage_whenMessageIsSystem_returnsTrue() { diff --git a/fastlane/Fastfile b/fastlane/Fastfile index f45aa1c0b2f..2fc4c31ee46 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -793,13 +793,9 @@ desc 'Run source code formatting/linting' lane :run_swift_format do |options| Dir.chdir('..') do strict = options[:strict] ? '--lint' : nil - sources_matrix[:swiftformat].each do |path| - sh("swiftformat #{strict} --config .swiftformat #{path}") - next if path == 'Tests' - - sh("swiftlint lint --config .swiftlint.yml --fix --progress --reporter json #{path}") unless strict - sh("swiftlint lint --config .swiftlint.yml --strict --progress --reporter json #{path}") - end + sh("swiftformat #{strict} --config .swiftformat .") + sh("swiftlint lint --config .swiftlint.yml --fix --progress --reporter json") unless strict + sh("swiftlint lint --config .swiftlint.yml --strict --progress --reporter json") end end @@ -829,8 +825,7 @@ lane :sources_matrix do ruby: ['fastlane', 'Gemfile', 'Gemfile.lock'], size: ['Sources', xcode_project], xcmetrics: ['Sources'], - public_interface: ['Sources'], - swiftformat: ['Sources', 'DemoApp', 'Tests', 'Integration'] + public_interface: ['Sources'] } end @@ -902,7 +897,7 @@ lane :update_img_shields_sdk_sizes do |options| ) end -def frameworks_sizes +private_lane :frameworks_sizes do root_dir = 'Build/SDKSize' archive_dir = "#{root_dir}/DemoApp.xcarchive" @@ -915,7 +910,9 @@ def frameworks_sizes scheme: 'DemoApp', archive_path: archive_dir, export_method: 'ad-hoc', - export_options: 'fastlane/sdk_size_export_options.plist' + export_options: 'fastlane/sdk_size_export_options.plist', + derived_data_path: derived_data_path, + cloned_source_packages_path: source_packages_path ) # Parse the thinned size of Assets.car from Packaging.log @@ -936,3 +933,20 @@ def frameworks_sizes StreamChatUI: stream_chat_ui_size_kb.round(0) } end + +lane :size_analyze do + next unless is_check_required(sources: sources_matrix[:size], force_check: @force_check) + + gym( + scheme: 'DemoApp', + configuration: 'Release', + skip_archive: true, + skip_package_ipa: true, + skip_package_pkg: true, + skip_codesigning: true, + derived_data_path: derived_data_path, + cloned_source_packages_path: source_packages_path + ) + + show_detailed_sdk_size(sdk_names: sdk_names, threshold: 42) +end diff --git a/fastlane/Pluginfile b/fastlane/Pluginfile index 1da41d0e38c..40535271b04 100644 --- a/fastlane/Pluginfile +++ b/fastlane/Pluginfile @@ -5,4 +5,5 @@ gem 'fastlane-plugin-versioning' gem 'fastlane-plugin-create_xcframework' gem 'fastlane-plugin-sonarcloud_metric_kit' -gem 'fastlane-plugin-stream_actions', '0.3.90' +gem 'fastlane-plugin-stream_actions', '0.3.101' +gem 'fastlane-plugin-xcsize', '1.1.0' diff --git a/fastlane/sync_mock_server.rb b/fastlane/sync_mock_server.rb index 55a8cec6029..ff046fcbf38 100644 --- a/fastlane/sync_mock_server.rb +++ b/fastlane/sync_mock_server.rb @@ -114,7 +114,7 @@ def send_giphy_link(channel_id) def create_channel(connection_id) payload = { data: { - members: [STREAM_USER_ID, 'han_solo', 'count_dooku'], + members: [STREAM_USER_ID, 'lando_calrissian', 'count_dooku'], name: 'Sync Mock Server' }, presence: true, @@ -262,6 +262,8 @@ def http_delete(url, headers = STREAM_HEADERS) save_json(JSON.parse(event.data), 'ws_events_member.json') when 'channel.updated' json = JSON.parse(event.data) + json['user']['privacy_settings']['typing_indicators']['enabled'] = true + json['user']['privacy_settings']['read_receipts']['enabled'] = true json['channel']['members'].each do |member| member['user']['privacy_settings']['typing_indicators']['enabled'] = true member['user']['privacy_settings']['read_receipts']['enabled'] = true diff --git a/lefthook.yml b/lefthook.yml index 10e854bb4bb..61221d999af 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -4,11 +4,6 @@ pre-commit: - run: swiftlint lint --config .swiftlint.yml --fix --progress --reporter json {staged_files} glob: "*.{swift}" stage_fixed: true - exclude: - - "**/Generated/**" - - Sources/StreamChatUI/StreamNuke/** - - Sources/StreamChatUI/StreamSwiftyGif/** - - Sources/StreamChatUI/StreamDifferenceKit/** skip: - merge - rebase @@ -16,11 +11,6 @@ pre-commit: - run: swiftformat --config .swiftformat {staged_files} glob: "*.{swift}" stage_fixed: true - exclude: - - "**/Generated/**" - - Sources/StreamChatUI/StreamNuke/** - - Sources/StreamChatUI/StreamSwiftyGif/** - - Sources/StreamChatUI/StreamDifferenceKit/** skip: - merge - rebase @@ -29,10 +19,5 @@ pre-push: jobs: - run: swiftlint lint --config .swiftlint.yml --strict --progress --reporter json {push_files} glob: "*.{swift}" - exclude: - - "**/Generated/**" - - Sources/StreamChatUI/StreamNuke/** - - Sources/StreamChatUI/StreamSwiftyGif/** - - Sources/StreamChatUI/StreamDifferenceKit/** skip: - merge-commit