core: split tests (#1870)

This commit is contained in:
Evgeny Poberezkin 2023-02-01 17:21:13 +00:00 committed by GitHub
parent b206868730
commit 4a58ca60ac
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 6093 additions and 6013 deletions

View file

@ -289,6 +289,11 @@ test-suite simplex-chat-test
other-modules:
ChatClient
ChatTests
ChatTests.Direct
ChatTests.Files
ChatTests.Groups
ChatTests.Profiles
ChatTests.Utils
MarkdownTests
MobileTests
ProtocolTests

File diff suppressed because it is too large Load diff

1594
tests/ChatTests/Direct.hs Normal file

File diff suppressed because it is too large Load diff

934
tests/ChatTests/Files.hs Normal file
View file

@ -0,0 +1,934 @@
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PostfixOperators #-}
module ChatTests.Files where
import ChatClient
import ChatTests.Utils
import Control.Concurrent (threadDelay)
import Control.Concurrent.Async (concurrently_)
import qualified Data.ByteString.Char8 as B
import Simplex.Chat.Controller (ChatConfig (..), InlineFilesConfig (..), defaultInlineFilesConfig)
import Simplex.Chat.Options (ChatOpts (..))
import Simplex.Messaging.Util (unlessM)
import System.Directory (copyFile, doesFileExist)
import System.FilePath ((</>))
import Test.Hspec
chatFileTests :: SpecWith FilePath
chatFileTests = do
describe "sending and receiving files" $ do
describe "send and receive file" $ fileTestMatrix2 runTestFileTransfer
it "send and receive file inline (without accepting)" testInlineFileTransfer
it "accept inline file transfer, sender cancels during transfer" testAcceptInlineFileSndCancelDuringTransfer
it "send and receive small file inline (default config)" testSmallInlineFileTransfer
it "small file sent without acceptance is ignored in terminal by default" testSmallInlineFileIgnored
it "receive file inline with inline=on option" testReceiveInline
describe "send and receive a small file" $ fileTestMatrix2 runTestSmallFileTransfer
describe "sender cancelled file transfer before transfer" $ fileTestMatrix2 runTestFileSndCancelBeforeTransfer
it "sender cancelled file transfer during transfer" testFileSndCancelDuringTransfer
it "recipient cancelled file transfer" testFileRcvCancel
describe "send and receive file to group" $ fileTestMatrix3 runTestGroupFileTransfer
it "send and receive file inline to group (without accepting)" testInlineGroupFileTransfer
it "send and receive small file inline to group (default config)" testSmallInlineGroupFileTransfer
it "small file sent without acceptance is ignored in terminal by default" testSmallInlineGroupFileIgnored
describe "sender cancelled group file transfer before transfer" $ fileTestMatrix3 runTestGroupFileSndCancelBeforeTransfer
describe "messages with files" $ do
describe "send and receive message with file" $ fileTestMatrix2 runTestMessageWithFile
it "send and receive image" testSendImage
it "files folder: send and receive image" testFilesFoldersSendImage
it "files folder: sender deleted file during transfer" testFilesFoldersImageSndDelete
it "files folder: recipient deleted file during transfer" testFilesFoldersImageRcvDelete
it "send and receive image with text and quote" testSendImageWithTextAndQuote
describe "send and receive image to group" testGroupSendImage
it "send and receive image with text and quote to group" testGroupSendImageWithTextAndQuote
describe "async sending and receiving files" $ do
it "send and receive file, sender restarts" testAsyncFileTransferSenderRestarts
it "send and receive file, receiver restarts" testAsyncFileTransferReceiverRestarts
xdescribe "send and receive file, fully asynchronous" $ do
it "v2" testAsyncFileTransfer
it "v1" testAsyncFileTransferV1
xit "send and receive file to group, fully asynchronous" testAsyncGroupFileTransfer
runTestFileTransfer :: HasCallStack => TestCC -> TestCC -> IO ()
runTestFileTransfer alice bob = do
connectUsers alice bob
startFileTransfer' alice bob "test.pdf" "266.0 KiB / 272376 bytes"
concurrentlyN_
[ do
bob #> "@alice receiving here..."
bob <## "completed receiving file 1 (test.pdf) from alice",
alice
<### [ WithTime "bob> receiving here...",
"completed sending file 1 (test.pdf) to bob"
]
]
src <- B.readFile "./tests/fixtures/test.pdf"
dest <- B.readFile "./tests/tmp/test.pdf"
dest `shouldBe` src
testInlineFileTransfer :: HasCallStack => FilePath -> IO ()
testInlineFileTransfer =
testChatCfg2 cfg aliceProfile bobProfile $ \alice bob -> do
connectUsers alice bob
bob ##> "/_files_folder ./tests/tmp/"
bob <## "ok"
alice ##> "/_send @2 json {\"msgContent\":{\"type\":\"voice\", \"duration\":10, \"text\":\"\"}, \"filePath\":\"./tests/fixtures/test.jpg\"}"
alice <# "@bob voice message (00:10)"
alice <# "/f @bob ./tests/fixtures/test.jpg"
-- below is not shown in "sent" mode
-- alice <## "use /fc 1 to cancel sending"
bob <# "alice> voice message (00:10)"
bob <# "alice> sends file test.jpg (136.5 KiB / 139737 bytes)"
-- below is not shown in "sent" mode
-- bob <## "use /fr 1 [<dir>/ | <path>] to receive it"
bob <## "started receiving file 1 (test.jpg) from alice"
concurrently_
(alice <## "completed sending file 1 (test.jpg) to bob")
(bob <## "completed receiving file 1 (test.jpg) from alice")
src <- B.readFile "./tests/fixtures/test.jpg"
dest <- B.readFile "./tests/tmp/test.jpg"
dest `shouldBe` src
where
cfg = testCfg {inlineFiles = defaultInlineFilesConfig {offerChunks = 100, sendChunks = 100, receiveChunks = 100}}
testAcceptInlineFileSndCancelDuringTransfer :: HasCallStack => FilePath -> IO ()
testAcceptInlineFileSndCancelDuringTransfer =
testChatCfg2 cfg aliceProfile bobProfile $ \alice bob -> do
connectUsers alice bob
bob ##> "/_files_folder ./tests/tmp/"
bob <## "ok"
alice #> "/f @bob ./tests/fixtures/test_1MB.pdf"
alice <## "use /fc 1 to cancel sending"
bob <# "alice> sends file test_1MB.pdf (1017.7 KiB / 1042157 bytes)"
bob <## "use /fr 1 [<dir>/ | <path>] to receive it"
bob ##> "/fr 1 inline=on"
bob <## "saving file 1 from alice to test_1MB.pdf"
alice <## "started sending file 1 (test_1MB.pdf) to bob"
bob <## "started receiving file 1 (test_1MB.pdf) from alice"
alice ##> "/fc 1" -- test that inline file cancel doesn't delete contact connection
concurrentlyN_
[ do
alice <##. "cancelled sending file 1 (test_1MB.pdf)"
alice <## "completed sending file 1 (test_1MB.pdf) to bob",
do
bob <## "completed receiving file 1 (test_1MB.pdf) from alice"
bob <## "alice cancelled sending file 1 (test_1MB.pdf)"
]
alice #> "@bob hi"
bob <# "alice> hi"
bob #> "@alice hey"
alice <# "bob> hey"
where
cfg = testCfg {inlineFiles = defaultInlineFilesConfig {offerChunks = 100, receiveChunks = 50}}
testSmallInlineFileTransfer :: HasCallStack => FilePath -> IO ()
testSmallInlineFileTransfer =
testChat2 aliceProfile bobProfile $ \alice bob -> do
connectUsers alice bob
bob ##> "/_files_folder ./tests/tmp/"
bob <## "ok"
alice ##> "/_send @2 json {\"msgContent\":{\"type\":\"voice\", \"duration\":10, \"text\":\"\"}, \"filePath\":\"./tests/fixtures/logo.jpg\"}"
alice <# "@bob voice message (00:10)"
alice <# "/f @bob ./tests/fixtures/logo.jpg"
-- below is not shown in "sent" mode
-- alice <## "use /fc 1 to cancel sending"
bob <# "alice> voice message (00:10)"
bob <# "alice> sends file logo.jpg (31.3 KiB / 32080 bytes)"
-- below is not shown in "sent" mode
-- bob <## "use /fr 1 [<dir>/ | <path>] to receive it"
bob <## "started receiving file 1 (logo.jpg) from alice"
concurrently_
(alice <## "completed sending file 1 (logo.jpg) to bob")
(bob <## "completed receiving file 1 (logo.jpg) from alice")
src <- B.readFile "./tests/fixtures/logo.jpg"
dest <- B.readFile "./tests/tmp/logo.jpg"
dest `shouldBe` src
testSmallInlineFileIgnored :: HasCallStack => FilePath -> IO ()
testSmallInlineFileIgnored tmp = do
withNewTestChat tmp "alice" aliceProfile $ \alice ->
withNewTestChatOpts tmp testOpts {allowInstantFiles = False} "bob" bobProfile $ \bob -> do
connectUsers alice bob
bob ##> "/_files_folder ./tests/tmp/"
bob <## "ok"
alice ##> "/_send @2 json {\"msgContent\":{\"type\":\"voice\", \"duration\":10, \"text\":\"\"}, \"filePath\":\"./tests/fixtures/logo.jpg\"}"
alice <# "@bob voice message (00:10)"
alice <# "/f @bob ./tests/fixtures/logo.jpg"
-- below is not shown in "sent" mode
-- alice <## "use /fc 1 to cancel sending"
bob <# "alice> voice message (00:10)"
bob <# "alice> sends file logo.jpg (31.3 KiB / 32080 bytes)"
bob <## "use /fr 1 [<dir>/ | <path>] to receive it"
bob <## "A small file sent without acceptance - you can enable receiving such files with -f option."
-- below is not shown in "sent" mode
-- bob <## "use /fr 1 [<dir>/ | <path>] to receive it"
alice <## "completed sending file 1 (logo.jpg) to bob"
bob ##> "/fr 1"
bob <## "file is already being received: logo.jpg"
testReceiveInline :: HasCallStack => FilePath -> IO ()
testReceiveInline =
testChatCfg2 cfg aliceProfile bobProfile $ \alice bob -> do
connectUsers alice bob
alice #> "/f @bob ./tests/fixtures/test.jpg"
alice <## "use /fc 1 to cancel sending"
bob <# "alice> sends file test.jpg (136.5 KiB / 139737 bytes)"
bob <## "use /fr 1 [<dir>/ | <path>] to receive it"
bob ##> "/fr 1 inline=on ./tests/tmp"
bob <## "saving file 1 from alice to ./tests/tmp/test.jpg"
alice <## "started sending file 1 (test.jpg) to bob"
alice <## "completed sending file 1 (test.jpg) to bob"
bob <## "started receiving file 1 (test.jpg) from alice"
bob <## "completed receiving file 1 (test.jpg) from alice"
src <- B.readFile "./tests/fixtures/test.jpg"
dest <- B.readFile "./tests/tmp/test.jpg"
dest `shouldBe` src
where
cfg = testCfg {inlineFiles = defaultInlineFilesConfig {offerChunks = 10, receiveChunks = 5}}
runTestSmallFileTransfer :: HasCallStack => TestCC -> TestCC -> IO ()
runTestSmallFileTransfer alice bob = do
connectUsers alice bob
alice #> "/f @bob ./tests/fixtures/test.txt"
alice <## "use /fc 1 to cancel sending"
bob <# "alice> sends file test.txt (11 bytes / 11 bytes)"
bob <## "use /fr 1 [<dir>/ | <path>] to receive it"
bob ##> "/fr 1 ./tests/tmp"
bob <## "saving file 1 from alice to ./tests/tmp/test.txt"
concurrentlyN_
[ do
bob <## "started receiving file 1 (test.txt) from alice"
bob <## "completed receiving file 1 (test.txt) from alice",
do
alice <## "started sending file 1 (test.txt) to bob"
alice <## "completed sending file 1 (test.txt) to bob"
]
src <- B.readFile "./tests/fixtures/test.txt"
dest <- B.readFile "./tests/tmp/test.txt"
dest `shouldBe` src
runTestFileSndCancelBeforeTransfer :: HasCallStack => TestCC -> TestCC -> IO ()
runTestFileSndCancelBeforeTransfer alice bob = do
connectUsers alice bob
alice #> "/f @bob ./tests/fixtures/test.txt"
alice <## "use /fc 1 to cancel sending"
bob <# "alice> sends file test.txt (11 bytes / 11 bytes)"
bob <## "use /fr 1 [<dir>/ | <path>] to receive it"
alice ##> "/fc 1"
concurrentlyN_
[ alice <##. "cancelled sending file 1 (test.txt)",
bob <## "alice cancelled sending file 1 (test.txt)"
]
alice ##> "/fs 1"
alice
<##.. [ "sending file 1 (test.txt): no file transfers",
"sending file 1 (test.txt) cancelled: bob"
]
alice <## "file transfer cancelled"
bob ##> "/fs 1"
bob <## "receiving file 1 (test.txt) cancelled"
bob ##> "/fr 1 ./tests/tmp"
bob <## "file cancelled: test.txt"
testFileSndCancelDuringTransfer :: HasCallStack => FilePath -> IO ()
testFileSndCancelDuringTransfer =
testChat2 aliceProfile bobProfile $
\alice bob -> do
connectUsers alice bob
startFileTransfer' alice bob "test_1MB.pdf" "1017.7 KiB / 1042157 bytes"
alice ##> "/fc 1"
concurrentlyN_
[ do
alice <## "cancelled sending file 1 (test_1MB.pdf) to bob"
alice ##> "/fs 1"
alice <## "sending file 1 (test_1MB.pdf) cancelled: bob"
alice <## "file transfer cancelled",
do
bob <## "alice cancelled sending file 1 (test_1MB.pdf)"
bob ##> "/fs 1"
bob <## "receiving file 1 (test_1MB.pdf) cancelled, received part path: ./tests/tmp/test_1MB.pdf"
]
checkPartialTransfer "test_1MB.pdf"
testFileRcvCancel :: HasCallStack => FilePath -> IO ()
testFileRcvCancel =
testChat2 aliceProfile bobProfile $
\alice bob -> do
connectUsers alice bob
startFileTransfer alice bob
bob ##> "/fs 1"
getTermLine bob >>= (`shouldStartWith` "receiving file 1 (test.jpg) progress")
waitFileExists "./tests/tmp/test.jpg"
bob ##> "/fc 1"
concurrentlyN_
[ do
bob <## "cancelled receiving file 1 (test.jpg) from alice"
bob ##> "/fs 1"
bob <## "receiving file 1 (test.jpg) cancelled, received part path: ./tests/tmp/test.jpg",
do
alice <## "bob cancelled receiving file 1 (test.jpg)"
alice ##> "/fs 1"
alice <## "sending file 1 (test.jpg) cancelled: bob"
]
checkPartialTransfer "test.jpg"
runTestGroupFileTransfer :: HasCallStack => TestCC -> TestCC -> TestCC -> IO ()
runTestGroupFileTransfer alice bob cath = do
createGroup3 "team" alice bob cath
alice #> "/f #team ./tests/fixtures/test.jpg"
alice <## "use /fc 1 to cancel sending"
concurrentlyN_
[ do
bob <# "#team alice> sends file test.jpg (136.5 KiB / 139737 bytes)"
bob <## "use /fr 1 [<dir>/ | <path>] to receive it",
do
cath <# "#team alice> sends file test.jpg (136.5 KiB / 139737 bytes)"
cath <## "use /fr 1 [<dir>/ | <path>] to receive it"
]
alice ##> "/fs 1"
getTermLine alice >>= (`shouldStartWith` "sending file 1 (test.jpg): no file transfers")
bob ##> "/fr 1 ./tests/tmp/"
bob <## "saving file 1 from alice to ./tests/tmp/test.jpg"
concurrentlyN_
[ do
alice <## "started sending file 1 (test.jpg) to bob"
alice <## "completed sending file 1 (test.jpg) to bob"
alice ##> "/fs 1"
alice <## "sending file 1 (test.jpg) complete: bob",
do
bob <## "started receiving file 1 (test.jpg) from alice"
bob <## "completed receiving file 1 (test.jpg) from alice"
]
cath ##> "/fr 1 ./tests/tmp/"
cath <## "saving file 1 from alice to ./tests/tmp/test_1.jpg"
concurrentlyN_
[ do
alice <## "started sending file 1 (test.jpg) to cath"
alice <## "completed sending file 1 (test.jpg) to cath"
alice ##> "/fs 1"
getTermLine alice >>= (`shouldStartWith` "sending file 1 (test.jpg) complete"),
do
cath <## "started receiving file 1 (test.jpg) from alice"
cath <## "completed receiving file 1 (test.jpg) from alice"
]
src <- B.readFile "./tests/fixtures/test.jpg"
dest1 <- B.readFile "./tests/tmp/test.jpg"
dest2 <- B.readFile "./tests/tmp/test_1.jpg"
dest1 `shouldBe` src
dest2 `shouldBe` src
testInlineGroupFileTransfer :: HasCallStack => FilePath -> IO ()
testInlineGroupFileTransfer =
testChatCfg3 cfg aliceProfile bobProfile cathProfile $
\alice bob cath -> do
createGroup3 "team" alice bob cath
bob ##> "/_files_folder ./tests/tmp/bob/"
bob <## "ok"
cath ##> "/_files_folder ./tests/tmp/cath/"
cath <## "ok"
alice ##> "/_send #1 json {\"msgContent\":{\"type\":\"voice\", \"duration\":10, \"text\":\"\"}, \"filePath\":\"./tests/fixtures/logo.jpg\"}"
alice <# "#team voice message (00:10)"
alice <# "/f #team ./tests/fixtures/logo.jpg"
-- below is not shown in "sent" mode
-- alice <## "use /fc 1 to cancel sending"
concurrentlyN_
[ do
alice
<### [ "completed sending file 1 (logo.jpg) to bob",
"completed sending file 1 (logo.jpg) to cath"
]
alice ##> "/fs 1"
alice <##. "sending file 1 (logo.jpg) complete",
do
bob <# "#team alice> voice message (00:10)"
bob <# "#team alice> sends file logo.jpg (31.3 KiB / 32080 bytes)"
bob <## "started receiving file 1 (logo.jpg) from alice"
bob <## "completed receiving file 1 (logo.jpg) from alice",
do
cath <# "#team alice> voice message (00:10)"
cath <# "#team alice> sends file logo.jpg (31.3 KiB / 32080 bytes)"
cath <## "started receiving file 1 (logo.jpg) from alice"
cath <## "completed receiving file 1 (logo.jpg) from alice"
]
src <- B.readFile "./tests/fixtures/logo.jpg"
dest1 <- B.readFile "./tests/tmp/bob/logo.jpg"
dest2 <- B.readFile "./tests/tmp/cath/logo.jpg"
dest1 `shouldBe` src
dest2 `shouldBe` src
where
cfg = testCfg {inlineFiles = defaultInlineFilesConfig {offerChunks = 100, sendChunks = 100, totalSendChunks = 100, receiveChunks = 100}}
testSmallInlineGroupFileTransfer :: HasCallStack => FilePath -> IO ()
testSmallInlineGroupFileTransfer =
testChatCfg3 testCfg aliceProfile bobProfile cathProfile $
\alice bob cath -> do
createGroup3 "team" alice bob cath
bob ##> "/_files_folder ./tests/tmp/bob/"
bob <## "ok"
cath ##> "/_files_folder ./tests/tmp/cath/"
cath <## "ok"
alice ##> "/_send #1 json {\"msgContent\":{\"type\":\"voice\", \"duration\":10, \"text\":\"\"}, \"filePath\":\"./tests/fixtures/logo.jpg\"}"
alice <# "#team voice message (00:10)"
alice <# "/f #team ./tests/fixtures/logo.jpg"
-- below is not shown in "sent" mode
-- alice <## "use /fc 1 to cancel sending"
concurrentlyN_
[ do
alice
<### [ "completed sending file 1 (logo.jpg) to bob",
"completed sending file 1 (logo.jpg) to cath"
]
alice ##> "/fs 1"
alice <##. "sending file 1 (logo.jpg) complete",
do
bob <# "#team alice> voice message (00:10)"
bob <# "#team alice> sends file logo.jpg (31.3 KiB / 32080 bytes)"
bob <## "started receiving file 1 (logo.jpg) from alice"
bob <## "completed receiving file 1 (logo.jpg) from alice",
do
cath <# "#team alice> voice message (00:10)"
cath <# "#team alice> sends file logo.jpg (31.3 KiB / 32080 bytes)"
cath <## "started receiving file 1 (logo.jpg) from alice"
cath <## "completed receiving file 1 (logo.jpg) from alice"
]
src <- B.readFile "./tests/fixtures/logo.jpg"
dest1 <- B.readFile "./tests/tmp/bob/logo.jpg"
dest2 <- B.readFile "./tests/tmp/cath/logo.jpg"
dest1 `shouldBe` src
dest2 `shouldBe` src
testSmallInlineGroupFileIgnored :: HasCallStack => FilePath -> IO ()
testSmallInlineGroupFileIgnored tmp = do
withNewTestChat tmp "alice" aliceProfile $ \alice ->
withNewTestChatOpts tmp testOpts {allowInstantFiles = False} "bob" bobProfile $ \bob -> do
withNewTestChatOpts tmp testOpts {allowInstantFiles = False} "cath" cathProfile $ \cath -> do
createGroup3 "team" alice bob cath
bob ##> "/_files_folder ./tests/tmp/bob/"
bob <## "ok"
cath ##> "/_files_folder ./tests/tmp/cath/"
cath <## "ok"
alice ##> "/_send #1 json {\"msgContent\":{\"type\":\"voice\", \"duration\":10, \"text\":\"\"}, \"filePath\":\"./tests/fixtures/logo.jpg\"}"
alice <# "#team voice message (00:10)"
alice <# "/f #team ./tests/fixtures/logo.jpg"
-- below is not shown in "sent" mode
-- alice <## "use /fc 1 to cancel sending"
concurrentlyN_
[ do
alice
<### [ "completed sending file 1 (logo.jpg) to bob",
"completed sending file 1 (logo.jpg) to cath"
]
alice ##> "/fs 1"
alice <##. "sending file 1 (logo.jpg) complete",
do
bob <# "#team alice> voice message (00:10)"
bob <# "#team alice> sends file logo.jpg (31.3 KiB / 32080 bytes)"
bob <## "use /fr 1 [<dir>/ | <path>] to receive it"
bob <## "A small file sent without acceptance - you can enable receiving such files with -f option."
bob ##> "/fr 1"
bob <## "file is already being received: logo.jpg",
do
cath <# "#team alice> voice message (00:10)"
cath <# "#team alice> sends file logo.jpg (31.3 KiB / 32080 bytes)"
cath <## "use /fr 1 [<dir>/ | <path>] to receive it"
cath <## "A small file sent without acceptance - you can enable receiving such files with -f option."
cath ##> "/fr 1"
cath <## "file is already being received: logo.jpg"
]
runTestGroupFileSndCancelBeforeTransfer :: HasCallStack => TestCC -> TestCC -> TestCC -> IO ()
runTestGroupFileSndCancelBeforeTransfer alice bob cath = do
createGroup3 "team" alice bob cath
alice #> "/f #team ./tests/fixtures/test.txt"
alice <## "use /fc 1 to cancel sending"
concurrentlyN_
[ do
bob <# "#team alice> sends file test.txt (11 bytes / 11 bytes)"
bob <## "use /fr 1 [<dir>/ | <path>] to receive it",
do
cath <# "#team alice> sends file test.txt (11 bytes / 11 bytes)"
cath <## "use /fr 1 [<dir>/ | <path>] to receive it"
]
alice ##> "/fc 1"
concurrentlyN_
[ alice <## "cancelled sending file 1 (test.txt)",
bob <## "alice cancelled sending file 1 (test.txt)",
cath <## "alice cancelled sending file 1 (test.txt)"
]
alice ##> "/fs 1"
alice <## "sending file 1 (test.txt): no file transfers"
alice <## "file transfer cancelled"
bob ##> "/fs 1"
bob <## "receiving file 1 (test.txt) cancelled"
bob ##> "/fr 1 ./tests/tmp"
bob <## "file cancelled: test.txt"
runTestMessageWithFile :: HasCallStack => TestCC -> TestCC -> IO ()
runTestMessageWithFile alice bob = do
connectUsers alice bob
alice ##> "/_send @2 json {\"filePath\": \"./tests/fixtures/test.jpg\", \"msgContent\": {\"type\": \"text\", \"text\": \"hi, sending a file\"}}"
alice <# "@bob hi, sending a file"
alice <# "/f @bob ./tests/fixtures/test.jpg"
alice <## "use /fc 1 to cancel sending"
bob <# "alice> hi, sending a file"
bob <# "alice> sends file test.jpg (136.5 KiB / 139737 bytes)"
bob <## "use /fr 1 [<dir>/ | <path>] to receive it"
bob ##> "/fr 1 ./tests/tmp"
bob <## "saving file 1 from alice to ./tests/tmp/test.jpg"
concurrently_
(bob <## "started receiving file 1 (test.jpg) from alice")
(alice <## "started sending file 1 (test.jpg) to bob")
concurrently_
(bob <## "completed receiving file 1 (test.jpg) from alice")
(alice <## "completed sending file 1 (test.jpg) to bob")
src <- B.readFile "./tests/fixtures/test.jpg"
dest <- B.readFile "./tests/tmp/test.jpg"
dest `shouldBe` src
alice #$> ("/_get chat @2 count=100", chatF, chatFeaturesF <> [((1, "hi, sending a file"), Just "./tests/fixtures/test.jpg")])
bob #$> ("/_get chat @2 count=100", chatF, chatFeaturesF <> [((0, "hi, sending a file"), Just "./tests/tmp/test.jpg")])
testSendImage :: HasCallStack => FilePath -> IO ()
testSendImage =
testChat2 aliceProfile bobProfile $
\alice bob -> do
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 <# "/f @bob ./tests/fixtures/test.jpg"
alice <## "use /fc 1 to cancel sending"
bob <# "alice> sends file test.jpg (136.5 KiB / 139737 bytes)"
bob <## "use /fr 1 [<dir>/ | <path>] to receive it"
bob ##> "/fr 1 ./tests/tmp"
bob <## "saving file 1 from alice to ./tests/tmp/test.jpg"
concurrently_
(bob <## "started receiving file 1 (test.jpg) from alice")
(alice <## "started sending file 1 (test.jpg) to bob")
concurrently_
(bob <## "completed receiving file 1 (test.jpg) from alice")
(alice <## "completed sending file 1 (test.jpg) to bob")
src <- B.readFile "./tests/fixtures/test.jpg"
dest <- B.readFile "./tests/tmp/test.jpg"
dest `shouldBe` src
alice #$> ("/_get chat @2 count=100", chatF, chatFeaturesF <> [((1, ""), Just "./tests/fixtures/test.jpg")])
bob #$> ("/_get chat @2 count=100", chatF, chatFeaturesF <> [((0, ""), Just "./tests/tmp/test.jpg")])
-- deleting contact without files folder set should not remove file
bob ##> "/d alice"
bob <## "alice: contact is deleted"
fileExists <- doesFileExist "./tests/tmp/test.jpg"
fileExists `shouldBe` True
testFilesFoldersSendImage :: HasCallStack => FilePath -> IO ()
testFilesFoldersSendImage =
testChat2 aliceProfile bobProfile $
\alice bob -> do
connectUsers alice bob
alice #$> ("/_files_folder ./tests/fixtures", 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 <# "/f @bob test.jpg"
alice <## "use /fc 1 to cancel sending"
bob <# "alice> sends file test.jpg (136.5 KiB / 139737 bytes)"
bob <## "use /fr 1 [<dir>/ | <path>] to receive it"
bob ##> "/fr 1"
bob <## "saving file 1 from alice to test.jpg"
concurrently_
(bob <## "started receiving file 1 (test.jpg) from alice")
(alice <## "started sending file 1 (test.jpg) to bob")
concurrently_
(bob <## "completed receiving file 1 (test.jpg) from alice")
(alice <## "completed sending file 1 (test.jpg) to bob")
src <- B.readFile "./tests/fixtures/test.jpg"
dest <- B.readFile "./tests/tmp/app_files/test.jpg"
dest `shouldBe` src
alice #$> ("/_get chat @2 count=100", chatF, chatFeaturesF <> [((1, ""), Just "test.jpg")])
bob #$> ("/_get chat @2 count=100", chatF, chatFeaturesF <> [((0, ""), Just "test.jpg")])
-- deleting contact with files folder set should remove file
checkActionDeletesFile "./tests/tmp/app_files/test.jpg" $ do
bob ##> "/d alice"
bob <## "alice: contact is deleted"
testFilesFoldersImageSndDelete :: HasCallStack => FilePath -> IO ()
testFilesFoldersImageSndDelete =
testChat2 aliceProfile bobProfile $
\alice bob -> do
connectUsers alice bob
alice #$> ("/_files_folder ./tests/tmp/alice_app_files", id, "ok")
copyFile "./tests/fixtures/test_1MB.pdf" "./tests/tmp/alice_app_files/test_1MB.pdf"
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 <# "/f @bob test_1MB.pdf"
alice <## "use /fc 1 to cancel sending"
bob <# "alice> sends file test_1MB.pdf (1017.7 KiB / 1042157 bytes)"
bob <## "use /fr 1 [<dir>/ | <path>] to receive it"
bob ##> "/fr 1"
bob <## "saving file 1 from alice to test_1MB.pdf"
concurrently_
(bob <## "started receiving file 1 (test_1MB.pdf) from alice")
(alice <## "started sending file 1 (test_1MB.pdf) to bob")
-- deleting contact should cancel and remove file
checkActionDeletesFile "./tests/tmp/alice_app_files/test_1MB.pdf" $ do
alice ##> "/d bob"
alice <## "bob: contact is deleted"
bob ##> "/fs 1"
bob <##. "receiving file 1 (test_1MB.pdf) progress"
-- deleting contact should remove cancelled file
checkActionDeletesFile "./tests/tmp/bob_app_files/test_1MB.pdf" $ do
bob ##> "/d alice"
bob <## "alice: contact is deleted"
testFilesFoldersImageRcvDelete :: HasCallStack => FilePath -> IO ()
testFilesFoldersImageRcvDelete =
testChat2 aliceProfile bobProfile $
\alice bob -> do
connectUsers alice bob
alice #$> ("/_files_folder ./tests/fixtures", 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 <# "/f @bob test.jpg"
alice <## "use /fc 1 to cancel sending"
bob <# "alice> sends file test.jpg (136.5 KiB / 139737 bytes)"
bob <## "use /fr 1 [<dir>/ | <path>] to receive it"
bob ##> "/fr 1"
bob <## "saving file 1 from alice to test.jpg"
concurrently_
(bob <## "started receiving file 1 (test.jpg) from alice")
(alice <## "started sending file 1 (test.jpg) to bob")
-- deleting contact should cancel and remove file
waitFileExists "./tests/tmp/app_files/test.jpg"
checkActionDeletesFile "./tests/tmp/app_files/test.jpg" $ do
bob ##> "/d alice"
bob <## "alice: contact is deleted"
alice <## "bob cancelled receiving file 1 (test.jpg)"
alice ##> "/fs 1"
alice <## "sending file 1 (test.jpg) cancelled: bob"
testSendImageWithTextAndQuote :: HasCallStack => FilePath -> IO ()
testSendImageWithTextAndQuote =
testChat2 aliceProfile bobProfile $
\alice bob -> do
connectUsers alice bob
bob #> "@alice 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 <# "@bob > hi alice"
alice <## " hey bob"
alice <# "/f @bob ./tests/fixtures/test.jpg"
alice <## "use /fc 1 to cancel sending"
bob <# "alice> > hi alice"
bob <## " hey bob"
bob <# "alice> sends file test.jpg (136.5 KiB / 139737 bytes)"
bob <## "use /fr 1 [<dir>/ | <path>] to receive it"
bob ##> "/fr 1 ./tests/tmp"
bob <## "saving file 1 from alice to ./tests/tmp/test.jpg"
concurrently_
(bob <## "started receiving file 1 (test.jpg) from alice")
(alice <## "started sending file 1 (test.jpg) to bob")
concurrently_
(bob <## "completed receiving file 1 (test.jpg) from alice")
(alice <## "completed sending file 1 (test.jpg) to bob")
src <- B.readFile "./tests/fixtures/test.jpg"
B.readFile "./tests/tmp/test.jpg" `shouldReturn` src
alice #$> ("/_get chat @2 count=100", chat'', chatFeatures'' <> [((0, "hi alice"), Nothing, Nothing), ((1, "hey bob"), Just (0, "hi alice"), Just "./tests/fixtures/test.jpg")])
alice @@@ [("@bob", "hey bob")]
bob #$> ("/_get chat @2 count=100", chat'', chatFeatures'' <> [((1, "hi alice"), Nothing, Nothing), ((0, "hey bob"), Just (1, "hi alice"), Just "./tests/tmp/test.jpg")])
bob @@@ [("@alice", "hey bob")]
-- 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 <# "@alice > hey bob"
bob <## " test.pdf"
bob <# "/f @alice ./tests/fixtures/test.pdf"
bob <## "use /fc 2 to cancel sending"
alice <# "bob> > hey bob"
alice <## " test.pdf"
alice <# "bob> sends file test.pdf (266.0 KiB / 272376 bytes)"
alice <## "use /fr 2 [<dir>/ | <path>] to receive it"
alice ##> "/fr 2 ./tests/tmp"
alice <## "saving file 2 from bob to ./tests/tmp/test.pdf"
concurrently_
(alice <## "started receiving file 2 (test.pdf) from bob")
(bob <## "started sending file 2 (test.pdf) to alice")
concurrently_
(alice <## "completed receiving file 2 (test.pdf) from bob")
(bob <## "completed sending file 2 (test.pdf) to alice")
txtSrc <- B.readFile "./tests/fixtures/test.pdf"
B.readFile "./tests/tmp/test.pdf" `shouldReturn` txtSrc
-- 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 <# "@bob > test.pdf"
alice <## " test.jpg"
alice <# "/f @bob ./tests/fixtures/test.jpg"
alice <## "use /fc 3 to cancel sending"
bob <# "alice> > test.pdf"
bob <## " test.jpg"
bob <# "alice> sends file test.jpg (136.5 KiB / 139737 bytes)"
bob <## "use /fr 3 [<dir>/ | <path>] to receive it"
bob ##> "/fr 3 ./tests/tmp"
bob <## "saving file 3 from alice to ./tests/tmp/test_1.jpg"
concurrently_
(bob <## "started receiving file 3 (test.jpg) from alice")
(alice <## "started sending file 3 (test.jpg) to bob")
concurrently_
(bob <## "completed receiving file 3 (test.jpg) from alice")
(alice <## "completed sending file 3 (test.jpg) to bob")
B.readFile "./tests/tmp/test_1.jpg" `shouldReturn` src
testGroupSendImage :: SpecWith FilePath
testGroupSendImage = versionTestMatrix3 runTestGroupSendImage
where
runTestGroupSendImage :: HasCallStack => TestCC -> TestCC -> TestCC -> IO ()
runTestGroupSendImage alice bob cath = do
createGroup3 "team" alice bob cath
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 <# "/f #team ./tests/fixtures/test.jpg"
alice <## "use /fc 1 to cancel sending"
concurrentlyN_
[ do
bob <# "#team alice> sends file test.jpg (136.5 KiB / 139737 bytes)"
bob <## "use /fr 1 [<dir>/ | <path>] to receive it",
do
cath <# "#team alice> sends file test.jpg (136.5 KiB / 139737 bytes)"
cath <## "use /fr 1 [<dir>/ | <path>] to receive it"
]
bob ##> "/fr 1 ./tests/tmp/"
bob <## "saving file 1 from alice to ./tests/tmp/test.jpg"
concurrentlyN_
[ do
alice <## "started sending file 1 (test.jpg) to bob"
alice <## "completed sending file 1 (test.jpg) to bob",
do
bob <## "started receiving file 1 (test.jpg) from alice"
bob <## "completed receiving file 1 (test.jpg) from alice"
]
cath ##> "/fr 1 ./tests/tmp/"
cath <## "saving file 1 from alice to ./tests/tmp/test_1.jpg"
concurrentlyN_
[ do
alice <## "started sending file 1 (test.jpg) to cath"
alice <## "completed sending file 1 (test.jpg) to cath",
do
cath <## "started receiving file 1 (test.jpg) from alice"
cath <## "completed receiving file 1 (test.jpg) from alice"
]
src <- B.readFile "./tests/fixtures/test.jpg"
dest <- B.readFile "./tests/tmp/test.jpg"
dest `shouldBe` src
dest2 <- B.readFile "./tests/tmp/test_1.jpg"
dest2 `shouldBe` src
alice #$> ("/_get chat #1 count=1", chatF, [((1, ""), Just "./tests/fixtures/test.jpg")])
bob #$> ("/_get chat #1 count=1", chatF, [((0, ""), Just "./tests/tmp/test.jpg")])
cath #$> ("/_get chat #1 count=1", chatF, [((0, ""), Just "./tests/tmp/test_1.jpg")])
testGroupSendImageWithTextAndQuote :: HasCallStack => FilePath -> IO ()
testGroupSendImageWithTextAndQuote =
testChat3 aliceProfile bobProfile cathProfile $
\alice bob cath -> do
createGroup3 "team" alice bob cath
threadDelay 1000000
bob #> "#team hi team"
concurrently_
(alice <# "#team bob> hi team")
(cath <# "#team bob> hi team")
threadDelay 1000000
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 <# "#team > bob hi team"
alice <## " hey bob"
alice <# "/f #team ./tests/fixtures/test.jpg"
alice <## "use /fc 1 to cancel sending"
concurrentlyN_
[ do
bob <# "#team alice> > bob hi team"
bob <## " hey bob"
bob <# "#team alice> sends file test.jpg (136.5 KiB / 139737 bytes)"
bob <## "use /fr 1 [<dir>/ | <path>] to receive it",
do
cath <# "#team alice> > bob hi team"
cath <## " hey bob"
cath <# "#team alice> sends file test.jpg (136.5 KiB / 139737 bytes)"
cath <## "use /fr 1 [<dir>/ | <path>] to receive it"
]
bob ##> "/fr 1 ./tests/tmp/"
bob <## "saving file 1 from alice to ./tests/tmp/test.jpg"
concurrentlyN_
[ do
alice <## "started sending file 1 (test.jpg) to bob"
alice <## "completed sending file 1 (test.jpg) to bob",
do
bob <## "started receiving file 1 (test.jpg) from alice"
bob <## "completed receiving file 1 (test.jpg) from alice"
]
cath ##> "/fr 1 ./tests/tmp/"
cath <## "saving file 1 from alice to ./tests/tmp/test_1.jpg"
concurrentlyN_
[ do
alice <## "started sending file 1 (test.jpg) to cath"
alice <## "completed sending file 1 (test.jpg) to cath",
do
cath <## "started receiving file 1 (test.jpg) from alice"
cath <## "completed receiving file 1 (test.jpg) from alice"
]
src <- B.readFile "./tests/fixtures/test.jpg"
dest <- B.readFile "./tests/tmp/test.jpg"
dest `shouldBe` src
dest2 <- B.readFile "./tests/tmp/test_1.jpg"
dest2 `shouldBe` src
alice #$> ("/_get chat #1 count=2", chat'', [((0, "hi team"), Nothing, Nothing), ((1, "hey bob"), Just (0, "hi team"), Just "./tests/fixtures/test.jpg")])
alice @@@ [("#team", "hey bob"), ("@bob", "sent invitation to join group team as admin"), ("@cath", "sent invitation to join group team as admin")]
bob #$> ("/_get chat #1 count=2", chat'', [((1, "hi team"), Nothing, Nothing), ((0, "hey bob"), Just (1, "hi team"), Just "./tests/tmp/test.jpg")])
bob @@@ [("#team", "hey bob"), ("@alice", "received invitation to join group team as admin")]
cath #$> ("/_get chat #1 count=2", chat'', [((0, "hi team"), Nothing, Nothing), ((0, "hey bob"), Just (0, "hi team"), Just "./tests/tmp/test_1.jpg")])
cath @@@ [("#team", "hey bob"), ("@alice", "received invitation to join group team as admin")]
testAsyncFileTransferSenderRestarts :: HasCallStack => FilePath -> IO ()
testAsyncFileTransferSenderRestarts tmp = do
withNewTestChat tmp "bob" bobProfile $ \bob -> do
withNewTestChat tmp "alice" aliceProfile $ \alice -> do
connectUsers alice bob
startFileTransfer' alice bob "test_1MB.pdf" "1017.7 KiB / 1042157 bytes"
threadDelay 100000
withTestChatContactConnected tmp "alice" $ \alice -> do
alice <## "completed sending file 1 (test_1MB.pdf) to bob"
bob <## "completed receiving file 1 (test_1MB.pdf) from alice"
src <- B.readFile "./tests/fixtures/test_1MB.pdf"
dest <- B.readFile "./tests/tmp/test_1MB.pdf"
dest `shouldBe` src
testAsyncFileTransferReceiverRestarts :: HasCallStack => FilePath -> IO ()
testAsyncFileTransferReceiverRestarts tmp = do
withNewTestChat tmp "alice" aliceProfile $ \alice -> do
withNewTestChat tmp "bob" bobProfile $ \bob -> do
connectUsers alice bob
startFileTransfer' alice bob "test_1MB.pdf" "1017.7 KiB / 1042157 bytes"
threadDelay 100000
withTestChatContactConnected tmp "bob" $ \bob -> do
alice <## "completed sending file 1 (test_1MB.pdf) to bob"
bob <## "completed receiving file 1 (test_1MB.pdf) from alice"
src <- B.readFile "./tests/fixtures/test_1MB.pdf"
dest <- B.readFile "./tests/tmp/test_1MB.pdf"
dest `shouldBe` src
testAsyncFileTransfer :: HasCallStack => FilePath -> IO ()
testAsyncFileTransfer tmp = do
withNewTestChat tmp "alice" aliceProfile $ \alice ->
withNewTestChat tmp "bob" bobProfile $ \bob ->
connectUsers alice bob
withTestChatContactConnected tmp "alice" $ \alice -> do
alice ##> "/_send @2 json {\"filePath\": \"./tests/fixtures/test.jpg\", \"msgContent\": {\"type\":\"text\", \"text\": \"hi, sending a file\"}}"
alice <# "@bob hi, sending a file"
alice <# "/f @bob ./tests/fixtures/test.jpg"
alice <## "use /fc 1 to cancel sending"
withTestChatContactConnected tmp "bob" $ \bob -> do
bob <# "alice> hi, sending a file"
bob <# "alice> sends file test.jpg (136.5 KiB / 139737 bytes)"
bob <## "use /fr 1 [<dir>/ | <path>] to receive it"
bob ##> "/fr 1 ./tests/tmp"
bob <## "saving file 1 from alice to ./tests/tmp/test.jpg"
-- withTestChatContactConnected' tmp "alice" -- TODO not needed in v2
-- withTestChatContactConnected' tmp "bob" -- TODO not needed in v2
withTestChatContactConnected' tmp "alice"
withTestChatContactConnected' tmp "bob"
withTestChatContactConnected tmp "alice" $ \alice -> do
alice <## "started sending file 1 (test.jpg) to bob"
alice <## "completed sending file 1 (test.jpg) to bob"
withTestChatContactConnected tmp "bob" $ \bob -> do
bob <## "started receiving file 1 (test.jpg) from alice"
bob <## "completed receiving file 1 (test.jpg) from alice"
src <- B.readFile "./tests/fixtures/test.jpg"
dest <- B.readFile "./tests/tmp/test.jpg"
dest `shouldBe` src
testAsyncFileTransferV1 :: HasCallStack => FilePath -> IO ()
testAsyncFileTransferV1 tmp = do
withNewTestChatV1 tmp "alice" aliceProfile $ \alice ->
withNewTestChatV1 tmp "bob" bobProfile $ \bob ->
connectUsers alice bob
withTestChatContactConnectedV1 tmp "alice" $ \alice -> do
alice ##> "/_send @2 json {\"filePath\": \"./tests/fixtures/test.jpg\", \"msgContent\": {\"type\":\"text\", \"text\": \"hi, sending a file\"}}"
alice <# "@bob hi, sending a file"
alice <# "/f @bob ./tests/fixtures/test.jpg"
alice <## "use /fc 1 to cancel sending"
withTestChatContactConnectedV1 tmp "bob" $ \bob -> do
bob <# "alice> hi, sending a file"
bob <# "alice> sends file test.jpg (136.5 KiB / 139737 bytes)"
bob <## "use /fr 1 [<dir>/ | <path>] to receive it"
bob ##> "/fr 1 ./tests/tmp"
bob <## "saving file 1 from alice to ./tests/tmp/test.jpg"
withTestChatContactConnectedV1' tmp "alice" -- TODO not needed in v2
withTestChatContactConnectedV1' tmp "bob" -- TODO not needed in v2
withTestChatContactConnectedV1' tmp "alice"
withTestChatContactConnectedV1' tmp "bob"
withTestChatContactConnectedV1 tmp "alice" $ \alice -> do
alice <## "started sending file 1 (test.jpg) to bob"
alice <## "completed sending file 1 (test.jpg) to bob"
withTestChatContactConnectedV1 tmp "bob" $ \bob -> do
bob <## "started receiving file 1 (test.jpg) from alice"
bob <## "completed receiving file 1 (test.jpg) from alice"
src <- B.readFile "./tests/fixtures/test.jpg"
dest <- B.readFile "./tests/tmp/test.jpg"
dest `shouldBe` src
testAsyncGroupFileTransfer :: HasCallStack => FilePath -> IO ()
testAsyncGroupFileTransfer tmp = do
withNewTestChat tmp "alice" aliceProfile $ \alice ->
withNewTestChat tmp "bob" bobProfile $ \bob ->
withNewTestChat tmp "cath" cathProfile $ \cath ->
createGroup3 "team" alice bob cath
withTestChatGroup3Connected tmp "alice" $ \alice -> do
alice ##> "/_send #1 json {\"filePath\": \"./tests/fixtures/test.jpg\", \"msgContent\": {\"text\":\"\",\"type\":\"text\"}}"
alice <# "/f #team ./tests/fixtures/test.jpg"
alice <## "use /fc 1 to cancel sending"
withTestChatGroup3Connected tmp "bob" $ \bob -> do
bob <# "#team alice> sends file test.jpg (136.5 KiB / 139737 bytes)"
bob <## "use /fr 1 [<dir>/ | <path>] to receive it"
bob ##> "/fr 1 ./tests/tmp/"
bob <## "saving file 1 from alice to ./tests/tmp/test.jpg"
withTestChatGroup3Connected tmp "cath" $ \cath -> do
cath <# "#team alice> sends file test.jpg (136.5 KiB / 139737 bytes)"
cath <## "use /fr 1 [<dir>/ | <path>] to receive it"
cath ##> "/fr 1 ./tests/tmp/"
cath <## "saving file 1 from alice to ./tests/tmp/test_1.jpg"
withTestChatGroup3Connected' tmp "alice"
withTestChatGroup3Connected' tmp "bob"
withTestChatGroup3Connected' tmp "cath"
-- withTestChatGroup3Connected' tmp "alice" -- TODO not needed in v2
-- withTestChatGroup3Connected' tmp "bob" -- TODO not needed in v2
-- withTestChatGroup3Connected' tmp "cath" -- TODO not needed in v2
withTestChatGroup3Connected' tmp "alice"
withTestChatGroup3Connected tmp "bob" $ \bob -> do
bob <## "started receiving file 1 (test.jpg) from alice"
withTestChatGroup3Connected tmp "cath" $ \cath -> do
cath <## "started receiving file 1 (test.jpg) from alice"
withTestChatGroup3Connected tmp "alice" $ \alice -> do
alice
<### [ "started sending file 1 (test.jpg) to bob",
"completed sending file 1 (test.jpg) to bob",
"started sending file 1 (test.jpg) to cath",
"completed sending file 1 (test.jpg) to cath"
]
withTestChatGroup3Connected tmp "bob" $ \bob -> do
bob <## "completed receiving file 1 (test.jpg) from alice"
withTestChatGroup3Connected tmp "cath" $ \cath -> do
cath <## "completed receiving file 1 (test.jpg) from alice"
src <- B.readFile "./tests/fixtures/test.jpg"
dest <- B.readFile "./tests/tmp/test.jpg"
dest `shouldBe` src
dest2 <- B.readFile "./tests/tmp/test_1.jpg"
dest2 `shouldBe` src
startFileTransfer :: HasCallStack => TestCC -> TestCC -> IO ()
startFileTransfer alice bob =
startFileTransfer' alice bob "test.jpg" "136.5 KiB / 139737 bytes"
startFileTransfer' :: HasCallStack => TestCC -> TestCC -> String -> String -> IO ()
startFileTransfer' cc1 cc2 fileName fileSize = startFileTransferWithDest' cc1 cc2 fileName fileSize $ Just "./tests/tmp"
checkPartialTransfer :: HasCallStack => String -> IO ()
checkPartialTransfer fileName = do
src <- B.readFile $ "./tests/fixtures/" <> fileName
dest <- B.readFile $ "./tests/tmp/" <> fileName
B.unpack src `shouldStartWith` B.unpack dest
B.length src > B.length dest `shouldBe` True
waitFileExists :: HasCallStack => FilePath -> IO ()
waitFileExists f = unlessM (doesFileExist f) $ waitFileExists f

1781
tests/ChatTests/Groups.hs Normal file

File diff suppressed because it is too large Load diff

1328
tests/ChatTests/Profiles.hs Normal file

File diff suppressed because it is too large Load diff

442
tests/ChatTests/Utils.hs Normal file
View file

@ -0,0 +1,442 @@
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
module ChatTests.Utils where
import ChatClient
import Control.Concurrent.Async (concurrently_)
import Control.Concurrent.STM
import Control.Monad (unless, when)
import qualified Data.ByteString.Char8 as B
import Data.Char (isDigit)
import Data.List (isPrefixOf, isSuffixOf)
import Data.Maybe (fromMaybe)
import Data.String
import qualified Data.Text as T
import Simplex.Chat.Controller (ChatConfig (..), ChatController (..), InlineFilesConfig (..), defaultInlineFilesConfig)
import Simplex.Chat.Store (getUserContactProfiles)
import Simplex.Chat.Types
import Simplex.Messaging.Agent.Store.SQLite (withTransaction)
import Simplex.Messaging.Encoding.String
import System.Directory (doesFileExist)
import System.FilePath ((</>))
import Test.Hspec
defaultPrefs :: Maybe Preferences
defaultPrefs = Just $ toChatPrefs defaultChatPrefs
aliceProfile :: Profile
aliceProfile = Profile {displayName = "alice", fullName = "Alice", image = Nothing, preferences = defaultPrefs}
bobProfile :: Profile
bobProfile = Profile {displayName = "bob", fullName = "Bob", image = Just (ImageData "data:image/png;base64,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"), preferences = defaultPrefs}
cathProfile :: Profile
cathProfile = Profile {displayName = "cath", fullName = "Catherine", image = Nothing, preferences = defaultPrefs}
danProfile :: Profile
danProfile = Profile {displayName = "dan", fullName = "Daniel", image = Nothing, preferences = defaultPrefs}
versionTestMatrix2 :: (HasCallStack => TestCC -> TestCC -> IO ()) -> SpecWith FilePath
versionTestMatrix2 runTest = do
it "v2" $ testChat2 aliceProfile bobProfile runTest
it "v1" $ testChatCfg2 testCfgV1 aliceProfile bobProfile runTest
it "v1 to v2" $ runTestCfg2 testCfg testCfgV1 runTest
it "v2 to v1" $ runTestCfg2 testCfgV1 testCfg runTest
versionTestMatrix3 :: (HasCallStack => TestCC -> TestCC -> TestCC -> IO ()) -> SpecWith FilePath
versionTestMatrix3 runTest = do
it "v2" $ testChat3 aliceProfile bobProfile cathProfile runTest
-- it "v1" $ testChatCfg3 testCfgV1 aliceProfile bobProfile cathProfile runTest
-- it "v1 to v2" $ runTestCfg3 testCfg testCfgV1 testCfgV1 runTest
-- it "v2+v1 to v2" $ runTestCfg3 testCfg testCfg testCfgV1 runTest
-- it "v2 to v1" $ runTestCfg3 testCfgV1 testCfg testCfg runTest
-- it "v2+v1 to v1" $ runTestCfg3 testCfgV1 testCfg testCfgV1 runTest
inlineCfg :: Integer -> ChatConfig
inlineCfg n = testCfg {inlineFiles = defaultInlineFilesConfig {sendChunks = 0, offerChunks = n, receiveChunks = n}}
fileTestMatrix2 :: (HasCallStack => TestCC -> TestCC -> IO ()) -> SpecWith FilePath
fileTestMatrix2 runTest = do
it "via connection" $ runTestCfg2 viaConn viaConn runTest
it "inline (accepting)" $ runTestCfg2 inline inline runTest
it "via connection (inline offered)" $ runTestCfg2 inline viaConn runTest
it "via connection (inline supported)" $ runTestCfg2 viaConn inline runTest
where
inline = inlineCfg 100
viaConn = inlineCfg 0
fileTestMatrix3 :: (HasCallStack => TestCC -> TestCC -> TestCC -> IO ()) -> SpecWith FilePath
fileTestMatrix3 runTest = do
it "via connection" $ runTestCfg3 viaConn viaConn viaConn runTest
it "inline" $ runTestCfg3 inline inline inline runTest
it "via connection (inline offered)" $ runTestCfg3 inline viaConn viaConn runTest
it "via connection (inline supported)" $ runTestCfg3 viaConn inline inline runTest
where
inline = inlineCfg 100
viaConn = inlineCfg 0
runTestCfg2 :: ChatConfig -> ChatConfig -> (HasCallStack => TestCC -> TestCC -> IO ()) -> FilePath -> IO ()
runTestCfg2 aliceCfg bobCfg runTest tmp =
withNewTestChatCfg tmp aliceCfg "alice" aliceProfile $ \alice ->
withNewTestChatCfg tmp bobCfg "bob" bobProfile $ \bob ->
runTest alice bob
runTestCfg3 :: ChatConfig -> ChatConfig -> ChatConfig -> (HasCallStack => TestCC -> TestCC -> TestCC -> IO ()) -> FilePath -> IO ()
runTestCfg3 aliceCfg bobCfg cathCfg runTest tmp =
withNewTestChatCfg tmp aliceCfg "alice" aliceProfile $ \alice ->
withNewTestChatCfg tmp bobCfg "bob" bobProfile $ \bob ->
withNewTestChatCfg tmp cathCfg "cath" cathProfile $ \cath ->
runTest alice bob cath
withTestChatGroup3Connected :: HasCallStack => FilePath -> String -> (HasCallStack => TestCC -> IO a) -> IO a
withTestChatGroup3Connected tmp dbPrefix action = do
withTestChat tmp dbPrefix $ \cc -> do
cc <## "2 contacts connected (use /cs for the list)"
cc <## "#team: connected to server(s)"
action cc
withTestChatGroup3Connected' :: HasCallStack => FilePath -> String -> IO ()
withTestChatGroup3Connected' tmp dbPrefix = withTestChatGroup3Connected tmp dbPrefix $ \_ -> pure ()
withTestChatContactConnected :: HasCallStack => FilePath -> String -> (HasCallStack => TestCC -> IO a) -> IO a
withTestChatContactConnected tmp dbPrefix action =
withTestChat tmp dbPrefix $ \cc -> do
cc <## "1 contacts connected (use /cs for the list)"
action cc
withTestChatContactConnected' :: HasCallStack => FilePath -> String -> IO ()
withTestChatContactConnected' tmp dbPrefix = withTestChatContactConnected tmp dbPrefix $ \_ -> pure ()
withTestChatContactConnectedV1 :: HasCallStack => FilePath -> String -> (HasCallStack => TestCC -> IO a) -> IO a
withTestChatContactConnectedV1 tmp dbPrefix action =
withTestChatV1 tmp dbPrefix $ \cc -> do
cc <## "1 contacts connected (use /cs for the list)"
action cc
withTestChatContactConnectedV1' :: HasCallStack => FilePath -> String -> IO ()
withTestChatContactConnectedV1' tmp dbPrefix = withTestChatContactConnectedV1 tmp dbPrefix $ \_ -> pure ()
-- | test sending direct messages
(<##>) :: HasCallStack => TestCC -> TestCC -> IO ()
cc1 <##> cc2 = do
name1 <- userName cc1
name2 <- userName cc2
cc1 #> ("@" <> name2 <> " hi")
cc2 <# (name1 <> "> hi")
cc2 #> ("@" <> name1 <> " hey")
cc1 <# (name2 <> "> hey")
(##>) :: HasCallStack => TestCC -> String -> IO ()
cc ##> cmd = do
cc `send` cmd
cc <## cmd
(#>) :: HasCallStack => TestCC -> String -> IO ()
cc #> cmd = do
cc `send` cmd
cc <# cmd
(?#>) :: HasCallStack => TestCC -> String -> IO ()
cc ?#> cmd = do
cc `send` cmd
cc <# ("i " <> cmd)
(#$>) :: (Eq a, Show a, HasCallStack) => TestCC -> (String, String -> a, a) -> Expectation
cc #$> (cmd, f, res) = do
cc ##> cmd
(f <$> getTermLine cc) `shouldReturn` res
chat :: String -> [(Int, String)]
chat = map (\(a, _, _) -> a) . chat''
chat' :: String -> [((Int, String), Maybe (Int, String))]
chat' = map (\(a, b, _) -> (a, b)) . chat''
chatF :: String -> [((Int, String), Maybe String)]
chatF = map (\(a, _, c) -> (a, c)) . chat''
chat'' :: String -> [((Int, String), Maybe (Int, String), Maybe String)]
chat'' = read
chatFeatures :: [(Int, String)]
chatFeatures = map (\(a, _, _) -> a) chatFeatures''
chatFeatures' :: [((Int, String), Maybe (Int, String))]
chatFeatures' = map (\(a, b, _) -> (a, b)) chatFeatures''
chatFeaturesF :: [((Int, String), Maybe String)]
chatFeaturesF = map (\(a, _, c) -> (a, c)) chatFeatures''
chatFeatures'' :: [((Int, String), Maybe (Int, String), Maybe String)]
chatFeatures'' = [((0, "Disappearing messages: off"), Nothing, Nothing), ((0, "Full deletion: off"), Nothing, Nothing), ((0, "Voice messages: enabled"), Nothing, Nothing)]
groupFeatures :: [(Int, String)]
groupFeatures = map (\(a, _, _) -> a) groupFeatures''
groupFeatures'' :: [((Int, String), Maybe (Int, String), Maybe String)]
groupFeatures'' = [((0, "Disappearing messages: off"), Nothing, Nothing), ((0, "Direct messages: on"), Nothing, Nothing), ((0, "Full deletion: off"), Nothing, Nothing), ((0, "Voice messages: on"), Nothing, Nothing)]
itemId :: Int -> String
itemId i = show $ length chatFeatures + i
(@@@) :: HasCallStack => TestCC -> [(String, String)] -> Expectation
(@@@) = getChats mapChats
mapChats :: [(String, String, Maybe ConnStatus)] -> [(String, String)]
mapChats = map $ \(ldn, msg, _) -> (ldn, msg)
chats :: String -> [(String, String)]
chats = mapChats . read
(@@@!) :: HasCallStack => TestCC -> [(String, String, Maybe ConnStatus)] -> Expectation
(@@@!) = getChats id
getChats :: HasCallStack => (Eq a, Show a) => ([(String, String, Maybe ConnStatus)] -> [a]) -> TestCC -> [a] -> Expectation
getChats f cc res = do
cc ##> "/_get chats 1 pcc=on"
line <- getTermLine cc
f (read line) `shouldMatchList` res
send :: TestCC -> String -> IO ()
send TestCC {chatController = cc} cmd = atomically $ writeTBQueue (inputQ cc) cmd
(<##) :: HasCallStack => TestCC -> String -> Expectation
cc <## line = do
l <- getTermLine cc
when (l /= line) $ print ("expected: " <> line, ", got: " <> l)
l `shouldBe` line
(<##.) :: HasCallStack => TestCC -> String -> Expectation
cc <##. line = do
l <- getTermLine cc
let prefix = line `isPrefixOf` l
unless prefix $ print ("expected to start from: " <> line, ", got: " <> l)
prefix `shouldBe` True
(<#.) :: HasCallStack => TestCC -> String -> Expectation
cc <#. line = do
l <- dropTime <$> getTermLine cc
let prefix = line `isPrefixOf` l
unless prefix $ print ("expected to start from: " <> line, ", got: " <> l)
prefix `shouldBe` True
(<##..) :: HasCallStack => TestCC -> [String] -> Expectation
cc <##.. ls = do
l <- getTermLine cc
let prefix = any (`isPrefixOf` l) ls
unless prefix $ print ("expected to start from one of: " <> show ls, ", got: " <> l)
prefix `shouldBe` True
data ConsoleResponse = ConsoleString String | WithTime String | EndsWith String
deriving (Show)
instance IsString ConsoleResponse where fromString = ConsoleString
-- this assumes that the string can only match one option
getInAnyOrder :: HasCallStack => (String -> String) -> TestCC -> [ConsoleResponse] -> Expectation
getInAnyOrder _ _ [] = pure ()
getInAnyOrder f cc ls = do
line <- f <$> getTermLine cc
let rest = filter (not . expected line) ls
if length rest < length ls
then getInAnyOrder f cc rest
else error $ "unexpected output: " <> line
where
expected :: String -> ConsoleResponse -> Bool
expected l = \case
ConsoleString s -> l == s
WithTime s -> dropTime_ l == Just s
EndsWith s -> s `isSuffixOf` l
(<###) :: HasCallStack => TestCC -> [ConsoleResponse] -> Expectation
(<###) = getInAnyOrder id
(<##?) :: HasCallStack => TestCC -> [ConsoleResponse] -> Expectation
(<##?) = getInAnyOrder dropTime
(<#) :: HasCallStack => TestCC -> String -> Expectation
cc <# line = (dropTime <$> getTermLine cc) `shouldReturn` line
(?<#) :: HasCallStack => TestCC -> String -> Expectation
cc ?<# line = (dropTime <$> getTermLine cc) `shouldReturn` "i " <> line
($<#) :: HasCallStack => (TestCC, String) -> String -> Expectation
(cc, uName) $<# line = (dropTime . dropUser uName <$> getTermLine cc) `shouldReturn` line
(</) :: HasCallStack => TestCC -> Expectation
(</) = (<// 500000)
(<#?) :: HasCallStack => TestCC -> TestCC -> Expectation
cc1 <#? cc2 = do
name <- userName cc2
sName <- showName cc2
cc2 <## "connection request sent!"
cc1 <## (sName <> " wants to connect to you!")
cc1 <## ("to accept: /ac " <> name)
cc1 <## ("to reject: /rc " <> name <> " (the sender will NOT be notified)")
dropUser :: HasCallStack => String -> String -> String
dropUser uName msg = fromMaybe err $ dropUser_ uName msg
where
err = error $ "invalid user: " <> msg
dropUser_ :: String -> String -> Maybe String
dropUser_ uName msg = do
let userPrefix = "[user: " <> uName <> "] "
if userPrefix `isPrefixOf` msg
then Just $ drop (length userPrefix) msg
else Nothing
dropTime :: HasCallStack => String -> String
dropTime msg = fromMaybe err $ dropTime_ msg
where
err = error $ "invalid time: " <> msg
dropTime_ :: String -> Maybe String
dropTime_ msg = case splitAt 6 msg of
([m, m', ':', s, s', ' '], text) ->
if all isDigit [m, m', s, s'] then Just text else Nothing
_ -> Nothing
getInvitation :: HasCallStack => TestCC -> IO String
getInvitation cc = do
cc <## "pass this invitation link to your contact (via another channel):"
cc <## ""
inv <- getTermLine cc
cc <## ""
cc <## "and ask them to connect: /c <invitation_link_above>"
pure inv
getContactLink :: HasCallStack => TestCC -> Bool -> IO String
getContactLink cc created = do
cc <## if created then "Your new chat address is created!" else "Your chat address:"
cc <## ""
link <- getTermLine cc
cc <## ""
cc <## "Anybody can send you contact requests with: /c <contact_link_above>"
cc <## "to show it again: /sa"
cc <## "to delete it: /da (accepted contacts will remain connected)"
pure link
getGroupLink :: HasCallStack => TestCC -> String -> Bool -> IO String
getGroupLink cc gName created = do
cc <## if created then "Group link is created!" else "Group link:"
cc <## ""
link <- getTermLine cc
cc <## ""
cc <## "Anybody can connect to you and join group with: /c <group_link_above>"
cc <## ("to show it again: /show link #" <> gName)
cc <## ("to delete it: /delete link #" <> gName <> " (joined members will remain connected to you)")
pure link
hasContactProfiles :: HasCallStack => TestCC -> [ContactName] -> Expectation
hasContactProfiles cc names =
getContactProfiles cc >>= \ps -> ps `shouldMatchList` names
getContactProfiles :: TestCC -> IO [ContactName]
getContactProfiles cc = do
user_ <- readTVarIO (currentUser $ chatController cc)
case user_ of
Nothing -> pure []
Just user -> do
profiles <- withTransaction (chatStore $ chatController cc) $ \db -> getUserContactProfiles db user
pure $ map (\Profile {displayName} -> displayName) profiles
lastItemId :: HasCallStack => TestCC -> IO String
lastItemId cc = do
cc ##> "/last_item_id"
getTermLine cc
showActiveUser :: HasCallStack => TestCC -> String -> Expectation
showActiveUser cc name = do
cc <## ("user profile: " <> name)
cc <## "use /p <display name> [<full name>] to change it"
cc <## "(the updated profile will be sent to all your contacts)"
connectUsers :: HasCallStack => TestCC -> TestCC -> IO ()
connectUsers cc1 cc2 = do
name1 <- showName cc1
name2 <- showName cc2
cc1 ##> "/c"
inv <- getInvitation cc1
cc2 ##> ("/c " <> inv)
cc2 <## "confirmation sent!"
concurrently_
(cc2 <## (name1 <> ": contact is connected"))
(cc1 <## (name2 <> ": contact is connected"))
showName :: TestCC -> IO String
showName (TestCC ChatController {currentUser} _ _ _ _) = do
Just User {localDisplayName, profile = LocalProfile {fullName}} <- readTVarIO currentUser
pure . T.unpack $ localDisplayName <> optionalFullName localDisplayName fullName
createGroup2 :: HasCallStack => String -> TestCC -> TestCC -> IO ()
createGroup2 gName cc1 cc2 = do
connectUsers cc1 cc2
name2 <- userName cc2
cc1 ##> ("/g " <> gName)
cc1 <## ("group #" <> gName <> " is created")
cc1 <## ("to add members use /a " <> gName <> " <name> or /create link #" <> gName)
addMember gName cc1 cc2 GRAdmin
cc2 ##> ("/j " <> gName)
concurrently_
(cc1 <## ("#" <> gName <> ": " <> name2 <> " joined the group"))
(cc2 <## ("#" <> gName <> ": you joined the group"))
createGroup3 :: HasCallStack => String -> TestCC -> TestCC -> TestCC -> IO ()
createGroup3 gName cc1 cc2 cc3 = do
createGroup2 gName cc1 cc2
connectUsers cc1 cc3
name3 <- userName cc3
sName2 <- showName cc2
sName3 <- showName cc3
addMember gName cc1 cc3 GRAdmin
cc3 ##> ("/j " <> gName)
concurrentlyN_
[ cc1 <## ("#" <> gName <> ": " <> name3 <> " joined the group"),
do
cc3 <## ("#" <> gName <> ": you joined the group")
cc3 <## ("#" <> gName <> ": member " <> sName2 <> " is connected"),
do
cc2 <## ("#" <> gName <> ": alice added " <> sName3 <> " to the group (connecting...)")
cc2 <## ("#" <> gName <> ": new member " <> name3 <> " is connected")
]
addMember :: HasCallStack => String -> TestCC -> TestCC -> GroupMemberRole -> IO ()
addMember gName inviting invitee role = do
name1 <- userName inviting
memName <- userName invitee
inviting ##> ("/a " <> gName <> " " <> memName <> " " <> B.unpack (strEncode role))
concurrentlyN_
[ inviting <## ("invitation to join the group #" <> gName <> " sent to " <> memName),
do
invitee <## ("#" <> gName <> ": " <> name1 <> " invites you to join the group as " <> B.unpack (strEncode role))
invitee <## ("use /j " <> gName <> " to accept")
]
checkActionDeletesFile :: HasCallStack => FilePath -> IO () -> IO ()
checkActionDeletesFile file action = do
fileExistsBefore <- doesFileExist file
fileExistsBefore `shouldBe` True
action
fileExistsAfter <- doesFileExist file
fileExistsAfter `shouldBe` False
startFileTransferWithDest' :: HasCallStack => TestCC -> TestCC -> String -> String -> Maybe String -> IO ()
startFileTransferWithDest' cc1 cc2 fileName fileSize fileDest_ = do
name1 <- userName cc1
name2 <- userName cc2
cc1 #> ("/f @" <> name2 <> " ./tests/fixtures/" <> fileName)
cc1 <## "use /fc 1 to cancel sending"
cc2 <# (name1 <> "> sends file " <> fileName <> " (" <> fileSize <> ")")
cc2 <## "use /fr 1 [<dir>/ | <path>] to receive it"
cc2 ##> ("/fr 1" <> maybe "" (" " <>) fileDest_)
cc2 <## ("saving file 1 from " <> name1 <> " to " <> maybe id (</>) fileDest_ fileName)
concurrently_
(cc2 <## ("started receiving file 1 (" <> fileName <> ") from " <> name1))
(cc1 <## ("started sending file 1 (" <> fileName <> ") to " <> name2))

View file

@ -2,7 +2,7 @@
module MobileTests where
import ChatTests
import ChatTests.Utils
import Control.Monad.Except
import Simplex.Chat.Mobile
import Simplex.Chat.Store