module Test.Migration.TeamFeatures where

import qualified API.Galley as Public
import qualified API.GalleyInternal as Internal
import Control.Monad.Codensity
import Control.Monad.Reader
import SetupHelpers
import Test.FeatureFlags.Util
import Test.Migration.Util (waitForMigration)
import Testlib.Prelude hiding (pairs)
import Testlib.ResourcePool

testTeamFeaturesMigration :: (HasCallStack) => App ()
testTeamFeaturesMigration :: HasCallStack => App ()
testTeamFeaturesMigration = do
  resourcePool <- (Env -> ResourcePool BackendResource)
-> App (ResourcePool BackendResource)
forall r (m :: * -> *) a. MonadReader r m => (r -> a) -> m a
asks (.resourcePool)
  runCodensity (acquireResources 1 resourcePool) $ \[BackendResource
backend] -> do
    let preMigration :: App ([(Value, String, [Value])], [(Value, String, [Value])])
-> App ([(Value, String, [Value])], [(Value, String, [Value])])
preMigration = Codensity App String -> forall b. (String -> App b) -> App b
forall k (m :: k -> *) a.
Codensity m a -> forall (b :: k). (a -> m b) -> m b
runCodensity (HasCallStack =>
BackendResource -> ServiceOverrides -> Codensity App String
BackendResource -> ServiceOverrides -> Codensity App String
startDynamicBackend BackendResource
backend (String -> Bool -> ServiceOverrides
conf String
"cassandra" Bool
False)) ((String
  -> App ([(Value, String, [Value])], [(Value, String, [Value])]))
 -> App ([(Value, String, [Value])], [(Value, String, [Value])]))
-> (App ([(Value, String, [Value])], [(Value, String, [Value])])
    -> String
    -> App ([(Value, String, [Value])], [(Value, String, [Value])]))
-> App ([(Value, String, [Value])], [(Value, String, [Value])])
-> App ([(Value, String, [Value])], [(Value, String, [Value])])
forall b c a. (b -> c) -> (a -> b) -> a -> c
. App ([(Value, String, [Value])], [(Value, String, [Value])])
-> String
-> App ([(Value, String, [Value])], [(Value, String, [Value])])
forall a b. a -> b -> a
const
        switchToMigratingInterpreter :: App () -> App ()
switchToMigratingInterpreter = Codensity App String -> forall b. (String -> App b) -> App b
forall k (m :: k -> *) a.
Codensity m a -> forall (b :: k). (a -> m b) -> m b
runCodensity (HasCallStack =>
BackendResource -> ServiceOverrides -> Codensity App String
BackendResource -> ServiceOverrides -> Codensity App String
startDynamicBackend BackendResource
backend (String -> Bool -> ServiceOverrides
conf String
"migration-to-postgresql" Bool
False)) ((String -> App ()) -> App ())
-> (App () -> String -> App ()) -> App () -> App ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. App () -> String -> App ()
forall a b. a -> b -> a
const
        startMigration :: App () -> App ()
startMigration = Codensity App String -> forall b. (String -> App b) -> App b
forall k (m :: k -> *) a.
Codensity m a -> forall (b :: k). (a -> m b) -> m b
runCodensity (HasCallStack =>
BackendResource -> ServiceOverrides -> Codensity App String
BackendResource -> ServiceOverrides -> Codensity App String
startDynamicBackend BackendResource
backend (String -> Bool -> ServiceOverrides
conf String
"migration-to-postgresql" Bool
True)) ((String -> App ()) -> App ())
-> (App () -> String -> App ()) -> App () -> App ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. App () -> String -> App ()
forall a b. a -> b -> a
const
        stopMigration :: App () -> App ()
stopMigration = Codensity App String -> forall b. (String -> App b) -> App b
forall k (m :: k -> *) a.
Codensity m a -> forall (b :: k). (a -> m b) -> m b
runCodensity (HasCallStack =>
BackendResource -> ServiceOverrides -> Codensity App String
BackendResource -> ServiceOverrides -> Codensity App String
startDynamicBackend BackendResource
backend (String -> Bool -> ServiceOverrides
conf String
"migration-to-postgresql" Bool
False)) ((String -> App ()) -> App ())
-> (App () -> String -> App ()) -> App () -> App ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. App () -> String -> App ()
forall a b. a -> b -> a
const
        switchToPostgresInterpreter :: App () -> App ()
