|
| 1 | +import Foundation |
| 2 | +import XCTest |
| 3 | +import Postbox |
| 4 | +@testable import TelegramCore |
| 5 | +@testable import ChatHistoryModel |
| 6 | + |
| 7 | +final class EphemeralBotCommandTests: XCTestCase { |
| 8 | + private let firstId = PeerId(namespace: Namespaces.Peer.CloudUser, id: ._internalFromInt64Value(1)) |
| 9 | + private let secondId = PeerId(namespace: Namespaces.Peer.CloudUser, id: ._internalFromInt64Value(2)) |
| 10 | + private let chatId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: ._internalFromInt64Value(3)) |
| 11 | + |
| 12 | + private func command(_ name: String, botId: PeerId, username: String?, ephemeral: Bool) -> PeerCommand { |
| 13 | + let peer = TelegramUser(id: botId, accessHash: nil, firstName: "Bot", lastName: nil, username: username, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil) |
| 14 | + return PeerCommand(peer: EnginePeer(peer), command: BotCommand(text: name, description: name, isEphemeral: ephemeral)) |
| 15 | + } |
| 16 | + |
| 17 | + private func message(_ text: String, entities: [MessageTextEntity] = []) -> EnqueueMessage { |
| 18 | + return .message(text: text, attributes: [TextEntitiesMessageAttribute(entities: entities)], inlineStickers: [:], mediaReference: nil, threadId: 42, replyToMessageId: EngineMessageReplySubject(messageId: MessageId(peerId: chatId, namespace: Namespaces.Message.Cloud, id: 7), quote: nil, innerSubject: nil), replyToStoryId: nil, localGroupingKey: nil, correlationId: 11, bubbleUpEmojiOrStickersets: []) |
| 19 | + } |
| 20 | + |
| 21 | + func testResolvesOnlyAdvertisedEphemeralCommands() { |
| 22 | + let commands = PeerCommands(commands: [ |
| 23 | + command("secret", botId: firstId, username: "WhisperBot", ephemeral: true), |
| 24 | + command("help", botId: firstId, username: "WhisperBot", ephemeral: false), |
| 25 | + ]) |
| 26 | + XCTAssertEqual(resolveEphemeralBotCommand(text: "/secret", peerCommands: commands)?.botPeerId, firstId) |
| 27 | + XCTAssertEqual(resolveEphemeralBotCommand(text: "/secret@wHiSpErBoT argument", peerCommands: commands)?.botPeerId, firstId) |
| 28 | + for text in ["/help", "/missing", "/secret@OtherBot", "/secret@", "/secret!", "before /secret", "/", "//secret", "/secret😀"] { |
| 29 | + XCTAssertNil(resolveEphemeralBotCommand(text: text, peerCommands: commands), text) |
| 30 | + } |
| 31 | + } |
| 32 | + |
| 33 | + func testAmbiguousBareCommandDoesNotChooseAnotherBot() { |
| 34 | + let commands = PeerCommands(commands: [ |
| 35 | + command("secret", botId: firstId, username: "FirstBot", ephemeral: true), |
| 36 | + command("secret", botId: secondId, username: "SecondBot", ephemeral: false), |
| 37 | + ]) |
| 38 | + XCTAssertNil(resolveEphemeralBotCommand(text: "/secret", peerCommands: commands)) |
| 39 | + XCTAssertEqual(resolveEphemeralBotCommand(text: "/secret@FirstBot", peerCommands: commands)?.botPeerId, firstId) |
| 40 | + XCTAssertNil(resolveEphemeralBotCommand(text: "/secret@SecondBot", peerCommands: commands)) |
| 41 | + } |
| 42 | + |
| 43 | + func testArgumentsFormattingReplyAndTopicSurviveRouting() throws { |
| 44 | + let text = "/secret@WhisperBot 😀 аргумент\nsecond line" |
| 45 | + let bold = MessageTextEntity(range: 21 ..< 29, type: .Bold) |
| 46 | + let resolved = ephemeralBotCommand(botPeerId: firstId, text: text) |
| 47 | + let messages = resolved.apply(to: [message(text, entities: [bold])]) |
| 48 | + guard case let .message(sentText, attributes, _, _, threadId, reply, _, _, correlationId, _) = try XCTUnwrap(messages.first) else { |
| 49 | + return XCTFail("Expected a text message") |
| 50 | + } |
| 51 | + XCTAssertEqual(sentText, text) |
| 52 | + XCTAssertEqual(threadId, 42) |
| 53 | + XCTAssertEqual(reply?.messageId.id, 7) |
| 54 | + XCTAssertEqual(correlationId, 11) |
| 55 | + let outgoing = try XCTUnwrap(attributes.compactMap { $0 as? EphemeralOutgoingMessageAttribute }.first) |
| 56 | + XCTAssertEqual(outgoing.botPeerId, firstId) |
| 57 | + XCTAssertEqual(outgoing.randomId, 0) |
| 58 | + XCTAssertEqual(outgoing.state, .sending) |
| 59 | + let entities = try XCTUnwrap(attributes.compactMap { $0 as? TextEntitiesMessageAttribute }.first).entities |
| 60 | + XCTAssertTrue(entities.contains(bold)) |
| 61 | + XCTAssertTrue(entities.contains(MessageTextEntity(range: 0 ..< 18, type: .BotCommand))) |
| 62 | + } |
| 63 | + |
| 64 | + func testEveryChunkRemainsPrivateWithoutRepeatingCommandEntity() throws { |
| 65 | + let resolved = ephemeralBotCommand(botPeerId: firstId, text: "/secret argument") |
| 66 | + let messages = resolved.apply(to: [message("/secret " + String(repeating: "x", count: 4088)), message("remaining arguments")]) |
| 67 | + for (index, message) in messages.enumerated() { |
| 68 | + guard case let .message(_, attributes, _, _, _, _, _, _, _, _) = message else { |
| 69 | + return XCTFail("Expected a text message") |
| 70 | + } |
| 71 | + let outgoing = attributes.compactMap { $0 as? EphemeralOutgoingMessageAttribute } |
| 72 | + XCTAssertEqual(outgoing.count, 1) |
| 73 | + XCTAssertEqual(outgoing.first?.botPeerId, firstId) |
| 74 | + let entities = try XCTUnwrap(attributes.compactMap { $0 as? TextEntitiesMessageAttribute }.first).entities |
| 75 | + XCTAssertEqual(entities.filter { $0.type == .BotCommand }.count, index == 0 ? 1 : 0) |
| 76 | + } |
| 77 | + } |
| 78 | + |
| 79 | + func testSelectedCommandDoesNotRequireCachedUsernameOrCommandList() throws { |
| 80 | + let resolved = ephemeralBotCommand(botPeerId: firstId, text: "/secret") |
| 81 | + guard case let .message(_, attributes, _, _, _, _, _, _, _, _) = try XCTUnwrap(resolved.apply(to: [message("/secret")]).first) else { |
| 82 | + return XCTFail("Expected a text message") |
| 83 | + } |
| 84 | + XCTAssertEqual(attributes.compactMap { $0 as? EphemeralOutgoingMessageAttribute }.first?.botPeerId, firstId) |
| 85 | + } |
| 86 | + |
| 87 | + func testExistingCommandEntityIsNotDuplicated() throws { |
| 88 | + let entity = MessageTextEntity(range: 0 ..< 7, type: .BotCommand) |
| 89 | + let resolved = ephemeralBotCommand(botPeerId: firstId, text: "/secret argument") |
| 90 | + guard case let .message(_, attributes, _, _, _, _, _, _, _, _) = try XCTUnwrap(resolved.apply(to: [message("/secret argument", entities: [entity])]).first) else { |
| 91 | + return XCTFail("Expected a text message") |
| 92 | + } |
| 93 | + XCTAssertEqual(attributes.compactMap { $0 as? TextEntitiesMessageAttribute }.first?.entities, [entity]) |
| 94 | + } |
| 95 | + |
| 96 | + func testForwardIsNotConvertedToEphemeralMessage() throws { |
| 97 | + let source = MessageId(peerId: chatId, namespace: Namespaces.Message.Cloud, id: 10) |
| 98 | + let forward = EnqueueMessage.forward(source: source, threadId: 42, grouping: .none, attributes: [], correlationId: 11) |
| 99 | + let resolved = ephemeralBotCommand(botPeerId: firstId, text: "/secret") |
| 100 | + guard case let .forward(sentSource, threadId, _, attributes, correlationId) = try XCTUnwrap(resolved.apply(to: [forward]).first) else { |
| 101 | + return XCTFail("Expected a forward") |
| 102 | + } |
| 103 | + XCTAssertEqual(sentSource, source) |
| 104 | + XCTAssertEqual(threadId, 42) |
| 105 | + XCTAssertEqual(correlationId, 11) |
| 106 | + XCTAssertTrue(attributes.isEmpty) |
| 107 | + } |
| 108 | +} |
0 commit comments