packages feed

irc-dcc-2.0.0: test/Network/IRC/DCCTest.hs

{-# LANGUAGE TemplateHaskell #-}

module Network.IRC.DCCTest where

import           Network.IRC.DCC.Internal

import           Control.Monad            (replicateM)
import           Data.ByteString.Char8    (pack)
import           Data.IP                  (IPv4, toIPv4)
import           Network.Socket           (PortNumber)
import           Path                     (mkAbsFile, mkRelFile)
import           Test.Hspec.Attoparsec
import           Test.Tasty               (TestTree)
import           Test.Tasty.Hspec         (describe, it, testSpec)
import           Test.Tasty.QuickCheck    (Arbitrary (..), Gen, choose,
                                           elements, forAll, oneof, property)

instance Arbitrary Token where
  arbitrary = Token . pack <$> arbitrary

instance Arbitrary PortNumber where
  arbitrary = fromIntegral <$> (choose (1, 65535) :: Gen Int)

instance Arbitrary IPv4 where
  arbitrary = toIPv4 <$> replicateM 4 (choose (0, 255))

instance Arbitrary Path where
  arbitrary = elements [ Rel Simple $(mkRelFile "filename")
                       , Rel Simple $(mkRelFile "filename.txt")
                       , Rel Simple $(mkRelFile "file\8195name.txt")
                       , Rel Simple $(mkRelFile "\128110\127997")
                       , Abs Simple $(mkAbsFile "/home/user/dirname/filename.txt")
                       , Rel Quoted $(mkRelFile "filename.txt")
                       , Rel Quoted $(mkRelFile "file name.txt")
                       , Rel Quoted $(mkRelFile "file\8195 name.txt")
                       , Rel Quoted $(mkRelFile "\128110\127997")
                       , Abs Quoted $(mkAbsFile "/home/user/dir name/filename.txt")
                       ]

instance Arbitrary FileOffset where
  arbitrary = FileOffset <$> arbitrary

instance Arbitrary DccChat where
  arbitrary = oneof [ Chat <$> arbitrary <*> arbitrary
                    , ChatWhiteboard <$> arbitrary <*> arbitrary
                    ]

instance Arbitrary DccSend where
  arbitrary = oneof [ Send <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
                    , SendReverseServer <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
                    ]

instance Arbitrary DccResume where
  arbitrary = oneof [ Resume <$> arbitrary <*> arbitrary <*> arbitrary
                    , ResumeReverse <$> arbitrary <*> arbitrary <*> arbitrary
                    ]

instance Arbitrary DccAccept where
  arbitrary = oneof [ Accept <$> arbitrary <*> arbitrary <*> arbitrary
                    , AcceptReverse <$> arbitrary <*> arbitrary <*> arbitrary
                    ]

instance Arbitrary DccSendReverseClient where
  arbitrary = SendReverseClient
      <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary

spec :: IO TestTree
spec = testSpec "DCC message serialization" $ do

  describe "Parsing single elements" $ do

    describe "Network byte order IP" $ do
      describe "[SUCCESS]" $ do
        it "parse . encode == id" $ property $ \ip ->
          ipToBigEndianBS ip ~> ipBigEndian `parseSatisfies` (== ip)
        it "At beginning of stream" $
          pack "0abcd" ~?> ipBigEndian
              `leavesUnconsumed` pack "abcd"
      describe "[FAILURE]" $ do
        it "Parse < 0.0.0.0" $
          forAll (choose (minBound, -1 :: Int)) $ \ip ->
             ipBigEndian `shouldFailOn` pack (show ip)
        it "Parse > 255.255.255.255" $
          forAll (choose (4294967296, maxBound :: Int)) $ \ip ->
             ipBigEndian `shouldFailOn` pack (show ip)
        it "Non-digits" $
          ipBigEndian `shouldFailOn` pack "abcd"
        it "When not at beginning of stream" $
          ipBigEndian `shouldFailOn` pack " 0"

    describe "File name" $ do
      describe "[SUCCESS]" $ do
        it "parse . encode == id" $ property $ \name ->
          pathToBS name ~> path `parseSatisfies` (== name)
        it "At beginning of stream" $
          pack "filename 122350" ~?> path
            `leavesUnconsumed` pack " 122350"
-- TODO Not sure exactly what to do with this yet. On Unix the whole thing
--      could be a file name, while on windows it is obviously an absolute path
--                 it "File name from absolute unix path" $
--                     ("c:\\Users\\user\\filename.txt" ~> path
--                         `shouldParse` $(mkRelFile "filename.txt")
      describe "[FAILURE]" $ do
        it "ASCII filename with space" $
          pack "file name.txt" ~?> path
            `leavesUnconsumed` pack " name.txt"
        it "Not at beginning of stream" $
          path `shouldFailOn` pack " filename"

    describe "Token" $
      describe "[SUCCESS]" $
        it "parse . encode == id" $ property $ \t ->
          tokenToBS t ~> token `parseSatisfies` (== t)

    describe "TCP port" $ do
      describe "[SUCCESS]" $
        it "parse . encode == id" $ property $ \p ->
          tcpPortToBS p ~> tcpPort `parseSatisfies` (== p)
      describe "[FAILURE]" $ do
        it "Parse <= 0" $
          forAll (choose (minBound, 0 :: Int)) $ \p ->
             tcpPort `shouldFailOn` pack (show p)
        it "Parse > 65535" $
          forAll (choose (65535, maxBound :: Int)) $ \p ->
             tcpPort `shouldFailOn` pack (show p)

    describe "File offset" $ do
      describe "[SUCCESS]" $
        it "parse . encode == id" $ property $ \o ->
          fileOffsetToBS o ~> fileOffset `parseSatisfies` (== o)
      describe "[FAILURE]" $
        it "Parse < 0" $
          forAll (choose (minBound, -1 :: Int)) $ \o ->
             fileOffset `shouldFailOn` pack (show o)

  describe "DCC commands" $ do

    describe "DCC CHAT" $
      describe "[SUCCESS]" $
        it "fromCtcp . toCtcp == id" $ property $ \cmd ->
          fromCtcp (toCtcp cmd) == Right (cmd :: DccChat)

    describe "DCC CLOSE" $
      describe "[SUCCESS]" $
        it "fromCtcp . toCtcp == id" $
          fromCtcp (toCtcp Close) == Right Close

    describe "DCC SEND" $
      describe "[SUCCESS]" $
        it "fromCtcp . toCtcp == id" $ property $ \cmd ->
          fromCtcp (toCtcp cmd) == Right (cmd :: DccSend)

    describe "DCC RESUME" $
      describe "[SUCCESS]" $
        it "fromCtcp . toCtcp == id" $ property $ \cmd ->
          fromCtcp (toCtcp cmd) == Right (cmd :: DccResume)

    describe "DCC ACCEPT" $
      describe "[SUCCESS]" $
        it "fromCtcp . toCtcp == id" $ property $ \cmd ->
          fromCtcp (toCtcp cmd) == Right (cmd :: DccAccept)

    describe "DCC SEND (reverse client)" $
      describe "[SUCCESS]" $
        it "fromCtcp . toCtcp == id" $ property $ \cmd ->
          fromCtcp (toCtcp cmd) == Right (cmd :: DccSendReverseClient)