switchToPostgresInterpreter = Codensity App String -> forall b. (String -> App b) -> App b
forall k (m :: k -> *) a.
Codensity m a -> forall (b :: k). (a -> m b) -> m b
runCodensity (HasCallStack =>
BackendResource -> ServiceOverrides -> Codensity App String
BackendResource -> ServiceOverrides -> Codensity App String
startDynamicBackend BackendResource
backend (String -> Bool -> ServiceOverrides
conf String
"postgresql" Bool
False)) ((String -> App ()) -> App ())
-> (App () -> String -> App ()) -> App () -> App ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. App () -> String -> App ()
forall a b. a -> b -> a
const
        domain :: String
domain = BackendResource
backend.berDomain

    (teams0, teams1) <-
      App ([(Value, String, [Value])], [(Value, String, [Value])])
-> App ([(Value, String, [Value])], [(Value, String, [Value])])
preMigration (App ([(Value, String, [Value])], [(Value, String, [Value])])
 -> App ([(Value, String, [Value])], [(Value, String, [Value])]))
-> App ([(Value, String, [Value])], [(Value, String, [Value])])
-> App ([(Value, String, [Value])], [(Value, String, [Value])])
forall a b. (a -> b) -> a -> b
$ do
        teams0 <- Int
-> App (Value, String, [Value]) -> App [(Value, String, [Value])]
forall (m :: * -> *) a. Applicative m => Int -> m a -> m [a]
replicateM Int
3 (App (Value, String, [Value]) -> App [(Value, String, [Value])])
-> App (Value, String, [Value]) -> App [(Value, String, [Value])]
forall a b. (a -> b) -> a -> b
$ String -> Int -> App (Value, String, [Value])
forall domain.
(HasCallStack, MakesValue domain) =>
domain -> Int -> App (Value, String, [Value])
createTeam String
domain Int
2
        teams1@(team1 : _) <- replicateM 5 $ createTeam domain 1
        for_ teams0 $ \(Value
owner, String
tid, [Value]
_) -> Value -> String -> [String] -> App ()
enableFeatures Value
owner String
tid [String]
unlockableFeatures
        testSetFeatures team1
        testGetFeatures team1
        pure (teams0, teams1)

    team1 : team2 : team3 : team4 : team5 : _ <- pure teams1

    switchToMigratingInterpreter $ do
      assertModifiedFeatures domain teams0
      testSetFeatures team2
      testGetFeatures team1
      testGetFeatures team2

    startMigration $ do
      assertModifiedFeatures domain teams0
      testSetFeatures team3
      testGetFeatures team1
      testGetFeatures team2
      testGetFeatures team3
      waitForMigration domain counterName

    stopMigration $ do
      assertModifiedFeatures domain teams0
      testSetFeatures team4
      testGetFeatures team1
      testGetFeatures team2
      testGetFeatures team3
      testGetFeatures team4

    switchToPostgresInterpreter $ do
      assertModifiedFeatures domain teams0
      testSetFeatures team5
      testGetFeatures team1
      testGetFeatures team2
      testGetFeatures team3
      testGetFeatures team4
      testGetFeatures team5
  where
    unlockableFeatures :: [String]
    unlockableFeatures :: [String]
unlockableFeatures =
      [ String
"fileSharing",
        String
"conferenceCalling",
        String
"selfDeletingMessages",
        String
"conversationGuestLinks",
        String
"sndFactorPasswordChallenge",
        String
"mls",
        String
"outlookCalIntegration",
        String
"mlsE2EId",
        String
"mlsMigration",
        String
"enforceFileDownloadLocation",
        String
"domainRegistration",
        String
"channels",
        String
"cells",
        String
"consumableNotifications",
        String
"chatBubbles",
        String
"apps",
        String
"simplifiedUserConnectionRequestQRCode",
        String
"stealthUsers",
        String
"meetings",
        String
"meetingsPremium"
      ]

    assertModifiedFeatures :: String -> [(Value, String, [Value])] -> App ()
    assertModifiedFeatures :: String -> [(Value, String, [Value])] -> App ()
assertModifiedFeatures String
domain [(Value, String, [Value])]
teams = do
      expectedModifiedFeatures <-
        Value -> App Value
mkExpectedModifiedFeatures Value
defAllFeatures
          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
>>= String -> [String] -> Value -> App Value
forall a b.
(HasCallStack, MakesValue a, ToJSON b) =>
String -> b -> a -> App Value
setField String
"classifiedDomains.config.domains" [String
domain]
      for_ teams $ \(Value
owner, String
tid, [Value]
_) ->
        App Response -> (Response -> App ()) -> App ()
