packages feed

shomei-core-0.2.0.0: test/Shomei/Account/Password/DomainSpec.hs

module Shomei.Account.Password.DomainSpec (tests) where

import Shomei.Account.Password.Common.Domain (commonPasswordCount, isCommonPassword)
import Shomei.Account.Password.Domain
  ( PasswordContext (..),
    PasswordPolicy (..),
    PlainPassword (..),
    defaultPasswordPolicy,
    validatePassword,
  )
import Shomei.Error (PasswordPolicyViolation (..))
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.HUnit (assertBool, testCase, (@?=))

aliceCtx :: PasswordContext
aliceCtx = PasswordContext {contextEmail = Just "alice@example.com", contextDisplayName = Just "Alice"}

-- | The default policy (rejectCommonPasswords=True, rejectContextualPasswords=True) but with a
-- small minLength so the length guard does not pre-empt the common/contextual checks under test.
basePolicy :: PasswordPolicy
basePolicy = defaultPasswordPolicy {minLength = 4}

tests :: TestTree
tests =
  testGroup
    "Shomei.Account.Password.DomainSpec"
    [ testCase "dictionary is non-empty" $
        assertBool "expected a non-empty common-password dictionary" (commonPasswordCount > 0),
      testCase "a known common password is detected" $
        isCommonPassword "password" @?= True,
      testCase "case and whitespace are normalized" $
        isCommonPassword "  PASSWORD  " @?= True,
      testCase "a strong passphrase is not common" $
        isCommonPassword "correct horse battery staple" @?= False,
      testCase "too short" $
        validatePassword defaultPasswordPolicy aliceCtx (PlainPassword "short")
          @?= Left (PasswordTooShort defaultPasswordPolicy.minLength),
      testCase "common password rejected" $
        validatePassword basePolicy aliceCtx (PlainPassword "password123")
          @?= Left PasswordTooCommon,
      testCase "email local-part rejected" $
        validatePassword basePolicy aliceCtx (PlainPassword "alice")
          @?= Left PasswordResemblesIdentity,
      testCase "full email rejected" $
        validatePassword basePolicy aliceCtx (PlainPassword "alice@example.com")
          @?= Left PasswordResemblesIdentity,
      testCase "display name rejected" $
        validatePassword basePolicy aliceCtx (PlainPassword "Alice")
          @?= Left PasswordResemblesIdentity,
      testCase "strong unrelated password accepted" $
        validatePassword basePolicy aliceCtx (PlainPassword "correct horse battery staple")
          @?= Right (),
      testCase "flags off let common and contextual through" $ do
        let off = basePolicy {rejectCommonPasswords = False, rejectContextualPasswords = False}
        validatePassword off aliceCtx (PlainPassword "password123") @?= Right ()
        validatePassword off aliceCtx (PlainPassword "alice@example.com") @?= Right ()
    ]