packages feed

xrefcheck-0.3.0: tests/Test/Xrefcheck/ConfigSpec.hs

{- SPDX-FileCopyrightText: 2019-2021 Serokell <https://serokell.io>
 -
 - SPDX-License-Identifier: MPL-2.0
 -}

module Test.Xrefcheck.ConfigSpec where

import Universum hiding ((.~))

import Control.Concurrent (forkIO, killThread)
import Control.Exception qualified as E
import Control.Lens ((.~))

import Data.List (isInfixOf)
import Data.Yaml (ParseException (..), decodeEither')
import Network.HTTP.Types (Status (..))
import Test.Tasty (TestTree, askOption, testGroup)
import Test.Tasty.HUnit (assertFailure, testCase, (@?=))
import Test.Tasty.QuickCheck (ioProperty, testProperty)

import Xrefcheck.Config
import Xrefcheck.Core (Flavor (GitHub), allFlavors)
import Xrefcheck.Scan (ecIgnoreExternalRefsToL)
import Xrefcheck.Verify (VerifyError (..), checkExternalResource)

import Test.Xrefcheck.Util (mockServer, mockServerUrl)

test_config :: [TestTree]
test_config =
  [ testGroup "Default config is valid" [
      testProperty (show flavor) $
        ioProperty $ evaluateWHNF_ @_ @Config (defConfig flavor)
        | flavor <- allFlavors]
  , testGroup "Filled default config matches the expected format"
    -- The config we match against can be regenerated with
    -- stack exec xrefcheck -- dump-config -t GitHub -o tests/configs/github-config.yaml --force
      [ testCase "Config matches" $ do
        config <- readFile "tests/configs/github-config.yaml"
        when (config /= defConfigText GitHub) $
          assertFailure $ toString $ unwords
            [ "Config does not match the expected format."
            , "Run"
            , "`stack exec xrefcheck -- dump-config -t GitHub -o tests/configs/github-config.yaml --force`"
            , "and verify changes"
            ]
      ]
  , askOption $ \mockServerPort ->
    testGroup "`ignoreAuthFailures` working as expected" $
    let config = defConfig GitHub & cExclusionsL . ecIgnoreExternalRefsToL .~ []

        setIgnoreAuthFailures value =
          config & cNetworkingL . ncIgnoreAuthFailuresL .~ value
    in [ testCase "when True - assume 401 status is valid" $
          checkLinkWithServer mockServerPort (setIgnoreAuthFailures True)
            "/401" $ Right ()

       , testCase "when False - assume 401 status is invalid" $
          checkLinkWithServer mockServerPort (setIgnoreAuthFailures False)
            "/401" $
                Left $ ExternalHttpResourceUnavailable $
                  Status { statusCode = 401, statusMessage = "Unauthorized" }

       , testCase "when True - assume 403 status is valid" $
          checkLinkWithServer mockServerPort (setIgnoreAuthFailures True)
            "/403" $ Right ()

       , testCase "when False - assume 403 status is invalid" $
          checkLinkWithServer mockServerPort (setIgnoreAuthFailures False)
            "/403" $
                Left $ ExternalHttpResourceUnavailable $
                  Status { statusCode = 403, statusMessage = "Forbidden" }
       ]
  , testGroup "Config parser reject input with unknown fields"
      [ testCase "throws error with useful messages" $ do
          case decodeEither' @Config $ encodeUtf8 $ defConfigText GitHub <> "strangeField: []" of
            Left (AesonException str) ->
              if "unknown fields: [\"strangeField\"]" `isInfixOf` str
              then pure ()
              else assertFailure $ "Bad error message: " <> str
            _ -> assertFailure "Config parser accepted config with unknown field"
      ]
  ]

  where
    checkLinkWithServer mockServerPort config link expectation =
      E.bracket (forkIO (mockServer mockServerPort)) killThread $ \_ -> do
        let url = mockServerUrl mockServerPort link
        result <- runExceptT $ checkExternalResource emptyChain config url
        result @?= expectation