Skip to content

Commit

Permalink
[FS-1191] List the MLS Self-conversation Automatically (#2856)
Browse files Browse the repository at this point in the history
* Create self-conversation before listing

* Test: self-conversation created automatically

* Create an MLS self-conversation only in v3

* Adjust tests for listing the MLS self-conversation

* Drop `GET /conversations/ids` starting v3

* Make tests for `GET /conversations/ids` use v2

- The endpoint is removed starting v3, and tests by default use the
latest version, which is currently v3
  • Loading branch information
mdimjasevic authored Nov 23, 2022
1 parent c3adc5e commit 58604d7
Show file tree
Hide file tree
Showing 8 changed files with 121 additions and 22 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
List the MLS self-conversation automatically without needing to call `GET /conversations/mls-self` first
13 changes: 13 additions & 0 deletions libs/wire-api/src/Wire/API/Routes/Public/Galley/Conversation.hs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ type ConversationAPI =
"list-conversation-ids-unqualified"
( Summary "[deprecated] Get all local conversation IDs."
-- FUTUREWORK: add bounds to swagger schema for Range
:> Until 'V3
:> ZLocalUser
:> "conversations"
:> "ids"
Expand All @@ -179,9 +180,21 @@ type ConversationAPI =
(Range 1 1000 Int32)
:> Get '[Servant.JSON] (ConversationList ConvId)
)
:<|> Named
"list-conversation-ids-v2"
( Summary "Get all conversation IDs."
:> Until 'V3
:> Description PaginationDocs
:> ZLocalUser
:> "conversations"
:> "list-ids"
:> ReqBody '[Servant.JSON] GetPaginatedConversationIds
:> Post '[Servant.JSON] ConvIdsPage
)
:<|> Named
"list-conversation-ids"
( Summary "Get all conversation IDs."
:> From 'V3
:> Description PaginationDocs
:> ZLocalUser
:> "conversations"
Expand Down
9 changes: 5 additions & 4 deletions services/galley/src/Galley/API/Internal.hs
Original file line number Diff line number Diff line change
Expand Up @@ -653,19 +653,20 @@ rmUser ::
'[ BrigAccess,
ClientStore,
ConversationStore,
Error InternalError,
ExternalAccess,
FederatorAccess,
GundeckAccess,
Input UTCTime,
Input Env,
Input (Local ()),
Input UTCTime,
ListItems p1 ConvId,
ListItems p1 (Remote ConvId),
ListItems p2 TeamId,
Input (Local ()),
MemberStore,
ProposalStore,
TeamStore,
P.TinyLog
P.TinyLog,
TeamStore
]
r
) =>
Expand Down
1 change: 1 addition & 0 deletions services/galley/src/Galley/API/Public/Conversation.hs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ conversationAPI =
<@> mkNamedAPI @"get-conversation-roles" getConversationRoles
<@> mkNamedAPI @"get-group-info" getGroupInfo
<@> mkNamedAPI @"list-conversation-ids-unqualified" conversationIdsPageFromUnqualified
<@> mkNamedAPI @"list-conversation-ids-v2" conversationIdsPageFromV2
<@> mkNamedAPI @"list-conversation-ids" conversationIdsPageFrom
<@> mkNamedAPI @"get-conversations" getConversations
<@> mkNamedAPI @"list-conversations-v1" listConversations
Expand Down
53 changes: 48 additions & 5 deletions services/galley/src/Galley/API/Query.hs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ module Galley.API.Query
getGlobalTeamConversation,
getConversationRoles,
conversationIdsPageFromUnqualified,
conversationIdsPageFromV2,
conversationIdsPageFrom,
getConversations,
listConversations,
Expand Down Expand Up @@ -334,15 +335,26 @@ conversationIdsPageFromUnqualified lusr start msize = do
--
-- - After local conversations, remote conversations are listed ordered
-- - lexicographically by their domain and then by their id.
conversationIdsPageFrom ::
--
-- FUTUREWORK: Move the body of this function to 'conversationIdsPageFrom' once
-- support for V2 is dropped.
conversationIdsPageFromV2 ::
forall p r.
( p ~ CassandraPaging,
Members '[ListItems p ConvId, ListItems p (Remote ConvId)] r
Members
'[ ConversationStore,
Error InternalError,
Input Env,
ListItems p ConvId,
ListItems p (Remote ConvId),
P.TinyLog
]
r
) =>
Local UserId ->
Public.GetPaginatedConversationIds ->
Sem r Public.ConvIdsPage
conversationIdsPageFrom lusr Public.GetMultiTablePageRequest {..} = do
conversationIdsPageFromV2 lusr Public.GetMultiTablePageRequest {..} = do
let localDomain = tDomain lusr
case gmtprState of
Just (Public.ConversationPagingState Public.PagingRemotes stateBS) ->
Expand Down Expand Up @@ -386,6 +398,37 @@ conversationIdsPageFrom lusr Public.GetMultiTablePageRequest {..} = do
mtpPagingState = Public.ConversationPagingState table (LBS.toStrict . C.unPagingState <$> pwsState)
}

