packages feed

hup-0.3.0.3: test-hspec/Distribution/Hup/UploadSpec.hs

{-# LANGUAGE OverloadedStrings #-}

module Distribution.Hup.UploadSpec 
  (
    main
  , spec
  )
  where

import Control.Monad.IO.Class                 (liftIO)

import qualified Data.ByteString.Char8 as BS
import qualified Data.ByteString.Lazy.Char8 as LBS   --   pack
import Data.Maybe                             (fromJust)
import Data.Monoid                            ( (<>) )

import Network.HTTP.Client.MultipartFormData  (renderParts,webkitBoundary)
import Network.HTTP.Types as T                (statusCode,methodPost)
import Network.Wai.Test                       (simpleStatus,SResponse
                                              ,simpleBody, runSession)

import Test.Hspec
import Test.Hspec.Wai.Internal                (WaiSession,runWaiSession,
                                               getApp)
import Test.QuickCheck
import Test.QuickCheck.Monadic

import Distribution.Hup.Parse.Test            ( arbUpload )
import Distribution.Hup.Upload                (mkPart, bodyToByteString,
                                                getUploadUrl, Package(..),
                                                IsDocumentation(..),
                                                Upload(..), IsCandidate(..)
                                              )

import Distribution.Hup.Upload.MockWebApp (webApp)

import qualified Distribution.Hup.WebTest

import Network.Wai
import qualified Network.Wai.Test as Wai
import Network.HTTP.Types as HT

{-# ANN module ("HLint: ignore Redundant do"      :: String)  #-}

type ParsedTgz = Either String (IsDocumentation, Package)


-- `main` is here so that this module can be run from GHCi on its own.  It is
-- not needed for automatic spec discovery.
main :: IO ()
main =
  hspec spec

spec :: Spec
spec = do
  describe "testing with mocked requests" $
    describe "mocked requests" $
      context "when processed by a mock hackage server" $
        it "should go to the right web app path" $
          monadicIO $ do
            webApp' <- run webApp
            return $ httpMetadataRoundtripsOK' webApp'
  describe "testing with live HTTP requests" $
    -- this will be replaced with a stub unless the WEB_TESTS macro
    -- is defined.
    --
    -- 'webApp' is a very simple web application
    -- intended to mock some of the behaviour of a
    -- hackage server.
    Distribution.Hup.WebTest.liveTest webApp

httpMetadataRoundtripsOK' :: Application -> Property
httpMetadataRoundtripsOK' webApp =
  forAll arbUpload $ \upl -> httpMetadataRoundtripsOK webApp upl

httpMetadataRoundtripsOK
  :: Application -> Upload -> Expectation
httpMetadataRoundtripsOK webApp upl = do
  upl <- return $ upl { fileConts = Just "" }
  testRequest   <- buildTestRequest "" upl
  testResponse  <- sendTestRequest webApp testRequest

  let sentIsCand = isCandidate upl
      sentIsDoc  = uploadType  upl
      sentPkg    = package     upl

  let resStatus = T.statusCode $ simpleStatus testResponse
      resBody :: String
      resBody =   LBS.unpack $ simpleBody testResponse
      _unserialized :: (IsDocumentation, IsCandidate, ParsedTgz)
      _unserialized@(recdIsDoc1, recdIsCand, parsedTgz) = read resBody

  resStatus `shouldBe` 200
  sentIsCand `shouldBe` recdIsCand
  sentIsDoc  `shouldBe` recdIsDoc1
  parsedTgz `shouldBe` Right (sentIsDoc, sentPkg)




sendTestRequest ::
  Application -> (Request, LBS.ByteString) -> IO SResponse
sendTestRequest webApp req = do
  runWaiSession (sendRequestImproved req) webApp



testPut ::
  String -> LBS.ByteString -> (Request, LBS.ByteString)
testPut url conts =
  mkPut (BS.pack url) conts

testPost ::
  String
  -> FilePath -> LBS.ByteString -> IO (Request, LBS.ByteString)
testPost url fileName fileConts = do
  boundary <- webkitBoundary
  let part    = mkPart fileName fileConts
      headers = [("Content-Type",
                    "multipart/form-data; boundary=" <> boundary)]
  body <- bodyToByteString <$> renderParts boundary [part]
  return $ mkRequest T.methodPost (BS.pack url) headers body


-- Only call when fileConts has something in it.
buildTestRequest ::
  String -> Upload -> IO (Request, LBS.ByteString)
buildTestRequest serverUrl upl  = do
  let (Upload _ filePath fileConts uploadType _pkgType ) = upl
      url = getUploadUrl serverUrl upl
  fileConts <- return (fromJust fileConts)
  case uploadType of
      IsPackage ->
         testPost url filePath fileConts
      IsDocumentation ->
         return $ testPut url fileConts

-- expose more of hspec wai's 'request' internals

mkRequest ::
  Method -> BS.ByteString -> RequestHeaders -> LBS.ByteString -> (Request, LBS.ByteString)
mkRequest method path headers body =
  let req = defaultRequest {requestMethod = method, requestHeaders = headers}
  in (Wai.setPath req path, body)

mkPut ::
  BS.ByteString -> LBS.ByteString -> (Request, LBS.ByteString)
mkPut path body = mkRequest methodPut path [] body

sendRequestImproved :: (Request, LBS.ByteString) -> WaiSession SResponse
sendRequestImproved (req, body) = getApp >>= liftIO . runSession (Wai.srequest $ Wai.SRequest req body)