-- This file is part of the Wire Server implementation.
--
-- Copyright (C) 2026 Wire Swiss GmbH <opensource@wire.com>
--
-- This program is free software: you can redistribute it and/or modify it under
-- the terms of the GNU Affero General Public License as published by the Free
-- Software Foundation, either version 3 of the License, or (at your option) any
-- later version.
--
-- This program is distributed in the hope that it will be useful, but WITHOUT
-- ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-- FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
-- details.
--
-- You should have received a copy of the GNU Affero General Public License along
-- with this program. If not, see <https://www.gnu.org/licenses/>.

module Test.Spar.GetByEmail where

import API.BrigInternal (CreateUser (..))
import API.GalleyInternal
import API.Spar
import GHC.Stack
import qualified SAML2.WebSSO.Test.Util as SAML
import SetupHelpers
import Testlib.Prelude

-- | Test the /sso/get-by-email endpoint with multi-ingress setup
testGetSsoCodeByEmailWithMultiIngress ::
  (HasCallStack) =>
  TaggedBool "validateSAMLemails" ->
  TaggedBool "idpScimToken" ->
  App ()
testGetSsoCodeByEmailWithMultiIngress :: HasCallStack =>
TaggedBool "validateSAMLemails"
-> TaggedBool "idpScimToken" -> App ()
testGetSsoCodeByEmailWithMultiIngress (TaggedBool Bool
validateSAMLemails) (TaggedBool Bool
isIdPScimToken) = do
  let ernieZHost :: [Char]
ernieZHost = [Char]
"nginz-https.ernie.example.com"
      bertZHost :: [Char]
bertZHost = [Char]
"nginz-https.bert.example.com"

  ServiceOverrides -> (HasCallStack => [Char] -> App ()) -> App ()
forall a.
HasCallStack =>
ServiceOverrides -> (HasCallStack => [Char] -> App a) -> App a
withModifiedBackend
    ServiceOverrides
forall a. Default a => a
def
      { sparCfg =
          setField "enableIdPByEmailDiscovery" True
            >=> removeField "saml.spSsoUri"
            >=> removeField "saml.spAppUri"
            >=> removeField "saml.contacts"
            >=> setField
              "saml.spDomainConfigs"
              ( object
                  [ ernieZHost
                      .= object
                        [ "spAppUri" .= ("https://webapp." ++ ernieZHost),
                          "spSsoUri" .= ("https://" ++ ernieZHost ++ "/sso"),
                          "contacts" .= [object ["type" .= ("ContactTechnical" :: String)]]
                        ],
                    bertZHost
                      .= object
                        [ "spAppUri" .= ("https://webapp." ++ bertZHost),
                          "spSsoUri" .= ("https://" ++ bertZHost ++ "/sso"),
                          "contacts" .= [object ["type" .= ("ContactTechnical" :: String)]]
                        ]
                  ]
              )
      }
    ((HasCallStack => [Char] -> App ()) -> App ())
-> (HasCallStack => [Char] -> App ()) -> App ()
forall a b. (a -> b) -> a -> b
$ \[Char]
domain -> do
      (owner, tid, _) <- [Char] -> Int -> App (Value, [Char], [Value])
forall domain.
(HasCallStack, MakesValue domain) =>
domain -> Int -> App (Value, [Char], [Value])
createTeam [Char]
domain Int
1
      assertSuccess =<< setTeamFeatureStatus domain tid "sso" "enabled"

      -- The test should work for both: SCIM user with and without email confirmation
      let status = if Bool
validateSAMLemails then [Char]
"enabled" else [Char]
"disabled"
      assertSuccess =<< setTeamFeatureStatus owner tid "validateSAMLemails" status

      -- Create IdP for ernie domain
      SAML.SampleIdP idpmetaErnie _ _ _ <- SAML.makeSampleIdPMetadata
      idpIdErnie <-
        createIdpWithZHost owner (Just ernieZHost) idpmetaErnie `bindResponse` \Response
resp -> do
          Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
201
          Response
resp.json Maybe Value -> [Char] -> App Value
forall a. (HasCallStack, MakesValue a) => a -> [Char] -> App Value
%. [Char]
"extraInfo.domain" App Value -> [Char] -> App ()
forall a b.
(MakesValue a, MakesValue b, HasCallStack) =>
a -> b -> App ()
`shouldMatch` [Char]
ernieZHost
          Response
