Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
haskell-gargantext
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
157
Issues
157
List
Board
Labels
Milestones
Merge Requests
9
Merge Requests
9
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
gargantext
haskell-gargantext
Commits
1bec4e19
Verified
Commit
1bec4e19
authored
Nov 13, 2024
by
Przemyslaw Kaminski
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
[notifications] improve tests, ref
#418
,
#238
,
#341
parent
8fb583cb
Pipeline
#6962
passed with stages
in 38 minutes and 38 seconds
Changes
8
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
95 additions
and
34 deletions
+95
-34
WebSocket.hs
src/Gargantext/Core/Notifications/Dispatcher/WebSocket.hs
+1
-0
Logging.hs
src/Gargantext/System/Logging.hs
+4
-5
GraphQL.hs
test/Test/API/GraphQL.hs
+1
-1
Notifications.hs
test/Test/API/Notifications.hs
+54
-26
Setup.hs
test/Test/API/Setup.hs
+3
-2
Types.hs
test/Test/Database/Types.hs
+2
-0
Utils.hs
test/Test/Utils.hs
+23
-0
Notifications.hs
test/Test/Utils/Notifications.hs
+7
-0
No files found.
src/Gargantext/Core/Notifications/Dispatcher/WebSocket.hs
View file @
1bec4e19
...
@@ -66,6 +66,7 @@ wsServer = WSAPI { wsAPIServer = streamData }
...
@@ -66,6 +66,7 @@ wsServer = WSAPI { wsAPIServer = streamData }
)
[
Exc
.
Handler
$
\
(
err
::
WS
.
ConnectionException
)
->
)
[
Exc
.
Handler
$
\
(
err
::
WS
.
ConnectionException
)
->
case
err
of
case
err
of
WS
.
ConnectionClosed
->
logM
DEBUG
$
"[wsServer] connection closed"
WS
.
ConnectionClosed
->
logM
DEBUG
$
"[wsServer] connection closed"
WS
.
CloseRequest
_
_
->
logM
DEBUG
$
"[wsServer] close request"
_
->
Exc
.
throw
err
]
_
->
Exc
.
throw
err
]
...
...
src/Gargantext/System/Logging.hs
View file @
1bec4e19
...
@@ -14,6 +14,7 @@ module Gargantext.System.Logging (
...
@@ -14,6 +14,7 @@ module Gargantext.System.Logging (
)
where
)
where
import
Control.Exception.Safe
(
MonadMask
,
bracket
)
import
Control.Exception.Safe
(
MonadMask
,
bracket
)
import
Control.Monad
(
when
)
import
Control.Monad.IO.Class
import
Control.Monad.IO.Class
import
Control.Monad.Trans.Control
import
Control.Monad.Trans.Control
import
Data.Kind
(
Type
)
import
Data.Kind
(
Type
)
...
@@ -142,9 +143,7 @@ instance HasLogger IO where
...
@@ -142,9 +143,7 @@ instance HasLogger IO where
destroyLogger
_
=
pure
()
destroyLogger
_
=
pure
()
logMsg
(
IOLogger
minLvl
)
lvl
msg
=
do
logMsg
(
IOLogger
minLvl
)
lvl
msg
=
do
t
<-
getCurrentTime
t
<-
getCurrentTime
if
lvl
<
minLvl
when
(
lvl
>=
minLvl
)
$
do
then
pure
()
let
pfx
=
"["
<>
show
t
<>
"] ["
<>
show
lvl
<>
"] "
else
do
putStrLn
$
pfx
<>
msg
let
pfx
=
"["
<>
show
t
<>
"] ["
<>
show
lvl
<>
"] "
putStrLn
$
pfx
<>
msg
logTxt
lgr
lvl
msg
=
logMsg
lgr
lvl
(
T
.
unpack
msg
)
logTxt
lgr
lvl
msg
=
logMsg
lgr
lvl
(
T
.
unpack
msg
)
test/Test/API/GraphQL.hs
View file @
1bec4e19
...
@@ -21,7 +21,7 @@ tests :: Spec
...
@@ -21,7 +21,7 @@ tests :: Spec
tests
=
parallel
$
aroundAll
withTestDBAndPort
$
beforeAllWith
dbEnvSetup
$
do
tests
=
parallel
$
aroundAll
withTestDBAndPort
$
beforeAllWith
dbEnvSetup
$
do
describe
"GraphQL"
$
do
describe
"GraphQL"
$
do
describe
"get_user_infos"
$
do
describe
"get_user_infos"
$
do
it
"allows 'alice' to see her own info"
$
\
(
SpecContext
testEnv
port
app
_
)
->
do
it
"allows 'alice' to see her own info"
$
\
(
SpecContext
_
testEnv
port
app
_
)
->
do
withApplication
app
$
do
withApplication
app
$
do
withValidLogin
port
"alice"
(
GargPassword
"alice"
)
$
\
_clientEnv
token
->
do
withValidLogin
port
"alice"
(
GargPassword
"alice"
)
$
\
_clientEnv
token
->
do
let
query
=
[
r
|
{ "query": "{ user_infos(user_id: 2) { ui_id, ui_email } }" }
|]
let
query
=
[
r
|
{ "query": "{ user_infos(user_id: 2) { ui_id, ui_email } }" }
|]
...
...
test/Test/API/Notifications.hs
View file @
1bec4e19
...
@@ -19,12 +19,11 @@ module Test.API.Notifications (
...
@@ -19,12 +19,11 @@ module Test.API.Notifications (
)
where
)
where
import
Control.Concurrent
(
threadDelay
)
import
Control.Concurrent
(
threadDelay
)
import
Control.Concurrent.Async
(
withAsync
)
import
Control.Concurrent.STM.TChan
import
Control.Concurrent.STM.TChan
import
Control.Concurrent.STM.TSem
(
newTSem
,
signalTSem
)
import
Control.Lens
((
^.
))
import
Control.Lens
((
^.
))
import
Control.Monad.STM
(
atomically
)
import
Control.Monad.STM
(
atomically
)
import
Data.Aeson
qualified
as
Aeson
import
Data.Aeson
qualified
as
Aeson
import
Data.Maybe
(
isJust
)
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
...
@@ -32,47 +31,76 @@ import Gargantext.Core.Notifications.Dispatcher.Types qualified as DT
...
@@ -32,47 +31,76 @@ import Gargantext.Core.Notifications.Dispatcher.Types qualified as DT
import
Gargantext.System.Logging
(
logMsg
,
LogLevel
(
DEBUG
),
withLogger
)
import
Gargantext.System.Logging
(
logMsg
,
LogLevel
(
DEBUG
),
withLogger
)
import
Network.WebSockets
qualified
as
WS
import
Network.WebSockets
qualified
as
WS
import
Prelude
import
Prelude
import
System.Timeout
qualified
as
Timeout
import
Test.API.Setup
(
SpecContext
(
..
),
withTestDBAndPort
)
import
Test.API.Setup
(
SpecContext
(
..
),
withTestDBAndPort
)
import
Test.Database.Types
(
test_config
)
import
Test.Database.Types
(
test_config
)
import
Test.Hspec
import
Test.Hspec
import
Test.Instances
()
import
Test.Instances
()
import
Test.Utils.Notifications
import
Test.Utils
(
waitForTChanValue
,
waitForTSem
)
import
Test.Utils.Notifications
(
withAsyncWSConnection
)
tests
::
Spec
tests
::
Spec
tests
=
sequential
$
aroundAll
withTestDBAndPort
$
do
tests
=
sequential
$
aroundAll
withTestDBAndPort
$
do
describe
"Notifications"
$
do
describe
"Notifications"
$
do
it
"
simple
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
-- withLogger () $ \ioL -> do
-- logMsg ioL DEBUG $ "[ping WS notification works] nc: " <> show nc
let
topic
=
DT
.
UpdateTree
0
let
topic
=
DT
.
Ping
-- This semaphore is used to inform the main thread that the WS
-- client has subscribed. I think it's better to use async
-- locking mechanisms than blindly call 'threadDelay'.
waitWSTSem
<-
atomically
$
newTSem
0
tchan
<-
newTChanIO
::
IO
(
TChan
(
Maybe
DT
.
Notification
))
tchan
<-
newTChanIO
::
IO
(
TChan
(
Maybe
DT
.
Notification
))
-- setup a websocket connection
-- setup a websocket connection
let
wsConnect
=
let
wsConnect
conn
=
withLogger
()
$
\
ioL
->
do
withWSConnection
(
"127.0.0.1"
,
port
)
$
\
conn
->
do
-- logMsg ioL DEBUG $ "[wsConnect] subscribing topic: " <> show topic
-- We wait a bit before the server settles
WS
.
sendTextData
conn
$
Aeson
.
encode
(
DT
.
WSSubscribe
topic
)
-- threadDelay (100 * millisecond)
-- inform the test process that we sent the subscription request
withLogger
()
$
\
ioL
->
atomically
$
signalTSem
waitWSTSem
logMsg
ioL
DEBUG
$
"[wsConnect] subscribing topic: "
<>
show
topic
WS
.
sendTextData
conn
$
Aeson
.
encode
(
DT
.
WSSubscribe
topic
)
-- logMsg ioL DEBUG $ "[wsConnect] waiting for notification"
d
<-
WS
.
receiveData
conn
d
<-
WS
.
receiveData
conn
let
dec
=
Aeson
.
decode
d
::
Maybe
DT
.
Notification
-- logMsg ioL DEBUG $ "[wsConnect] received " <> show d
atomically
$
writeTChan
tchan
dec
let
dec
=
Aeson
.
decode
d
::
Maybe
DT
.
Notification
atomically
$
writeTChan
tchan
dec
-- wait a bit to settle
withAsyncWSConnection
(
"127.0.0.1"
,
port
)
wsConnect
$
\
_a
->
do
threadDelay
(
100
*
millisecond
)
-- wait for ws process to inform us about topic subscription
waitForTSem
waitWSTSem
500
threadDelay
300
_000
CE
.
notify
nc
$
CET
.
Ping
withAsync
wsConnect
$
\
_a
->
do
-- the ping value that should come from the notification
-- wait a bit to connect
waitForTChanValue
tchan
(
Just
DT
.
NPing
)
1
_000
threadDelay
(
500
*
millisecond
)
it
"simple update tree WS notification works"
$
\
(
SpecContext
testEnv
port
_app
_
)
->
do
let
nc
=
(
test_config
testEnv
)
^.
gc_notifications_config
let
topic
=
DT
.
UpdateTree
0
waitWSTSem
<-
atomically
$
newTSem
0
-- initially locked
tchan
<-
newTChanIO
::
IO
(
TChan
(
Maybe
DT
.
Notification
))
-- setup a websocket connection
let
wsConnect
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
waitWSTSem
-- 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
waitForTSem
waitWSTSem
500
let
nodeId
=
0
let
nodeId
=
0
CE
.
notify
nc
$
CET
.
UpdateTreeFirstLevel
nodeId
CE
.
notify
nc
$
CET
.
UpdateTreeFirstLevel
nodeId
mTimeout
<-
Timeout
.
timeout
(
5
*
1000000
)
$
do
waitForTChanValue
tchan
(
Just
$
DT
.
NUpdateTree
nodeId
)
1
_000
md
<-
atomically
$
readTChan
tchan
md
`
shouldBe
`
Just
(
DT
.
NUpdateTree
nodeId
)
mTimeout
`
shouldSatisfy
`
isJust
test/Test/API/Setup.hs
View file @
1bec4e19
...
@@ -115,7 +115,8 @@ withTestDBAndPort action = withNotifications nc $ \dispatcher -> do
...
@@ -115,7 +115,8 @@ withTestDBAndPort action = withNotifications nc $ \dispatcher -> do
-- An exception can be thrown by the websocket server (when client closes connection)
-- An exception can be thrown by the websocket server (when client closes connection)
-- TODO I don't quite understand why the exception has to be caught here
-- TODO I don't quite understand why the exception has to be caught here
-- and not under 'WS.runClient'
-- and not under 'WS.runClient'
catches
(
Warp
.
testWithApplicationSettings
stgs
(
pure
app
)
$
\
port
->
action
(
SpecContext
testEnv
port
app
()
))
catches
(
Warp
.
testWithApplicationSettings
stgs
(
pure
app
)
$
\
port
->
action
(
SpecContext
testEnv
port
app
()
))
[
Handler
$
\
(
err
::
WS
.
ConnectionException
)
->
[
Handler
$
\
(
err
::
WS
.
ConnectionException
)
->
case
err
of
case
err
of
WS
.
CloseRequest
_
_
->
WS
.
CloseRequest
_
_
->
...
@@ -131,7 +132,7 @@ withTestDBAndPort action = withNotifications nc $ \dispatcher -> do
...
@@ -131,7 +132,7 @@ withTestDBAndPort action = withNotifications nc $ \dispatcher -> do
-- re-throw any other exceptions
-- re-throw any other exceptions
,
Handler
$
\
(
err
::
SomeException
)
->
throw
err
]
,
Handler
$
\
(
err
::
SomeException
)
->
throw
err
]
-- | Starts the backend server /and/ the microservices proxy, the former at
-- | Starts the backend server /and/ the microservices proxy, the former at
-- a random port, the latter at a predictable port.
-- a random port, the latter at a predictable port.
...
...
test/Test/Database/Types.hs
View file @
1bec4e19
...
@@ -23,6 +23,7 @@ import Data.IORef
...
@@ -23,6 +23,7 @@ import Data.IORef
import
Data.Map
qualified
as
Map
import
Data.Map
qualified
as
Map
import
Data.Pool
import
Data.Pool
import
Data.Text
qualified
as
T
import
Data.Text
qualified
as
T
import
Data.Time.Clock
(
getCurrentTime
)
import
Database.PostgreSQL.Simple
qualified
as
PG
import
Database.PostgreSQL.Simple
qualified
as
PG
import
Database.Postgres.Temp
qualified
as
Tmp
import
Database.Postgres.Temp
qualified
as
Tmp
import
Gargantext
hiding
(
to
)
import
Gargantext
hiding
(
to
)
...
@@ -146,6 +147,7 @@ instance HasLogger (GargM TestEnv BackendInternalError) where
...
@@ -146,6 +147,7 @@ instance HasLogger (GargM TestEnv BackendInternalError) where
pure
$
GargTestLogger
mode
test_logger_set
pure
$
GargTestLogger
mode
test_logger_set
destroyLogger
GargTestLogger
{
..
}
=
liftIO
$
FL
.
rmLoggerSet
test_logger_set
destroyLogger
GargTestLogger
{
..
}
=
liftIO
$
FL
.
rmLoggerSet
test_logger_set
logMsg
(
GargTestLogger
mode
logger_set
)
lvl
msg
=
do
logMsg
(
GargTestLogger
mode
logger_set
)
lvl
msg
=
do
t
<-
liftIO
$
getCurrentTime
let
pfx
=
"["
<>
show
lvl
<>
"] "
::
Text
let
pfx
=
"["
<>
show
lvl
<>
"] "
::
Text
when
(
lvl
`
elem
`
(
modeToLoggingLevels
mode
))
$
when
(
lvl
`
elem
`
(
modeToLoggingLevels
mode
))
$
liftIO
$
FL
.
pushLogStrLn
logger_set
$
FL
.
toLogStr
pfx
<>
msg
liftIO
$
FL
.
pushLogStrLn
logger_set
$
FL
.
toLogStr
pfx
<>
msg
...
...
test/Test/Utils.hs
View file @
1bec4e19
...
@@ -3,6 +3,8 @@
...
@@ -3,6 +3,8 @@
module
Test.Utils
where
module
Test.Utils
where
import
Control.Concurrent.STM.TChan
(
TChan
,
readTChan
)
import
Control.Concurrent.STM.TSem
(
TSem
,
waitTSem
)
import
Control.Concurrent.STM.TVar
(
newTVarIO
,
writeTVar
,
readTVarIO
)
import
Control.Concurrent.STM.TVar
(
newTVarIO
,
writeTVar
,
readTVarIO
)
import
Control.Exception.Safe
()
import
Control.Exception.Safe
()
import
Control.Monad
()
import
Control.Monad
()
...
@@ -318,3 +320,24 @@ waitUntil pred' timeoutMs = do
...
@@ -318,3 +320,24 @@ waitUntil pred' timeoutMs = do
else
do
else
do
threadDelay
50000
threadDelay
50000
performTest
performTest
-- wait for given number of milliseconds for a given tchan value
waitForTChanValue
::
(
HasCallStack
,
Eq
a
,
Show
a
)
=>
TChan
a
->
a
->
Int
->
IO
()
waitForTChanValue
tchan
expected
timeoutMs
=
do
mTimeout
<-
Timeout
.
timeout
(
timeoutMs
*
1000
)
$
do
v
<-
atomically
$
readTChan
tchan
unless
(
v
==
expected
)
$
panicTrace
$
"[waitForTChanValue] v != expected ("
<>
show
v
<>
" != "
<>
show
expected
<>
")"
-- v `shouldBe` expected
-- no timeout should have occurred
-- mTimeout `shouldSatisfy` isJust
when
(
isNothing
mTimeout
)
$
panicTrace
$
"[waitForTChanValue] timeout when waiting for "
<>
show
expected
<>
" on tchan"
waitForTSem
::
HasCallStack
=>
TSem
->
Int
->
IO
()
waitForTSem
tsem
timeoutMs
=
do
mTimeout
<-
Timeout
.
timeout
(
timeoutMs
*
1000
)
$
do
atomically
$
waitTSem
tsem
when
(
isNothing
mTimeout
)
$
panicTrace
$
"[waitForTSem] timeout when waiting TSem"
test/Test/Utils/Notifications.hs
View file @
1bec4e19
...
@@ -5,6 +5,7 @@
...
@@ -5,6 +5,7 @@
module
Test.Utils.Notifications
where
module
Test.Utils.Notifications
where
import
Control.Concurrent.Async
(
Async
,
withAsync
)
import
Control.Exception.Safe
qualified
as
Exc
import
Control.Exception.Safe
qualified
as
Exc
import
Control.Monad
(
void
)
import
Control.Monad
(
void
)
import
Data.ByteString
qualified
as
BS
import
Data.ByteString
qualified
as
BS
...
@@ -46,3 +47,9 @@ withWSConnection' (host, port, path) cb = Exc.catches (
...
@@ -46,3 +47,9 @@ withWSConnection' (host, port, path) cb = Exc.catches (
-- re-throw any other exceptions
-- re-throw any other exceptions
,
Exc
.
Handler
$
\
(
err
::
Exc
.
SomeException
)
->
Exc
.
throw
err
]
,
Exc
.
Handler
$
\
(
err
::
Exc
.
SomeException
)
->
Exc
.
throw
err
]
-- | Same as 'withWSConnection', but spawns an async thread
withAsyncWSConnection
::
(
String
,
Int
)
->
WS
.
ClientApp
()
->
(
Async
()
->
IO
()
)
->
IO
()
withAsyncWSConnection
hp
wsCb
cb
=
withAsync
(
withWSConnection
hp
wsCb
)
cb
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment