core: multi forward api (#4704)

This commit is contained in:
spaced4ndy 2024-08-22 21:36:35 +04:00 committed by GitHub
parent c485837910
commit 791489e943
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 601 additions and 343 deletions

View file

@ -1848,7 +1848,6 @@ public enum ChatErrorType: Decodable, Hashable {
case inlineFileProhibited(fileId: Int64) case inlineFileProhibited(fileId: Int64)
case invalidQuote case invalidQuote
case invalidForward case invalidForward
case forwardNoFile
case invalidChatItemUpdate case invalidChatItemUpdate
case invalidChatItemDelete case invalidChatItemDelete
case hasCurrentCall case hasCurrentCall

View file

@ -46,7 +46,7 @@ mySquaringBot _user cc = do
CRContactConnected _ contact _ -> do CRContactConnected _ contact _ -> do
contactConnected contact contactConnected contact
sendMessage cc contact welcomeMessage sendMessage cc contact welcomeMessage
CRNewChatItem _ (AChatItem _ SMDRcv (DirectChat contact) ChatItem {content = mc@CIRcvMsgContent {}}) -> do CRNewChatItems {chatItems = (AChatItem _ SMDRcv (DirectChat contact) ChatItem {content = mc@CIRcvMsgContent {}}) : _} -> do
let msg = T.unpack $ ciContentToText mc let msg = T.unpack $ ciContentToText mc
number_ = readMaybe msg :: Maybe Integer number_ = readMaybe msg :: Maybe Integer
sendMessage cc contact $ case number_ of sendMessage cc contact $ case number_ of

View file

@ -40,7 +40,7 @@ broadcastBot BroadcastBotOpts {publishers, welcomeMessage, prohibitedMessage} _u
CRContactConnected _ ct _ -> do CRContactConnected _ ct _ -> do
contactConnected ct contactConnected ct
sendMessage cc ct welcomeMessage sendMessage cc ct welcomeMessage
CRNewChatItem _ (AChatItem _ SMDRcv (DirectChat ct) ci@ChatItem {content = CIRcvMsgContent mc}) CRNewChatItems {chatItems = (AChatItem _ SMDRcv (DirectChat ct) ci@ChatItem {content = CIRcvMsgContent mc}) : _}
| publisher `elem` publishers -> | publisher `elem` publishers ->
if allowContent mc if allowContent mc
then do then do

View file

@ -73,7 +73,7 @@ crDirectoryEvent = \case
CRGroupDeleted {groupInfo} -> Just $ DEGroupDeleted groupInfo CRGroupDeleted {groupInfo} -> Just $ DEGroupDeleted groupInfo
CRChatItemUpdated {chatItem = AChatItem _ SMDRcv (DirectChat ct) _} -> Just $ DEItemEditIgnored ct CRChatItemUpdated {chatItem = AChatItem _ SMDRcv (DirectChat ct) _} -> Just $ DEItemEditIgnored ct
CRChatItemsDeleted {chatItemDeletions = ((ChatItemDeletion (AChatItem _ SMDRcv (DirectChat ct) _) _) : _), byUser = False} -> Just $ DEItemDeleteIgnored ct CRChatItemsDeleted {chatItemDeletions = ((ChatItemDeletion (AChatItem _ SMDRcv (DirectChat ct) _) _) : _), byUser = False} -> Just $ DEItemDeleteIgnored ct
CRNewChatItem {chatItem = AChatItem _ SMDRcv (DirectChat ct) ci@ChatItem {content = CIRcvMsgContent mc, meta = CIMeta {itemLive}}} -> CRNewChatItems {chatItems = (AChatItem _ SMDRcv (DirectChat ct) ci@ChatItem {content = CIRcvMsgContent mc, meta = CIMeta {itemLive}}) : _} ->
Just $ case (mc, itemLive) of Just $ case (mc, itemLive) of
(MCText t, Nothing) -> DEContactCommand ct ciId $ fromRight err $ A.parseOnly (directoryCmdP <* A.endOfInput) $ T.dropWhileEnd isSpace t (MCText t, Nothing) -> DEContactCommand ct ciId $ fromRight err $ A.parseOnly (directoryCmdP <* A.endOfInput) $ T.dropWhileEnd isSpace t
_ -> DEUnsupportedMessage ct ciId _ -> DEUnsupportedMessage ct ciId

File diff suppressed because it is too large Load diff

View file

@ -11,6 +11,7 @@ import Control.Concurrent.Async
import Control.Concurrent.STM import Control.Concurrent.STM
import Control.Monad import Control.Monad
import qualified Data.ByteString.Char8 as B import qualified Data.ByteString.Char8 as B
import Data.List.NonEmpty (NonEmpty (..))
import qualified Data.Text as T import qualified Data.Text as T
import Simplex.Chat.Controller import Simplex.Chat.Controller
import Simplex.Chat.Core import Simplex.Chat.Core
@ -31,7 +32,7 @@ chatBotRepl welcome answer _user cc = do
CRContactConnected _ contact _ -> do CRContactConnected _ contact _ -> do
contactConnected contact contactConnected contact
void $ sendMessage cc contact welcome void $ sendMessage cc contact welcome
CRNewChatItem _ (AChatItem _ SMDRcv (DirectChat contact) ChatItem {content = mc@CIRcvMsgContent {}}) -> do CRNewChatItems {chatItems = (AChatItem _ SMDRcv (DirectChat contact) ChatItem {content = mc@CIRcvMsgContent {}}) : _} -> do
let msg = T.unpack $ ciContentToText mc let msg = T.unpack $ ciContentToText mc
void $ sendMessage cc contact =<< answer contact msg void $ sendMessage cc contact =<< answer contact msg
_ -> pure () _ -> pure ()
@ -68,8 +69,8 @@ sendComposedMessage cc = sendComposedMessage' cc . contactId'
sendComposedMessage' :: ChatController -> ContactId -> Maybe ChatItemId -> MsgContent -> IO () sendComposedMessage' :: ChatController -> ContactId -> Maybe ChatItemId -> MsgContent -> IO ()
sendComposedMessage' cc ctId quotedItemId msgContent = do sendComposedMessage' cc ctId quotedItemId msgContent = do
let cm = ComposedMessage {fileSource = Nothing, quotedItemId, msgContent} let cm = ComposedMessage {fileSource = Nothing, quotedItemId, msgContent}
sendChatCmd cc (APISendMessage (ChatRef CTDirect ctId) False Nothing cm) >>= \case sendChatCmd cc (APISendMessages (ChatRef CTDirect ctId) False Nothing (cm :| [])) >>= \case
CRNewChatItem {} -> printLog cc CLLInfo $ "sent message to contact ID " <> show ctId CRNewChatItems {} -> printLog cc CLLInfo $ "sent message to contact ID " <> show ctId
r -> putStrLn $ "unexpected send message response: " <> show r r -> putStrLn $ "unexpected send message response: " <> show r
deleteMessage :: ChatController -> Contact -> ChatItemId -> IO () deleteMessage :: ChatController -> Contact -> ChatItemId -> IO ()

View file

@ -292,13 +292,13 @@ data ChatCommand
| APIGetChat ChatRef ChatPagination (Maybe String) | APIGetChat ChatRef ChatPagination (Maybe String)
| APIGetChatItems ChatPagination (Maybe String) | APIGetChatItems ChatPagination (Maybe String)
| APIGetChatItemInfo ChatRef ChatItemId | APIGetChatItemInfo ChatRef ChatItemId
| APISendMessage {chatRef :: ChatRef, liveMessage :: Bool, ttl :: Maybe Int, composedMessage :: ComposedMessage} | APISendMessages {chatRef :: ChatRef, liveMessage :: Bool, ttl :: Maybe Int, composedMessages :: NonEmpty ComposedMessage}
| APICreateChatItem {noteFolderId :: NoteFolderId, composedMessage :: ComposedMessage} | APICreateChatItems {noteFolderId :: NoteFolderId, composedMessages :: NonEmpty ComposedMessage}
| APIUpdateChatItem {chatRef :: ChatRef, chatItemId :: ChatItemId, liveMessage :: Bool, msgContent :: MsgContent} | APIUpdateChatItem {chatRef :: ChatRef, chatItemId :: ChatItemId, liveMessage :: Bool, msgContent :: MsgContent}
| APIDeleteChatItem ChatRef (NonEmpty ChatItemId) CIDeleteMode | APIDeleteChatItem ChatRef (NonEmpty ChatItemId) CIDeleteMode
| APIDeleteMemberChatItem GroupId (NonEmpty ChatItemId) | APIDeleteMemberChatItem GroupId (NonEmpty ChatItemId)
| APIChatItemReaction {chatRef :: ChatRef, chatItemId :: ChatItemId, add :: Bool, reaction :: MsgReaction} | APIChatItemReaction {chatRef :: ChatRef, chatItemId :: ChatItemId, add :: Bool, reaction :: MsgReaction}
| APIForwardChatItem {toChatRef :: ChatRef, fromChatRef :: ChatRef, chatItemId :: ChatItemId, ttl :: Maybe Int} | APIForwardChatItems {toChatRef :: ChatRef, fromChatRef :: ChatRef, chatItemIds :: NonEmpty ChatItemId, ttl :: Maybe Int}
| APIUserRead UserId | APIUserRead UserId
| UserRead | UserRead
| APIChatRead ChatRef (Maybe (ChatItemId, ChatItemId)) | APIChatRead ChatRef (Maybe (ChatItemId, ChatItemId))
@ -597,7 +597,7 @@ data ChatResponse
| CRContactCode {user :: User, contact :: Contact, connectionCode :: Text} | CRContactCode {user :: User, contact :: Contact, connectionCode :: Text}
| CRGroupMemberCode {user :: User, groupInfo :: GroupInfo, member :: GroupMember, connectionCode :: Text} | CRGroupMemberCode {user :: User, groupInfo :: GroupInfo, member :: GroupMember, connectionCode :: Text}
| CRConnectionVerified {user :: User, verified :: Bool, expectedCode :: Text} | CRConnectionVerified {user :: User, verified :: Bool, expectedCode :: Text}
| CRNewChatItem {user :: User, chatItem :: AChatItem} | CRNewChatItems {user :: User, chatItems :: [AChatItem]}
| CRChatItemStatusUpdated {user :: User, chatItem :: AChatItem} | CRChatItemStatusUpdated {user :: User, chatItem :: AChatItem}
| CRChatItemUpdated {user :: User, chatItem :: AChatItem} | CRChatItemUpdated {user :: User, chatItem :: AChatItem}
| CRChatItemNotChanged {user :: User, chatItem :: AChatItem} | CRChatItemNotChanged {user :: User, chatItem :: AChatItem}
@ -1178,7 +1178,6 @@ data ChatErrorType
| CEInlineFileProhibited {fileId :: FileTransferId} | CEInlineFileProhibited {fileId :: FileTransferId}
| CEInvalidQuote | CEInvalidQuote
| CEInvalidForward | CEInvalidForward
| CEForwardNoFile
| CEInvalidChatItemUpdate | CEInvalidChatItemUpdate
| CEInvalidChatItemDelete | CEInvalidChatItemDelete
| CEHasCurrentCall | CEHasCurrentCall

View file

@ -17,16 +17,18 @@ import Simplex.Chat.Messages
data MsgBatch = MsgBatch ByteString [SndMessage] data MsgBatch = MsgBatch ByteString [SndMessage]
-- | Batches [SndMessage] into batches of ByteStrings in form of JSON arrays. -- | Batches SndMessages in [Either ChatError SndMessage] into batches of ByteStrings in form of JSON arrays.
-- Preserves original errors in the list.
-- Does not check if the resulting batch is a valid JSON. -- Does not check if the resulting batch is a valid JSON.
-- If a single element is passed, it is returned as is (a JSON string). -- If a single element is passed, it is returned as is (a JSON string).
-- If an element exceeds maxLen, it is returned as ChatError. -- If an element exceeds maxLen, it is returned as ChatError.
batchMessages :: Int -> [SndMessage] -> [Either ChatError MsgBatch] batchMessages :: Int -> [Either ChatError SndMessage] -> [Either ChatError MsgBatch]
batchMessages maxLen = addBatch . foldr addToBatch ([], [], 0, 0) batchMessages maxLen = addBatch . foldr addToBatch ([], [], 0, 0)
where where
msgBatch batch = Right (MsgBatch (encodeMessages batch) batch) msgBatch batch = Right (MsgBatch (encodeMessages batch) batch)
addToBatch :: SndMessage -> ([Either ChatError MsgBatch], [SndMessage], Int, Int) -> ([Either ChatError MsgBatch], [SndMessage], Int, Int) addToBatch :: Either ChatError SndMessage -> ([Either ChatError MsgBatch], [SndMessage], Int, Int) -> ([Either ChatError MsgBatch], [SndMessage], Int, Int)
addToBatch msg@SndMessage {msgBody} acc@(batches, batch, len, n) addToBatch (Left err) acc = (Left err : addBatch acc, [], 0, 0) -- step over original error
addToBatch (Right msg@SndMessage {msgBody}) acc@(batches, batch, len, n)
| batchLen <= maxLen = (batches, msg : batch, len', n + 1) | batchLen <= maxLen = (batches, msg : batch, len', n + 1)
| msgLen <= maxLen = (addBatch acc, [msg], msgLen, 1) | msgLen <= maxLen = (addBatch acc, [msg], msgLen, 1)
| otherwise = (errLarge msg : addBatch acc, [], 0, 0) | otherwise = (errLarge msg : addBatch acc, [], 0, 0)

View file

@ -966,20 +966,20 @@ lookupFileTransferRedirectMeta db User {userId} fileId = do
redirects <- DB.query db "SELECT file_id FROM files WHERE user_id = ? AND redirect_file_id = ?" (userId, fileId) redirects <- DB.query db "SELECT file_id FROM files WHERE user_id = ? AND redirect_file_id = ?" (userId, fileId)
rights <$> mapM (runExceptT . getFileTransferMeta_ db userId . fromOnly) redirects rights <$> mapM (runExceptT . getFileTransferMeta_ db userId . fromOnly) redirects
createLocalFile :: ToField (CIFileStatus d) => CIFileStatus d -> DB.Connection -> User -> NoteFolder -> ChatItemId -> UTCTime -> CryptoFile -> Integer -> Integer -> IO Int64 createLocalFile :: ToField (CIFileStatus d) => CIFileStatus d -> DB.Connection -> User -> NoteFolder -> UTCTime -> CryptoFile -> Integer -> Integer -> IO Int64
createLocalFile fileStatus db User {userId} NoteFolder {noteFolderId} chatItemId itemTs CryptoFile {filePath, cryptoArgs} fileSize fileChunkSize = do createLocalFile fileStatus db User {userId} NoteFolder {noteFolderId} itemTs CryptoFile {filePath, cryptoArgs} fileSize fileChunkSize = do
DB.execute DB.execute
db db
[sql| [sql|
INSERT INTO files INSERT INTO files
( user_id, note_folder_id, chat_item_id, ( user_id, note_folder_id,
file_name, file_path, file_size, file_name, file_path, file_size,
file_crypto_key, file_crypto_nonce, file_crypto_key, file_crypto_nonce,
chunk_size, file_inline, ci_file_status, protocol, created_at, updated_at chunk_size, file_inline, ci_file_status, protocol, created_at, updated_at
) )
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)
|] |]
( (userId, noteFolderId, chatItemId) ( (userId, noteFolderId)
:. (takeFileName filePath, filePath, fileSize) :. (takeFileName filePath, filePath, fileSize)
:. maybe (Nothing, Nothing) (\(CFArgs key nonce) -> (Just key, Just nonce)) cryptoArgs :. maybe (Nothing, Nothing) (\(CFArgs key nonce) -> (Just key, Just nonce)) cryptoArgs
:. (fileChunkSize, Nothing :: Maybe InlineFileMode, fileStatus, FPLocal, itemTs, itemTs) :. (fileChunkSize, Nothing :: Maybe InlineFileMode, fileStatus, FPLocal, itemTs, itemTs)

View file

@ -69,7 +69,7 @@ runInputLoop ct@ChatTerminal {termState, liveMessageState} cc = forever $ do
Nothing -> setActive ct "" Nothing -> setActive ct ""
Just rhId -> updateRemoteUser ct u rhId Just rhId -> updateRemoteUser ct u rhId
CRChatItems u chatName_ _ -> whenCurrUser cc u $ mapM_ (setActive ct . chatActiveTo) chatName_ CRChatItems u chatName_ _ -> whenCurrUser cc u $ mapM_ (setActive ct . chatActiveTo) chatName_
CRNewChatItem u (AChatItem _ SMDSnd cInfo _) -> whenCurrUser cc u $ setActiveChat ct cInfo CRNewChatItems u ((AChatItem _ SMDSnd cInfo _) : _) -> whenCurrUser cc u $ setActiveChat ct cInfo
CRChatItemUpdated u (AChatItem _ SMDSnd cInfo _) -> whenCurrUser cc u $ setActiveChat ct cInfo CRChatItemUpdated u (AChatItem _ SMDSnd cInfo _) -> whenCurrUser cc u $ setActiveChat ct cInfo
CRChatItemsDeleted u ((ChatItemDeletion (AChatItem _ _ cInfo _) _) : _) _ _ -> whenCurrUser cc u $ setActiveChat ct cInfo CRChatItemsDeleted u ((ChatItemDeletion (AChatItem _ _ cInfo _) _) : _) _ _ -> whenCurrUser cc u $ setActiveChat ct cInfo
CRContactDeleted u c -> whenCurrUser cc u $ unsetActiveContact ct c CRContactDeleted u c -> whenCurrUser cc u $ unsetActiveContact ct c
@ -93,7 +93,7 @@ runInputLoop ct@ChatTerminal {termState, liveMessageState} cc = forever $ do
Right SendMessageBroadcast {} -> True Right SendMessageBroadcast {} -> True
_ -> False _ -> False
startLiveMessage :: Either a ChatCommand -> ChatResponse -> IO () startLiveMessage :: Either a ChatCommand -> ChatResponse -> IO ()
startLiveMessage (Right (SendLiveMessage chatName msg)) (CRNewChatItem _ (AChatItem cType SMDSnd _ ChatItem {meta = CIMeta {itemId}})) = do startLiveMessage (Right (SendLiveMessage chatName msg)) (CRNewChatItems {chatItems = [AChatItem cType SMDSnd _ ChatItem {meta = CIMeta {itemId}}]}) = do
whenM (isNothing <$> readTVarIO liveMessageState) $ do whenM (isNothing <$> readTVarIO liveMessageState) $ do
let s = T.unpack msg let s = T.unpack msg
int = case cType of SCTGroup -> 5000000; _ -> 3000000 :: Int int = case cType of SCTGroup -> 5000000; _ -> 3000000 :: Int

View file

@ -44,7 +44,7 @@ simplexChatCLI' cfg opts@ChatOpts {chatCmd, chatCmdLog, chatCmdDelay, chatServer
when (chatCmdLog /= CCLNone) . void . forkIO . forever $ do when (chatCmdLog /= CCLNone) . void . forkIO . forever $ do
(_, _, r') <- atomically . readTBQueue $ outputQ cc (_, _, r') <- atomically . readTBQueue $ outputQ cc
case r' of case r' of
CRNewChatItem {} -> printResponse r' CRNewChatItems {} -> printResponse r'
_ -> when (chatCmdLog == CCLAll) $ printResponse r' _ -> when (chatCmdLog == CCLAll) $ printResponse r'
sendChatCmdStr cc chatCmd >>= printResponse sendChatCmdStr cc chatCmd >>= printResponse
threadDelay $ chatCmdDelay * 1000000 threadDelay $ chatCmdDelay * 1000000

View file

@ -147,7 +147,7 @@ runTerminalOutput ct cc@ChatController {outputQ, showLiveItems, logFilePath} Cha
forever $ do forever $ do
(_, outputRH, r) <- atomically $ readTBQueue outputQ (_, outputRH, r) <- atomically $ readTBQueue outputQ
case r of case r of
CRNewChatItem u ci -> when markRead $ markChatItemRead u ci CRNewChatItems u (ci : _) -> when markRead $ markChatItemRead u ci -- At the moment of writing received items are created one at a time
CRChatItemUpdated u ci -> when markRead $ markChatItemRead u ci CRChatItemUpdated u ci -> when markRead $ markChatItemRead u ci
CRRemoteHostConnected {remoteHost = RemoteHostInfo {remoteHostId}} -> getRemoteUser remoteHostId CRRemoteHostConnected {remoteHost = RemoteHostInfo {remoteHostId}} -> getRemoteUser remoteHostId
CRRemoteHostStopped {remoteHostId_} -> mapM_ removeRemoteUser remoteHostId_ CRRemoteHostStopped {remoteHostId_} -> mapM_ removeRemoteUser remoteHostId_
@ -175,7 +175,8 @@ runTerminalOutput ct cc@ChatController {outputQ, showLiveItems, logFilePath} Cha
responseNotification :: ChatTerminal -> ChatController -> ChatResponse -> IO () responseNotification :: ChatTerminal -> ChatController -> ChatResponse -> IO ()
responseNotification t@ChatTerminal {sendNotification} cc = \case responseNotification t@ChatTerminal {sendNotification} cc = \case
CRNewChatItem u (AChatItem _ SMDRcv cInfo ci@ChatItem {chatDir, content = CIRcvMsgContent mc, formattedText}) -> -- At the moment of writing received items are created one at a time
CRNewChatItems u ((AChatItem _ SMDRcv cInfo ci@ChatItem {chatDir, content = CIRcvMsgContent mc, formattedText}) : _) ->
when (chatDirNtf u cInfo chatDir $ isMention ci) $ do when (chatDirNtf u cInfo chatDir $ isMention ci) $ do
whenCurrUser cc u $ setActiveChat t cInfo whenCurrUser cc u $ setActiveChat t cInfo
case (cInfo, chatDir) of case (cInfo, chatDir) of

View file

@ -120,7 +120,10 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe
CRConnectionVerified u verified code -> ttyUser u [plain $ if verified then "connection verified" else "connection not verified, current code is " <> code] CRConnectionVerified u verified code -> ttyUser u [plain $ if verified then "connection verified" else "connection not verified, current code is " <> code]
CRContactCode u ct code -> ttyUser u $ viewContactCode ct code testView CRContactCode u ct code -> ttyUser u $ viewContactCode ct code testView
CRGroupMemberCode u g m code -> ttyUser u $ viewGroupMemberCode g m code testView CRGroupMemberCode u g m code -> ttyUser u $ viewGroupMemberCode g m code testView
CRNewChatItem u (AChatItem _ _ chat item) -> ttyUser u $ unmuted u chat item $ viewChatItem chat item False ts tz <> viewItemReactions item CRNewChatItems u chatItems ->
concatMap
(\(AChatItem _ _ chat item) -> ttyUser u $ unmuted u chat item $ viewChatItem chat item False ts tz <> viewItemReactions item)
chatItems
CRChatItems u _ chatItems -> ttyUser u $ concatMap (\(AChatItem _ _ chat item) -> viewChatItem chat item True ts tz <> viewItemReactions item) chatItems CRChatItems u _ chatItems -> ttyUser u $ concatMap (\(AChatItem _ _ chat item) -> viewChatItem chat item True ts tz <> viewItemReactions item) chatItems
CRChatItemInfo u ci ciInfo -> ttyUser u $ viewChatItemInfo ci ciInfo tz CRChatItemInfo u ci ciInfo -> ttyUser u $ viewChatItemInfo ci ciInfo tz
CRChatItemId u itemId -> ttyUser u [plain $ maybe "no item" show itemId] CRChatItemId u itemId -> ttyUser u [plain $ maybe "no item" show itemId]
@ -2025,7 +2028,6 @@ viewChatError isCmd logLevel testView = \case
CEInlineFileProhibited _ -> ["A small file sent without acceptance - you can enable receiving such files with -f option."] CEInlineFileProhibited _ -> ["A small file sent without acceptance - you can enable receiving such files with -f option."]
CEInvalidQuote -> ["cannot reply to this message"] CEInvalidQuote -> ["cannot reply to this message"]
CEInvalidForward -> ["cannot forward this message"] CEInvalidForward -> ["cannot forward this message"]
CEForwardNoFile -> ["cannot forward this message, file not found"]
CEInvalidChatItemUpdate -> ["cannot update this item"] CEInvalidChatItemUpdate -> ["cannot update this item"]
CEInvalidChatItemDelete -> ["cannot delete this item"] CEInvalidChatItemDelete -> ["cannot delete this item"]
CEHasCurrentCall -> ["call already in progress"] CEHasCurrentCall -> ["call already in progress"]

View file

@ -52,6 +52,7 @@ chatDirectTests = do
it "repeat AUTH errors disable contact" testRepeatAuthErrorsDisableContact it "repeat AUTH errors disable contact" testRepeatAuthErrorsDisableContact
it "should send multiline message" testMultilineMessage it "should send multiline message" testMultilineMessage
it "send large message" testLargeMessage it "send large message" testLargeMessage
it "send multiple messages api" testSendMulti
describe "duplicate contacts" $ do describe "duplicate contacts" $ do
it "duplicate contacts are separate (contacts don't merge)" testDuplicateContactsSeparate it "duplicate contacts are separate (contacts don't merge)" testDuplicateContactsSeparate
it "new contact is separate with multiple duplicate contacts (contacts don't merge)" testDuplicateContactsMultipleSeparate it "new contact is separate with multiple duplicate contacts (contacts don't merge)" testDuplicateContactsMultipleSeparate
@ -839,6 +840,18 @@ testLargeMessage =
bob <## "contact alice changed to alice2" bob <## "contact alice changed to alice2"
bob <## "use @alice2 <message> to send messages" bob <## "use @alice2 <message> to send messages"
testSendMulti :: HasCallStack => FilePath -> IO ()
testSendMulti =
testChat2 aliceProfile bobProfile $
\alice bob -> do
connectUsers alice bob
alice ##> "/_send @2 json [{\"msgContent\": {\"type\": \"text\", \"text\": \"test 1\"}}, {\"msgContent\": {\"type\": \"text\", \"text\": \"test 2\"}}]"
alice <# "@bob test 1"
alice <# "@bob test 2"
bob <# "alice> test 1"
bob <# "alice> test 2"
testGetSetSMPServers :: HasCallStack => FilePath -> IO () testGetSetSMPServers :: HasCallStack => FilePath -> IO ()
testGetSetSMPServers = testGetSetSMPServers =
testChat2 aliceProfile bobProfile $ testChat2 aliceProfile bobProfile $
@ -2162,7 +2175,7 @@ testSetChatItemTTL =
-- chat item with file -- chat item with file
alice #$> ("/_files_folder ./tests/tmp/app_files", id, "ok") alice #$> ("/_files_folder ./tests/tmp/app_files", id, "ok")
copyFile "./tests/fixtures/test.jpg" "./tests/tmp/app_files/test.jpg" copyFile "./tests/fixtures/test.jpg" "./tests/tmp/app_files/test.jpg"
alice ##> "/_send @2 json {\"filePath\": \"test.jpg\", \"msgContent\": {\"text\":\"\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}" alice ##> "/_send @2 json [{\"filePath\": \"test.jpg\", \"msgContent\": {\"text\":\"\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}]"
alice <# "/f @bob test.jpg" alice <# "/f @bob test.jpg"
alice <## "use /fc 1 to cancel sending" alice <## "use /fc 1 to cancel sending"
bob <# "alice> sends file test.jpg (136.5 KiB / 139737 bytes)" bob <# "alice> sends file test.jpg (136.5 KiB / 139737 bytes)"
@ -2410,7 +2423,7 @@ setupDesynchronizedRatchet tmp alice = do
(bob </) (bob </)
bob ##> "/tail @alice 1" bob ##> "/tail @alice 1"
bob <# "alice> decryption error, possibly due to the device change (header, 3 messages)" bob <# "alice> decryption error, possibly due to the device change (header, 3 messages)"
bob ##> "@alice 1" bob `send` "@alice 1"
bob <## "error: command is prohibited, sendMessagesB: send prohibited" bob <## "error: command is prohibited, sendMessagesB: send prohibited"
(alice </) (alice </)
where where

View file

@ -64,7 +64,7 @@ runTestMessageWithFile :: HasCallStack => FilePath -> IO ()
runTestMessageWithFile = testChat2 aliceProfile bobProfile $ \alice bob -> withXFTPServer $ do runTestMessageWithFile = testChat2 aliceProfile bobProfile $ \alice bob -> withXFTPServer $ do
connectUsers alice bob connectUsers alice bob
alice ##> "/_send @2 json {\"filePath\": \"./tests/fixtures/test.jpg\", \"msgContent\": {\"type\": \"text\", \"text\": \"hi, sending a file\"}}" alice ##> "/_send @2 json [{\"filePath\": \"./tests/fixtures/test.jpg\", \"msgContent\": {\"type\": \"text\", \"text\": \"hi, sending a file\"}}]"
alice <# "@bob hi, sending a file" alice <# "@bob hi, sending a file"
alice <# "/f @bob ./tests/fixtures/test.jpg" alice <# "/f @bob ./tests/fixtures/test.jpg"
alice <## "use /fc 1 to cancel sending" alice <## "use /fc 1 to cancel sending"
@ -91,7 +91,7 @@ testSendImage =
testChat2 aliceProfile bobProfile $ testChat2 aliceProfile bobProfile $
\alice bob -> withXFTPServer $ do \alice bob -> withXFTPServer $ do
connectUsers alice bob connectUsers alice bob
alice ##> "/_send @2 json {\"filePath\": \"./tests/fixtures/test.jpg\", \"msgContent\": {\"text\":\"\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}" alice ##> "/_send @2 json [{\"filePath\": \"./tests/fixtures/test.jpg\", \"msgContent\": {\"text\":\"\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}]"
alice <# "/f @bob ./tests/fixtures/test.jpg" alice <# "/f @bob ./tests/fixtures/test.jpg"
alice <## "use /fc 1 to cancel sending" alice <## "use /fc 1 to cancel sending"
bob <# "alice> sends file test.jpg (136.5 KiB / 139737 bytes)" bob <# "alice> sends file test.jpg (136.5 KiB / 139737 bytes)"
@ -122,7 +122,7 @@ testSenderMarkItemDeleted =
testChat2 aliceProfile bobProfile $ testChat2 aliceProfile bobProfile $
\alice bob -> withXFTPServer $ do \alice bob -> withXFTPServer $ do
connectUsers alice bob connectUsers alice bob
alice ##> "/_send @2 json {\"filePath\": \"./tests/fixtures/test_1MB.pdf\", \"msgContent\": {\"type\": \"text\", \"text\": \"hi, sending a file\"}}" alice ##> "/_send @2 json [{\"filePath\": \"./tests/fixtures/test_1MB.pdf\", \"msgContent\": {\"type\": \"text\", \"text\": \"hi, sending a file\"}}]"
alice <# "@bob hi, sending a file" alice <# "@bob hi, sending a file"
alice <# "/f @bob ./tests/fixtures/test_1MB.pdf" alice <# "/f @bob ./tests/fixtures/test_1MB.pdf"
alice <## "use /fc 1 to cancel sending" alice <## "use /fc 1 to cancel sending"
@ -147,7 +147,7 @@ testFilesFoldersSendImage =
connectUsers alice bob connectUsers alice bob
alice #$> ("/_files_folder ./tests/fixtures", id, "ok") alice #$> ("/_files_folder ./tests/fixtures", id, "ok")
bob #$> ("/_files_folder ./tests/tmp/app_files", id, "ok") bob #$> ("/_files_folder ./tests/tmp/app_files", id, "ok")
alice ##> "/_send @2 json {\"filePath\": \"test.jpg\", \"msgContent\": {\"text\":\"\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}" alice ##> "/_send @2 json [{\"filePath\": \"test.jpg\", \"msgContent\": {\"text\":\"\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}]"
alice <# "/f @bob test.jpg" alice <# "/f @bob test.jpg"
alice <## "use /fc 1 to cancel sending" alice <## "use /fc 1 to cancel sending"
bob <# "alice> sends file test.jpg (136.5 KiB / 139737 bytes)" bob <# "alice> sends file test.jpg (136.5 KiB / 139737 bytes)"
@ -180,7 +180,7 @@ testFilesFoldersImageSndDelete =
alice #$> ("/_files_folder ./tests/tmp/alice_app_files", id, "ok") alice #$> ("/_files_folder ./tests/tmp/alice_app_files", id, "ok")
copyFile "./tests/fixtures/test_1MB.pdf" "./tests/tmp/alice_app_files/test_1MB.pdf" copyFile "./tests/fixtures/test_1MB.pdf" "./tests/tmp/alice_app_files/test_1MB.pdf"
bob #$> ("/_files_folder ./tests/tmp/bob_app_files", id, "ok") bob #$> ("/_files_folder ./tests/tmp/bob_app_files", id, "ok")
alice ##> "/_send @2 json {\"filePath\": \"test_1MB.pdf\", \"msgContent\": {\"text\":\"\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}" alice ##> "/_send @2 json [{\"filePath\": \"test_1MB.pdf\", \"msgContent\": {\"text\":\"\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}]"
alice <# "/f @bob test_1MB.pdf" alice <# "/f @bob test_1MB.pdf"
alice <## "use /fc 1 to cancel sending" alice <## "use /fc 1 to cancel sending"
bob <# "alice> sends file test_1MB.pdf (1017.7 KiB / 1042157 bytes)" bob <# "alice> sends file test_1MB.pdf (1017.7 KiB / 1042157 bytes)"
@ -212,7 +212,7 @@ testFilesFoldersImageRcvDelete =
connectUsers alice bob connectUsers alice bob
alice #$> ("/_files_folder ./tests/fixtures", id, "ok") alice #$> ("/_files_folder ./tests/fixtures", id, "ok")
bob #$> ("/_files_folder ./tests/tmp/app_files", id, "ok") bob #$> ("/_files_folder ./tests/tmp/app_files", id, "ok")
alice ##> "/_send @2 json {\"filePath\": \"test.jpg\", \"msgContent\": {\"text\":\"\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}" alice ##> "/_send @2 json [{\"filePath\": \"test.jpg\", \"msgContent\": {\"text\":\"\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}]"
alice <# "/f @bob test.jpg" alice <# "/f @bob test.jpg"
alice <## "use /fc 1 to cancel sending" alice <## "use /fc 1 to cancel sending"
bob <# "alice> sends file test.jpg (136.5 KiB / 139737 bytes)" bob <# "alice> sends file test.jpg (136.5 KiB / 139737 bytes)"
@ -239,7 +239,7 @@ testSendImageWithTextAndQuote =
connectUsers alice bob connectUsers alice bob
bob #> "@alice hi alice" bob #> "@alice hi alice"
alice <# "bob> hi alice" alice <# "bob> hi alice"
alice ##> ("/_send @2 json {\"filePath\": \"./tests/fixtures/test.jpg\", \"quotedItemId\": " <> itemId 1 <> ", \"msgContent\": {\"text\":\"hey bob\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}") alice ##> ("/_send @2 json [{\"filePath\": \"./tests/fixtures/test.jpg\", \"quotedItemId\": " <> itemId 1 <> ", \"msgContent\": {\"text\":\"hey bob\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}]")
alice <# "@bob > hi alice" alice <# "@bob > hi alice"
alice <## " hey bob" alice <## " hey bob"
alice <# "/f @bob ./tests/fixtures/test.jpg" alice <# "/f @bob ./tests/fixtures/test.jpg"
@ -265,7 +265,7 @@ testSendImageWithTextAndQuote =
bob @@@ [("@alice", "hey bob")] bob @@@ [("@alice", "hey bob")]
-- quoting (file + text) with file uses quoted text -- quoting (file + text) with file uses quoted text
bob ##> ("/_send @2 json {\"filePath\": \"./tests/fixtures/test.pdf\", \"quotedItemId\": " <> itemId 2 <> ", \"msgContent\": {\"text\":\"\",\"type\":\"file\"}}") bob ##> ("/_send @2 json [{\"filePath\": \"./tests/fixtures/test.pdf\", \"quotedItemId\": " <> itemId 2 <> ", \"msgContent\": {\"text\":\"\",\"type\":\"file\"}}]")
bob <# "@alice > hey bob" bob <# "@alice > hey bob"
bob <## " test.pdf" bob <## " test.pdf"
bob <# "/f @alice ./tests/fixtures/test.pdf" bob <# "/f @alice ./tests/fixtures/test.pdf"
@ -287,7 +287,7 @@ testSendImageWithTextAndQuote =
B.readFile "./tests/tmp/test.pdf" `shouldReturn` txtSrc B.readFile "./tests/tmp/test.pdf" `shouldReturn` txtSrc
-- quoting (file without text) with file uses file name -- quoting (file without text) with file uses file name
alice ##> ("/_send @2 json {\"filePath\": \"./tests/fixtures/test.jpg\", \"quotedItemId\": " <> itemId 3 <> ", \"msgContent\": {\"text\":\"\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}") alice ##> ("/_send @2 json [{\"filePath\": \"./tests/fixtures/test.jpg\", \"quotedItemId\": " <> itemId 3 <> ", \"msgContent\": {\"text\":\"\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}]")
alice <# "@bob > test.pdf" alice <# "@bob > test.pdf"
alice <## " test.jpg" alice <## " test.jpg"
alice <# "/f @bob ./tests/fixtures/test.jpg" alice <# "/f @bob ./tests/fixtures/test.jpg"
@ -313,7 +313,7 @@ testGroupSendImage =
\alice bob cath -> withXFTPServer $ do \alice bob cath -> withXFTPServer $ do
createGroup3 "team" alice bob cath createGroup3 "team" alice bob cath
threadDelay 1000000 threadDelay 1000000
alice ##> "/_send #1 json {\"filePath\": \"./tests/fixtures/test.jpg\", \"msgContent\": {\"text\":\"\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}" alice ##> "/_send #1 json [{\"filePath\": \"./tests/fixtures/test.jpg\", \"msgContent\": {\"text\":\"\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}]"
alice <# "/f #team ./tests/fixtures/test.jpg" alice <# "/f #team ./tests/fixtures/test.jpg"
alice <## "use /fc 1 to cancel sending" alice <## "use /fc 1 to cancel sending"
concurrentlyN_ concurrentlyN_
@ -361,7 +361,7 @@ testGroupSendImageWithTextAndQuote =
(cath <# "#team bob> hi team") (cath <# "#team bob> hi team")
threadDelay 1000000 threadDelay 1000000
msgItemId <- lastItemId alice msgItemId <- lastItemId alice
alice ##> ("/_send #1 json {\"filePath\": \"./tests/fixtures/test.jpg\", \"quotedItemId\": " <> msgItemId <> ", \"msgContent\": {\"text\":\"hey bob\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}") alice ##> ("/_send #1 json [{\"filePath\": \"./tests/fixtures/test.jpg\", \"quotedItemId\": " <> msgItemId <> ", \"msgContent\": {\"text\":\"hey bob\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}]")
alice <# "#team > bob hi team" alice <# "#team > bob hi team"
alice <## " hey bob" alice <## " hey bob"
alice <# "/f #team ./tests/fixtures/test.jpg" alice <# "/f #team ./tests/fixtures/test.jpg"
@ -460,7 +460,7 @@ testXFTPFileTransferEncrypted =
let fileJSON = LB.unpack $ J.encode $ CryptoFile srcPath $ Just cfArgs let fileJSON = LB.unpack $ J.encode $ CryptoFile srcPath $ Just cfArgs
withXFTPServer $ do withXFTPServer $ do
connectUsers alice bob connectUsers alice bob
alice ##> ("/_send @2 json {\"msgContent\":{\"type\":\"file\", \"text\":\"\"}, \"fileSource\": " <> fileJSON <> "}") alice ##> ("/_send @2 json [{\"msgContent\":{\"type\":\"file\", \"text\":\"\"}, \"fileSource\": " <> fileJSON <> "}]")
alice <# "/f @bob ./tests/tmp/alice/test.pdf" alice <# "/f @bob ./tests/tmp/alice/test.pdf"
alice <## "use /fc 1 to cancel sending" alice <## "use /fc 1 to cancel sending"
bob <# "alice> sends file test.pdf (266.0 KiB / 272376 bytes)" bob <# "alice> sends file test.pdf (266.0 KiB / 272376 bytes)"

View file

@ -33,6 +33,8 @@ chatForwardTests = do
it "with relative paths: from contact to contact" testForwardFileContactToContact it "with relative paths: from contact to contact" testForwardFileContactToContact
it "with relative paths: from group to notes" testForwardFileGroupToNotes it "with relative paths: from group to notes" testForwardFileGroupToNotes
it "with relative paths: from notes to group" testForwardFileNotesToGroup it "with relative paths: from notes to group" testForwardFileNotesToGroup
describe "multi forward api" $ do
it "from contact to contact" testForwardContactToContactMulti
testForwardContactToContact :: HasCallStack => FilePath -> IO () testForwardContactToContact :: HasCallStack => FilePath -> IO ()
testForwardContactToContact = testForwardContactToContact =
@ -384,7 +386,7 @@ testForwardFileNoFilesFolder =
connectUsers bob cath connectUsers bob cath
-- send original file -- send original file
alice ##> "/_send @2 json {\"filePath\": \"./tests/fixtures/test.pdf\", \"msgContent\": {\"type\": \"text\", \"text\": \"hi\"}}" alice ##> "/_send @2 json [{\"filePath\": \"./tests/fixtures/test.pdf\", \"msgContent\": {\"type\": \"text\", \"text\": \"hi\"}}]"
alice <# "@bob hi" alice <# "@bob hi"
alice <# "/f @bob ./tests/fixtures/test.pdf" alice <# "/f @bob ./tests/fixtures/test.pdf"
alice <## "use /fc 1 to cancel sending" alice <## "use /fc 1 to cancel sending"
@ -441,7 +443,7 @@ testForwardFileContactToContact =
connectUsers bob cath connectUsers bob cath
-- send original file -- send original file
alice ##> "/_send @2 json {\"filePath\": \"test.pdf\", \"msgContent\": {\"type\": \"text\", \"text\": \"hi\"}}" alice ##> "/_send @2 json [{\"filePath\": \"test.pdf\", \"msgContent\": {\"type\": \"text\", \"text\": \"hi\"}}]"
alice <# "@bob hi" alice <# "@bob hi"
alice <# "/f @bob test.pdf" alice <# "/f @bob test.pdf"
alice <## "use /fc 1 to cancel sending" alice <## "use /fc 1 to cancel sending"
@ -506,7 +508,7 @@ testForwardFileGroupToNotes =
createCCNoteFolder cath createCCNoteFolder cath
-- send original file -- send original file
alice ##> "/_send #1 json {\"filePath\": \"test.pdf\", \"msgContent\": {\"type\": \"text\", \"text\": \"hi\"}}" alice ##> "/_send #1 json [{\"filePath\": \"test.pdf\", \"msgContent\": {\"type\": \"text\", \"text\": \"hi\"}}]"
alice <# "#team hi" alice <# "#team hi"
alice <# "/f #team test.pdf" alice <# "/f #team test.pdf"
alice <## "use /fc 1 to cancel sending" alice <## "use /fc 1 to cancel sending"
@ -555,7 +557,7 @@ testForwardFileNotesToGroup =
createGroup2 "team" alice cath createGroup2 "team" alice cath
-- create original file -- create original file
alice ##> "/_create *1 json {\"filePath\": \"test.pdf\", \"msgContent\": {\"type\": \"text\", \"text\": \"hi\"}}" alice ##> "/_create *1 json [{\"filePath\": \"test.pdf\", \"msgContent\": {\"type\": \"text\", \"text\": \"hi\"}}]"
alice <# "* hi" alice <# "* hi"
alice <# "* file 1 (test.pdf)" alice <# "* file 1 (test.pdf)"
@ -590,3 +592,31 @@ testForwardFileNotesToGroup =
alice <## "notes: all messages are removed" alice <## "notes: all messages are removed"
fwdFileExists <- doesFileExist "./tests/tmp/alice_files/test_1.pdf" fwdFileExists <- doesFileExist "./tests/tmp/alice_files/test_1.pdf"
fwdFileExists `shouldBe` True fwdFileExists `shouldBe` True
testForwardContactToContactMulti :: HasCallStack => FilePath -> IO ()
testForwardContactToContactMulti =
testChat3 aliceProfile bobProfile cathProfile $
\alice bob cath -> do
connectUsers alice bob
connectUsers alice cath
connectUsers bob cath
alice #> "@bob hi"
bob <# "alice> hi"
msgId1 <- lastItemId alice
threadDelay 1000000
bob #> "@alice hey"
alice <# "bob> hey"
msgId2 <- lastItemId alice
alice ##> ("/_forward @3 @2 " <> msgId1 <> "," <> msgId2)
alice <# "@cath <- you @bob"
alice <## " hi"
alice <# "@cath <- @bob"
alice <## " hey"
cath <# "alice> -> forwarded"
cath <## " hi"
cath <# "alice> -> forwarded"
cath <## " hey"

View file

@ -64,6 +64,7 @@ chatGroupTests = do
it "moderate message of another group member (full delete)" testGroupModerateFullDelete it "moderate message of another group member (full delete)" testGroupModerateFullDelete
it "moderate message that arrives after the event of moderation" testGroupDelayedModeration it "moderate message that arrives after the event of moderation" testGroupDelayedModeration
it "moderate message that arrives after the event of moderation (full delete)" testGroupDelayedModerationFullDelete it "moderate message that arrives after the event of moderation (full delete)" testGroupDelayedModerationFullDelete
it "send multiple messages api" testSendMulti
describe "async group connections" $ do describe "async group connections" $ do
xit "create and join group when clients go offline" testGroupAsync xit "create and join group when clients go offline" testGroupAsync
describe "group links" $ do describe "group links" $ do
@ -1818,6 +1819,18 @@ testGroupDelayedModerationFullDelete tmp = do
where where
cfg = testCfgCreateGroupDirect cfg = testCfgCreateGroupDirect
testSendMulti :: HasCallStack => FilePath -> IO ()
testSendMulti =
testChat2 aliceProfile bobProfile $
\alice bob -> do
createGroup2 "team" alice bob
alice ##> "/_send #1 json [{\"msgContent\": {\"type\": \"text\", \"text\": \"test 1\"}}, {\"msgContent\": {\"type\": \"text\", \"text\": \"test 2\"}}]"
alice <# "#team test 1"
alice <# "#team test 2"
bob <# "#team alice> test 1"
bob <# "#team alice> test 2"
testGroupAsync :: HasCallStack => FilePath -> IO () testGroupAsync :: HasCallStack => FilePath -> IO ()
testGroupAsync tmp = do testGroupAsync tmp = do
withNewTestChat tmp "alice" aliceProfile $ \alice -> do withNewTestChat tmp "alice" aliceProfile $ \alice -> do
@ -3468,7 +3481,8 @@ testGroupSyncRatchet tmp =
bob <## "1 contacts connected (use /cs for the list)" bob <## "1 contacts connected (use /cs for the list)"
bob <## "#team: connected to server(s)" bob <## "#team: connected to server(s)"
bob `send` "#team 1" bob `send` "#team 1"
bob <## "error: command is prohibited, sendMessagesB: send prohibited" -- silence? -- "send prohibited" error is not printed in group as SndMessage is created,
-- but it should be displayed in per member snd statuses
bob <# "#team 1" bob <# "#team 1"
(alice </) (alice </)
-- synchronize bob and alice -- synchronize bob and alice
@ -4908,7 +4922,7 @@ testGroupHistoryLargeFile =
createGroup2 "team" alice bob createGroup2 "team" alice bob
bob ##> "/_send #1 json {\"filePath\": \"./tests/tmp/testfile\", \"msgContent\": {\"text\":\"hello\",\"type\":\"file\"}}" bob ##> "/_send #1 json [{\"filePath\": \"./tests/tmp/testfile\", \"msgContent\": {\"text\":\"hello\",\"type\":\"file\"}}]"
bob <# "#team hello" bob <# "#team hello"
bob <# "/f #team ./tests/tmp/testfile" bob <# "/f #team ./tests/tmp/testfile"
bob <## "use /fc 1 to cancel sending" bob <## "use /fc 1 to cancel sending"
@ -4969,7 +4983,7 @@ testGroupHistoryMultipleFiles =
threadDelay 1000000 threadDelay 1000000
bob ##> "/_send #1 json {\"filePath\": \"./tests/tmp/testfile_bob\", \"msgContent\": {\"text\":\"hi alice\",\"type\":\"file\"}}" bob ##> "/_send #1 json [{\"filePath\": \"./tests/tmp/testfile_bob\", \"msgContent\": {\"text\":\"hi alice\",\"type\":\"file\"}}]"
bob <# "#team hi alice" bob <# "#team hi alice"
bob <# "/f #team ./tests/tmp/testfile_bob" bob <# "/f #team ./tests/tmp/testfile_bob"
bob <## "use /fc 1 to cancel sending" bob <## "use /fc 1 to cancel sending"
@ -4981,7 +4995,7 @@ testGroupHistoryMultipleFiles =
threadDelay 1000000 threadDelay 1000000
alice ##> "/_send #1 json {\"filePath\": \"./tests/tmp/testfile_alice\", \"msgContent\": {\"text\":\"hey bob\",\"type\":\"file\"}}" alice ##> "/_send #1 json [{\"filePath\": \"./tests/tmp/testfile_alice\", \"msgContent\": {\"text\":\"hey bob\",\"type\":\"file\"}}]"
alice <# "#team hey bob" alice <# "#team hey bob"
alice <# "/f #team ./tests/tmp/testfile_alice" alice <# "/f #team ./tests/tmp/testfile_alice"
alice <## "use /fc 2 to cancel sending" alice <## "use /fc 2 to cancel sending"
@ -5047,7 +5061,7 @@ testGroupHistoryFileCancel =
createGroup2 "team" alice bob createGroup2 "team" alice bob
bob ##> "/_send #1 json {\"filePath\": \"./tests/tmp/testfile_bob\", \"msgContent\": {\"text\":\"hi alice\",\"type\":\"file\"}}" bob ##> "/_send #1 json [{\"filePath\": \"./tests/tmp/testfile_bob\", \"msgContent\": {\"text\":\"hi alice\",\"type\":\"file\"}}]"
bob <# "#team hi alice" bob <# "#team hi alice"
bob <# "/f #team ./tests/tmp/testfile_bob" bob <# "/f #team ./tests/tmp/testfile_bob"
bob <## "use /fc 1 to cancel sending" bob <## "use /fc 1 to cancel sending"
@ -5063,7 +5077,7 @@ testGroupHistoryFileCancel =
threadDelay 1000000 threadDelay 1000000
alice ##> "/_send #1 json {\"filePath\": \"./tests/tmp/testfile_alice\", \"msgContent\": {\"text\":\"hey bob\",\"type\":\"file\"}}" alice ##> "/_send #1 json [{\"filePath\": \"./tests/tmp/testfile_alice\", \"msgContent\": {\"text\":\"hey bob\",\"type\":\"file\"}}]"
alice <# "#team hey bob" alice <# "#team hey bob"
alice <# "/f #team ./tests/tmp/testfile_alice" alice <# "/f #team ./tests/tmp/testfile_alice"
alice <## "use /fc 2 to cancel sending" alice <## "use /fc 2 to cancel sending"

View file

@ -17,6 +17,7 @@ chatLocalChatsTests :: SpecWith FilePath
chatLocalChatsTests = do chatLocalChatsTests = do
describe "note folders" $ do describe "note folders" $ do
it "create folders, add notes, read, search" testNotes it "create folders, add notes, read, search" testNotes
it "create multiple messages api" testCreateMulti
it "switch users" testUserNotes it "switch users" testUserNotes
it "preview pagination for notes" testPreviewsPagination it "preview pagination for notes" testPreviewsPagination
it "chat pagination" testChatPagination it "chat pagination" testChatPagination
@ -52,6 +53,14 @@ testNotes tmp = withNewTestChat tmp "alice" aliceProfile $ \alice -> do
alice ##> "/tail *" alice ##> "/tail *"
alice <# "* Greetings." alice <# "* Greetings."
testCreateMulti :: FilePath -> IO ()
testCreateMulti tmp = withNewTestChat tmp "alice" aliceProfile $ \alice -> do
createCCNoteFolder alice
alice ##> "/_create *1 json [{\"msgContent\": {\"type\": \"text\", \"text\": \"test 1\"}}, {\"msgContent\": {\"type\": \"text\", \"text\": \"test 2\"}}]"
alice <# "* test 1"
alice <# "* test 2"
testUserNotes :: FilePath -> IO () testUserNotes :: FilePath -> IO ()
testUserNotes tmp = withNewTestChat tmp "alice" aliceProfile $ \alice -> do testUserNotes tmp = withNewTestChat tmp "alice" aliceProfile $ \alice -> do
createCCNoteFolder alice createCCNoteFolder alice
@ -120,7 +129,7 @@ testFiles tmp = withNewTestChat tmp "alice" aliceProfile $ \alice -> do
let source = "./tests/fixtures/test.jpg" let source = "./tests/fixtures/test.jpg"
let stored = files </> "test.jpg" let stored = files </> "test.jpg"
copyFile source stored copyFile source stored
alice ##> "/_create *1 json {\"filePath\": \"test.jpg\", \"msgContent\": {\"text\":\"hi myself\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}" alice ##> "/_create *1 json [{\"filePath\": \"test.jpg\", \"msgContent\": {\"text\":\"hi myself\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}]"
alice <# "* hi myself" alice <# "* hi myself"
alice <# "* file 1 (test.jpg)" alice <# "* file 1 (test.jpg)"
@ -141,7 +150,7 @@ testFiles tmp = withNewTestChat tmp "alice" aliceProfile $ \alice -> do
-- one more file -- one more file
let stored2 = files </> "another_test.jpg" let stored2 = files </> "another_test.jpg"
copyFile source stored2 copyFile source stored2
alice ##> "/_create *1 json {\"filePath\": \"another_test.jpg\", \"msgContent\": {\"text\":\"\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}" alice ##> "/_create *1 json [{\"filePath\": \"another_test.jpg\", \"msgContent\": {\"text\":\"\",\"type\":\"image\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\"}}]"
alice <# "* file 2 (another_test.jpg)" alice <# "* file 2 (another_test.jpg)"
alice ##> "/_delete item *1 2 internal" alice ##> "/_delete item *1 2 internal"
@ -173,8 +182,8 @@ testOtherFiles =
bob ##> "/fr 1" bob ##> "/fr 1"
bob bob
<### [ "saving file 1 from alice to test.jpg", <### [ "saving file 1 from alice to test.jpg",
"started receiving file 1 (test.jpg) from alice" "started receiving file 1 (test.jpg) from alice"
] ]
bob <## "completed receiving file 1 (test.jpg) from alice" bob <## "completed receiving file 1 (test.jpg) from alice"
bob /* "test" bob /* "test"

View file

@ -1721,7 +1721,7 @@ testSetContactPrefs = testChat2 aliceProfile bobProfile $
let startFeatures = [(0, e2eeInfoPQStr), (0, "Disappearing messages: allowed"), (0, "Full deletion: off"), (0, "Message reactions: enabled"), (0, "Voice messages: off"), (0, "Audio/video calls: enabled")] let startFeatures = [(0, e2eeInfoPQStr), (0, "Disappearing messages: allowed"), (0, "Full deletion: off"), (0, "Message reactions: enabled"), (0, "Voice messages: off"), (0, "Audio/video calls: enabled")]
alice #$> ("/_get chat @2 count=100", chat, startFeatures) alice #$> ("/_get chat @2 count=100", chat, startFeatures)
bob #$> ("/_get chat @2 count=100", chat, startFeatures) bob #$> ("/_get chat @2 count=100", chat, startFeatures)
let sendVoice = "/_send @2 json {\"filePath\": \"test.txt\", \"msgContent\": {\"type\": \"voice\", \"text\": \"\", \"duration\": 10}}" let sendVoice = "/_send @2 json [{\"filePath\": \"test.txt\", \"msgContent\": {\"type\": \"voice\", \"text\": \"\", \"duration\": 10}}]"
voiceNotAllowed = "bad chat command: feature not allowed Voice messages" voiceNotAllowed = "bad chat command: feature not allowed Voice messages"
alice ##> sendVoice alice ##> sendVoice
alice <## voiceNotAllowed alice <## voiceNotAllowed
@ -2227,7 +2227,7 @@ testGroupPrefsSimplexLinksForRole = testChat3 aliceProfile bobProfile cathProfil
inv <- getInvitation bob inv <- getInvitation bob
bob ##> ("#team \"" <> inv <> "\\ntest\"") bob ##> ("#team \"" <> inv <> "\\ntest\"")
bob <## "bad chat command: feature not allowed SimpleX links" bob <## "bad chat command: feature not allowed SimpleX links"
bob ##> ("/_send #1 json {\"msgContent\": {\"type\": \"text\", \"text\": \"" <> inv <> "\\ntest\"}}") bob ##> ("/_send #1 json [{\"msgContent\": {\"type\": \"text\", \"text\": \"" <> inv <> "\\ntest\"}}]")
bob <## "bad chat command: feature not allowed SimpleX links" bob <## "bad chat command: feature not allowed SimpleX links"
(alice </) (alice </)
(cath </) (cath </)

View file

@ -112,7 +112,7 @@ runBatcherTest maxLen msgs expectedErrors expectedBatches =
runBatcherTest' :: Int -> [SndMessage] -> [ChatError] -> [ByteString] -> IO () runBatcherTest' :: Int -> [SndMessage] -> [ChatError] -> [ByteString] -> IO ()
runBatcherTest' maxLen msgs expectedErrors expectedBatches = do runBatcherTest' maxLen msgs expectedErrors expectedBatches = do
let (errors, batches) = partitionEithers $ batchMessages maxLen msgs let (errors, batches) = partitionEithers $ batchMessages maxLen (map Right msgs)
batchedStrs = map (\(MsgBatch batchBody _) -> batchBody) batches batchedStrs = map (\(MsgBatch batchBody _) -> batchBody) batches
testErrors errors `shouldBe` testErrors expectedErrors testErrors errors `shouldBe` testErrors expectedErrors
batchedStrs `shouldBe` expectedBatches batchedStrs `shouldBe` expectedBatches

View file

@ -238,7 +238,7 @@ remoteStoreFileTest =
desktop ##> "/get remote file 1 {\"userId\": 1, \"fileId\": 1, \"sent\": true, \"fileSource\": {\"filePath\": \"test_1.pdf\"}}" desktop ##> "/get remote file 1 {\"userId\": 1, \"fileId\": 1, \"sent\": true, \"fileSource\": {\"filePath\": \"test_1.pdf\"}}"
hostError desktop "SEFileNotFound" hostError desktop "SEFileNotFound"
-- send file not encrypted locally on mobile host -- send file not encrypted locally on mobile host
desktop ##> "/_send @2 json {\"filePath\": \"test_1.pdf\", \"msgContent\": {\"type\": \"file\", \"text\": \"sending a file\"}}" desktop ##> "/_send @2 json [{\"filePath\": \"test_1.pdf\", \"msgContent\": {\"type\": \"file\", \"text\": \"sending a file\"}}]"
desktop <# "@bob sending a file" desktop <# "@bob sending a file"
desktop <# "/f @bob test_1.pdf" desktop <# "/f @bob test_1.pdf"
desktop <## "use /fc 1 to cancel sending" desktop <## "use /fc 1 to cancel sending"
@ -268,7 +268,7 @@ remoteStoreFileTest =
B.readFile (desktopHostStore </> "test_1.pdf") `shouldReturn` src B.readFile (desktopHostStore </> "test_1.pdf") `shouldReturn` src
-- send file encrypted locally on mobile host -- send file encrypted locally on mobile host
desktop ##> ("/_send @2 json {\"fileSource\": {\"filePath\":\"test_2.pdf\", \"cryptoArgs\": " <> LB.unpack (J.encode cfArgs) <> "}, \"msgContent\": {\"type\": \"file\", \"text\": \"\"}}") desktop ##> ("/_send @2 json [{\"fileSource\": {\"filePath\":\"test_2.pdf\", \"cryptoArgs\": " <> LB.unpack (J.encode cfArgs) <> "}, \"msgContent\": {\"type\": \"file\", \"text\": \"\"}}]")
desktop <# "/f @bob test_2.pdf" desktop <# "/f @bob test_2.pdf"
desktop <## "use /fc 2 to cancel sending" desktop <## "use /fc 2 to cancel sending"
bob <# "alice> sends file test_2.pdf (266.0 KiB / 272376 bytes)" bob <# "alice> sends file test_2.pdf (266.0 KiB / 272376 bytes)"