resp.json Maybe Value -> [Char] -> App Value
forall a. (HasCallStack, MakesValue a) => a -> [Char] -> App Value
%. [Char]
"id" App Value -> (Value -> App [Char]) -> App [Char]
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 [Char]
forall a. (HasCallStack, MakesValue a) => a -> App [Char]
asString

      -- Create IdP for bert domain
      SAML.SampleIdP idpmetaBert _ _ _ <- SAML.makeSampleIdPMetadata
      idpIdBert <-
        createIdpWithZHost owner (Just bertZHost) idpmetaBert `bindResponse` \Response
resp -> do
          Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
201
          Response
resp.json Maybe Value -> [Char] -> App Value
forall a. (HasCallStack, MakesValue a) => a -> [Char] -> App Value
%. [Char]
"extraInfo.domain" App Value -> [Char] -> App ()
forall a b.
(MakesValue a, MakesValue b, HasCallStack) =>
a -> b -> App ()
`shouldMatch` [Char]
bertZHost
          Response
resp.json Maybe Value -> [Char] -> App Value
forall a. (HasCallStack, MakesValue a) => a -> [Char] -> App Value
%. [Char]
"id" App Value -> (Value -> App [Char]) -> App [Char]
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 [Char]
forall a. (HasCallStack, MakesValue a) => a -> App [Char]
asString

      -- Create a SCIM user managed by the ernie IdP
      scimUser <- randomScimUser
      userEmail <-
        scimUser %. "emails" >>= asList >>= \case
          (Value
e : [Value]
_) -> Value
e Value -> [Char] -> App Value
forall a. (HasCallStack, MakesValue a) => a -> [Char] -> App Value
%. [Char]
"value" App Value -> (Value -> App [Char]) -> App [Char]
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 [Char]
forall a. (HasCallStack, MakesValue a) => a -> App [Char]
asString
          [] -> [Char] -> App [Char]
forall a. HasCallStack => [Char] -> App a
assertFailure [Char]
"Expected at least one email"

      let idpTokenConfig = if Bool
isIdPScimToken then (CreateScimToken
forall a. Default a => a
def {idp = Just idpIdErnie}) else CreateScimToken
forall a. Default a => a
def
      scimTok <- createScimToken owner idpTokenConfig
      scimToken <- scimTok.json %. "token" & asString

      createScimUser domain scimToken scimUser >>= assertSuccess

      if isIdPScimToken
        then when validateSAMLemails $ do
          -- Activate the email so the user can be found by email
          activateEmail domain userEmail
        else
          -- Team members get an invitation (not an activation mail)
          registerInvitedUser domain tid userEmail

      -- Get the SSO code by email with matching Z-Host (ernie)
      getSsoCodeByEmailWithZHost domain (Just ernieZHost) userEmail `bindResponse` \Response
resp -> do
        Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
200
        ssoCodeStr <- Response
resp.json Maybe Value -> [Char] -> App Value
forall a. (HasCallStack, MakesValue a) => a -> [Char] -> App Value
%. [Char]
"sso_code" App Value -> (Value -> App [Char]) -> App [Char]
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 [Char]
forall a. (HasCallStack, MakesValue a) => a -> App [Char]
asString
        ssoCodeStr `shouldMatch` idpIdErnie

      -- Get the SSO code by email without Z-Host (should return 404 with null - multiple IdPs)
      getSsoCodeByEmail domain userEmail `bindResponse` \Response
resp -> do
        Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
404
        mbSsoCode <- Maybe Value -> [Char] -> App (Maybe Value)
