packages feed

swarm-0.6.0.0: test/tournament-host/Main.hs

{-# LANGUAGE OverloadedStrings #-}

-- |
-- SPDX-License-Identifier: BSD-3-Clause
--
-- Tournament hosting
module Main where

import Data.ByteString.Lazy qualified as LBS
import Data.Digest.Pure.SHA (sha1, showDigest)
import Data.List.NonEmpty qualified as NE
import Data.Map.NonEmpty (NEMap)
import Data.Map.NonEmpty qualified as NEM
import Data.String (fromString)
import Network.HTTP.Client hiding (Proxy)
import Network.HTTP.Client.MultipartFormData
import Network.HTTP.Types (ok200)
import Network.Wai.Handler.Warp (testWithApplication)
import Swarm.Game.State (Sha1 (..))
import Swarm.Web.Tournament qualified as Tournament
import Swarm.Web.Tournament.Database.Query
import Swarm.Web.Tournament.Type (UserAlias (..))
import Test.Tasty (defaultMain, testGroup)
import Test.Tasty.HUnit (Assertion, assertEqual, testCase)

main :: IO ()
main = do
  scenariosMap <- buildScenariosMap $ pure "data/scenarios/Challenges/arbitrage.yaml"
  let appData = mkAppData scenariosMap
  defaultMain $
    testGroup
      "Test database-agnostic server interactions"
      [ testCase "Test scenario upload" $ testScenarioUpload scenariosMap appData
      , testCase "Test solution upload" $ testSolutionUpload scenariosMap appData
      ]
 where
  noPersistence =
    ScenarioPersistence
      { lookupCache = const $ return Nothing
      , storeCache = const $ return $ Sha1 "bogus"
      , getContent = const $ return Nothing
      }

  mkPersistenceLayer scenariosMap =
    PersistenceLayer
      { scenarioStorage =
          noPersistence
            { getContent = return . fmap content . (`NEM.lookup` scenariosMap)
            }
      , solutionStorage = noPersistence
      , authenticationStorage =
          AuthenticationStorage
            { usernameFromCookie = const $ return $ Just fakeUser
            , cookieFromUsername = const $ return "fake-cookie-value"
            }
      }

  fakeUser = UserAlias "test-user"

  mkAppData scenariosMap =
    Tournament.AppData
      { Tournament.swarmGameGitVersion = Sha1 "abcdef"
      , Tournament.gitHubCredentials = Tournament.GitHubCredentials "" ""
      , Tournament.persistence = mkPersistenceLayer scenariosMap
      , Tournament.developmentMode = Tournament.LocalDevelopment fakeUser
      }

type LocalFileLookup = NEMap Sha1 FilePathAndContent

data FilePathAndContent = FilePathAndContent
  { filePath :: FilePath
  , content :: LBS.ByteString
  }

buildScenariosMap :: NE.NonEmpty FilePath -> IO LocalFileLookup
buildScenariosMap pathList =
  NEM.fromList <$> traverse getWithHash pathList
 where
  getWithHash fp = do
    fileContent <- LBS.readFile fp
    let h = Sha1 $ showDigest $ sha1 fileContent
    return (h, FilePathAndContent fp fileContent)

testScenarioUpload :: LocalFileLookup -> Tournament.AppData -> Assertion
testScenarioUpload fileLookup appData =
  mapM_ f testScenarioPaths
 where
  f x = uploadForm appData "/api/private/upload/scenario" [partFileSource "file" x]
  testScenarioPaths = map filePath $ NE.toList $ NEM.elems fileLookup

testSolutionUpload :: LocalFileLookup -> Tournament.AppData -> Assertion
testSolutionUpload fileLookup appData =
  uploadForm appData "/api/private/upload/solution" form
 where
  solutionFilePath = "data/scenarios/Challenges/_arbitrage/solution.sw"
  Sha1 scenarioSha1 = NE.head $ NEM.keys fileLookup
  form =
    [ partBS "scenario" $ fromString scenarioSha1
    , partFileSource "file" solutionFilePath
    ]

-- * Utils

uploadForm :: Tournament.AppData -> String -> [PartM IO] -> Assertion
uploadForm appData urlPath form =
  testWithApplication (pure tournamentApp) $ \p -> do
    manager <- newManager defaultManagerSettings

    let baseUrl = "http://localhost:" ++ show p
    reqLogin <- parseRequest $ baseUrl ++ "/api/private/login/local"
    respLogin <- httpLbs reqLogin manager

    let apiUrl = baseUrl ++ urlPath
    req <- parseRequest apiUrl
    resp <-
      flip httpLbs manager
        =<< formDataBody form (req {cookieJar = Just $ responseCookieJar respLogin})

    let assertionMsg =
          unwords
            [ "Server response from"
            , apiUrl
            , "should be 200;"
            , "'respLogin' was:"
            , show respLogin
            , "and 'resp' was:"
            , show resp
            ]
    assertEqual assertionMsg ok200 $ responseStatus resp
 where
  tournamentApp = Tournament.app True appData