File: Tests.hs

package info (click to toggle)
haskell-bsb-http-chunked 0.0.0.4-6
  • links: PTS
  • area: main
  • in suites: forky, sid, trixie
  • size: 96 kB
  • sloc: haskell: 260; makefile: 5
file content (108 lines) | stat: -rw-r--r-- 3,569 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
{-# language OverloadedStrings, MultiWayIf #-}
module Main where

import qualified Data.ByteString.Builder as B

import Control.Applicative
import Data.Attoparsec.ByteString.Char8 (Parser, (<?>))
import qualified Data.Attoparsec.ByteString.Char8 as A
import qualified Data.Attoparsec.ByteString.Lazy as AL
import qualified Blaze.ByteString.Builder.HTTP as Blaze
import Data.ByteString (ByteString)
import qualified Data.ByteString as S
import Data.ByteString.Builder.HTTP.Chunked
import qualified Data.ByteString.Lazy as L
import Data.Functor
import Data.Maybe

import Hedgehog
import qualified Hedgehog.Gen as Gen
import qualified Hedgehog.Range as Range
import Test.Tasty
import Test.Tasty.Hedgehog
import Test.Tasty.HUnit (testCase, (@?=))

main :: IO ()
main = defaultMain $ testGroup "Tests" [properties, unitTests]

chunkedTransferEncodingL :: L.ByteString -> L.ByteString
chunkedTransferEncodingL = B.toLazyByteString . chunkedTransferEncoding . B.lazyByteString

chunkedTransferEncodingLBlaze :: L.ByteString -> L.ByteString
chunkedTransferEncodingLBlaze = B.toLazyByteString . Blaze.chunkedTransferEncoding . B.lazyByteString

properties :: TestTree
properties = testGroup "Properties"
  [ p "Encoding and parsing roundtrips" $ do
      lbs <- forAll genLS
      tripping lbs
               chunkedTransferEncodingL
               parseTransferChunks
    -- This is about detecting differences in output,
    -- not about bug-to-bug compatibility.
  , p "Identical output as Blaze" $ do
      lbs <- forAll genLS
      chunkedTransferEncodingL lbs === chunkedTransferEncodingLBlaze lbs
  ]
  where
    p name = testProperty name . property

genLS :: Gen L.ByteString
genLS = L.fromChunks <$> genSs

genSs :: Gen [ByteString]
genSs = Gen.list (Range.linear 0 100) genSnippedS

genSnippedS :: Gen ByteString
genSnippedS = do
  d <- genOffSet
  e <- genOffSet
  S.drop d . dropEnd e <$> genPackedS
  where
    genOffSet = Gen.int (Range.linear 0 100)
    dropEnd n bs = S.take m bs
      where m = S.length bs - n

genPackedS :: Gen ByteString
genPackedS =
  S.replicate
  <$> Gen.int (Range.linear 0 mAX_CHUNK_SIZE)
  <*> Gen.word8 (Range.constantFrom 95 minBound maxBound)

parseTransferChunks :: L.ByteString -> Either String L.ByteString
parseTransferChunks = AL.eitherResult .
                      fmap (L.fromChunks . catMaybes) .
                      AL.parse (many transferChunkParser)

-- Adapted from snap-server
transferChunkParser :: Parser (Maybe ByteString)
transferChunkParser = parser <?> "encodedChunkParser"
  where
    parser = do
        hex <- A.hexadecimal <?> "hexadecimal"
        -- skipWhile (/= '\r') <?> "skipToEOL" -- We don't add chunk extensions
        void crlf <?> "linefeed"
        if | hex > mAX_CHUNK_SIZE
            -> fail $ "Chunk of size " ++ show hex ++
                 " is too long. Max chunk size is " ++ show mAX_CHUNK_SIZE
           | hex < 0
             -> fail $ "Negative chunk size: " ++ show hex
           | hex == 0
             -> (crlf >> return Nothing) <?> "terminal crlf after 0 length"
           | otherwise
             -> do
                x <- A.take hex <?> "reading data chunk"
                void crlf <?> "linefeed after data chunk"
                return $! Just x

    crlf = A.string "\r\n"

-- Chunks larger than this may indicate denial-of-service attack.
mAX_CHUNK_SIZE :: Int
mAX_CHUNK_SIZE = 256 * 1024 - 1

unitTests :: TestTree
unitTests = testGroup "Unit tests"
  [ testCase "Encoding an empty builder returns an empty builder" $
      chunkedTransferEncodingL "" @?= ""
  ]