SimpleX-Chat/tests/ChatTests/Utils.hs

778 lines
34 KiB
Haskell
Raw Permalink Normal View History

{-# LANGUAGE CPP #-}
2023-02-01 17:21:13 +00:00
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PatternSynonyms #-}
2023-02-01 17:21:13 +00:00
{-# LANGUAGE RankNTypes #-}
module ChatTests.Utils where
import ChatClient
import ChatTests.DBUtils
2023-05-24 16:14:41 +04:00
import Control.Concurrent (threadDelay)
import Control.Concurrent.Async (concurrently_, mapConcurrently_)
2023-02-01 17:21:13 +00:00
import Control.Concurrent.STM
import Control.Monad (unless, when)
core: add notes chat type (#3568) * Add chat type "self" * rename to Notes * cover more things * remove quote, tweak sql * resolve comments * constrain ACIQDirection to exclude CTLocal * add CILocalRcv handling * plug in migrations and tests * cover more API, implement new folders * working create/send/tail * remove interaction with messages * add note deletion (api-only) * add folder deletion * add getLocalChatItemIdByText * add APICreateChatItem and files * add protocol check for getFileTransfer protocol * replace FTLocal with createLocalFile * add chat previews * add folder clear * add reactions * add read/unread * add note updates * resolve some comments * remove local reactions * remove folder names, deletion, add autocreate * add file deletion check * add preview pagination test * add per-item file deletion check * pull mkChatItem out of createLocal to prevent ci record updates * use - as notes name * bump migration ts * update schema * resolve comments * add chat pagination test * use chat queries from Direct instead * evict note folders from createUserRecord * switch to - for note folder chat type prefix and use empty name * fix getLocalChatXxx * add explicit createCCNoteFolder for tests * use overloadedstrings for single-line queries * add suggested chat list tests * add notes chat to a user-creating test * throw correct error for missing file * remove unique check from schema * add UndecidableInstances for ghc8.10 * switch to * for chat type sigil * add file safety test * add drop index * remove indentation * remove repeated folder * remove redundant filter query, NoteFolderName * don't attempt to cancel local files when deleting chat item * rename function * fix comment * rename * fix merge * fix typo * remove editable limit * restore comment * remove local file cancel * Revert "remove editable limit" This reverts commit 65df55caf88df8538c593dfd77b3c62e9c4bce06. * refactor --------- Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
2024-01-11 19:01:44 +02:00
import Control.Monad.Except (runExceptT)
import Data.ByteString (ByteString)
import qualified Data.ByteString.Base64 as B64
2023-02-01 17:21:13 +00:00
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 (..))
import Simplex.Chat.Messages.CIContent (e2eInfoNoPQText, e2eInfoPQText)
import Simplex.Chat.Protocol
import Simplex.Chat.Store.Direct (getContact)
core: add notes chat type (#3568) * Add chat type "self" * rename to Notes * cover more things * remove quote, tweak sql * resolve comments * constrain ACIQDirection to exclude CTLocal * add CILocalRcv handling * plug in migrations and tests * cover more API, implement new folders * working create/send/tail * remove interaction with messages * add note deletion (api-only) * add folder deletion * add getLocalChatItemIdByText * add APICreateChatItem and files * add protocol check for getFileTransfer protocol * replace FTLocal with createLocalFile * add chat previews * add folder clear * add reactions * add read/unread * add note updates * resolve some comments * remove local reactions * remove folder names, deletion, add autocreate * add file deletion check * add preview pagination test * add per-item file deletion check * pull mkChatItem out of createLocal to prevent ci record updates * use - as notes name * bump migration ts * update schema * resolve comments * add chat pagination test * use chat queries from Direct instead * evict note folders from createUserRecord * switch to - for note folder chat type prefix and use empty name * fix getLocalChatXxx * add explicit createCCNoteFolder for tests * use overloadedstrings for single-line queries * add suggested chat list tests * add notes chat to a user-creating test * throw correct error for missing file * remove unique check from schema * add UndecidableInstances for ghc8.10 * switch to * for chat type sigil * add file safety test * add drop index * remove indentation * remove repeated folder * remove redundant filter query, NoteFolderName * don't attempt to cancel local files when deleting chat item * rename function * fix comment * rename * fix merge * fix typo * remove editable limit * restore comment * remove local file cancel * Revert "remove editable limit" This reverts commit 65df55caf88df8538c593dfd77b3c62e9c4bce06. * refactor --------- Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
2024-01-11 19:01:44 +02:00
import Simplex.Chat.Store.NoteFolders (createNoteFolder)
import Simplex.Chat.Store.Profiles (getUserContactProfiles)
2023-02-01 17:21:13 +00:00
import Simplex.Chat.Types
import Simplex.Chat.Types.Preferences
import Simplex.Chat.Types.Shared
core: support batch sending in groups, batch introductions; send recent message history to new members (#3519) * core: batch send stubs, comments * multiple events in ChatMessage and supporting types * Revert "multiple events in ChatMessage and supporting types" This reverts commit 9b239b26ba5c8fdec41c6689a6421baf7ffcc27d. * schema, refactor group processing for batched messages * encoding, refactor processing * refactor code to work with updated schema * encoding, remove instances * wip * implement batching * batch introductions * wip * collect and send message history * missing new line * rename * test * rework to build history via chat items * refactor, tests * correctly set member version range, dont include deleted items * tests * fix disappearing messages * check number of errors * comment * check size in encodeChatMessage * fix - don't check msg size for binary * use builder * rename * rename * rework batching * lazy msg body * use withStoreBatch * refactor * reverse batches * comment * possibly fix builder for single msg * refactor batcher * refactor * dont repopulate msg_deliveries on down migration * EncodedChatMessage type * remove type * batcher tests * add tests * group history preference * test group link * fix tests * fix for random update * add test testImageFitsSingleBatch * refactor * rename function * refactor * mconcat * rename feature * catch error on each batch * refactor file inv retrieval * refactor gathering item forward events * refactor message batching * unite migrations * move files * refactor * Revert "unite migrations" This reverts commit 0be7a3117a2b4eb7f13f1ff639188bb3ff826af8. * refactor splitFileDescr * improve tests * Revert "dont repopulate msg_deliveries on down migration" This reverts commit 2944c1cc28acf85282a85d8458c67cefb7787ac7. * fix down migration --------- Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-12-23 17:07:23 +04:00
import Simplex.FileTransfer.Client.Main (xftpClientCLI)
import Simplex.Messaging.Agent.Client (agentClientStore)
import Simplex.Messaging.Agent.Store.AgentStore (maybeFirstRow, withTransaction)
import qualified Simplex.Messaging.Agent.Store.DB as DB
import qualified Simplex.Messaging.Crypto as C
import Simplex.Messaging.Crypto.Ratchet (PQEncryption (..), PQSupport, pattern PQEncOff, pattern PQEncOn, pattern PQSupportOff)
2023-02-01 17:21:13 +00:00
import Simplex.Messaging.Encoding.String
import Simplex.Messaging.Version
2023-02-01 17:21:13 +00:00
import System.Directory (doesFileExist)
core: support batch sending in groups, batch introductions; send recent message history to new members (#3519) * core: batch send stubs, comments * multiple events in ChatMessage and supporting types * Revert "multiple events in ChatMessage and supporting types" This reverts commit 9b239b26ba5c8fdec41c6689a6421baf7ffcc27d. * schema, refactor group processing for batched messages * encoding, refactor processing * refactor code to work with updated schema * encoding, remove instances * wip * implement batching * batch introductions * wip * collect and send message history * missing new line * rename * test * rework to build history via chat items * refactor, tests * correctly set member version range, dont include deleted items * tests * fix disappearing messages * check number of errors * comment * check size in encodeChatMessage * fix - don't check msg size for binary * use builder * rename * rename * rework batching * lazy msg body * use withStoreBatch * refactor * reverse batches * comment * possibly fix builder for single msg * refactor batcher * refactor * dont repopulate msg_deliveries on down migration * EncodedChatMessage type * remove type * batcher tests * add tests * group history preference * test group link * fix tests * fix for random update * add test testImageFitsSingleBatch * refactor * rename function * refactor * mconcat * rename feature * catch error on each batch * refactor file inv retrieval * refactor gathering item forward events * refactor message batching * unite migrations * move files * refactor * Revert "unite migrations" This reverts commit 0be7a3117a2b4eb7f13f1ff639188bb3ff826af8. * refactor splitFileDescr * improve tests * Revert "dont repopulate msg_deliveries on down migration" This reverts commit 2944c1cc28acf85282a85d8458c67cefb7787ac7. * fix down migration --------- Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-12-23 17:07:23 +04:00
import System.Environment (lookupEnv, withArgs)
import System.IO.Silently (capture_)
import System.Info (os)
import Test.Hspec hiding (it)
import qualified Test.Hspec as Hspec
import UnliftIO (timeout)
#if defined(dbPostgres)
import Database.PostgreSQL.Simple (Only (..))
#else
import Database.SQLite.Simple (Only (..))
#endif
2023-02-01 17:21:13 +00:00
defaultPrefs :: Maybe Preferences
defaultPrefs = Just $ toChatPrefs defaultChatPrefs
aliceDesktopProfile :: Profile
aliceDesktopProfile = Profile {displayName = "alice_desktop", fullName = "Alice Desktop", image = Nothing, contactLink = Nothing, preferences = defaultPrefs}
2023-02-01 17:21:13 +00:00
aliceProfile :: Profile
aliceProfile = Profile {displayName = "alice", fullName = "Alice", image = Nothing, contactLink = Nothing, preferences = defaultPrefs}
2023-02-01 17:21:13 +00:00
bobProfile :: Profile
bobProfile = Profile {displayName = "bob", fullName = "Bob", image = Just (ImageData "data:image/png;base64,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
2023-02-01 17:21:13 +00:00
cathProfile :: Profile
cathProfile = Profile {displayName = "cath", fullName = "Catherine", image = Nothing, contactLink = Nothing, preferences = defaultPrefs}
2023-02-01 17:21:13 +00:00
danProfile :: Profile
danProfile = Profile {displayName = "dan", fullName = "Daniel", image = Nothing, contactLink = Nothing, preferences = defaultPrefs}
2023-02-01 17:21:13 +00:00
businessProfile :: Profile
businessProfile = Profile {displayName = "biz", fullName = "Biz Inc", image = Nothing, contactLink = Nothing, preferences = defaultPrefs}
it :: HasCallStack => String -> (TestParams -> Expectation) -> SpecWith (Arg (TestParams -> Expectation))
it name test =
Hspec.it name $ \tmp -> timeout t (test tmp) >>= maybe (error "test timed out") pure
where
t = 90 * 1000000
xit' :: HasCallStack => String -> (TestParams -> Expectation) -> SpecWith (Arg (TestParams -> Expectation))
xit' = if os == "linux" then xit else it
xit'' :: (HasCallStack, Example a) => String -> a -> SpecWith (Arg a)
xit'' = ifCI xit Hspec.it
xdescribe'' :: HasCallStack => String -> SpecWith a -> SpecWith a
xdescribe'' = ifCI xdescribe describe
ifCI :: HasCallStack => (HasCallStack => String -> a -> SpecWith b) -> (HasCallStack => String -> a -> SpecWith b) -> String -> a -> SpecWith b
ifCI xrun run d t = do
ci <- runIO $ lookupEnv "CI"
(if ci == Just "true" then xrun else run) d t
skip :: String -> SpecWith a -> SpecWith a
skip = before_ . pendingWith
-- Bool is pqExpected - see testAddContact
versionTestMatrix2 :: (HasCallStack => Bool -> TestCC -> TestCC -> IO ()) -> SpecWith TestParams
2023-02-01 17:21:13 +00:00
versionTestMatrix2 runTest = do
it "current" $ testChat2 aliceProfile bobProfile (runTest True)
it "prev" $ testChatCfg2 testCfgVPrev aliceProfile bobProfile (runTest False)
it "prev to curr" $ runTestCfg2 testCfg testCfgVPrev (runTest False)
it "curr to prev" $ runTestCfg2 testCfgVPrev testCfg (runTest False)
it "old (1st supported)" $ testChatCfg2 testCfgV1 aliceProfile bobProfile (runTest False)
it "old to curr" $ runTestCfg2 testCfg testCfgV1 (runTest False)
it "curr to old" $ runTestCfg2 testCfgV1 testCfg (runTest False)
2023-02-01 17:21:13 +00:00
versionTestMatrix3 :: (HasCallStack => TestCC -> TestCC -> TestCC -> IO ()) -> SpecWith TestParams
versionTestMatrix3 runTest = do
it "current" $ testChat3 aliceProfile bobProfile cathProfile runTest
it "prev" $ testChatCfg3 testCfgVPrev aliceProfile bobProfile cathProfile runTest
it "prev to curr" $ runTestCfg3 testCfg testCfgVPrev testCfgVPrev runTest
it "curr+prev to curr" $ runTestCfg3 testCfg testCfg testCfgVPrev runTest
it "curr to prev" $ runTestCfg3 testCfgVPrev testCfg testCfg runTest
it "curr+prev to prev" $ runTestCfg3 testCfgVPrev testCfg testCfgVPrev runTest
2023-02-01 17:21:13 +00:00
runTestCfg2 :: ChatConfig -> ChatConfig -> (HasCallStack => TestCC -> TestCC -> IO ()) -> TestParams -> IO ()
runTestCfg2 aliceCfg bobCfg runTest ps =
withNewTestChatCfg ps aliceCfg "alice" aliceProfile $ \alice ->
withNewTestChatCfg ps bobCfg "bob" bobProfile $ \bob ->
2023-02-01 17:21:13 +00:00
runTest alice bob
runTestCfg3 :: ChatConfig -> ChatConfig -> ChatConfig -> (HasCallStack => TestCC -> TestCC -> TestCC -> IO ()) -> TestParams -> IO ()
runTestCfg3 aliceCfg bobCfg cathCfg runTest ps =
withNewTestChatCfg ps aliceCfg "alice" aliceProfile $ \alice ->
withNewTestChatCfg ps bobCfg "bob" bobProfile $ \bob ->
withNewTestChatCfg ps cathCfg "cath" cathProfile $ \cath ->
2023-02-01 17:21:13 +00:00
runTest alice bob cath
withTestChatGroup3Connected :: HasCallStack => TestParams -> String -> (HasCallStack => TestCC -> IO a) -> IO a
withTestChatGroup3Connected ps dbPrefix action = do
withTestChat ps dbPrefix $ \cc -> do
2023-02-01 17:21:13 +00:00
cc <## "2 contacts connected (use /cs for the list)"
cc <## "#team: connected to server(s)"
action cc
withTestChatGroup3Connected' :: HasCallStack => TestParams -> String -> IO ()
withTestChatGroup3Connected' ps dbPrefix = withTestChatGroup3Connected ps dbPrefix $ \_ -> pure ()
2023-02-01 17:21:13 +00:00
withTestChatContactConnected :: HasCallStack => TestParams -> String -> (HasCallStack => TestCC -> IO a) -> IO a
withTestChatContactConnected ps dbPrefix action =
withTestChat ps dbPrefix $ \cc -> do
2023-02-01 17:21:13 +00:00
cc <## "1 contacts connected (use /cs for the list)"
action cc
withTestChatContactConnected' :: HasCallStack => TestParams -> String -> IO ()
withTestChatContactConnected' ps dbPrefix = withTestChatContactConnected ps dbPrefix $ \_ -> pure ()
2023-02-01 17:21:13 +00:00
withTestChatContactConnectedV1 :: HasCallStack => TestParams -> String -> (HasCallStack => TestCC -> IO a) -> IO a
withTestChatContactConnectedV1 ps dbPrefix action =
withTestChatV1 ps dbPrefix $ \cc -> do
2023-02-01 17:21:13 +00:00
cc <## "1 contacts connected (use /cs for the list)"
action cc
withTestChatContactConnectedV1' :: HasCallStack => TestParams -> String -> IO ()
withTestChatContactConnectedV1' ps dbPrefix = withTestChatContactConnectedV1 ps dbPrefix $ \_ -> pure ()
2023-02-01 17:21:13 +00:00
-- | 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
-- / PQ combinators
(\#>) :: HasCallStack => (TestCC, String) -> TestCC -> IO ()
(\#>) = sndRcv PQEncOff False
(+#>) :: HasCallStack => (TestCC, String) -> TestCC -> IO ()
(+#>) = sndRcv PQEncOn False
(++#>) :: HasCallStack => (TestCC, String) -> TestCC -> IO ()
(++#>) = sndRcv PQEncOn True
sndRcv :: HasCallStack => PQEncryption -> Bool -> (TestCC, String) -> TestCC -> IO ()
sndRcv pqEnc enabled (cc1, msg) cc2 = do
name1 <- userName cc1
name2 <- userName cc2
let cmd = "@" <> name2 <> " " <> msg
cc1 `send` cmd
when enabled $ cc1 <## (name2 <> ": quantum resistant end-to-end encryption enabled")
cc1 <# cmd
cc1 `pqSndForContact` 2 `shouldReturn` pqEnc
when enabled $ cc2 <## (name1 <> ": quantum resistant end-to-end encryption enabled")
cc2 <# (name1 <> "> " <> msg)
cc2 `pqRcvForContact` 2 `shouldReturn` pqEnc
(\:#>) :: HasCallStack => (TestCC, String, VersionChat) -> (TestCC, VersionChat) -> IO ()
(\:#>) = sndRcvImg PQEncOff False
(+:#>) :: HasCallStack => (TestCC, String, VersionChat) -> (TestCC, VersionChat) -> IO ()
(+:#>) = sndRcvImg PQEncOn False
(++:#>) :: HasCallStack => (TestCC, String, VersionChat) -> (TestCC, VersionChat) -> IO ()
(++:#>) = sndRcvImg PQEncOn True
sndRcvImg :: HasCallStack => PQEncryption -> Bool -> (TestCC, String, VersionChat) -> (TestCC, VersionChat) -> IO ()
sndRcvImg pqEnc enabled (cc1, msg, v1) (cc2, v2) = do
name1 <- userName cc1
name2 <- userName cc2
g <- C.newRandom
img <- atomically $ B64.encode <$> C.randomBytes lrgLen g
cc1 `send` ("/_send @2 json {\"msgContent\":{\"type\":\"image\",\"text\":\"" <> msg <> "\",\"image\":\"" <> B.unpack img <> "\"}}")
cc1 .<## "}}"
cc1 <### ([ConsoleString (name2 <> ": quantum resistant end-to-end encryption enabled") | enabled] <> [WithTime ("@" <> name2 <> " " <> msg)])
cc1 `pqSndForContact` 2 `shouldReturn` pqEnc
cc1 `pqVerForContact` 2 `shouldReturn` v1
cc2 <### ([ConsoleString (name1 <> ": quantum resistant end-to-end encryption enabled") | enabled] <> [WithTime (name1 <> "> " <> msg)])
cc2 `pqRcvForContact` 2 `shouldReturn` pqEnc
cc2 `pqVerForContact` 2 `shouldReturn` v2
where
lrgLen = maxEncodedMsgLength * 3 `div` 4 - 110 -- 98 is ~ max size for binary image preview given the rest of the message
genProfileImg :: IO ByteString
genProfileImg = do
g <- C.newRandom
atomically $ B64.encode <$> C.randomBytes lrgLen g
where
lrgLen = maxEncodedInfoLength * 3 `div` 4 - 420
-- PQ combinators /
2023-02-01 17:21:13 +00:00
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
2023-02-01 17:21:13 +00:00
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, e2eeInfoPQStr), Nothing, Nothing),
((0, "Disappearing messages: allowed"), Nothing, Nothing),
((0, "Full deletion: off"), Nothing, Nothing),
((0, "Message reactions: enabled"), Nothing, Nothing),
((0, "Voice messages: enabled"), Nothing, Nothing),
((0, "Audio/video calls: enabled"), Nothing, Nothing)
]
e2eeInfoNoPQStr :: String
e2eeInfoNoPQStr = T.unpack e2eInfoNoPQText
e2eeInfoPQStr :: String
e2eeInfoPQStr = T.unpack e2eInfoPQText
lastChatFeature :: String
lastChatFeature = snd $ last chatFeatures
2023-02-01 17:21:13 +00:00
groupFeatures :: [(Int, String)]
groupFeatures = map (\(a, _, _) -> a) $ groupFeatures'' 0
sndGroupFeatures :: [(Int, String)]
sndGroupFeatures = map (\(a, _, _) -> a) $ groupFeatures'' 1
groupFeatureStrs :: [String]
groupFeatureStrs = map (\(a, _, _) -> snd a) $ groupFeatures'' 0
groupFeatures'' :: Int -> [((Int, String), Maybe (Int, String), Maybe String)]
groupFeatures'' dir =
[ ((dir, e2eeInfoNoPQStr), Nothing, Nothing),
((dir, "Disappearing messages: off"), Nothing, Nothing),
((dir, "Direct messages: on"), Nothing, Nothing),
((dir, "Full deletion: off"), Nothing, Nothing),
((dir, "Message reactions: on"), Nothing, Nothing),
((dir, "Voice messages: on"), Nothing, Nothing),
((dir, "Files and media: on"), Nothing, Nothing),
((dir, "SimpleX links: on"), Nothing, Nothing),
((dir, "Member reports: on"), Nothing, Nothing),
((dir, "Recent history: on"), Nothing, Nothing)
2023-05-24 16:14:41 +04:00
]
2023-02-01 17:21:13 +00:00
itemId :: Int -> String
itemId i = show $ length chatFeatures + i
(@@@) :: HasCallStack => TestCC -> [(String, String)] -> Expectation
2023-05-24 16:14:41 +04:00
(@@@) cc res = do
threadDelay 100000
2023-05-24 16:14:41 +04:00
getChats mapChats cc res
2023-02-01 17:21:13 +00:00
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 <- getTermLine cc
let suffix = line `isSuffixOf` l
unless suffix $ print ("expected to end with: " <> line, ", got: " <> l)
suffix `shouldBe` True
2023-02-01 17:21:13 +00:00
(<#.) :: 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 .<# line = do
l <- dropTime <$> getTermLine cc
let suffix = line `isSuffixOf` l
unless suffix $ print ("expected to end with: " <> line, ", got: " <> l)
suffix `shouldBe` True
2023-02-01 17:21:13 +00:00
(<##..) :: 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
(>*) :: HasCallStack => TestCC -> String -> IO ()
cc >* note = do
core: add notes chat type (#3568) * Add chat type "self" * rename to Notes * cover more things * remove quote, tweak sql * resolve comments * constrain ACIQDirection to exclude CTLocal * add CILocalRcv handling * plug in migrations and tests * cover more API, implement new folders * working create/send/tail * remove interaction with messages * add note deletion (api-only) * add folder deletion * add getLocalChatItemIdByText * add APICreateChatItem and files * add protocol check for getFileTransfer protocol * replace FTLocal with createLocalFile * add chat previews * add folder clear * add reactions * add read/unread * add note updates * resolve some comments * remove local reactions * remove folder names, deletion, add autocreate * add file deletion check * add preview pagination test * add per-item file deletion check * pull mkChatItem out of createLocal to prevent ci record updates * use - as notes name * bump migration ts * update schema * resolve comments * add chat pagination test * use chat queries from Direct instead * evict note folders from createUserRecord * switch to - for note folder chat type prefix and use empty name * fix getLocalChatXxx * add explicit createCCNoteFolder for tests * use overloadedstrings for single-line queries * add suggested chat list tests * add notes chat to a user-creating test * throw correct error for missing file * remove unique check from schema * add UndecidableInstances for ghc8.10 * switch to * for chat type sigil * add file safety test * add drop index * remove indentation * remove repeated folder * remove redundant filter query, NoteFolderName * don't attempt to cancel local files when deleting chat item * rename function * fix comment * rename * fix merge * fix typo * remove editable limit * restore comment * remove local file cancel * Revert "remove editable limit" This reverts commit 65df55caf88df8538c593dfd77b3c62e9c4bce06. * refactor --------- Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
2024-01-11 19:01:44 +02:00
cc `send` ("/* " <> note)
(dropTime <$> getTermLine cc) `shouldReturn` ("* " <> note)
data ConsoleResponse
= ConsoleString String
| WithTime String
| EndsWith String
| StartsWith String
| Predicate (String -> Bool)
2023-02-01 17:21:13 +00:00
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 = filterFirst (expected line) ls
2023-02-01 17:21:13 +00:00
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
StartsWith s -> s `isPrefixOf` l
Predicate p -> p l
filterFirst :: (a -> Bool) -> [a] -> [a]
filterFirst _ [] = []
2023-11-26 18:16:37 +00:00
filterFirst p (x : xs)
| p x = xs
| otherwise = x : filterFirst p xs
2023-02-01 17:21:13 +00:00
(<###) :: 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
ccs *<# line = mapConcurrently_ (<# line) ccs
2023-02-01 17:21:13 +00:00
(?<#) :: 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, String) -> String -> Expectation
(cc, p) ^<# line = (dropTime . dropStrPrefix p <$> getTermLine cc) `shouldReturn` line
() :: HasCallStack => TestCC -> String -> Expectation
cc line = (dropTime . dropReceipt <$> getTermLine cc) `shouldReturn` line
(%) :: HasCallStack => TestCC -> String -> Expectation
cc % line = (dropTime . dropPartialReceipt <$> getTermLine cc) `shouldReturn` line
2023-02-01 17:21:13 +00:00
(</) :: 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
dropStrPrefix :: HasCallStack => String -> String -> String
dropStrPrefix pfx s =
let (p, rest) = splitAt (length pfx) s
in if p == pfx then rest else error $ "no prefix " <> pfx <> " in string : " <> s
dropReceipt :: HasCallStack => String -> String
dropReceipt msg = fromMaybe err $ dropReceipt_ msg
where
err = error $ "invalid receipt: " <> msg
dropReceipt_ :: String -> Maybe String
dropReceipt_ msg = case splitAt 2 msg of
("", text) -> Just text
_ -> Nothing
dropPartialReceipt :: HasCallStack => String -> String
dropPartialReceipt msg = fromMaybe err $ dropPartialReceipt_ msg
where
err = error $ "invalid partial receipt: " <> msg
dropPartialReceipt_ :: String -> Maybe String
dropPartialReceipt_ msg = case splitAt 2 msg of
("% ", text) -> Just text
_ -> Nothing
2023-02-01 17:21:13 +00:00
getInvitation :: HasCallStack => TestCC -> IO String
getInvitation = getInvitation_ False
getShortInvitation :: HasCallStack => TestCC -> IO String
getShortInvitation = getInvitation_ True
getInvitation_ :: HasCallStack => Bool -> TestCC -> IO String
getInvitation_ short cc = do
2023-02-01 17:21:13 +00:00
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>"
when short $ cc <##. "The invitation link for old clients: https://simplex.chat/invitation#"
2023-02-01 17:21:13 +00:00
pure inv
getShortContactLink :: HasCallStack => TestCC -> Bool -> IO (String, String)
getShortContactLink cc created = do
shortLink <- getContactLink cc created
fullLink <- dropLinePrefix "The contact link for old clients: " =<< getTermLine cc
pure (shortLink, fullLink)
2023-02-01 17:21:13 +00:00
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 share with your contacts: /profile_address on"
2023-02-01 17:21:13 +00:00
cc <## "to delete it: /da (accepted contacts will remain connected)"
pure link
dropLinePrefix :: String -> String -> IO String
dropLinePrefix line s
| line `isPrefixOf` s = pure $ drop (length line) s
| otherwise = error $ "expected to start from: " <> line <> ", got: " <> s
getShortGroupLink :: HasCallStack => TestCC -> String -> GroupMemberRole -> Bool -> IO (String, String)
getShortGroupLink cc gName mRole created = do
shortLink <- getGroupLink cc gName mRole created
fullLink <- dropLinePrefix "The group link for old clients: " =<< getTermLine cc
pure (shortLink, fullLink)
getGroupLink :: HasCallStack => TestCC -> String -> GroupMemberRole -> Bool -> IO String
getGroupLink cc gName mRole created = do
2023-02-01 17:21:13 +00:00
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 as " <> B.unpack (strEncode mRole) <> " with: /c <group_link_above>")
2023-02-01 17:21:13 +00:00
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
2023-11-07 17:45:59 +04:00
withCCUser :: TestCC -> (User -> IO a) -> IO a
withCCUser cc action = do
user_ <- readTVarIO (currentUser $ chatController cc)
case user_ of
Nothing -> error "no user"
Just user -> action user
withCCTransaction :: TestCC -> (DB.Connection -> IO a) -> IO a
withCCTransaction cc action =
withTransaction (chatStore $ chatController cc) $ \db -> action db
withCCAgentTransaction :: TestCC -> (DB.Connection -> IO a) -> IO a
withCCAgentTransaction TestCC {chatController = ChatController {smpAgent}} action =
withTransaction (agentClientStore smpAgent) $ \db -> action db
core: add notes chat type (#3568) * Add chat type "self" * rename to Notes * cover more things * remove quote, tweak sql * resolve comments * constrain ACIQDirection to exclude CTLocal * add CILocalRcv handling * plug in migrations and tests * cover more API, implement new folders * working create/send/tail * remove interaction with messages * add note deletion (api-only) * add folder deletion * add getLocalChatItemIdByText * add APICreateChatItem and files * add protocol check for getFileTransfer protocol * replace FTLocal with createLocalFile * add chat previews * add folder clear * add reactions * add read/unread * add note updates * resolve some comments * remove local reactions * remove folder names, deletion, add autocreate * add file deletion check * add preview pagination test * add per-item file deletion check * pull mkChatItem out of createLocal to prevent ci record updates * use - as notes name * bump migration ts * update schema * resolve comments * add chat pagination test * use chat queries from Direct instead * evict note folders from createUserRecord * switch to - for note folder chat type prefix and use empty name * fix getLocalChatXxx * add explicit createCCNoteFolder for tests * use overloadedstrings for single-line queries * add suggested chat list tests * add notes chat to a user-creating test * throw correct error for missing file * remove unique check from schema * add UndecidableInstances for ghc8.10 * switch to * for chat type sigil * add file safety test * add drop index * remove indentation * remove repeated folder * remove redundant filter query, NoteFolderName * don't attempt to cancel local files when deleting chat item * rename function * fix comment * rename * fix merge * fix typo * remove editable limit * restore comment * remove local file cancel * Revert "remove editable limit" This reverts commit 65df55caf88df8538c593dfd77b3c62e9c4bce06. * refactor --------- Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
2024-01-11 19:01:44 +02:00
createCCNoteFolder :: TestCC -> IO ()
createCCNoteFolder cc =
withCCTransaction cc $ \db ->
withCCUser cc $ \user ->
runExceptT (createNoteFolder db user) >>= either (fail . show) pure
getProfilePictureByName :: TestCC -> String -> IO (Maybe String)
getProfilePictureByName cc displayName =
withTransaction (chatStore $ chatController cc) $ \db ->
maybeFirstRow fromOnly $
DB.query db "SELECT image FROM contact_profiles WHERE display_name = ? LIMIT 1" (Only displayName)
pqSndForContact :: TestCC -> ContactId -> IO PQEncryption
pqSndForContact = pqForContact_ pqSndEnabled PQEncOff
pqRcvForContact :: TestCC -> ContactId -> IO PQEncryption
pqRcvForContact = pqForContact_ pqRcvEnabled PQEncOff
pqForContact :: TestCC -> ContactId -> IO PQEncryption
pqForContact = pqForContact_ (Just . connPQEnabled) (error "impossible")
pqSupportForCt :: TestCC -> ContactId -> IO PQSupport
pqSupportForCt = pqForContact_ (\Connection {pqSupport} -> Just pqSupport) PQSupportOff
pqVerForContact :: TestCC -> ContactId -> IO VersionChat
pqVerForContact = pqForContact_ (Just . connChatVersion) (error "impossible")
pqForContact_ :: (Connection -> Maybe a) -> a -> TestCC -> ContactId -> IO a
pqForContact_ pqSel def cc contactId = (fromMaybe def . pqSel) <$> getCtConn cc contactId
getCtConn :: TestCC -> ContactId -> IO Connection
getCtConn cc contactId = getTestCCContact cc contactId >>= maybe (fail "no connection") pure . contactConn
getTestCCContact :: TestCC -> ContactId -> IO Contact
getTestCCContact cc contactId = do
let TestCC {chatController = ChatController {config = ChatConfig {chatVRange = vr}}} = cc
withCCTransaction cc $ \db ->
withCCUser cc $ \user ->
runExceptT (getContact db vr user contactId) >>= either (fail . show) pure
2023-02-01 17:21:13 +00:00
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> to change it"
2023-02-01 17:21:13 +00:00
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
2023-05-24 16:14:41 +04:00
showName (TestCC ChatController {currentUser} _ _ _ _ _) = do
2023-02-01 17:21:13 +00:00
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 = createGroup2' gName cc1 cc2 True
createGroup2' :: HasCallStack => String -> TestCC -> TestCC -> Bool -> IO ()
createGroup2' gName cc1 cc2 doConnectUsers = do
when doConnectUsers $ connectUsers cc1 cc2
2023-02-01 17:21:13 +00:00
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
2023-02-01 17:21:13 +00:00
cc2 ##> ("/j " <> gName)
concurrently_
(cc1 <## ("#" <> gName <> ": " <> name2 <> " joined the group"))
(cc2 <## ("#" <> gName <> ": you joined the group"))
disableFullDeletion2 :: HasCallStack => String -> TestCC -> TestCC -> IO ()
disableFullDeletion2 gName cc1 cc2 = do
cc1 ##> ("/set delete #" <> gName <> " off")
cc1 <## "updated group preferences:"
cc1 <## "Full deletion: off"
name1 <- userName cc1
cc2 <## (name1 <> " updated group #" <> gName <> ":")
cc2 <## "updated group preferences:"
cc2 <## "Full deletion: off"
2023-02-01 17:21:13 +00:00
createGroup3 :: HasCallStack => String -> TestCC -> TestCC -> TestCC -> IO ()
createGroup3 gName cc1 cc2 cc3 = do
createGroup2 gName cc1 cc2
2023-02-01 17:21:13 +00:00
connectUsers cc1 cc3
name1 <- userName cc1
2023-02-01 17:21:13 +00:00
name3 <- userName cc3
sName2 <- showName cc2
sName3 <- showName cc3
addMember gName cc1 cc3 GRAdmin
2023-02-01 17:21:13 +00:00
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 <> ": " <> name1 <> " added " <> sName3 <> " to the group (connecting...)")
2023-02-01 17:21:13 +00:00
cc2 <## ("#" <> gName <> ": new member " <> name3 <> " is connected")
]
disableFullDeletion3 :: HasCallStack => String -> TestCC -> TestCC -> TestCC -> IO ()
disableFullDeletion3 gName cc1 cc2 cc3 = do
disableFullDeletion2 gName cc1 cc2
name1 <- userName cc1
cc3 <## (name1 <> " updated group #" <> gName <> ":")
cc3 <## "updated group preferences:"
cc3 <## "Full deletion: off"
create2Groups3 :: HasCallStack => String -> String -> TestCC -> TestCC -> TestCC -> IO ()
create2Groups3 gName1 gName2 cc1 cc2 cc3 = do
createGroup3 gName1 cc1 cc2 cc3
createGroup2' gName2 cc1 cc2 False
name1 <- userName cc1
name3 <- userName cc3
addMember gName2 cc1 cc3 GRAdmin
cc3 ##> ("/j " <> gName2)
concurrentlyN_
[ cc1 <## ("#" <> gName2 <> ": " <> name3 <> " joined the group"),
do
cc3 <## ("#" <> gName2 <> ": you joined the group")
cc3 <##. ("#" <> gName2 <> ": member "), -- "#gName2: member sName2 is connected"
do
cc2 <##. ("#" <> gName2 <> ": " <> name1 <> " added ") -- "#gName2: name1 added sName3 to the group (connecting...)"
cc2 <##. ("#" <> gName2 <> ": new member ") -- "#gName2: new member name3 is connected"
]
2023-02-01 17:21:13 +00:00
addMember :: HasCallStack => String -> TestCC -> TestCC -> GroupMemberRole -> IO ()
addMember gName = fullAddMember gName ""
fullAddMember :: HasCallStack => String -> String -> TestCC -> TestCC -> GroupMemberRole -> IO ()
fullAddMember gName fullName inviting invitee role = do
2023-02-01 17:21:13 +00:00
name1 <- userName inviting
memName <- userName invitee
inviting ##> ("/a " <> gName <> " " <> memName <> " " <> B.unpack (strEncode role))
let fullName' = if null fullName || fullName == gName then "" else " (" <> fullName <> ")"
2023-02-01 17:21:13 +00:00
concurrentlyN_
[ inviting <## ("invitation to join the group #" <> gName <> " sent to " <> memName),
do
invitee <## ("#" <> gName <> fullName' <> ": " <> name1 <> " invites you to join the group as " <> B.unpack (strEncode role))
2023-02-01 17:21:13 +00:00
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
currentChatVRangeInfo :: String
currentChatVRangeInfo =
"peer chat protocol version range: " <> vRangeStr supportedChatVRange
vRangeStr :: VersionRange v -> String
vRangeStr (VersionRange minVer maxVer) = "(" <> show minVer <> ", " <> show maxVer <> ")"
linkAnotherSchema :: String -> String
linkAnotherSchema link
| "https://simplex.chat/" `isPrefixOf` link =
2023-11-26 18:16:37 +00:00
T.unpack $ T.replace "https://simplex.chat/" "simplex:/" $ T.pack link
| "simplex:/" `isPrefixOf` link =
2023-11-26 18:16:37 +00:00
T.unpack $ T.replace "simplex:/" "https://simplex.chat/" $ T.pack link
| otherwise = error "link starts with neither https://simplex.chat/ nor simplex:/"
core: support batch sending in groups, batch introductions; send recent message history to new members (#3519) * core: batch send stubs, comments * multiple events in ChatMessage and supporting types * Revert "multiple events in ChatMessage and supporting types" This reverts commit 9b239b26ba5c8fdec41c6689a6421baf7ffcc27d. * schema, refactor group processing for batched messages * encoding, refactor processing * refactor code to work with updated schema * encoding, remove instances * wip * implement batching * batch introductions * wip * collect and send message history * missing new line * rename * test * rework to build history via chat items * refactor, tests * correctly set member version range, dont include deleted items * tests * fix disappearing messages * check number of errors * comment * check size in encodeChatMessage * fix - don't check msg size for binary * use builder * rename * rename * rework batching * lazy msg body * use withStoreBatch * refactor * reverse batches * comment * possibly fix builder for single msg * refactor batcher * refactor * dont repopulate msg_deliveries on down migration * EncodedChatMessage type * remove type * batcher tests * add tests * group history preference * test group link * fix tests * fix for random update * add test testImageFitsSingleBatch * refactor * rename function * refactor * mconcat * rename feature * catch error on each batch * refactor file inv retrieval * refactor gathering item forward events * refactor message batching * unite migrations * move files * refactor * Revert "unite migrations" This reverts commit 0be7a3117a2b4eb7f13f1ff639188bb3ff826af8. * refactor splitFileDescr * improve tests * Revert "dont repopulate msg_deliveries on down migration" This reverts commit 2944c1cc28acf85282a85d8458c67cefb7787ac7. * fix down migration --------- Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
2023-12-23 17:07:23 +04:00
xftpCLI :: [String] -> IO [String]
xftpCLI params = lines <$> capture_ (withArgs params xftpClientCLI)
setRelativePaths :: HasCallStack => TestCC -> String -> String -> IO ()
setRelativePaths cc filesFolder tempFolder = do
cc ##> "/_stop"
cc <## "chat stopped"
cc #$> ("/_files_folder " <> filesFolder, id, "ok")
cc #$> ("/_temp_folder " <> tempFolder, id, "ok")
cc ##> "/_start"
cc <## "chat started"