-- | Lists conversation ids for the logged in user in a paginated way.
--
-- Pagination requires an order, in this case the order is defined as:
--
-- - First all the local conversations are listed ordered by their id
--
-- - After local conversations, remote conversations are listed ordered
-- - lexicographically by their domain and then by their id.
conversationIdsPageFrom ::
forall p r.
( p ~ CassandraPaging,
Members
'[ ConversationStore,
Error InternalError,
Input Env,
ListItems p ConvId,
ListItems p (Remote ConvId),
P.TinyLog
]
r
) =>
Local UserId ->
Public.GetPaginatedConversationIds ->
Sem r Public.ConvIdsPage
conversationIdsPageFrom lusr state = do
-- NOTE: Getting the MLS self-conversation creates it in case it does not
-- exist yet. This is to ensure it is automatically listed without needing to
-- create it separately.
void $ getMLSSelfConversation lusr
conversationIdsPageFromV2 lusr state

getConversations ::
Members '[Error InternalError, ListItems LegacyPaging ConvId, ConversationStore, P.TinyLog] r =>
Local UserId ->
Expand Down Expand Up @@ -643,8 +686,8 @@ getMLSSelfConversation ::
Members
'[ ConversationStore,
Error InternalError,
P.TinyLog,
Input Env
Input Env,
P.TinyLog
]
r =>
Local UserId ->
Expand Down
20 changes: 11 additions & 9 deletions services/galley/test/integration/API.hs
Original file line number Diff line number Diff line change
Expand Up @@ -1786,12 +1786,14 @@ listConvIdsOk = do
connectUsers alice (singleton bob)
void $ postO2OConv alice bob (Just "gossip")
let paginationOpts = GetPaginatedConversationIds Nothing (toRange (Proxy @5))
-- Each of the users has a Proteus self-conversation, an MLS self-conversation
-- and the one-to-one coversation.
listConvIds alice paginationOpts !!! do
const 200 === statusCode
const (Right 2) === fmap length . decodeQualifiedConvIdList
const (Right 3) === fmap length . decodeQualifiedConvIdList
listConvIds bob paginationOpts !!! do
const 200 === statusCode
const (Right 2) === fmap length . decodeQualifiedConvIdList
const (Right 3) === fmap length . decodeQualifiedConvIdList

paginateConvListIds :: TestM ()
paginateConvListIds = do
Expand All @@ -1802,7 +1804,7 @@ paginateConvListIds = do
now <- liftIO getCurrentTime
fedGalleyClient <- view tsFedGalleyClient

replicateM_ 197 $
replicateM_ 196 $
postConv alice [bob, eve] (Just "gossip") [] Nothing Nothing
!!! const 201 === statusCode

Expand Down Expand Up @@ -1838,9 +1840,9 @@ paginateConvListIds = do
}
runFedClient @"on-conversation-updated" fedGalleyClient deeDomain cu

-- 1 self conv + 2 convs with bob and eve + 197 local convs + 25 convs on
-- chad.example.com + 31 on dee.example = 256 convs. Getting them 16 at a time
-- should get all them in 16 times.
-- 1 Proteus self conv + 1 MLS self conv + 2 convs with bob and eve + 196
-- local convs + 25 convs on chad.example.com + 31 on dee.example = 256 convs.
-- Getting them 16 at a time should get all them in 16 times.
foldM_ (getChunkedConvs 16 0 alice) Nothing [16, 15 .. 0 :: Int]

-- This test ensures to setup conversations so that a page would end exactly
Expand All @@ -1856,9 +1858,9 @@ paginateConvListIdsPageEndingAtLocalsAndDomain = do
now <- liftIO getCurrentTime
fedGalleyClient <- view tsFedGalleyClient

-- With page size 16, 29 group convs + 2 one-to-one convs + 1 self conv, we
-- get 32 convs. The 2nd page should end here.
replicateM_ 29 $
-- With page size 16, 28 group convs + 2 one-to-one convs + 1 Proteus self
-- conv + 1 MLS self conv, we get 32 convs. The 2nd page should end here.
replicateM_ 28 $
postConv alice [bob, eve] (Just "gossip") [] Nothing Nothing
!!! const 201 === statusCode

Expand Down
27 changes: 27 additions & 0 deletions services/galley/test/integration/API/MLS.hs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ import Wire.API.MLS.Keys
import Wire.API.MLS.Serialisation
import Wire.API.MLS.Welcome
import Wire.API.Message
import Wire.API.Routes.MultiTablePaging
import Wire.API.Team (teamCreator)
import Wire.API.User.Client

Expand Down Expand Up @@ -205,6 +206,8 @@ tests s =
testGroup
"Self conversation"
[ test s "create a self conversation" testSelfConversation,
test s "do not list a self conversation below v3" $ testSelfConversationList True,
test s "list a self conversation automatically from v3" $ testSelfConversationList False,
test s "attempt to add another user to a conversation fails" testSelfConversationOtherUser,
test s "attempt to leave fails" testSelfConversationLeave
]
Expand Down Expand Up @@ -2348,6 +2351,30 @@ testSelfConversation = do
wsAssertMLSWelcome alice welcome
WS.assertNoEvent (1 # WS.Second) wss

-- | The MLS self-conversation should be available even without explicitly
-- creating it by calling `GET /conversations/mls-self` starting from version 3
-- of the client API and should not be listed in versions less than 3.
testSelfConversationList :: Bool -> TestM ()
testSelfConversationList isBelowV3 = do
let (errMsg, justOrNothing, listCnvs) =
if isBelowV3
then ("The MLS self-conversation is listed", isNothing, listConvIdsV2)
else ("The MLS self-conversation is not listed", isJust, listConvIds)
alice <- randomUser
let paginationOpts = GetPaginatedConversationIds Nothing (toRange (Proxy @100))
convIds :: ConvIdsPage <-
responseJsonError
=<< listCnvs alice paginationOpts
<!! const 200 === statusCode
convs <-
forM (mtpResults convIds) (responseJsonError <=< getConvQualified alice)
let mMLSSelf = foldr (<|>) Nothing $ guard . isMLSSelf <$> convs
liftIO $ assertBool errMsg (justOrNothing mMLSSelf)
where
isMLSSelf conv =
cnvType conv == SelfConv
&& protocolTag (cnvProtocol conv) == ProtocolMLSTag

testSelfConversationOtherUser :: TestM ()
testSelfConversationOtherUser = do
users@[_alice, bob] <- createAndConnectUsers [Nothing, Nothing]
Expand Down
19 changes: 15 additions & 4 deletions services/galley/test/integration/API/Util.hs
Original file line number Diff line number Diff line change
Expand Up @@ -144,13 +144,14 @@ import Wire.API.User.Client.Prekey
-- API Operations

addPrefix :: Request -> Request
addPrefix r = r {HTTP.path = "v" <> toHeader latestVersion <> "/" <> removeSlash (HTTP.path r)}
addPrefix = addPrefixAtVersion maxBound

addPrefixAtVersion :: Version -> Request -> Request
addPrefixAtVersion v r = r {HTTP.path = "v" <> toHeader v <> "/" <> removeSlash (HTTP.path r)}
where
removeSlash s = case B8.uncons s of
Just ('/', s') -> s'
_ -> s
latestVersion :: Version
latestVersion = maxBound

-- | A class for monads with access to a Sem r instance
class HasGalley m where
Expand Down Expand Up @@ -1113,7 +1114,8 @@ getConvQualified u (Qualified conv domain) = do

getConvIds :: UserId -> Maybe (Either [ConvId] ConvId) -> Maybe Int32 -> TestM ResponseLBS
getConvIds u r s = do
g <- viewGalley
-- The endpoint is removed starting V3
g <- fmap (addPrefixAtVersion V2 .) (view tsUnversionedGalley)
get $
g
. path "/conversations/ids"
Expand All @@ -1131,6 +1133,15 @@ listConvIds u paginationOpts = do
. zUser u
. json paginationOpts

listConvIdsV2 :: UserId -> GetPaginatedConversationIds -> TestM ResponseLBS
listConvIdsV2 u paginationOpts = do
g <- fmap (addPrefixAtVersion V2 .) (view tsUnversionedGalley)
post $
g
. path "/conversations/list-ids"
. zUser u
. json paginationOpts

-- | Does not page through conversation list
listRemoteConvs :: Domain -> UserId -> TestM [Qualified ConvId]
listRemoteConvs remoteDomain uid = do
Expand Down

0 comments on commit 58604d7

Please sign in to comment.