forall a.
HasCallStack =>
App Response -> (Response -> App a) -> App a
bindResponse (Value -> String -> App Response
forall user tid.
(HasCallStack, MakesValue user, MakesValue tid) =>
user -> tid -> App Response
Public.getTeamFeatures Value
owner String
tid) ((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
          [String] -> (String -> App ()) -> App ()
forall (t :: * -> *) (f :: * -> *) a b.
(Foldable t, Applicative f) =>
t a -> (a -> f b) -> f ()
for_ [String]
unlockableFeatures ((String -> App ()) -> App ()) -> (String -> App ()) -> App ()
forall a b. (a -> b) -> a -> b
$ \String
feat -> do
            Response
resp.json App Value -> String -> App Value
forall a. (HasCallStack, MakesValue a) => a -> String -> App Value
%. String
feat App Value -> String -> App Value
forall a. (HasCallStack, MakesValue a) => a -> String -> App Value
%. String
"status" App Value -> String -> App ()
forall a b.
(MakesValue a, MakesValue b, HasCallStack) =>
a -> b -> App ()
`shouldMatch` String
"enabled"
            Response
resp.json App Value -> String -> App Value
forall a. (HasCallStack, MakesValue a) => a -> String -> App Value
%. String
feat App Value -> String -> App Value
forall a. (HasCallStack, MakesValue a) => a -> String -> App Value
%. String
"lockStatus" App Value -> String -> App ()
forall a b.
(MakesValue a, MakesValue b, HasCallStack) =>
a -> b -> App ()
`shouldMatch` String
"unlocked"
          Response
resp.json App Value -> Value -> App ()
forall a b.
(MakesValue a, MakesValue b, HasCallStack) =>
a -> b -> App ()
`shouldMatch` Value
expectedModifiedFeatures

    enableFeatures :: Value -> String -> [String] -> App ()
    enableFeatures :: Value -> String -> [String] -> App ()
enableFeatures Value
owner String
tid [String]
features = do
      [String] -> (String -> App ()) -> App ()
forall (t :: * -> *) (f :: * -> *) a b.
(Foldable t, Applicative f) =>
t a -> (a -> f b) -> f ()
for_ [String]
features ((String -> App ()) -> App ()) -> (String -> App ()) -> App ()
forall a b. (a -> b) -> a -> b
$ \String
name -> do
        Value -> String -> String -> String -> App ()
forall domain team.
(HasCallStack, MakesValue domain, MakesValue team) =>
domain -> team -> String -> String -> App ()
Internal.setTeamFeatureLockStatus Value
owner String
tid String
name String
"unlocked"
        HasCallStack => Response -> App ()
Response -> App ()
assertSuccess (Response -> App ()) -> App Response -> App ()
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< Value -> String -> String -> String -> App Response
forall domain team.
(HasCallStack, MakesValue domain, MakesValue team) =>
domain -> team -> String -> String -> App Response
Internal.setTeamFeatureStatus Value
owner String
tid String
name String
"enabled"

    testSetFeatures :: (HasCallStack) => (Value, String, [Value]) -> App ()
    testSetFeatures :: HasCallStack => (Value, String, [Value]) -> App ()
testSetFeatures (Value
owner, String
tid, [Value]
_) = do
      Value -> String -> String -> String -> App ()
forall domain team.
(HasCallStack, MakesValue domain, MakesValue team) =>
domain -> team -> String -> String -> App ()
Internal.setTeamFeatureLockStatus Value
owner String
tid String
"channels" String
"unlocked"
      Value -> String -> String -> String -> App ()
forall domain team.
(HasCallStack, MakesValue domain, MakesValue team) =>
domain -> team -> String -> String -> App ()
Internal.setTeamFeatureLockStatus Value
owner String
tid String
"enforceFileDownloadLocation" String
"unlocked"
      HasCallStack => Response -> App ()
Response -> App ()
assertSuccess (Response -> App ()) -> App Response -> App ()
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< Value -> 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
Internal.setTeamFeatureConfig Value
owner String
tid String
"channels" Value
channelsConfig
      HasCallStack => Response -> App ()
Response -> App ()
assertSuccess (Response -> App ()) -> App Response -> App ()
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< Value -> 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
Internal.setTeamFeatureConfig Value
owner String
tid String
"enforceFileDownloadLocation" Value
enforceDownloadLocationConfig
      where
        channelsConfig :: Value
        channelsConfig :: Value
channelsConfig =
          [Pair] -> Value
object
            [ String
"status" String -> String -> Pair
forall a. ToJSON a => String -> a -> Pair
.= String
"enabled",
              String
"config"
                String -> Value -> Pair
forall a. ToJSON a => String -> a -> Pair
.= [Pair] -> Value
object
                  [ String
"allowed_to_create_channels" String -> String -> Pair
forall a. ToJSON a => String -> a -> Pair
.= String
"team-members",
                    String
"allowed_to_open_channels" String -> String -> Pair
forall a. ToJSON a => String -> a -> Pair
.= String
"admins"
                  ]
            ]

        enforceDownloadLocationConfig :: Value
        enforceDownloadLocationConfig :: Value
enforceDownloadLocationConfig =
          [Pair] -> Value
object
            [ String
"status" String -> String -> Pair
forall a. ToJSON a => String -> a -> Pair
.= String
"enabled",
              String
"config" String -> Value -> Pair
forall a. ToJSON a => String -> a -> Pair
.= [Pair] -> Value
object [String
"enforcedDownloadLocation" String -> String -> Pair
forall a. ToJSON a => String -> a -> Pair
.= String
"/tmp/migration-test"]
            ]

    testGetFeatures :: (HasCallStack) => (Value, String, [Value]) -> App ()
    testGetFeatures :: HasCallStack => (Value, String, [Value]) -> App ()
testGetFeatures (Value
owner, String
tid, [Value]
_) = do
      expectedChannels <- App Value
expectedChannelsConfig
      expectedDownloadLocation <- expectedEnforceDownloadLocationConfig
      bindResponse (Public.getTeamFeature owner tid "channels") $ \Response
resp -> do
        Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
200
        Response
resp.json App Value -> Value -> App ()
forall a b.
(MakesValue a, MakesValue b, HasCallStack) =>
a -> b -> App ()
`shouldMatch` Value
expectedChannels
      bindResponse (Public.getTeamFeature owner tid "enforceFileDownloadLocation") $ \Response
resp -> do
        Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
200
        Response
resp.json App Value -> Value -> App ()
forall a b.
(MakesValue a, MakesValue b, HasCallStack) =>
a -> b -> App ()
`shouldMatch` Value
expectedDownloadLocation
      where
        expectedChannelsConfig :: App Value
        expectedChannelsConfig :: App Value
expectedChannelsConfig = do
          defChannels <- Value
defAllFeatures Value -> String -> App Value
forall a. (HasCallStack, MakesValue a) => a -> String -> App Value
%. String
"channels"
          defChannels
            & setField "lockStatus" "unlocked"
              >>= setField "status" "enabled"
              >>= setField "config.allowed_to_create_channels" "team-members"
              >>= setField "config.allowed_to_open_channels" "admins"

        expectedEnforceDownloadLocationConfig :: App Value
        expectedEnforceDownloadLocationConfig :: App Value
expectedEnforceDownloadLocationConfig = do
          defFeature <- Value
defAllFeatures Value -> String -> App Value
forall a. (HasCallStack, MakesValue a) => a -> String -> App Value
%. String
"enforceFileDownloadLocation"
          defFeature
            & setField "lockStatus" "unlocked"
              >>= setField "status" "enabled"
              >>= setField "config.enforcedDownloadLocation" "/tmp/migration-test"

    mkExpectedModifiedFeatures :: Value -> App Value
    mkExpectedModifiedFeatures :: Value -> App Value
mkExpectedModifiedFeatures Value
features =
      (App Value -> String -> App Value)
-> App Value -> [String] -> App Value
forall b a. (b -> a -> b) -> b -> [a] -> b
forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl ((String -> App Value -> App Value)
-> App Value -> String -> App Value
forall a b c. (a -> b -> c) -> b -> a -> c
flip String -> App Value -> App Value
forall {a}. MakesValue a => String -> a -> App Value
update) (Value -> App Value
forall a. a -> App a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Value
features) [String]
unlockableFeatures
      where
        update :: String -> a -> App Value
update String
feat =
          String -> String -> a -> App Value
forall a b.
(HasCallStack, MakesValue a, ToJSON b) =>
String -> b -> a -> App Value
setField (String
feat String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
".status") String
"enabled"
            (a -> App Value) -> (Value -> App Value) -> a -> App Value
forall (m :: * -> *) a b c.
Monad m =>
(a -> m b) -> (b -> m c) -> a -> m c
>=> String -> String -> Value -> App Value
forall a b.
(HasCallStack, MakesValue a, ToJSON b) =>
String -> b -> a -> App Value
setField (String
feat String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
".lockStatus") String
"unlocked"

    conf :: String -> Bool -> ServiceOverrides
    conf :: String -> Bool -> ServiceOverrides
conf String
db Bool
runMigration =
      ServiceOverrides
forall a. Default a => a
def
        { galleyCfg = setField "postgresMigration.teamFeatures" db,
          backgroundWorkerCfg = setField "migrateTeamFeatures" runMigration
        }

counterName :: String
counterName :: String
counterName = String
"^wire_team_features_migration_finished"