module Test.AdminlessGroups where
import API.Brig
import API.Galley
import API.GalleyInternal hiding (getConversation)
import qualified API.GalleyInternal as GalleyI
import Control.Concurrent (threadDelay)
import MLS.Util
import Notifications
import SetupHelpers hiding (deleteUser)
import Testlib.Prelude
import Testlib.VersionedFed (FedDomain)
testOnLastAdminLeaveReturnEligibleMembers :: (HasCallStack) => App ()
testOnLastAdminLeaveReturnEligibleMembers :: HasCallStack => App ()
testOnLastAdminLeaveReturnEligibleMembers = do
(alice, tid, [bob]) <- Domain -> Int -> App (Value, String, [Value])
forall domain.
(HasCallStack, MakesValue domain) =>
domain -> Int -> App (Value, String, [Value])
createTeam Domain
OwnDomain Int
2
setTeamFeatureLockStatus OwnDomain tid "preventAdminlessGroups" "unlocked"
patchTeamFeature OwnDomain tid "preventAdminlessGroups" (object ["status" .= "enabled"]) >>= assertSuccess
localUser <- randomUser OwnDomain def
connectTwoUsers alice localUser
tmpUser <- ephemeralUser OwnDomain
remoteUser <- randomUser OtherDomain def
connectTwoUsers alice remoteUser
let newApp = NewApp
forall a. Default a => a
def {name = "some-app", description = "non-eligible app member"}
app <- bindResponse (createApp alice tid newApp) $ \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
200
Response
resp.json Maybe Value -> String -> App Value
forall a. (HasCallStack, MakesValue a) => a -> String -> App Value
%. String
"user"
clients@(alice1 : tmpUser1 : _) <- traverse (createMLSClient def) [alice, tmpUser, bob, localUser, remoteUser, app]
for_ clients (uploadNewKeyPackage def)
conv <- createTeamMLSConversation alice tid alice1 [bob, app, localUser, remoteUser]
convId <- objConvId conv
(key, code) <- bindResponse (postConversationCode alice conv Nothing Nothing) $ \Response
resp -> do
res <- HasCallStack => Int -> Response -> App Value
Int -> Response -> App Value
getJSON Int
201 Response
resp
(,) <$> (res %. "data.key" & asString) <*> (res %. "data.code" & asString)
postJoinCodeConv tmpUser key code >>= assertSuccess
void $ createExternalCommit convId tmpUser1 Nothing >>= sendAndConsumeCommitBundle
GalleyI.getConversation conv `bindResponse` \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
200
members <- Response
resp.json Maybe Value -> String -> App Value
forall a. (HasCallStack, MakesValue a) => a -> String -> App Value
%. String
"members.others" App Value -> (Value -> App [Value]) -> App [Value]
forall a b. App a -> (a -> App b) -> App b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= Value -> App [Value]
forall a. (HasCallStack, MakesValue a) => a -> App [Value]
asList
actual <- traverse (\Value
m -> Value
m Value -> String -> App Value
forall a. (HasCallStack, MakesValue a) => a -> String -> App Value
%. String
"qualified_id") members
expected <- traverse (\Value
m -> Value
m Value -> String -> App Value
forall a. (HasCallStack, MakesValue a) => a -> String -> App Value
%. String
"qualified_id") [alice, tmpUser, bob, localUser, remoteUser, app]
actual `shouldMatchSet` expected
assertAttemptToLeaveFails conv alice [bob, localUser]
void $ updateRole alice bob "wire_admin" (conv %. "qualified_id") >>= assertSuccess
bindResponse (removeMember alice conv alice) $ \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
200
assertAttemptToLeaveFails conv bob [localUser]
bindResponse (getConversation localUser conv) $ \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
200
Response
resp.json Maybe Value -> String -> App Value
forall a. (HasCallStack, MakesValue a) => a -> String -> App Value
%. String
"members.self.conversation_role" App Value -> String -> App ()
forall a b.
(MakesValue a, MakesValue b, HasCallStack) =>
a -> b -> App ()
`shouldMatch` String
"wire_member"
bindResponse (removeMemberV15 bob conv bob) $ \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
200
bindResponse (getConversation localUser conv) $ \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
200
Response
resp.json Maybe Value -> String -> App Value
forall a. (HasCallStack, MakesValue a) => a -> String -> App Value
%. String
"members.self.conversation_role" App Value -> String -> App ()
forall a b.
(MakesValue a, MakesValue b, HasCallStack) =>
a -> b -> App ()
`shouldMatch` String
"wire_admin"
where
assertAttemptToLeaveFails :: conv -> removed -> t a -> App ()
assertAttemptToLeaveFails conv
conv removed
user t a
eligible =
App Response -> (Response -> App ()) -> App ()
forall a.
HasCallStack =>
App Response -> (Response -> App a) -> App a
bindResponse (removed -> conv -> removed -> App Response
forall remover conv removed.
(HasCallStack, MakesValue remover, MakesValue conv,
MakesValue removed) =>
remover -> conv -> removed -> App Response
removeMember removed
user conv
conv removed
user) ((Response -> App ()) -> App ()) -> (Response -> App ()) -> App ()
forall a b. (a -> b) -> a -> b
$ \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
403
Response
resp.json Maybe Value -> String -> App Value
forall a. (HasCallStack, MakesValue a) => a -> String -> App Value
%. String
"label" App Value -> String -> App ()
forall a b.
(MakesValue a, MakesValue b, HasCallStack) =>
a -> b -> App ()
`shouldMatch` String
"adminless-conversation"
eligibleMembers <- Response
resp.json Maybe Value -> String -> App Value
forall a. (HasCallStack, MakesValue a) => a -> String -> App Value
%. String
"eligible_members" App Value -> (App Value -> App [Value]) -> App [Value]
forall a b. a -> (a -> b) -> b
& App Value -> App [Value]
forall a. (HasCallStack, MakesValue a) => a -> App [Value]
asList
expected <- for eligible $ \a
u -> a
u a -> String -> App Value
forall a. (HasCallStack, MakesValue a) => a -> String -> App Value
%. String
"qualified_id"
eligibleMembers `shouldMatchSet` expected
removeMemberV15 :: (HasCallStack, MakesValue remover, MakesValue conv, MakesValue removed) => remover -> conv -> removed -> App Response
removeMemberV15 :: forall remover conv removed.
(HasCallStack, MakesValue remover, MakesValue conv,
MakesValue removed) =>
remover -> conv -> removed -> App Response
removeMemberV15 remover
remover conv
qcnv removed
removed = do
(convDomain, convId) <- conv -> App (String, String)
forall a. (HasCallStack, MakesValue a) => a -> App (String, String)
objQid conv
qcnv
(removedDomain, removedId) <- objQid removed
req <- baseRequest remover Galley (ExplicitVersion 15) (joinHttpPath ["conversations", convDomain, convId, "members", removedDomain, removedId])
submit "DELETE" req
testOnLastAdminLeaveNoEligibleMembersExist :: (HasCallStack) => App ()
testOnLastAdminLeaveNoEligibleMembersExist :: HasCallStack => App ()
testOnLastAdminLeaveNoEligibleMembersExist = do
(alice, tid, _) <- Domain -> Int -> App (Value, String, [Value])
forall domain.
(HasCallStack, MakesValue domain) =>
domain -> Int -> App (Value, String, [Value])
createTeam Domain
OwnDomain Int
1
configureAdminlessGroupsFeature OwnDomain tid "enabled" "10s" ["9s", "8s"]
tmpUser <- ephemeralUser OwnDomain
alice1 <- createMLSClient def alice
tmpUser1 <- createMLSClient def tmpUser
traverse_ (uploadNewKeyPackage def) [alice1, tmpUser1]
conv <- createTeamMLSConversation alice tid alice1 []
let newApp = NewApp
forall a. Default a => a
def {name = "adminless-reminder-app", description = "not eligible for promotion"}
(app, _) <- createAndAddAppMember alice tid alice1 conv newApp
convId <- objConvId conv
(key, code) <- bindResponse (postConversationCode alice conv Nothing Nothing) $ \Response
resp -> do
res <- HasCallStack => Int -> Response -> App Value
Int -> Response -> App Value
getJSON Int
201 Response
resp
(,) <$> (res %. "data.key" & asString) <*> (res %. "data.code" & asString)
postJoinCodeConv tmpUser key code >>= assertSuccess
void $ createExternalCommit convId tmpUser1 Nothing >>= sendAndConsumeCommitBundle
GalleyI.getConversation conv `bindResponse` \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
200
members <- Response
resp.json Maybe Value -> String -> App Value
forall a. (HasCallStack, MakesValue a) => a -> String -> App Value
%. String
"members.others" App Value -> (Value -> App [Value]) -> App [Value]
forall a b. App a -> (a -> App b) -> App b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= Value -> App [Value]
forall a. (HasCallStack, MakesValue a) => a -> App [Value]
asList
actual <- traverse (\Value
m -> Value
m Value -> String -> App Value
forall a. (HasCallStack, MakesValue a) => a -> String -> App Value
%. String
"qualified_id") members
expected <- traverse (\Value
m -> Value
m Value -> String -> App Value
forall a. (HasCallStack, MakesValue a) => a -> String -> App Value
%. String
"qualified_id") [app, alice, tmpUser]
actual `shouldMatchSet` expected
withWebSockets [app, tmpUser] $ \[WebSocket
wsApp, WebSocket
wsTmpUser] -> do
App Response -> (Response -> App ()) -> App ()
forall a.
HasCallStack =>
App Response -> (Response -> App a) -> App a
bindResponse (Value -> Value -> Value -> App Response
forall remover conv removed.
(HasCallStack, MakesValue remover, MakesValue conv,
MakesValue removed) =>
remover -> conv -> removed -> App Response
removeMember Value
alice Value
conv Value
alice) ((Response -> App ()) -> App ()) -> (Response -> App ()) -> App ()
forall a b. (a -> b) -> a -> b
$ \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
200
App [Value] -> App ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (App [Value] -> App ()) -> App [Value] -> App ()
forall a b. (a -> b) -> a -> b
$ HasCallStack =>
Int -> (Value -> App Bool) -> WebSocket -> App [Value]
Int -> (Value -> App Bool) -> WebSocket -> App [Value]
awaitNMatches Int
2 Value -> App Bool
forall a. (HasCallStack, MakesValue a) => a -> App Bool
isConvAdminlessReminderNotif WebSocket
wsApp
App [Value] -> App ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (App [Value] -> App ()) -> App [Value] -> App ()
forall a b. (a -> b) -> a -> b
$ HasCallStack =>
Int -> (Value -> App Bool) -> WebSocket -> App [Value]
Int -> (Value -> App Bool) -> WebSocket -> App [Value]
awaitNMatches Int
2 Value -> App Bool
forall a. (HasCallStack, MakesValue a) => a -> App Bool
isConvAdminlessReminderNotif WebSocket
wsTmpUser
App Value -> App ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (App Value -> App ()) -> App Value -> App ()
forall a b. (a -> b) -> a -> b
$ HasCallStack =>
Int -> (Value -> App Bool) -> WebSocket -> App Value
Int -> (Value -> App Bool) -> WebSocket -> App Value
awaitMatchFor Int
15 Value -> App Bool
forall a. (HasCallStack, MakesValue a) => a -> App Bool
isConvDeleteNotif WebSocket
wsApp
App Response -> (Response -> App ()) -> App ()
forall a.
HasCallStack =>
App Response -> (Response -> App a) -> App a
bindResponse (Value -> App Response
forall conv.
(HasCallStack, MakesValue conv) =>
conv -> App Response
GalleyI.getConversation Value
conv) ((Response -> App ()) -> App ()) -> (Response -> App ()) -> App ()
forall a b. (a -> b) -> a -> b
$ \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
404
testAdminlessSetupOnFeatureEnable :: (HasCallStack) => App ()
testAdminlessSetupOnFeatureEnable :: HasCallStack => App ()
testAdminlessSetupOnFeatureEnable = do
(alice, tid, _) <- Domain -> Int -> App (Value, String, [Value])
forall domain.
(HasCallStack, MakesValue domain) =>
domain -> Int -> App (Value, String, [Value])
createTeam Domain
OwnDomain Int
1
setTeamFeatureLockStatus OwnDomain tid "preventAdminlessGroups" "unlocked"
patchTeamFeature OwnDomain tid "preventAdminlessGroups" (object ["status" .= "disabled"]) >>= assertSuccess
alice1 <- createMLSClient def alice
void $ uploadNewKeyPackage def alice1
conv <- createTeamMLSConversation alice tid alice1 []
let newApp = NewApp
forall a. Default a => a
def {name = "adminless-setup-app", description = "not eligible for promotion"}
(app, _) <- createAndAddAppMember alice tid alice1 conv newApp
bindResponse (removeMember alice conv alice) $ \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
200
bindResponse (GalleyI.getConversation conv) $ \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
200
withWebSockets [app] $ \[WebSocket
wsApp] -> do
Domain -> String -> String -> String -> [String] -> App ()
forall domain.
MakesValue domain =>
domain -> String -> String -> String -> [String] -> App ()
configureAdminlessGroupsFeature Domain
OwnDomain String
tid String
"enabled" String
"5s" [String
"4s"]
reminder <- HasCallStack =>
Int -> (Value -> App Bool) -> WebSocket -> App Value
Int -> (Value -> App Bool) -> WebSocket -> App Value
awaitMatchFor Int
20 Value -> App Bool
forall a. (HasCallStack, MakesValue a) => a -> App Bool
isConvSystemAdminlessReminderNotif WebSocket
wsApp
reminder %. "payload.0.qualified_conversation" `shouldMatch` objQidObject conv
void $ reminder %. "payload.0.data.deletion_scheduled_for" & asString
void $ awaitMatchFor 20 isConvDeleteNotif wsApp
bindResponse (GalleyI.getConversation conv) $ \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
404
testAdminlessReplaceMembers :: (HasCallStack) => App ()
testAdminlessReplaceMembers :: HasCallStack => App ()
testAdminlessReplaceMembers = do
Int -> (Value -> Value -> Value -> Int -> App ()) -> App ()
forall {t} {b}.
t -> (Value -> Value -> Value -> t -> App b) -> App b
testVersion Int
16 ((Value -> Value -> Value -> Int -> App ()) -> App ())
-> (Value -> Value -> Value -> Int -> App ()) -> App ()
forall a b. (a -> b) -> a -> b
$ \Value
alice Value
bob Value
conv Int
version -> do
bobId <- Value
bob Value -> String -> App Value
forall a. (HasCallStack, MakesValue a) => a -> String -> App Value
%. String
"qualified_id"
bindResponse (replaceMembers alice conv def {users = [bobId], version = Just version}) $ \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
200
bindResponse (getConversation bob conv) $ \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
200
Response
resp.json Maybe Value -> String -> App Value
forall a. (HasCallStack, MakesValue a) => a -> String -> App Value
%. String
"members.self.conversation_role" App Value -> String -> App ()
forall a b.
(MakesValue a, MakesValue b, HasCallStack) =>
a -> b -> App ()
`shouldMatch` String
"wire_admin"
members <- Response
resp.json Maybe Value -> String -> App Value
forall a. (HasCallStack, MakesValue a) => a -> String -> App Value
%. String
"members.others" App Value -> (App Value -> App [Value]) -> App [Value]
forall a b. a -> (a -> b) -> b
& App Value -> App [Value]
forall a. (HasCallStack, MakesValue a) => a -> App [Value]
asList
shouldBeEmpty members
Int -> (Value -> Value -> Value -> Int -> App ()) -> App ()
forall {t} {b}.
t -> (Value -> Value -> Value -> t -> App b) -> App b
testVersion Int
18 ((Value -> Value -> Value -> Int -> App ()) -> App ())
-> (Value -> Value -> Value -> Int -> App ()) -> App ()
forall a b. (a -> b) -> a -> b
$ \Value
alice Value
bob Value
conv Int
version -> do
bobId <- Value
bob Value -> String -> App Value
forall a. (HasCallStack, MakesValue a) => a -> String -> App Value
%. String
"qualified_id"
bindResponse (replaceMembers alice conv def {users = [bobId], version = Just version}) $ \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
403
Response
resp.json Maybe Value -> String -> App Value
forall a. (HasCallStack, MakesValue a) => a -> String -> App Value
%. String
"label" App Value -> String -> App ()
forall a b.
(MakesValue a, MakesValue b, HasCallStack) =>
a -> b -> App ()
`shouldMatch` String
"adminless-conversation"
eligibleMembers <- Response
resp.json Maybe Value -> String -> App Value
forall a. (HasCallStack, MakesValue a) => a -> String -> App Value
%. String
"eligible_members" App Value -> (App Value -> App [Value]) -> App [Value]
forall a b. a -> (a -> b) -> b
& App Value -> App [Value]
forall a. (HasCallStack, MakesValue a) => a -> App [Value]
asList
eligibleMembers `shouldMatchSet` [bobId]
bindResponse (getConversation alice conv) $ \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
200
Response
resp.json Maybe Value -> String -> App Value
forall a. (HasCallStack, MakesValue a) => a -> String -> App Value
%. String
"members.self.conversation_role" App Value -> String -> App ()
forall a b.
(MakesValue a, MakesValue b, HasCallStack) =>
a -> b -> App ()
`shouldMatch` String
"wire_admin"
members <- Response
resp.json Maybe Value -> String -> App Value
forall a. (HasCallStack, MakesValue a) => a -> String -> App Value
%. String
"members.others" App Value -> (App Value -> App [Value]) -> App [Value]
forall a b. a -> (a -> b) -> b
& App Value -> App [Value]
forall a. (HasCallStack, MakesValue a) => a -> App [Value]
asList
memberIds <- traverse (%. "qualified_id") members
memberIds `shouldMatchSet` [bobId]
where
testVersion :: t -> (Value -> Value -> Value -> t -> App b) -> App b
testVersion t
version Value -> Value -> Value -> t -> App b
assertResult = do
(alice, tid, [bob]) <- Domain -> Int -> App (Value, String, [Value])
forall domain.
(HasCallStack, MakesValue domain) =>
domain -> Int -> App (Value, String, [Value])
createTeam Domain
OwnDomain Int
2
configureAdminlessGroupsFeature OwnDomain tid "enabled" "10s" []
conv <- postConversation alice (defProteus {team = Just tid, qualifiedUsers = [bob], newUsersRole = "wire_member"}) >>= getJSON 201
assertResult alice bob conv version
testAdminlessReplaceMembersAddsAdmin :: (HasCallStack) => App ()
testAdminlessReplaceMembersAddsAdmin :: HasCallStack => App ()
testAdminlessReplaceMembersAddsAdmin = do
(alice, tid, [bob, charlie]) <- Domain -> Int -> App (Value, String, [Value])
forall domain.
(HasCallStack, MakesValue domain) =>
domain -> Int -> App (Value, String, [Value])
createTeam Domain
OwnDomain Int
3
configureAdminlessGroupsFeature OwnDomain tid "enabled" "10s" []
conv <- postConversation alice (defProteus {team = Just tid, qualifiedUsers = [bob], newUsersRole = "wire_member"}) >>= getJSON 201
bobId <- bob %. "qualified_id"
charlieId <- charlie %. "qualified_id"
bindResponse (replaceMembers alice conv def {users = [bobId, charlieId], role = Just "wire_admin", version = Just 18}) $ \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
200
bindResponse (getConversation charlie conv) $ \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
200
Response
resp.json Maybe Value -> String -> App Value
forall a. (HasCallStack, MakesValue a) => a -> String -> App Value
%. String
"members.self.conversation_role" App Value -> String -> App ()
forall a b.
(MakesValue a, MakesValue b, HasCallStack) =>
a -> b -> App ()
`shouldMatch` String
"wire_admin"
members <- Response
resp.json Maybe Value -> String -> App Value
forall a. (HasCallStack, MakesValue a) => a -> String -> App Value
%. String
"members.others" App Value -> (App Value -> App [Value]) -> App [Value]
forall a b. a -> (a -> b) -> b
& App Value -> App [Value]
forall a. (HasCallStack, MakesValue a) => a -> App [Value]
asList
memberIds <- traverse (%. "qualified_id") members
memberIds `shouldMatchSet` [bobId]
testAdminlessReplaceMembersAddsEligibleMember :: (HasCallStack) => App ()
testAdminlessReplaceMembersAddsEligibleMember :: HasCallStack => App ()
testAdminlessReplaceMembersAddsEligibleMember = do
(alice, tid, [bob]) <- Domain -> Int -> App (Value, String, [Value])
forall domain.
(HasCallStack, MakesValue domain) =>
domain -> Int -> App (Value, String, [Value])
createTeam Domain
OwnDomain Int
2
configureAdminlessGroupsFeature OwnDomain tid "enabled" "10s" []
conv <- postConversation alice (defProteus {team = Just tid, qualifiedUsers = [], newUsersRole = "wire_member"}) >>= getJSON 201
bobId <- bob %. "qualified_id"
bindResponse
(replaceMembers alice conv def {users = [bobId], role = Just "wire_member", version = Just 18})
$ \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
403
Response
resp.json Maybe Value -> String -> App Value
forall a. (HasCallStack, MakesValue a) => a -> String -> App Value
%. String
"label" App Value -> String -> App ()
forall a b.
(MakesValue a, MakesValue b, HasCallStack) =>
a -> b -> App ()
`shouldMatch` String
"adminless-conversation"
eligibleMembers <- Response
resp.json Maybe Value -> String -> App Value
forall a. (HasCallStack, MakesValue a) => a -> String -> App Value
%. String
"eligible_members" App Value -> (App Value -> App [Value]) -> App [Value]
forall a b. a -> (a -> b) -> b
& App Value -> App [Value]
forall a. (HasCallStack, MakesValue a) => a -> App [Value]
asList
eligibleMembers `shouldMatchSet` [bobId]
bindResponse (getConversation alice conv) $ \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
200
Response
resp.json Maybe Value -> String -> App Value
forall a. (HasCallStack, MakesValue a) => a -> String -> App Value
%. String
"members.self.conversation_role" App Value -> String -> App ()
forall a b.
(MakesValue a, MakesValue b, HasCallStack) =>
a -> b -> App ()
`shouldMatch` String
"wire_admin"
testAdminlessSetupSystemMemberUpdate :: (HasCallStack) => App ()
testAdminlessSetupSystemMemberUpdate :: HasCallStack => App ()
testAdminlessSetupSystemMemberUpdate = do
(alice, tid, [bob]) <- Domain -> Int -> App (Value, String, [Value])
forall domain.
(HasCallStack, MakesValue domain) =>
domain -> Int -> App (Value, String, [Value])
createTeam Domain
OwnDomain Int
2
setTeamFeatureLockStatus OwnDomain tid "preventAdminlessGroups" "unlocked"
patchTeamFeature OwnDomain tid "preventAdminlessGroups" (object ["status" .= "disabled"]) >>= assertSuccess
[alice1, bob1] <- traverse (createMLSClient def) [alice, bob]
traverse_ (uploadNewKeyPackage def) [alice1, bob1]
conv <- createTeamMLSConversation alice tid alice1 [bob]
bindResponse (removeMember alice conv alice) $ \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
200
withWebSockets [bob] $ \[WebSocket
wsBob] -> do
Domain -> String -> String -> Value -> App Response
forall domain team featureName payload.
(HasCallStack, MakesValue domain, MakesValue team,
MakesValue featureName, MakesValue payload) =>
domain -> team -> featureName -> payload -> App Response
patchTeamFeature Domain
OwnDomain String
tid String
"preventAdminlessGroups" ([Pair] -> Value
object [String
"status" String -> String -> Pair
forall a. ToJSON a => String -> a -> Pair
.= String
"enabled"]) App Response -> (Response -> App ()) -> App ()
forall a b. App a -> (a -> App b) -> App b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= HasCallStack => Response -> App ()
Response -> App ()
assertSuccess
notif <- HasCallStack =>
Int -> (Value -> App Bool) -> WebSocket -> App Value
Int -> (Value -> App Bool) -> WebSocket -> App Value
awaitMatchFor Int
20 Value -> App Bool
forall a. (HasCallStack, MakesValue a) => a -> App Bool
isConvSystemMemberUpdateNotif WebSocket
wsBob
notif %. "payload.0.qualified_conversation" `shouldMatch` objQidObject conv
notif %. "payload.0.data.qualified_target" `shouldMatch` objQidObject bob
notif %. "payload.0.data.conversation_role" `shouldMatch` "wire_admin"
bindResponse (getConversation bob conv) $ \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
200
Response
resp.json Maybe Value -> String -> App Value
forall a. (HasCallStack, MakesValue a) => a -> String -> App Value
%. String
"members.self.conversation_role" App Value -> String -> App ()
forall a b.
(MakesValue a, MakesValue b, HasCallStack) =>
a -> b -> App ()
`shouldMatch` String
"wire_admin"
testAdminlessSetupMemberUpdateAfterAdminLeaves :: (HasCallStack) => App ()
testAdminlessSetupMemberUpdateAfterAdminLeaves :: HasCallStack => App ()
testAdminlessSetupMemberUpdateAfterAdminLeaves = do
(alice, tid, [bob]) <- Domain -> Int -> App (Value, String, [Value])
forall domain.
(HasCallStack, MakesValue domain) =>
domain -> Int -> App (Value, String, [Value])
createTeam Domain
OwnDomain Int
2
setTeamFeatureLockStatus OwnDomain tid "preventAdminlessGroups" "unlocked"
patchTeamFeature OwnDomain tid "preventAdminlessGroups" (object ["status" .= "disabled"]) >>= assertSuccess
conv <-
postConversation
alice
(defProteus {team = Just tid, qualifiedUsers = [bob], newUsersRole = "wire_member"})
>>= getJSON 201
bindResponse (removeMember alice conv alice) $ \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
200
withWebSockets [bob] $ \[WebSocket
wsBob] -> do
Versioned -> Value -> String -> String -> Value -> App Response
forall user team featureName payload.
(HasCallStack, MakesValue user, MakesValue team,
MakesValue featureName, MakesValue payload) =>
Versioned -> user -> team -> featureName -> payload -> App Response
setTeamFeatureConfigVersioned (Int -> Versioned
ExplicitVersion Int
18) Value
alice String
tid String
"preventAdminlessGroups" (String -> String -> [String] -> Value
mkAdminlessFeature String
"enabled" String
"10s" []) App Response -> (Response -> App ()) -> App ()
forall a b. App a -> (a -> App b) -> App b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= HasCallStack => Response -> App ()
Response -> App ()
assertSuccess
notif <- HasCallStack =>
Int -> (Value -> App Bool) -> WebSocket -> App Value
Int -> (Value -> App Bool) -> WebSocket -> App Value
awaitMatchFor Int
20 Value -> App Bool
forall a. (HasCallStack, MakesValue a) => a -> App Bool
isMemberUpdateNotif WebSocket
wsBob
notif %. "payload.0.qualified_conversation" `shouldMatch` objQidObject conv
notif %. "payload.0.data.qualified_target" `shouldMatch` objQidObject bob
notif %. "payload.0.data.conversation_role" `shouldMatch` "wire_admin"
bindResponse (getConversation bob conv) $ \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
200
Response
resp.json Maybe Value -> String -> App Value
forall a. (HasCallStack, MakesValue a) => a -> String -> App Value
%. String
"members.self.conversation_role" App Value -> String -> App ()
forall a b.
(MakesValue a, MakesValue b, HasCallStack) =>
a -> b -> App ()
`shouldMatch` String
"wire_admin"
testAdminlessSetupDeletesWithOriginAndRemoteMembers :: (HasCallStack) => App ()
testAdminlessSetupDeletesWithOriginAndRemoteMembers :: HasCallStack => App ()
testAdminlessSetupDeletesWithOriginAndRemoteMembers = do
(alice, tid, _) <- Domain -> Int -> App (Value, String, [Value])
forall domain.
(HasCallStack, MakesValue domain) =>
domain -> Int -> App (Value, String, [Value])
createTeam Domain
OwnDomain Int
1
remoteUser <- randomUser OtherDomain def
connectTwoUsers alice remoteUser
setTeamFeatureLockStatus OwnDomain tid "preventAdminlessGroups" "unlocked"
patchTeamFeature OwnDomain tid "preventAdminlessGroups" (object ["status" .= "disabled"]) >>= assertSuccess
conv <-
postConversation
alice
(defProteus {team = Just tid, qualifiedUsers = [remoteUser], newUsersRole = "wire_member"})
>>= getJSON 201
convQid <- objQidObject conv
removeMember alice conv alice >>= assertSuccess
eventually $ bindResponse (listConversationIds remoteUser def) $ \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
200
conversationIds <- Response
resp.json Maybe Value -> String -> App Value
forall a. (HasCallStack, MakesValue a) => a -> String -> App Value
%. String
"qualified_conversations" App Value -> (App Value -> App [Value]) -> App [Value]
forall a b. a -> (a -> b) -> b
& App Value -> App [Value]
forall a. (HasCallStack, MakesValue a) => a -> App [Value]
asList
conversationIds `shouldContain` [convQid]
withWebSocket remoteUser $ \WebSocket
wsRemoteUser -> do
Versioned -> Value -> String -> String -> Value -> App Response
forall user team featureName payload.
(HasCallStack, MakesValue user, MakesValue team,
MakesValue featureName, MakesValue payload) =>
Versioned -> user -> team -> featureName -> payload -> App Response
setTeamFeatureConfigVersioned (Int -> Versioned
ExplicitVersion Int
18) Value
alice String
tid String
"preventAdminlessGroups" (String -> String -> [String] -> Value
mkAdminlessFeature String
"enabled" String
"1s" []) App Response -> (Response -> App ()) -> App ()
forall a b. App a -> (a -> App b) -> App b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= HasCallStack => Response -> App ()
Response -> App ()
assertSuccess
deleteNotif <- HasCallStack =>
Int -> (Value -> App Bool) -> WebSocket -> App Value
Int -> (Value -> App Bool) -> WebSocket -> App Value
awaitMatchFor Int
20 Value -> App Bool
forall a. (HasCallStack, MakesValue a) => a -> App Bool
isConvDeleteNotif WebSocket
wsRemoteUser
deleteNotif %. "payload.0.qualified_from" `shouldMatch` objQidObject alice
eventually $ bindResponse (listConversationIds remoteUser def) $ \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
200
conversationIds <- Response
resp.json Maybe Value -> String -> App Value
forall a. (HasCallStack, MakesValue a) => a -> String -> App Value
%. String
"qualified_conversations" App Value -> (App Value -> App [Value]) -> App [Value]
forall a b. a -> (a -> b) -> b
& App Value -> App [Value]
forall a. (HasCallStack, MakesValue a) => a -> App [Value]
asList
conversationIds `shouldNotContain` [convQid]
testAdminlessSetupDeletesWithSystemEventAndRemoteMembers :: (HasCallStack) => App ()
testAdminlessSetupDeletesWithSystemEventAndRemoteMembers :: HasCallStack => App ()
testAdminlessSetupDeletesWithSystemEventAndRemoteMembers = do
(alice, tid, _) <- Domain -> Int -> App (Value, String, [Value])
forall domain.
(HasCallStack, MakesValue domain) =>
domain -> Int -> App (Value, String, [Value])
createTeam Domain
OwnDomain Int
1
remoteUser <- randomUser OtherDomain def
connectTwoUsers alice remoteUser
configureAdminlessGroupsFeature OwnDomain tid "disabled" "1s" []
alice1 <- createMLSClient def alice
remoteUser1 <- createMLSClient def remoteUser
traverse_ (uploadNewKeyPackage def) [alice1, remoteUser1]
conv <- createTeamMLSConversation alice tid alice1 [remoteUser]
convQid <- objQidObject conv
removeMember alice conv alice >>= assertSuccess
withWebSocket remoteUser $ \WebSocket
wsRemoteUser -> do
Domain -> String -> String -> String -> [String] -> App ()
forall domain.
MakesValue domain =>
domain -> String -> String -> String -> [String] -> App ()
configureAdminlessGroupsFeature Domain
OwnDomain String
tid String
"enabled" String
"1s" []
App Value -> App ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (App Value -> App ()) -> App Value -> App ()
forall a b. (a -> b) -> a -> b
$ HasCallStack =>
Int -> (Value -> App Bool) -> WebSocket -> App Value
Int -> (Value -> App Bool) -> WebSocket -> App Value
awaitMatchFor Int
20 Value -> App Bool
forall a. (HasCallStack, MakesValue a) => a -> App Bool
isConvSystemDeleteNotif WebSocket
wsRemoteUser
App () -> App ()
forall a. App a -> App a
eventually (App () -> App ()) -> App () -> App ()
forall a b. (a -> b) -> a -> b
$ App Response -> (Response -> App ()) -> App ()
forall a.
HasCallStack =>
App Response -> (Response -> App a) -> App a
bindResponse (Value -> App Response
forall conv.
(HasCallStack, MakesValue conv) =>
conv -> App Response
GalleyI.getConversation Value
conv) ((Response -> App ()) -> App ()) -> (Response -> App ()) -> App ()
forall a b. (a -> b) -> a -> b
$ \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
404
App () -> App ()
forall a. App a -> App a
eventually (App () -> App ()) -> App () -> App ()
forall a b. (a -> b) -> a -> b
$ App Response -> (Response -> App ()) -> App ()
forall a.
HasCallStack =>
App Response -> (Response -> App a) -> App a
bindResponse (Value -> ListConversationIds -> App Response
forall user.
MakesValue user =>
user -> ListConversationIds -> App Response
listConversationIds Value
remoteUser ListConversationIds
forall a. Default a => a
def) ((Response -> App ()) -> App ()) -> (Response -> App ()) -> App ()
forall a b. (a -> b) -> a -> b
$ \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
200
conversationIds <- Response
resp.json Maybe Value -> String -> App Value
forall a. (HasCallStack, MakesValue a) => a -> String -> App Value
%. String
"qualified_conversations" App Value -> (App Value -> App [Value]) -> App [Value]
forall a b. a -> (a -> b) -> b
& App Value -> App [Value]
forall a. (HasCallStack, MakesValue a) => a -> App [Value]
asList
conversationIds `shouldNotContain` [convQid]
testAdminlessSetupSkipsDeletionForUnsupportedRemote :: (HasCallStack) => FedDomain 2 -> App ()
testAdminlessSetupSkipsDeletionForUnsupportedRemote :: HasCallStack => FedDomain 2 -> App ()
testAdminlessSetupSkipsDeletionForUnsupportedRemote FedDomain 2
fedDomain = do
(alice, tid, _) <- Domain -> Int -> App (Value, String, [Value])
forall domain.
(HasCallStack, MakesValue domain) =>
domain -> Int -> App (Value, String, [Value])
createTeam Domain
OwnDomain Int
1
remoteUser <- randomUser fedDomain def
connectTwoUsers alice remoteUser
configureAdminlessGroupsFeature OwnDomain tid "disabled" "1s" []
alice1 <- createMLSClient def alice
remoteUser1 <- createMLSClient def remoteUser
traverse_ (uploadNewKeyPackage def) [alice1, remoteUser1]
conv <- createTeamMLSConversation alice tid alice1 [remoteUser]
removeMember alice conv alice >>= assertSuccess
withWebSocket remoteUser $ \WebSocket
wsRemoteUser -> do
Domain -> String -> String -> String -> [String] -> App ()
forall domain.
MakesValue domain =>
domain -> String -> String -> String -> [String] -> App ()
configureAdminlessGroupsFeature Domain
OwnDomain String
tid String
"enabled" String
"1s" []
deleteResult <- HasCallStack =>
Int -> Int -> (Value -> App Bool) -> WebSocket -> App AwaitResult
Int -> Int -> (Value -> App Bool) -> WebSocket -> App AwaitResult
awaitNMatchesResultFor Int
8 Int
1 Value -> App Bool
forall a. (HasCallStack, MakesValue a) => a -> App Bool
isConvSystemDeleteNotif WebSocket
wsRemoteUser
deleteResult.success `shouldMatch` False
reminderResult <- awaitNMatchesResultFor 8 1 isConvSystemAdminlessReminderNotif wsRemoteUser
reminderResult.success `shouldMatch` False
bindResponse (GalleyI.getConversation conv) $ \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
200
testAdminlessSetupSendsReminderWithRemoteMembers :: (HasCallStack) => App ()
testAdminlessSetupSendsReminderWithRemoteMembers :: HasCallStack => App ()
testAdminlessSetupSendsReminderWithRemoteMembers = do
(alice, tid, _) <- Domain -> Int -> App (Value, String, [Value])
forall domain.
(HasCallStack, MakesValue domain) =>
domain -> Int -> App (Value, String, [Value])
createTeam Domain
OwnDomain Int
1
remoteUser <- randomUser OtherDomain def
connectTwoUsers alice remoteUser
configureAdminlessGroupsFeature OwnDomain tid "disabled" "10s" ["1s"]
alice1 <- createMLSClient def alice
remoteUser1 <- createMLSClient def remoteUser
traverse_ (uploadNewKeyPackage def) [alice1, remoteUser1]
conv <- createTeamMLSConversation alice tid alice1 [remoteUser]
let newApp = NewApp
forall a. Default a => a
def {name = "adminless-federated-reminder-app", description = "not eligible for promotion"}
(app, _) <- createAndAddAppMember alice tid alice1 conv newApp
removeMember alice conv alice >>= assertSuccess
withWebSockets [app, remoteUser] $ \[WebSocket
wsApp, WebSocket
wsRemoteUser] -> do
Domain -> String -> String -> String -> [String] -> App ()
forall domain.
MakesValue domain =>
domain -> String -> String -> String -> [String] -> App ()
configureAdminlessGroupsFeature Domain
OwnDomain String
tid String
"enabled" String
"10s" [String
"1s"]
reminder <- HasCallStack =>
Int -> (Value -> App Bool) -> WebSocket -> App Value
Int -> (Value -> App Bool) -> WebSocket -> App Value
awaitMatchFor Int
20 Value -> App Bool
forall a. (HasCallStack, MakesValue a) => a -> App Bool
isConvSystemAdminlessReminderNotif WebSocket
wsApp
reminder %. "payload.0.qualified_conversation" `shouldMatch` objQidObject conv
remoteReminder <- awaitMatchFor 20 isConvSystemAdminlessReminderNotif wsRemoteUser
remoteReminder %. "payload.0.qualified_conversation" `shouldMatch` objQidObject conv
bindResponse (GalleyI.getConversation conv) $ \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
200
testAdminlessSetupAutopromotesWithRemoteMembers :: (HasCallStack) => App ()
testAdminlessSetupAutopromotesWithRemoteMembers :: HasCallStack => App ()
testAdminlessSetupAutopromotesWithRemoteMembers = do
(alice, tid, [bob]) <- Domain -> Int -> App (Value, String, [Value])
forall domain.
(HasCallStack, MakesValue domain) =>
domain -> Int -> App (Value, String, [Value])
createTeam Domain
OwnDomain Int
2
remoteUser <- randomUser OtherDomain def
connectTwoUsers alice remoteUser
configureAdminlessGroupsFeature OwnDomain tid "disabled" "1s" []
alice1 <- createMLSClient def alice
bob1 <- createMLSClient def bob
remoteUser1 <- createMLSClient def remoteUser
traverse_ (uploadNewKeyPackage def) [alice1, bob1, remoteUser1]
conv <- createTeamMLSConversation alice tid alice1 [bob, remoteUser]
removeMember alice conv alice >>= assertSuccess
withWebSocket remoteUser $ \WebSocket
wsRemoteUser -> do
Domain -> String -> String -> String -> [String] -> App ()
forall domain.
MakesValue domain =>
domain -> String -> String -> String -> [String] -> App ()
configureAdminlessGroupsFeature Domain
OwnDomain String
tid String
"enabled" String
"1s" []
IO () -> App ()
forall a. IO a -> App a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> App ()) -> IO () -> App ()
forall a b. (a -> b) -> a -> b
$ Int -> IO ()
threadDelay Int
2_000_000
App Response -> (Response -> App ()) -> App ()
forall a.
HasCallStack =>
App Response -> (Response -> App a) -> App a
bindResponse (Value -> Value -> App Response
forall user qcnv.
(HasCallStack, MakesValue user, MakesValue qcnv) =>
user -> qcnv -> App Response
getConversation Value
bob Value
conv) ((Response -> App ()) -> App ()) -> (Response -> App ()) -> App ()
forall a b. (a -> b) -> a -> b
$ \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
200
Response
resp.json Maybe Value -> String -> App Value
forall a. (HasCallStack, MakesValue a) => a -> String -> App Value
%. String
"members.self.conversation_role" App Value -> String -> App ()
forall a b.
(MakesValue a, MakesValue b, HasCallStack) =>
a -> b -> App ()
`shouldMatch` String
"wire_admin"
memberUpdate <- HasCallStack =>
Int -> (Value -> App Bool) -> WebSocket -> App Value
Int -> (Value -> App Bool) -> WebSocket -> App Value
awaitMatchFor Int
20 Value -> App Bool
forall a. (HasCallStack, MakesValue a) => a -> App Bool
isConvSystemMemberUpdateNotif WebSocket
wsRemoteUser
memberUpdate %. "payload.0.qualified_conversation" `shouldMatch` objQidObject conv
memberUpdate %. "payload.0.data.qualified_target" `shouldMatch` objQidObject bob
memberUpdate %. "payload.0.data.conversation_role" `shouldMatch` "wire_admin"
testAdminlessJobsCancelledOnFeatureDisable :: (HasCallStack) => App ()
testAdminlessJobsCancelledOnFeatureDisable :: HasCallStack => App ()
testAdminlessJobsCancelledOnFeatureDisable = do
(alice, tid, _) <- Domain -> Int -> App (Value, String, [Value])
forall domain.
(HasCallStack, MakesValue domain) =>
domain -> Int -> App (Value, String, [Value])
createTeam Domain
OwnDomain Int
1
configureAdminlessGroupsFeature OwnDomain tid "enabled" "5s" []
alice1 <- createMLSClient def alice
void $ uploadNewKeyPackage def alice1
conv <- createTeamMLSConversation alice tid alice1 []
let newApp = NewApp
forall a. Default a => a
def {name = "adminless-cancel-app", description = "not eligible for promotion"}
(app, _) <- createAndAddAppMember alice tid alice1 conv newApp
withWebSockets [app] $ \[WebSocket
wsApp] -> do
App Response -> (Response -> App ()) -> App ()
forall a.
HasCallStack =>
App Response -> (Response -> App a) -> App a
bindResponse (Value -> Value -> Value -> App Response
forall remover conv removed.
(HasCallStack, MakesValue remover, MakesValue conv,
MakesValue removed) =>
remover -> conv -> removed -> App Response
removeMember Value
alice Value
conv Value
alice) ((Response -> App ()) -> App ()) -> (Response -> App ()) -> App ()
forall a b. (a -> b) -> a -> b
$ \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
200
Domain -> String -> String -> Value -> App Response
forall domain team featureName payload.
(HasCallStack, MakesValue domain, MakesValue team,
MakesValue featureName, MakesValue payload) =>
domain -> team -> featureName -> payload -> App Response
patchTeamFeature Domain
OwnDomain String
tid String
"preventAdminlessGroups" ([Pair] -> Value
object [String
"status" String -> String -> Pair
forall a. ToJSON a => String -> a -> Pair
.= String
"disabled"]) App Response -> (Response -> App ()) -> App ()
forall a b. App a -> (a -> App b) -> App b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= HasCallStack => Response -> App ()
Response -> App ()
assertSuccess
result <- HasCallStack =>
Int -> Int -> (Value -> App Bool) -> WebSocket -> App AwaitResult
Int -> Int -> (Value -> App Bool) -> WebSocket -> App AwaitResult
awaitNMatchesResultFor Int
15 Int
1 Value -> App Bool
forall a. (HasCallStack, MakesValue a) => a -> App Bool
isConvDeleteNotif WebSocket
wsApp
result.success `shouldMatch` False
bindResponse (GalleyI.getConversation conv) $ \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
200
testAdminlessJobsRecreatedOnFeatureConfigChange :: (HasCallStack) => App ()
testAdminlessJobsRecreatedOnFeatureConfigChange :: HasCallStack => App ()
testAdminlessJobsRecreatedOnFeatureConfigChange = do
(alice, tid, _) <- Domain -> Int -> App (Value, String, [Value])
forall domain.
(HasCallStack, MakesValue domain) =>
domain -> Int -> App (Value, String, [Value])
createTeam Domain
OwnDomain Int
1
configureAdminlessGroupsFeature OwnDomain tid "enabled" "10s" []
alice1 <- createMLSClient def alice
void $ uploadNewKeyPackage def alice1
conv <- createTeamMLSConversation alice tid alice1 []
let newApp = NewApp
forall a. Default a => a
def {name = "adminless-reschedule-app", description = "not eligible for promotion"}
(app, _) <- createAndAddAppMember alice tid alice1 conv newApp
withWebSockets [app] $ \[WebSocket
wsApp] -> do
App Response -> (Response -> App ()) -> App ()
forall a.
HasCallStack =>
App Response -> (Response -> App a) -> App a
bindResponse (Value -> Value -> Value -> App Response
forall remover conv removed.
(HasCallStack, MakesValue remover, MakesValue conv,
MakesValue removed) =>
remover -> conv -> removed -> App Response
removeMember Value
alice Value
conv Value
alice) ((Response -> App ()) -> App ()) -> (Response -> App ()) -> App ()
forall a b. (a -> b) -> a -> b
$ \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
200
Domain -> String -> String -> String -> [String] -> App ()
forall domain.
MakesValue domain =>
domain -> String -> String -> String -> [String] -> App ()
configureAdminlessGroupsFeature Domain
OwnDomain String
tid String
"enabled" String
"20s" []
oldJobResult <- HasCallStack =>
Int -> Int -> (Value -> App Bool) -> WebSocket -> App AwaitResult
Int -> Int -> (Value -> App Bool) -> WebSocket -> App AwaitResult
awaitNMatchesResultFor Int
15 Int
1 Value -> App Bool
forall a. (HasCallStack, MakesValue a) => a -> App Bool
isConvDeleteNotif WebSocket
wsApp
oldJobResult.success `shouldMatch` False
bindResponse (GalleyI.getConversation conv) $ \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
200
void $ awaitMatchFor 30 isConvDeleteNotif wsApp
bindResponse (GalleyI.getConversation conv) $ \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
404
testAdminlessJobCancellationIsTeamScoped :: (HasCallStack) => App ()
testAdminlessJobCancellationIsTeamScoped :: HasCallStack => App ()
testAdminlessJobCancellationIsTeamScoped = do
(alice, canceledTid, _) <- Domain -> Int -> App (Value, String, [Value])
forall domain.
(HasCallStack, MakesValue domain) =>
domain -> Int -> App (Value, String, [Value])
createTeam Domain
OwnDomain Int
1
(bob, activeTid, _) <- createTeam OwnDomain 1
let enabledFeature = String -> String -> [String] -> Value
mkAdminlessFeature String
"enabled" String
"10s" []
for_ [canceledTid, activeTid] $ \String
tid -> do
Domain -> String -> String -> String -> App ()
forall domain team.
(HasCallStack, MakesValue domain, MakesValue team) =>
domain -> team -> String -> String -> App ()
setTeamFeatureLockStatus Domain
OwnDomain String
tid String
"preventAdminlessGroups" String
"unlocked"
Domain -> String -> String -> Value -> App Response
forall domain team featureName payload.
(HasCallStack, MakesValue domain, MakesValue team,
MakesValue featureName, MakesValue payload) =>
domain -> team -> featureName -> payload -> App Response
patchTeamFeature Domain
OwnDomain String
tid String
"preventAdminlessGroups" Value
enabledFeature App Response -> (Response -> App ()) -> App ()
forall a b. App a -> (a -> App b) -> App b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= HasCallStack => Response -> App ()
Response -> App ()
assertSuccess
let newApp String
name = NewApp
forall a. Default a => a
def {name = name, description = "not eligible for promotion"}
alice1 <- createMLSClient def alice
bob1 <- createMLSClient def bob
traverse_ (uploadNewKeyPackage def) [alice1, bob1]
canceledConv <- createTeamMLSConversation alice canceledTid alice1 []
(canceledApp, _) <- createAndAddAppMember alice canceledTid alice1 canceledConv (newApp "adminless-cancel-team-app")
activeConv <- createTeamMLSConversation bob activeTid bob1 []
(activeApp, _) <- createAndAddAppMember bob activeTid bob1 activeConv (newApp "adminless-active-team-app")
withWebSockets [canceledApp, activeApp] $ \[WebSocket
wsCanceled, WebSocket
wsActive] -> do
App Response -> (Response -> App ()) -> App ()
forall a.
HasCallStack =>
App Response -> (Response -> App a) -> App a
bindResponse (Value -> Value -> Value -> App Response
forall remover conv removed.
(HasCallStack, MakesValue remover, MakesValue conv,
MakesValue removed) =>
remover -> conv -> removed -> App Response
removeMember Value
alice Value
canceledConv Value
alice) ((Response -> App ()) -> App ()) -> (Response -> App ()) -> App ()
forall a b. (a -> b) -> a -> b
$ \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
200
App Response -> (Response -> App ()) -> App ()
forall a.
HasCallStack =>
App Response -> (Response -> App a) -> App a
bindResponse (Value -> Value -> Value -> App Response
forall remover conv removed.
(HasCallStack, MakesValue remover, MakesValue conv,
MakesValue removed) =>
remover -> conv -> removed -> App Response
removeMember Value
bob Value
activeConv Value
bob) ((Response -> App ()) -> App ()) -> (Response -> App ()) -> App ()
forall a b. (a -> b) -> a -> b
$ \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
200
Domain -> String -> String -> Value -> App Response
forall domain team featureName payload.
(HasCallStack, MakesValue domain, MakesValue team,
MakesValue featureName, MakesValue payload) =>
domain -> team -> featureName -> payload -> App Response
patchTeamFeature Domain
OwnDomain String
canceledTid String
"preventAdminlessGroups" ([Pair] -> Value
object [String
"status" String -> String -> Pair
forall a. ToJSON a => String -> a -> Pair
.= String
"disabled"]) App Response -> (Response -> App ()) -> App ()
forall a b. App a -> (a -> App b) -> App b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= HasCallStack => Response -> App ()
Response -> App ()
assertSuccess
canceledResult <- HasCallStack =>
Int -> Int -> (Value -> App Bool) -> WebSocket -> App AwaitResult
Int -> Int -> (Value -> App Bool) -> WebSocket -> App AwaitResult
awaitNMatchesResultFor Int
20 Int
1 Value -> App Bool
forall a. (HasCallStack, MakesValue a) => a -> App Bool
isConvDeleteNotif WebSocket
wsCanceled
canceledResult.success `shouldMatch` False
void $ awaitMatchFor 30 isConvDeleteNotif wsActive
bindResponse (GalleyI.getConversation canceledConv) $ \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
200
bindResponse (GalleyI.getConversation activeConv) $ \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
404
testOnLastAdminLeaveFeatureDisabled :: (HasCallStack) => App ()
testOnLastAdminLeaveFeatureDisabled :: HasCallStack => App ()
testOnLastAdminLeaveFeatureDisabled = do
(alice, tid, [bob]) <- Domain -> Int -> App (Value, String, [Value])
forall domain.
(HasCallStack, MakesValue domain) =>
domain -> Int -> App (Value, String, [Value])
createTeam Domain
OwnDomain Int
2
setTeamFeatureLockStatus OwnDomain tid "preventAdminlessGroups" "unlocked"
patchTeamFeature OwnDomain tid "preventAdminlessGroups" (object ["status" .= "disabled"]) >>= assertSuccess
clients@(alice1 : _) <- traverse (createMLSClient def) [alice, bob]
for_ clients (uploadNewKeyPackage def)
conv <- postConversation alice defMLS {team = Just tid} >>= getJSON 201
convId <- objConvId conv
createGroup def alice1 convId
void $ createAddCommit alice1 convId [bob] >>= sendAndConsumeCommitBundle
bindResponse (removeMember alice conv alice) $ \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
200
testOnLastAdminTeamMemberDeletionAutopromotes :: (HasCallStack) => App ()
testOnLastAdminTeamMemberDeletionAutopromotes :: HasCallStack => App ()
testOnLastAdminTeamMemberDeletionAutopromotes = do
(alice, tid, [charlie]) <- Domain -> Int -> App (Value, String, [Value])
forall domain.
(HasCallStack, MakesValue domain) =>
domain -> Int -> App (Value, String, [Value])
createTeam Domain
OwnDomain Int
2
setTeamFeatureLockStatus OwnDomain tid "preventAdminlessGroups" "unlocked"
patchTeamFeature OwnDomain tid "preventAdminlessGroups" (object ["status" .= "enabled"]) >>= assertSuccess
[alice1, charlie1] <- traverse (createMLSClient def) [alice, charlie]
traverse_ (uploadNewKeyPackage def) [alice1, charlie1]
conv <- postConversation charlie defMLS {team = Just tid} >>= getJSON 201
convId <- objConvId conv
createGroup def charlie1 convId
void $ createAddCommit charlie1 convId [alice] >>= sendAndConsumeCommitBundle
bindResponse (getConversation alice conv) $ \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
200
Response
resp.json Maybe Value -> String -> App Value
forall a. (HasCallStack, MakesValue a) => a -> String -> App Value
%. String
"members.self.conversation_role" App Value -> String -> App ()
forall a b.
(MakesValue a, MakesValue b, HasCallStack) =>
a -> b -> App ()
`shouldMatch` String
"wire_member"
others <- Response
resp.json Maybe Value -> String -> App Value
forall a. (HasCallStack, MakesValue a) => a -> String -> App Value
%. String
"members.others" App Value -> (App Value -> App [Value]) -> App [Value]
forall a b. a -> (a -> b) -> b
& App Value -> App [Value]
forall a. (HasCallStack, MakesValue a) => a -> App [Value]
asList
[other] <- pure others
other %. "qualified_id" `shouldMatch` objQidObject charlie
other %. "conversation_role" `shouldMatch` "wire_admin"
void $ deleteTeamMember tid alice charlie >>= getBody 202
eventually $ do
bindResponse (getConversation alice conv) $ \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
200
Response
resp.json Maybe Value -> String -> App Value
forall a. (HasCallStack, MakesValue a) => a -> String -> App Value
%. String
"members.self.conversation_role" App Value -> String -> App ()
forall a b.
(MakesValue a, MakesValue b, HasCallStack) =>
a -> b -> App ()
`shouldMatch` String
"wire_admin"
members <- Response
resp.json Maybe Value -> String -> App Value
forall a. (HasCallStack, MakesValue a) => a -> String -> App Value
%. String
"members.others" App Value -> (App Value -> App [Value]) -> App [Value]
forall a b. a -> (a -> b) -> b
& App Value -> App [Value]
forall a. (HasCallStack, MakesValue a) => a -> App [Value]
asList
shouldBeEmpty members
testOnLastAdminSelfDeletionAutopromotes :: (HasCallStack) => App ()
testOnLastAdminSelfDeletionAutopromotes :: HasCallStack => App ()
testOnLastAdminSelfDeletionAutopromotes = do
(alice, tid, [charlie]) <- Domain -> Int -> App (Value, String, [Value])
forall domain.
(HasCallStack, MakesValue domain) =>
domain -> Int -> App (Value, String, [Value])
createTeam Domain
OwnDomain Int
2
setTeamFeatureLockStatus OwnDomain tid "preventAdminlessGroups" "unlocked"
patchTeamFeature OwnDomain tid "preventAdminlessGroups" (object ["status" .= "enabled"]) >>= assertSuccess
[alice1, charlie1] <- traverse (createMLSClient def) [alice, charlie]
traverse_ (uploadNewKeyPackage def) [alice1, charlie1]
conv <- postConversation charlie defMLS {team = Just tid} >>= getJSON 201
convId <- objConvId conv
createGroup def charlie1 convId
void $ createAddCommit charlie1 convId [alice] >>= sendAndConsumeCommitBundle
bindResponse (getConversation alice conv) $ \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
200
Response
resp.json Maybe Value -> String -> App Value
forall a. (HasCallStack, MakesValue a) => a -> String -> App Value
%. String
"members.self.conversation_role" App Value -> String -> App ()
forall a b.
(MakesValue a, MakesValue b, HasCallStack) =>
a -> b -> App ()
`shouldMatch` String
"wire_member"
others <- Response
resp.json Maybe Value -> String -> App Value
forall a. (HasCallStack, MakesValue a) => a -> String -> App Value
%. String
"members.others" App Value -> (App Value -> App [Value]) -> App [Value]
forall a b. a -> (a -> b) -> b
& App Value -> App [Value]
forall a. (HasCallStack, MakesValue a) => a -> App [Value]
asList
[other] <- pure others
other %. "qualified_id" `shouldMatch` objQidObject charlie
other %. "conversation_role" `shouldMatch` "wire_admin"
void $ deleteUser charlie >>= getBody 200
eventually $ do
bindResponse (getConversation alice conv) $ \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
200
Response
resp.json Maybe Value -> String -> App Value
forall a. (HasCallStack, MakesValue a) => a -> String -> App Value
%. String
"members.self.conversation_role" App Value -> String -> App ()
forall a b.
(MakesValue a, MakesValue b, HasCallStack) =>
a -> b -> App ()
`shouldMatch` String
"wire_admin"
members <- Response
resp.json Maybe Value -> String -> App Value
forall a. (HasCallStack, MakesValue a) => a -> String -> App Value
%. String
"members.others" App Value -> (App Value -> App [Value]) -> App [Value]
forall a b. a -> (a -> b) -> b
& App Value -> App [Value]
forall a. (HasCallStack, MakesValue a) => a -> App [Value]
asList
shouldBeEmpty members
createTeamMLSConversation :: (HasCallStack, MakesValue owner) => owner -> String -> ClientIdentity -> [Value] -> App Value
createTeamMLSConversation :: forall owner.
(HasCallStack, MakesValue owner) =>
owner -> String -> ClientIdentity -> [Value] -> App Value
createTeamMLSConversation owner
owner String
tid ClientIdentity
ownerClient [Value]
members = do
conv <- owner -> CreateConv -> App Response
forall user.
(HasCallStack, MakesValue user) =>
user -> CreateConv -> App Response
postConversation owner
owner (CreateConv -> CreateConv
allowAll CreateConv
defMLS) {team = Just tid} App Response -> (Response -> App Value) -> App Value
forall a b. App a -> (a -> App b) -> App b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= HasCallStack => Int -> Response -> App Value
Int -> Response -> App Value
getJSON Int
201
convId <- objConvId conv
createGroup def ownerClient convId
unless (null members)
$ void
$ createAddCommit ownerClient convId members
>>= sendAndConsumeCommitBundle
pure conv
createAndAddAppMember :: (HasCallStack, MakesValue creator, MakesValue conv) => creator -> String -> ClientIdentity -> conv -> NewApp -> App (Value, ClientIdentity)
createAndAddAppMember :: forall creator conv.
(HasCallStack, MakesValue creator, MakesValue conv) =>
creator
-> String
-> ClientIdentity
-> conv
-> NewApp
-> App (Value, ClientIdentity)
createAndAddAppMember creator
creator String
tid ClientIdentity
ownerClient conv
conv NewApp
newApp = do
app <- App Response -> (Response -> App Value) -> App Value
forall a.
HasCallStack =>
App Response -> (Response -> App a) -> App a
bindResponse (creator -> String -> NewApp -> App Response
forall creator.
MakesValue creator =>
creator -> String -> NewApp -> App Response
createApp creator
creator String
tid NewApp
newApp) ((Response -> App Value) -> App Value)
-> (Response -> App Value) -> App Value
forall a b. (a -> b) -> a -> b
$ \Response
resp -> do
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
200
Response
resp.json Maybe Value -> String -> App Value
forall a. (HasCallStack, MakesValue a) => a -> String -> App Value
%. String
"user"
appClient <- createMLSClient def app
void $ uploadNewKeyPackage def appClient
convId <- objConvId conv
void $ createAddCommit ownerClient convId [app] >>= sendAndConsumeCommitBundle
pure (app, appClient)
configureAdminlessGroupsFeature :: (MakesValue domain) => domain -> String -> String -> String -> [String] -> App ()
configureAdminlessGroupsFeature :: forall domain.
MakesValue domain =>
domain -> String -> String -> String -> [String] -> App ()
configureAdminlessGroupsFeature domain
domain String
tid String
status String
deletionTimeout [String]
reminderTimeouts = do
domain -> String -> String -> String -> App ()
forall domain team.
(HasCallStack, MakesValue domain, MakesValue team) =>
domain -> team -> String -> String -> App ()
setTeamFeatureLockStatus domain
domain String
tid String
"preventAdminlessGroups" String
"unlocked"
domain -> String -> String -> Value -> App Response
forall domain team featureName payload.
(HasCallStack, MakesValue domain, MakesValue team,
MakesValue featureName, MakesValue payload) =>
domain -> team -> featureName -> payload -> App Response
patchTeamFeature domain
domain String
tid String
"preventAdminlessGroups" (String -> String -> [String] -> Value
mkAdminlessFeature String
status String
deletionTimeout [String]
reminderTimeouts) App Response -> (Response -> App ()) -> App ()
forall a b. App a -> (a -> App b) -> App b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= HasCallStack => Response -> App ()
Response -> App ()
assertSuccess
mkAdminlessFeature :: String -> String -> [String] -> Value
mkAdminlessFeature :: String -> String -> [String] -> Value
mkAdminlessFeature String
status String
deletionTimeout [String]
reminderTimeouts =
[Pair] -> Value
object
[ String
"status" String -> String -> Pair
forall a. ToJSON a => String -> a -> Pair
.= String
status,
String
"config"
String -> Value -> Pair
forall a. ToJSON a => String -> a -> Pair
.= [Pair] -> Value
object
[ String
"deletionTimeoutDuration" String -> String -> Pair
forall a. ToJSON a => String -> a -> Pair
.= String
deletionTimeout,
String
"reminderTimeoutDurations" String -> [String] -> Pair
forall a. ToJSON a => String -> a -> Pair
.= [String]
reminderTimeouts,
String
"promotionStrategy" String -> String -> Pair
forall a. ToJSON a => String -> a -> Pair
.= String
"random"
]
]