[tests] notifications: test async notifications for update tree

Related to
#418
parent 874785e9
Pipeline #7256 canceled with stages
in 3 minutes and 34 seconds
...@@ -11,6 +11,7 @@ Portability : POSIX ...@@ -11,6 +11,7 @@ Portability : POSIX
{-# OPTIONS_GHC -Wno-orphans #-} {-# OPTIONS_GHC -Wno-orphans #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE ScopedTypeVariables #-}
...@@ -20,28 +21,37 @@ module Test.API.Notifications ( ...@@ -20,28 +21,37 @@ module Test.API.Notifications (
import Control.Concurrent (threadDelay) import Control.Concurrent (threadDelay)
import Control.Concurrent.STM.TChan import Control.Concurrent.STM.TChan
import Control.Concurrent.STM.TSem (newTSem, signalTSem) import Control.Concurrent.STM.TSem (newTSem, signalTSem, TSem)
import Control.Lens ((^.)) import Control.Lens ((^.))
import Control.Monad (void)
import Control.Monad.STM (atomically) import Control.Monad.STM (atomically)
import Data.Aeson qualified as Aeson import Data.Aeson qualified as Aeson
import Fmt ((+|), (|+))
import Gargantext.API.Admin.Auth.Types (AuthResponse, authRes_token, authRes_tree_id)
import Gargantext.Core.Config (gc_notifications_config) import Gargantext.Core.Config (gc_notifications_config)
import Gargantext.Core.Notifications.CentralExchange qualified as CE import Gargantext.Core.Notifications.CentralExchange qualified as CE
import Gargantext.Core.Notifications.CentralExchange.Types qualified as CET import Gargantext.Core.Notifications.CentralExchange.Types qualified as CET
import Gargantext.Core.Notifications.Dispatcher.Types qualified as DT import Gargantext.Core.Notifications.Dispatcher.Types qualified as DT
import Gargantext.Core.Types.Individu (GargPassword(..))
import Gargantext.System.Logging (withLogger) import Gargantext.System.Logging (withLogger)
import Network.WebSockets qualified as WS import Network.WebSockets qualified as WS
import Prelude import Prelude
import System.Timeout qualified as Timeout import System.Timeout qualified as Timeout
import Test.API.Setup (SpecContext(..), withTestDBAndPort) import Test.API.Prelude (newCorpusForUser)
import Test.API.Routes (mkUrl)
import Test.API.Setup (SpecContext(..), dbEnvSetup, withTestDBAndPort)
import Test.Database.Types (test_config) import Test.Database.Types (test_config)
import Test.Hspec import Test.Hspec
import Test.Hspec.Wai.Internal (withApplication)
import Test.Instances () import Test.Instances ()
import Test.Utils (waitForTChanValue, waitForTSem) import Text.RawString.QQ (r)
import Test.Utils (protected, waitForTChanValue, waitForTSem, withValidLoginA)
import Test.Utils.Notifications (withAsyncWSConnection) import Test.Utils.Notifications (withAsyncWSConnection)
tests :: Spec tests :: Spec
tests = sequential $ aroundAll withTestDBAndPort $ do tests = sequential $ around withTestDBAndPort $ do
describe "Notifications" $ do describe "Notifications" $ do
it "ping WS notification works" $ \(SpecContext testEnv port _app _) -> do it "ping WS notification works" $ \(SpecContext testEnv port _app _) -> do
let nc = (test_config testEnv) ^. gc_notifications_config let nc = (test_config testEnv) ^. gc_notifications_config
...@@ -54,20 +64,8 @@ tests = sequential $ aroundAll withTestDBAndPort $ do ...@@ -54,20 +64,8 @@ tests = sequential $ aroundAll withTestDBAndPort $ do
-- locking mechanisms than blindly call 'threadDelay'. -- locking mechanisms than blindly call 'threadDelay'.
wsTSem <- atomically $ newTSem 0 wsTSem <- atomically $ newTSem 0
tchan <- newTChanIO :: IO (TChan (Maybe DT.Notification)) tchan <- newTChanIO :: IO (TChan (Maybe DT.Notification))
-- setup a websocket connection
let wsConnect conn = withLogger () $ \_ioL -> do withAsyncWSConnection ("127.0.0.1", port) (wsConnection topic wsTSem tchan) $ \_a -> do
-- logMsg ioL DEBUG $ "[wsConnect] subscribing topic: " <> show topic
WS.sendTextData conn $ Aeson.encode (DT.WSSubscribe topic)
-- inform the test process that we sent the subscription request
atomically $ signalTSem wsTSem
-- logMsg ioL DEBUG $ "[wsConnect] waiting for notification"
d <- WS.receiveData conn
-- logMsg ioL DEBUG $ "[wsConnect] received " <> show d
let dec = Aeson.decode d :: Maybe DT.Notification
atomically $ writeTChan tchan dec
withAsyncWSConnection ("127.0.0.1", port) wsConnect $ \_a -> do
-- wait for ws process to inform us about topic subscription -- wait for ws process to inform us about topic subscription
waitForTSem wsTSem 500 waitForTSem wsTSem 500
...@@ -133,31 +131,86 @@ tests = sequential $ aroundAll withTestDBAndPort $ do ...@@ -133,31 +131,86 @@ tests = sequential $ aroundAll withTestDBAndPort $ do
-- wait for the value -- wait for the value
waitForTChanValue tchan Nothing 1_000 waitForTChanValue tchan Nothing 1_000
it "simple update tree WS notification works" $ \(SpecContext testEnv port _app _) -> do describe "Update tree notifications" $ do
let nc = (test_config testEnv) ^. gc_notifications_config it "simple WS notification works" $ \(SpecContext testEnv port _app _) -> do
let nc = (test_config testEnv) ^. gc_notifications_config
let topic = DT.UpdateTree 0
wsTSem <- atomically $ newTSem 0 -- initially locked
tchan <- newTChanIO :: IO (TChan (Maybe DT.Notification))
withAsyncWSConnection ("127.0.0.1", port) (wsConnection topic wsTSem tchan) $ \_a -> do
waitForTSem wsTSem 500
let nodeId = 0
CE.notify nc $ CET.UpdateTreeFirstLevel nodeId
waitForTChanValue tchan (Just $ DT.NUpdateTree nodeId) 1_000
let topic = DT.UpdateTree 0 it "WS notification on node creation works" $ \ctx@(SpecContext _testEnv port app _) -> do
wsTSem <- atomically $ newTSem 0 -- initially locked checkNotification ctx $ \authRes -> do
tchan <- newTChanIO :: IO (TChan (Maybe DT.Notification)) let token = authRes ^. authRes_token
-- setup a websocket connection let treeId = authRes ^. authRes_tree_id
let wsConnect conn = withLogger () $ \_ioL -> do let query = [r| {"pn_name": "test", "pn_typename": "NodeCorpus"} |]
-- logMsg ioL DEBUG $ "[wsConnect] subscribing topic: " <> show topic void $ withApplication app $ do
WS.sendTextData conn $ Aeson.encode (DT.WSSubscribe topic) protected token "POST" (mkUrl port $ "/node/" +| treeId |+ "") query
-- inform the test process that we sent the subscription request
atomically $ signalTSem wsTSem it "WS notification on node deletion works" $ \ctx@(SpecContext testEnv port app _) -> do
checkNotification ctx $ \authRes -> do
let token = authRes ^. authRes_token
cId <- newCorpusForUser testEnv "alice"
-- logMsg ioL DEBUG $ "[wsConnect] waiting for notification" void $ withApplication app $ do
d <- WS.receiveData conn protected token "DELETE" (mkUrl port $ "/node/" +| cId |+ "") ""
-- logMsg ioL DEBUG $ "[wsConnect] received " <> show d
let dec = Aeson.decode d :: Maybe DT.Notification it "WS notification on node rename works" $ \ctx@(SpecContext testEnv port app _) -> do
atomically $ writeTChan tchan dec checkNotification ctx $ \authRes -> do
let token = authRes ^. authRes_token
withAsyncWSConnection ("127.0.0.1", port) wsConnect $ \_a -> do cId <- newCorpusForUser testEnv "alice"
waitForTSem wsTSem 500
void $ withApplication app $ do
let nodeId = 0 let query = [r| {"name": "newName"} |]
CE.notify nc $ CET.UpdateTreeFirstLevel nodeId protected token "PUT" (mkUrl port $ "/node/" +| cId |+ "/rename") query
waitForTChanValue tchan (Just $ DT.NUpdateTree nodeId) 1_000
checkNotification :: SpecContext a
-> (AuthResponse -> IO ())
-> IO ()
checkNotification ctx@(SpecContext _testEnv port _app _) act = do
_ <- dbEnvSetup ctx
withValidLoginA port "alice" (GargPassword "alice") $ \_clientEnv authRes -> do
-- Subscribe to user tree notifications
let treeId = authRes ^. authRes_tree_id
let topic = DT.UpdateTree treeId
wsTSem <- atomically $ newTSem 0 -- initially locked
tchan <- newTChanIO :: IO (TChan (Maybe DT.Notification))
withAsyncWSConnection ("127.0.0.1", port) (wsConnection topic wsTSem tchan) $ \_a -> do
waitForTSem wsTSem 500
act authRes
waitForTChanValue tchan (Just $ DT.NUpdateTree treeId) 1_000
wsConnection :: DT.Topic
-> TSem
-> TChan (Maybe DT.Notification)
-> WS.Connection
-> IO ()
wsConnection topic wsTSem tchan conn = withLogger () $ \_ioL -> do
-- logMsg ioL DEBUG $ "[wsConnect] subscribing topic: " <> show topic
WS.sendTextData conn $ Aeson.encode (DT.WSSubscribe topic)
-- inform the test process that we sent the subscription request
atomically $ signalTSem wsTSem
-- logMsg ioL DEBUG $ "[wsConnect] waiting for notification"
d <- WS.receiveData conn
-- logMsg ioL DEBUG $ "[wsConnect] received " <> show d
let dec = Aeson.decode d :: Maybe DT.Notification
atomically $ writeTChan tchan dec
...@@ -16,8 +16,7 @@ import Data.Aeson qualified as JSON ...@@ -16,8 +16,7 @@ import Data.Aeson qualified as JSON
import Data.Text qualified as T import Data.Text qualified as T
import Gargantext.API.Errors import Gargantext.API.Errors
import Gargantext.Core.Types.Individu import Gargantext.Core.Types.Individu
import Gargantext.Core.Types (NodeId) import Gargantext.Core.Types (NodeId, NodeType(..))
import Gargantext.Core.Types (NodeType(..))
import Gargantext.Core.Worker.Env () -- instance HasNodeError import Gargantext.Core.Worker.Env () -- instance HasNodeError
import Gargantext.Database.Action.User import Gargantext.Database.Action.User
import Gargantext.Database.Admin.Types.Hyperdata.Corpus import Gargantext.Database.Admin.Types.Hyperdata.Corpus
......
...@@ -35,5 +35,5 @@ qcTests :: TestTree ...@@ -35,5 +35,5 @@ qcTests :: TestTree
qcTests = qcTests =
testGroup "Notifications QuickCheck tests" $ do testGroup "Notifications QuickCheck tests" $ do
[ QC.testProperty "CEMessage aeson encoding" $ \m -> A.decode (A.encode (m :: CEMessage)) == Just m [ QC.testProperty "CEMessage aeson encoding" $ \m -> A.decode (A.encode (m :: CEMessage)) == Just m
, QC.testProperty "Topic aeson encoding" $ \t -> A.decode (A.encode (t :: Topic)) == Just t , QC.testProperty "Topic aeson encoding" $ \t -> A.decode (A.encode (t :: Topic)) == Just t
, QC.testProperty "WSRequest aeson encoding" $ \ws -> A.decode (A.encode (ws :: WSRequest)) == Just ws ] , QC.testProperty "WSRequest aeson encoding" $ \ws -> A.decode (A.encode (ws :: WSRequest)) == Just ws ]
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment