Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

integration: Fix testAddingUserNonFullyConnectedFederation and testNotificationsForOfflineBackends #3529

Merged
merged 4 commits into from
Aug 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions integration/test/API/BrigInternal.hs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,14 @@ deleteFedConn' owndom dom = do
req <- rawBaseRequest owndom Brig Unversioned ("/i/federation/remotes/" <> dom)
submit "DELETE" req

deleteAllFedConns :: (HasCallStack, MakesValue dom) => dom -> App ()
deleteAllFedConns dom = do
readFedConns dom >>= \resp ->
resp.json %. "remotes"
& asList
>>= traverse (\v -> v %. "domain" & asString)
>>= mapM_ (deleteFedConn dom)

registerOAuthClient :: (HasCallStack, MakesValue user, MakesValue name, MakesValue url) => user -> name -> url -> App Response
registerOAuthClient user name url = do
req <- baseRequest user Brig Unversioned "i/oauth/clients"
Expand Down
34 changes: 21 additions & 13 deletions integration/test/API/Galley.hs
Original file line number Diff line number Diff line change
Expand Up @@ -171,22 +171,30 @@ postProteusMessage user conv msgs = do
submit "POST" (addProtobuf bytes req)

mkProteusRecipient :: (HasCallStack, MakesValue user, MakesValue client) => user -> client -> String -> App Proto.QualifiedUserEntry
mkProteusRecipient user client msg = do
userDomain <- objDomain user
userId <- LBS.toStrict . UUID.toByteString . fromJust . UUID.fromString <$> objId user
clientId <- (^?! hex) <$> objId client
mkProteusRecipient user client = mkProteusRecipients user [(user, [client])]

mkProteusRecipients :: (HasCallStack, MakesValue domain, MakesValue user, MakesValue client) => domain -> [(user, [client])] -> String -> App Proto.QualifiedUserEntry
mkProteusRecipients dom userClients msg = do
userDomain <- asString =<< objDomain dom
userEntries <- mapM mkUserEntry userClients
pure $
Proto.defMessage
& #domain .~ fromString userDomain
& #entries
.~ [ Proto.defMessage
& #user . #uuid .~ userId
& #clients
.~ [ Proto.defMessage
& #client . #client .~ clientId
& #text .~ fromString msg
]
]
& #entries .~ userEntries
where
mkUserEntry (user, clients) = do
userId <- LBS.toStrict . UUID.toByteString . fromJust . UUID.fromString <$> objId user
clientEntries <- mapM mkClientEntry clients
pure $
Proto.defMessage
& #user . #uuid .~ userId
& #clients .~ clientEntries
mkClientEntry client = do
clientId <- (^?! hex) <$> objId client
pure $
Proto.defMessage
& #client . #client .~ clientId
& #text .~ fromString msg

getGroupInfo ::
(HasCallStack, MakesValue user, MakesValue conv) =>
Expand Down
39 changes: 28 additions & 11 deletions integration/test/Test/Conversation.hs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{-# OPTIONS_GHC -Wno-ambiguous-fields #-}
{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}

module Test.Conversation where

Expand Down Expand Up @@ -410,19 +411,35 @@ testAddUnreachable = do
testAddingUserNonFullyConnectedFederation :: HasCallStack => App ()
testAddingUserNonFullyConnectedFederation = do
let overrides =
def {dbBrig = setField "optSettings.setFederationStrategy" "allowAll"}
<> fullSearchWithAll
startDynamicBackends [overrides] $ \domains -> do
own <- make OwnDomain & asString
other <- make OtherDomain & asString
[alice, alex, bob, charlie] <-
createAndConnectUsers $ [own, own, other] <> domains

let newConv = defProteus {qualifiedUsers = [alex]}
def
{ dbBrig =
setField "optSettings.setFederationStrategy" "allowDynamic"
>=> removeField "optSettings.setFederationDomainConfigs"
}
startDynamicBackends [overrides] $ \[dynBackend] -> do
own <- asString OwnDomain
other <- asString OtherDomain

-- Ensure that dynamic backend only federates with own domain, but not other
-- domain.
--
-- FUTUREWORK: deleteAllFedConns at the time of acquiring a backend, so
-- tests don't affect each other.
deleteAllFedConns dynBackend
void $ createFedConn dynBackend (FedConn own "full_search")
Comment on lines +426 to +429
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please explain how this dance of deleting and then immediately making a connection works?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you have two tests: one of them connects dynBackend1 with all backends and second one only connects dynBackend1 with OwnDomain. if the first test runs first, the second test will always fail. So, the second test must always delete all the connections dynBackend1 has and then only create a connection with OwnDomain to ensure that it is not affected by the first test.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see.

Wasn't the plan to provide more isolation between test executions? This dance within the test is very fragile, it incurs what I hope can be called a needless brain load, and is not a general solution.

Is there something we can do in the (dynamic) backend setup that would make them isolated from each other (even if we go with e.g. randomly assigning identifiers (names) and such, which should be good enough if they are very unlikely to collide)?

Copy link
Member Author

@akshaymankar akshaymankar Aug 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's why I added the FUTUREWORK. While acquiring an environment we could simply truncate the table which keeps this information around.


alice <- randomUser own def
bob <- randomUser other def
charlie <- randomUser dynBackend def
-- We use retryT here so the dynamic federated connection changes can take
-- some time to be propagated. Remove after fixing https://wearezeta.atlassian.net/browse/WPB-3797
mapM_ (retryT . connectUsers alice) [bob, charlie]

let newConv = defProteus {qualifiedUsers = []}
conv <- postConversation alice newConv >>= getJSON 201

bobId <- bob %. "qualified_id"
charlieId <- charlie %. "qualified_id"
bindResponse (addMembers alex conv [bobId, charlieId]) $ \resp -> do
bindResponse (addMembers alice conv [bobId, charlieId]) $ \resp -> do
resp.status `shouldMatchInt` 409
resp.json %. "non_federating_backends" `shouldMatchSet` (other : domains)
resp.json %. "non_federating_backends" `shouldMatchSet` [other, dynBackend]
41 changes: 20 additions & 21 deletions integration/test/Test/Federation.hs
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ testNotificationsForOfflineBackends :: HasCallStack => App ()
testNotificationsForOfflineBackends = do
resourcePool <- asks (.resourcePool)
-- `delUser` will eventually get deleted.
[delUser, otherUser] <- createAndConnectUsers [OwnDomain, OtherDomain]
[delUser, otherUser, otherUser2] <- createAndConnectUsers [OwnDomain, OtherDomain, OtherDomain]
delClient <- objId $ bindResponse (API.addClient delUser def) $ getJSON 201
otherClient <- objId $ bindResponse (API.addClient otherUser def) $ getJSON 201
otherClient2 <- objId $ bindResponse (API.addClient otherUser2 def) $ getJSON 201

-- We call it 'downBackend' because it is down for the most of this test
-- except for setup and assertions. Perhaps there is a better name.
Expand All @@ -36,18 +37,18 @@ testNotificationsForOfflineBackends = do
connectUsers delUser downUser1
connectUsers delUser downUser2
connectUsers otherUser downUser1
upBackendConv <- bindResponse (postConversation delUser (defProteus {qualifiedUsers = [otherUser, downUser1]})) $ getJSON 201
upBackendConv <- bindResponse (postConversation delUser (defProteus {qualifiedUsers = [otherUser, otherUser2, downUser1]})) $ getJSON 201
downBackendConv <- bindResponse (postConversation downUser1 (defProteus {qualifiedUsers = [otherUser, delUser]})) $ getJSON 201
pure (downUser1, downClient1, downUser2, upBackendConv, downBackendConv)

-- Even when a participating backend is down, messages to conversations
-- owned by other backends should go.
successfulMsgForOtherUser <- mkProteusRecipient otherUser otherClient "success message for other user"
successfulMsgForOtherUsers <- mkProteusRecipients otherUser [(otherUser, [otherClient]), (otherUser2, [otherClient2])] "success message for other user"
successfulMsgForDownUser <- mkProteusRecipient downUser1 downClient1 "success message for down user"
let successfulMsg =
Proto.defMessage @Proto.QualifiedNewOtrMessage
& #sender . Proto.client .~ (delClient ^?! hex)
& #recipients .~ [successfulMsgForOtherUser, successfulMsgForDownUser]
& #recipients .~ [successfulMsgForOtherUsers, successfulMsgForDownUser]
& #reportAll .~ Proto.defMessage
bindResponse (postProteusMessage delUser upBackendConv successfulMsg) assertSuccess

Expand All @@ -68,12 +69,13 @@ testNotificationsForOfflineBackends = do
bindResponse (postConversation delUser (defProteus {qualifiedUsers = [otherUser, downUser1]})) $ \resp ->
resp.status `shouldMatchInt` 533

-- Adding users to an up backend conversation should work even when one of
-- the participating backends is down
otherUser2 <- randomUser OtherDomain def
connectUsers delUser otherUser2
bindResponse (addMembers delUser upBackendConv [otherUser2]) $ \resp ->
resp.status `shouldMatchInt` 200
-- Adding users to an up backend conversation should not work when one of
-- the participating backends is down. This is due to not being able to
-- check non-fully connected graph between all participating backends
otherUser3 <- randomUser OtherDomain def
connectUsers delUser otherUser3
bindResponse (addMembers delUser upBackendConv [otherUser3]) $ \resp ->
resp.status `shouldMatchInt` 533

-- Adding users from down backend to a conversation should also fail
bindResponse (addMembers delUser upBackendConv [downUser2]) $ \resp ->
Expand All @@ -86,14 +88,17 @@ testNotificationsForOfflineBackends = do

-- User deletions should eventually make it to the other backend.
deleteUser delUser

let isOtherUser2LeaveUpConvNotif = allPreds [isConvLeaveNotif, isNotifConv upBackendConv, isNotifForUser otherUser2]
isDelUserLeaveUpConvNotif = allPreds [isConvLeaveNotif, isNotifConv upBackendConv, isNotifForUser delUser]

do
newMsgNotif <- awaitNotification otherUser otherClient noValue 1 isNewMessageNotif
newMsgNotif %. "payload.0.qualified_conversation" `shouldMatch` objQidObject upBackendConv
newMsgNotif %. "payload.0.data.text" `shouldMatchBase64` "success message for other user"

memberJoinNotif <- awaitNotification otherUser otherClient (Just newMsgNotif) 1 isMemberJoinNotif
memberJoinNotif %. "payload.0.qualified_conversation" `shouldMatch` objQidObject upBackendConv
asListOf objQidObject (memberJoinNotif %. "payload.0.data.users") `shouldMatch` mapM objQidObject [otherUser2]
void $ awaitNotification otherUser otherClient (Just newMsgNotif) 1 isOtherUser2LeaveUpConvNotif
void $ awaitNotification otherUser otherClient (Just newMsgNotif) 1 isDelUserLeaveUpConvNotif

delUserDeletedNotif <- nPayload $ awaitNotification otherUser otherClient (Just newMsgNotif) 1 isDeleteUserNotif
objQid delUserDeletedNotif `shouldMatch` objQid delUser
Expand All @@ -103,23 +108,17 @@ testNotificationsForOfflineBackends = do
newMsgNotif %. "payload.0.qualified_conversation" `shouldMatch` objQidObject upBackendConv
newMsgNotif %. "payload.0.data.text" `shouldMatchBase64` "success message for down user"

-- FUTUREWORK: Uncomment after fixing this bug: https://wearezeta.atlassian.net/browse/WPB-3664
-- memberJoinNotif <- awaitNotification downUser1 downClient1 (Just newMsgNotif) 1 isMemberJoinNotif
-- memberJoinNotif %. "payload.0.qualified_conversation" `shouldMatch` objQidObject upBackendConv
-- asListOf objQidObject (memberJoinNotif %. "payload.0.data.users") `shouldMatch` mapM objQidObject [downUser2]

let isDelUserLeaveDownConvNotif =
allPreds
[ isConvLeaveNotif,
isNotifConv downBackendConv,
isNotifForUser delUser
]
void $ awaitNotification downUser1 downClient1 (Just newMsgNotif) 1 isDelUserLeaveDownConvNotif
void $ awaitNotification otherUser otherClient noValue 1 isDelUserLeaveDownConvNotif

-- FUTUREWORK: Uncomment after fixing this bug: https://wearezeta.atlassian.net/browse/WPB-3664
-- void $ awaitNotification downUser1 downClient1 (Just newMsgNotif) 1 (allPreds [isConvLeaveNotif, isNotifConv upBackendConv, isNotifForUser otherUser])
-- void $ awaitNotification downUser1 downClient1 (Just newMsgNotif) 1 (allPreds [isConvLeaveNotif, isNotifConv upBackendConv, isNotifForUser delUser])
-- void $ awaitNotification downUser1 downClient1 (Just newMsgNotif) 1 isOtherUser2LeaveUpConvNotif
-- void $ awaitNotification otherUser otherClient (Just newMsgNotif) 1 isDelUserLeaveDownConvNotif
Comment on lines +120 to +121
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you planning to uncomment or remove these two lines?

Copy link
Member Author

@akshaymankar akshaymankar Aug 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, see the comment above them. These don't work due to a bug: https://wearezeta.atlassian.net/browse/WPB-3664


delUserDeletedNotif <- nPayload $ awaitNotification downUser1 downClient1 (Just newMsgNotif) 1 isDeleteUserNotif
objQid delUserDeletedNotif `shouldMatch` objQid delUser
Expand Down
2 changes: 1 addition & 1 deletion integration/test/Testlib/HTTP.hs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ getJSON status resp = withResponse resp $ \r -> do
r.status `shouldMatch` status
r.json

assertSuccess :: Response -> App ()
assertSuccess :: HasCallStack => Response -> App ()
assertSuccess resp = withResponse resp $ \r -> r.status `shouldMatchRange` (200, 299)

onFailureAddResponse :: HasCallStack => Response -> App a -> App a
Expand Down