forall a.
(HasCallStack, MakesValue a) =>
a -> [Char] -> App (Maybe Value)
lookupField Response
resp.json [Char]
"sso_code"
        mbSsoCode `shouldMatch` (Nothing :: Maybe Value)

      -- Get the SSO code by email with Z-Host for bert domain (should return bert's IdP)
      getSsoCodeByEmailWithZHost domain (Just bertZHost) userEmail `bindResponse` \Response
resp -> do
        Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
200
        ssoCodeStr <- Response
resp.json Maybe Value -> [Char] -> App Value
forall a. (HasCallStack, MakesValue a) => a -> [Char] -> App Value
%. [Char]
"sso_code" App Value -> (Value -> App [Char]) -> App [Char]
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 [Char]
forall a. (HasCallStack, MakesValue a) => a -> App [Char]
asString
        ssoCodeStr `shouldMatch` idpIdBert

-- | Test the /sso/get-by-email endpoint with regular (non-multi-ingress) setup
testGetSsoCodeByEmailRegular :: (HasCallStack) => (TaggedBool "validateSAMLemails") -> (TaggedBool "idpScimToken") -> App ()
testGetSsoCodeByEmailRegular :: HasCallStack =>
TaggedBool "validateSAMLemails"
-> TaggedBool "idpScimToken" -> App ()
testGetSsoCodeByEmailRegular (TaggedBool Bool
validateSAMLemails) (TaggedBool Bool
isIdPScimToken) =
  ServiceOverrides -> (HasCallStack => [Char] -> App ()) -> App ()
forall a.
HasCallStack =>
ServiceOverrides -> (HasCallStack => [Char] -> App a) -> App a
withModifiedBackend ServiceOverrides
forall a. Default a => a
def {sparCfg = setField "enableIdPByEmailDiscovery" True}
    ((HasCallStack => [Char] -> App ()) -> App ())
-> (HasCallStack => [Char] -> App ()) -> App ()
forall a b. (a -> b) -> a -> b
$ \[Char]
domain -> do
      (owner, tid, _) <- [Char] -> Int -> App (Value, [Char], [Value])
forall domain.
(HasCallStack, MakesValue domain) =>
domain -> Int -> App (Value, [Char], [Value])
createTeam [Char]
domain Int
1
      void $ setTeamFeatureStatus owner tid "sso" "enabled"

      -- The test should work for both: SCIM user with and without email confirmation
      let status = if Bool
validateSAMLemails then [Char]
"enabled" else [Char]
"disabled"
      assertSuccess =<< setTeamFeatureStatus owner tid "validateSAMLemails" status

      -- Create IdP without domain binding
      SAML.SampleIdP idpmeta _ _ _ <- SAML.makeSampleIdPMetadata
      idpId <-
        createIdp owner idpmeta `bindResponse` \Response
resp -> do
          Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
201
          Response
resp.json Maybe Value -> [Char] -> App Value
forall a. (HasCallStack, MakesValue a) => a -> [Char] -> App Value
%. [Char]
"id" App Value -> (Value -> App [Char]) -> App [Char]
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 [Char]
forall a. (HasCallStack, MakesValue a) => a -> App [Char]
asString

      -- Create a SCIM user
      scimUser <- randomScimUser
      userEmail <-
        scimUser %. "emails" >>= asList >>= \case
          (Value
e : [Value]
_) -> Value
e Value -> [Char] -> App Value
forall a. (HasCallStack, MakesValue a) => a -> [Char] -> App Value
%. [Char]
"value" App Value -> (Value -> App [Char]) -> App [Char]
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 [Char]
forall a. (HasCallStack, MakesValue a) => a -> App [Char]
asString
          [] -> [Char] -> App [Char]
forall a. HasCallStack => [Char] -> App a
assertFailure [Char]
"Expected at least one email"

      let idpTokenConfig = if Bool
isIdPScimToken then (CreateScimToken
forall a. Default a => a
def {idp = Just idpId}) else CreateScimToken
forall a. Default a => a
def
      scimTok <- createScimToken owner idpTokenConfig
      scimToken <- scimTok.json %. "token" & asString

      createScimUser domain scimToken scimUser >>= assertSuccess

      if isIdPScimToken
        then when validateSAMLemails $ do
          -- Activate the email so the user can be found by email
          activateEmail domain userEmail
        else
          -- Team members get an invitation (not an activation mail)
          registerInvitedUser domain tid userEmail

      -- Get the SSO code by email
      getSsoCodeByEmail domain userEmail `bindResponse` \Response
resp -> do
        Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
200
        ssoCodeStr <- Response
resp.json Maybe Value -> [Char] -> App Value
forall a. (HasCallStack, MakesValue a) => a -> [Char] -> App Value
%. [Char]
"sso_code" App Value -> (Value -> App [Char]) -> App [Char]
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 [Char]
forall a. (HasCallStack, MakesValue a) => a -> App [Char]
asString
        ssoCodeStr `shouldMatch` idpId

-- | Test that non-SCIM users get no SSO code
testGetSsoCodeByEmailNonScimUser :: (HasCallStack) => App ()
testGetSsoCodeByEmailNonScimUser :: HasCallStack => App ()
testGetSsoCodeByEmailNonScimUser = do
  ServiceOverrides -> (HasCallStack => [Char] -> App ()) -> App ()
forall a.
HasCallStack =>
ServiceOverrides -> (HasCallStack => [Char] -> App a) -> App a
withModifiedBackend
    ServiceOverrides
forall a. Default a => a
def {sparCfg = setField "enableIdPByEmailDiscovery" True}
    ((HasCallStack => [Char] -> App ()) -> App ())
-> (HasCallStack => [Char] -> App ()) -> App ()
forall a b. (a -> b) -> a -> b
$ \[Char]
domain -> do
      (owner, tid, _) <- [Char] -> Int -> App (Value, [Char], [Value])
forall domain.
(HasCallStack, MakesValue domain) =>
domain -> Int -> App (Value, [Char], [Value])
createTeam [Char]
domain Int
1
      void $ setTeamFeatureStatus owner tid "sso" "enabled"

      -- Create IdP without domain binding
      SAML.SampleIdP idpmeta _ _ _ <- SAML.makeSampleIdPMetadata
      createIdp owner idpmeta >>= assertSuccess

      -- Register user
      usr <- randomUser domain def {activate = True}
      userEmail <- usr %. "email" & asString

      -- Try to get SSO code for regular (non-SCIM) user - should return 404 with null
      getSsoCodeByEmail domain userEmail `bindResponse` \Response
resp -> do
        Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
404
        mbSsoCode <- Maybe Value -> [Char] -> App (Maybe Value)
forall a.
(HasCallStack, MakesValue a) =>
a -> [Char] -> App (Maybe Value)
lookupField Response
resp.json [Char]
"sso_code"
        mbSsoCode `shouldMatch` (Nothing :: Maybe Value)

-- | Test that the endpoint returns 404 with null when the feature is disabled (regular setup)
testGetSsoCodeByEmailDisabledRegular :: (HasCallStack) => App ()
testGetSsoCodeByEmailDisabledRegular :: HasCallStack => App ()
testGetSsoCodeByEmailDisabledRegular = do
  ServiceOverrides -> (HasCallStack => [Char] -> App ()) -> App ()
forall a.
HasCallStack =>
ServiceOverrides -> (HasCallStack => [Char] -> App a) -> App a
withModifiedBackend
    ServiceOverrides
forall a. Default a => a
def {sparCfg = setField "enableIdPByEmailDiscovery" False}
    ((HasCallStack => [Char] -> App ()) -> App ())
-> (HasCallStack => [Char] -> App ()) -> App ()
forall a b. (a -> b) -> a -> b
$ \[Char]
domain -> do
      (owner, tid, _) <- [Char] -> Int -> App (Value, [Char], [Value])
forall domain.
(HasCallStack, MakesValue domain) =>
domain -> Int -> App (Value, [Char], [Value])
createTeam [Char]
domain Int
1
      void $ setTeamFeatureStatus owner tid "sso" "enabled"

      -- Create IdP
      SAML.SampleIdP idpmeta _ _ _ <- SAML.makeSampleIdPMetadata
      idpId <-
        createIdp owner idpmeta `bindResponse` \Response
resp -> do
          Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
201
          Response
resp.json Maybe Value -> [Char] -> App Value
forall a. (HasCallStack, MakesValue a) => a -> [Char] -> App Value
%. [Char]
"id" App Value -> (Value -> App [Char]) -> App [Char]
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 [Char]
forall a. (HasCallStack, MakesValue a) => a -> App [Char]
asString

      -- Create a SCIM user
      scimUser <- randomScimUser
      userEmail <-
        scimUser %. "emails" >>= asList >>= \case
          (Value
e : [Value]
_) -> Value
e Value -> [Char] -> App Value
forall a. (HasCallStack, MakesValue a) => a -> [Char] -> App Value
%. [Char]
"value" App Value -> (Value -> App [Char]) -> App [Char]
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 [Char]
forall a. (HasCallStack, MakesValue a) => a -> App [Char]
asString
          [] -> [Char] -> App [Char]
forall a. HasCallStack => [Char] -> App a
assertFailure [Char]
"Expected at least one email"

      scimTok <- createScimToken owner def {idp = Just idpId}
      scimToken <- scimTok.json %. "token" & asString

      createScimUser domain scimToken scimUser >>= assertSuccess

      -- Activate the email so the user can be found by email
      activateEmail domain userEmail

      -- With feature disabled, should return 404 with empty ssoCode
      getSsoCodeByEmail domain userEmail `bindResponse` \Response
resp -> do
        Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
404
        mbSsoCode <- Maybe Value -> [Char] -> App (Maybe Value)
forall a.
(HasCallStack, MakesValue a) =>
a -> [Char] -> App (Maybe Value)
lookupField Response
resp.json [Char]
"sso_code"
        mbSsoCode `shouldMatch` (Nothing :: Maybe Value)

-- | Test that the endpoint returns 404 with null when the feature is disabled (multi-ingress setup)
testGetSsoCodeByEmailDisabledMultiIngress :: (HasCallStack) => App ()
testGetSsoCodeByEmailDisabledMultiIngress :: HasCallStack => App ()
testGetSsoCodeByEmailDisabledMultiIngress = do
  let ernieZHost :: [Char]
ernieZHost = [Char]
"nginz-https.ernie.example.com"
      bertZHost :: [Char]
bertZHost = [Char]
"nginz-https.bert.example.com"

  ServiceOverrides -> (HasCallStack => [Char] -> App ()) -> App ()
forall a.
HasCallStack =>
ServiceOverrides -> (HasCallStack => [Char] -> App a) -> App a
withModifiedBackend
    ServiceOverrides
forall a. Default a => a
def
      { sparCfg =
          setField "enableIdPByEmailDiscovery" False
            >=> removeField "saml.spSsoUri"
            >=> removeField "saml.spAppUri"
            >=> removeField "saml.contacts"
            >=> setField
              "saml.spDomainConfigs"
              ( object
                  [ ernieZHost
                      .= object
                        [ "spAppUri" .= ("https://webapp." ++ ernieZHost),
                          "spSsoUri" .= ("https://" ++ ernieZHost ++ "/sso"),
                          "contacts" .= [object ["type" .= ("ContactTechnical" :: String)]]
                        ],
                    bertZHost
                      .= object
                        [ "spAppUri" .= ("https://webapp." ++ bertZHost),
                          "spSsoUri" .= ("https://" ++ bertZHost ++ "/sso"),
                          "contacts" .= [object ["type" .= ("ContactTechnical" :: String)]]
                        ]
                  ]
              )
      }
    ((HasCallStack => [Char] -> App ()) -> App ())
-> (HasCallStack => [Char] -> App ()) -> App ()
forall a b. (a -> b) -> a -> b
$ \[Char]
domain -> do
      (owner, tid, _) <- [Char] -> Int -> App (Value, [Char], [Value])
forall domain.
(HasCallStack, MakesValue domain) =>
domain -> Int -> App (Value, [Char], [Value])
createTeam [Char]
domain Int
1
      void $ setTeamFeatureStatus owner tid "sso" "enabled"

      -- Create IdP for ernie domain
      SAML.SampleIdP idpmetaErnie _ _ _ <- SAML.makeSampleIdPMetadata
      idpIdErnie <-
        createIdpWithZHost owner (Just ernieZHost) idpmetaErnie `bindResponse` \Response
resp -> do
          Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
201
          Response
resp.json Maybe Value -> [Char] -> App Value
forall a. (HasCallStack, MakesValue a) => a -> [Char] -> App Value
%. [Char]
"extraInfo.domain" App Value -> [Char] -> App ()
forall a b.
(MakesValue a, MakesValue b, HasCallStack) =>
a -> b -> App ()
`shouldMatch` [Char]
ernieZHost
          Response
resp.json Maybe Value -> [Char] -> App Value
forall a. (HasCallStack, MakesValue a) => a -> [Char] -> App Value
%. [Char]
"id" App Value -> (Value -> App [Char]) -> App [Char]
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 [Char]
forall a. (HasCallStack, MakesValue a) => a -> App [Char]
asString

      -- Create a SCIM user
      scimUser <- randomScimUser
      userEmail <-
        scimUser %. "emails" >>= asList >>= \case
          (Value
e : [Value]
_) -> Value
e Value -> [Char] -> App Value
forall a. (HasCallStack, MakesValue a) => a -> [Char] -> App Value
%. [Char]
"value" App Value -> (Value -> App [Char]) -> App [Char]
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 [Char]
forall a. (HasCallStack, MakesValue a) => a -> App [Char]
asString
          [] -> [Char] -> App [Char]
forall a. HasCallStack => [Char] -> App a
assertFailure [Char]
"Expected at least one email"

      scimTok <- createScimToken owner def {idp = Just idpIdErnie}
      scimToken <- scimTok.json %. "token" & asString

      createScimUser domain scimToken scimUser >>= assertSuccess

      -- Activate the email so the user can be found by email
      activateEmail domain userEmail

      -- With feature disabled, should return 404 with null even with valid IdP
      bindResponse (getSsoCodeByEmailWithZHost domain (Just ernieZHost) userEmail) $ \Response
resp -> do
        Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
404
        mbSsoCode <- Maybe Value -> [Char] -> App (Maybe Value)
forall a.
(HasCallStack, MakesValue a) =>
a -> [Char] -> App (Maybe Value)
lookupField Response
resp.json [Char]
"sso_code"
        mbSsoCode `shouldMatch` (Nothing :: Maybe Value)