packages feed

codec-rpm-0.1.0: tests/Codec/RPM/ParseSpec.hs

module Codec.RPM.ParseSpec (spec) where

import Test.Hspec
import Test.Hspec.Attoparsec
import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as BC


import Codec.RPM.Parse
import Codec.RPM.Tags
import Codec.RPM.Types

spec :: Spec
spec = describe "Codec.RPM.Parse" $ do
  describe "parseLead" $ do
    it "fails with wrong file signature" $ do
      let stream = BS.pack [
            0xFF, 0xFF, 0xFF, 0xFF, -- *WRONG* File signature
            8, -- Major
            9, -- Minor
            0, 1, -- Type
            0, 2,  -- ArchNum
            65, 66, 67, 68, 69, 70, -- name 66 bytes ABCDEF
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 1, -- OS num
            0, 4, -- Sig type
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] -- 16 bytes padding
      parseLead `shouldFailOn` stream
      -- NOTE: http://alpmestan.com/posts/2014-06-18-testing-attoparsec-parsers-with-hspec.html
      -- Right now, hspec-attoparsec will only consider leftovers when the parser succeeds.
      -- I’m not really sure whether we should return Fail’s unconsumed input or not.
      --
      -- We can't use `leavesUnconsumed` when the parser fails
      -- so the following assertion is invalid for now!
      -- stream ~?> parseLead `leavesUnconsumed` BS.pack []

    it "fails when name < 66 chars" $ do
      let stream = BS.pack [
            0xED, 0xAB, 0xEE, 0xDB, -- File signature
            8, -- Major
            9, -- Minor
            0, 1, -- Type
            0, 1,  -- ArchNum
            65, 66, 67, 68, 69, 70, -- name 26 instead of 66 bytes
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 1, -- OS num
            0, 4, -- Sig type
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] -- 16 bytes padding

      parseLead `shouldFailOn` stream

    it "fails when padding < 16 bytes" $ do
      let stream = BS.pack [
            0xED, 0xAB, 0xEE, 0xDB, -- File signature
            8, -- Major
            9, -- Minor
            0, 1, -- Type
            0, 1,  -- ArchNum
            65, 66, 67, 68, 69, 70, -- name 66 bytes
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 1, -- OS num
            0, 4, -- Sig type
            0, 0, 0, 0, 0, 0] -- 6 instead of 16 bytes padding

      parseLead `shouldFailOn` stream

    it "succeeds with valid data" $ do
      let stream = BS.pack [
            0xED, 0xAB, 0xEE, 0xDB, -- File signature
            8, -- Major
            9, -- Minor
            0, 1, -- Type
            0, 2,  -- ArchNum
            65, 66, 67, 68, 69, 70, -- name 66 bytes ABCDEF
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 1, -- OS num
            0, 4, -- Sig type
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] -- 16 bytes padding
      let expected = Lead 8 9 1 2 "ABCDEF" 1 4

      -- parsing succeeds
      parseLead `shouldSucceedOn` stream
      -- no unconsumed input
      stream ~?> parseLead `leavesUnconsumed` BS.pack []
      -- result is as expected
      stream ~> parseLead `parseSatisfies` (==expected)

  describe "parseSectionHeader" $ do
    it "fails with invalid section header signature" $ do
      let stream = BS.pack [
            0xFF, 0xFF, 0xFF, -- *WRONG* section header signature
            8, -- sectionVersion
            0, 0, 0, 0, -- 4 reserved bytes
            255, 255, 255, 255, -- sectionCount 4 bytes
            0, 0, 255, 255] -- sectionSize 4 bytes

      parseSectionHeader `shouldFailOn` stream

    it "fails when reserved section < 4 bytes" $ do
      let stream = BS.pack [
            0x8E, 0xAD, 0xE8, -- section header signature
            8, -- sectionVersion
            0, 0, -- *2 instead of* 4 reserved bytes
            255, 255, 255, 255, -- sectionCount 4 bytes
            0, 0, 255, 255] -- sectionSize 4 bytes

      parseSectionHeader `shouldFailOn` stream

    it "succeeds with valid data" $ do
      let stream = BS.pack [
            0x8E, 0xAD, 0xE8, -- section header signature
            8, -- sectionVersion
            0, 0, 0, 0, -- 4 reserved bytes
            255, 255, 255, 255, -- sectionCount 4 bytes
            0, 0, 255, 255] -- sectionSize 4 bytes
      let expected = SectionHeader 8 4294967295 65535

      -- parsing succeeds
      parseSectionHeader `shouldSucceedOn` stream
      -- no unconsumed input
      stream ~?> parseSectionHeader `leavesUnconsumed` BS.pack []
      -- result is as expected
      stream ~> parseSectionHeader `parseSatisfies` (==expected)

  describe "parseOneTag" $ do
    it "returns Nothing when any of the input streams is empty" $ do
      parseOneTag (BS.pack []) (BS.pack [])              `shouldBe` Nothing
      parseOneTag (BS.pack [1, 2, 3, 4, 5]) (BS.pack []) `shouldBe` Nothing
      parseOneTag (BS.pack []) (BS.pack [100, 8, 0, 7])  `shouldBe` Nothing

    it "returns valid Tag when both input streams are valid" $ do
      let store = BC.pack "123-test-me"
      let bs = BS.pack [0, 0, 0, 100, -- tag
                        0, 0, 0, 8, -- ty
                        0, 0, 0, 4, -- offset
                        0, 0, 0, 7 -- count
                       ]
      parseOneTag store bs `shouldBe` Just (HeaderI18NTable ["test-me"])

  describe "parseSection" $
    it "succeeds with valid data" $ do
      let stream = BS.pack [
            0x8E, 0xAD, 0xE8, -- section header signature
            1, -- sectionVersion
            0, 0, 0, 0, -- 4 reserved bytes
            0, 0, 0, 7, -- sectionCount 4 bytes
            0, 0, 0, 0xE4, -- sectionSize 4 bytes

            -- tags defined in this section, sectionCount * 16 bytes
            -- 62 7 212 16 == HeaderSignatures (Null), ty(7) /= 0, returns Nothing
            0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0xd4, 0x00, 0x00, 0x00, 0x10,
            -- 267 7 0 72 == DSAHeader (binary)
            0x00, 0x00, 0x01, 0x0b, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48,
            -- 269 6 72 1 == SHA1Header (string)
            0x00, 0x00, 0x01, 0x0d, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01,
            -- 1000 4 116 1 == Name (string), ty(4) /= 6, returns Nothing
            0x00, 0x00, 0x03, 0xe8, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x74, 0x00, 0x00, 0x00, 0x01,
            -- 1004 7 120 16 == Summary (i18n string), ty(7) /= 9, returns Nothing
            0x00, 0x00, 0x03, 0xec, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x10,
            -- 1005 7 136 72 == Description (i18n string), ty(7) /= 9, returns Nothing
            0x00, 0x00, 0x03, 0xed, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0x48,
            -- 1007 4 208 1 == BuildHost (string), ty(4) /= 6, returns Nothing
            0x00, 0x00, 0x03, 0xef, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xd0, 0x00, 0x00, 0x00, 0x01,

            -- section payload (228 bytes in this example)
            -- DSAHeader
            0x88, 0x46, 0x04, 0x00, 0x11, 0x02, 0x00, 0x06, 0x05, 0x02, 0x53, 0x67, 0xab, 0x9d, 0x00, 0x0a,
            0x09, 0x10, 0x50, 0x8c, 0xe5, 0xe6, 0x66, 0x53, 0x4c, 0x2b, 0x6b, 0x83, 0x00, 0xa0, 0x9e, 0x1c,
            0x4e, 0x19, 0xd5, 0x78, 0x37, 0x53, 0x61, 0x8a, 0x34, 0x4b, 0x40, 0x91, 0xfb, 0xc1, 0x24, 0xbb,
            0x1c, 0x62, 0x00, 0x9f, 0x50, 0xe6, 0x5c, 0x34, 0x72, 0xa6, 0x54, 0x70, 0x45, 0xce, 0xe9, 0xec,
            0x02, 0x6b, 0x98, 0xfa, 0x45, 0x72, 0x8f, 0xca, -- SHA1Header
                                                            0x66, 0x36, 0x37, 0x35, 0x64, 0x37, 0x39, 0x62,
            0x66, 0x66, 0x33, 0x34, 0x34, 0x66, 0x36, 0x63, 0x63, 0x63, 0x32, 0x64, 0x34, 0x65, 0x37, 0x31,
            0x66, 0x66, 0x62, 0x62, 0x38, 0x61, 0x39, 0x63, 0x36, 0x38, 0x39, 0x62, 0x61, 0x64, 0x65, 0x63,
            0x00, 0x00, 0x00, 0x00, -- Name
                                    0x01, 0x2b, 0x00, 0xf6, -- Summary
                                                            0x00, 0x7c, 0xa1, 0x57, 0x31, 0x3e, 0x1f, 0x20,
            0x34, 0x4f, 0xf7, 0x1e, 0xc9, 0xbb, 0xd7, 0xdc, -- Description
                                                            0x88, 0x46, 0x04, 0x00, 0x11, 0x02, 0x00, 0x06,
            0x05, 0x02, 0x53, 0x67, 0xab, 0x9d, 0x00, 0x0a, 0x09, 0x10, 0x50, 0x8c, 0xe5, 0xe6, 0x66, 0x53,
            0x4c, 0x2b, 0x6f, 0xdf, 0x00, 0x9d, 0x13, 0x63, 0xe0, 0x2f, 0xed, 0x88, 0x8b, 0x27, 0xad, 0x46,
            0x23, 0x26, 0xb7, 0xa8, 0xda, 0xb7, 0xea, 0x64, 0x71, 0x88, 0x00, 0xa0, 0x80, 0x31, 0xf5, 0x32,
            0x25, 0x81, 0xf9, 0xce, 0xe1, 0x63, 0x15, 0x15, 0x9d, 0xe3, 0x74, 0xc1, 0x23, 0xa8, 0xba, 0xaf,
            -- BuildHost
            0x01, 0x2a, 0xeb, 0x70, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0x90,
            0x00, 0x00, 0x00, 0x00] -- followed by 4 bytes of signature padding before the next section

      -- parsing succeeds
      parseSection `shouldSucceedOn` stream
      -- no unconsumed input
      stream ~?> parseSection `leavesUnconsumed` BS.pack []

      -- verify the result matches expected
      stream ~> parseSection `parseSatisfies` matchExpected
        where
          matchExpected h = do
            let expSH = SectionHeader 1 7 228
            let expT = [
                        DSAHeader (BS.pack [
                            0x88, 0x46, 0x04, 0x00, 0x11, 0x02, 0x00, 0x06, 0x05, 0x02, 0x53, 0x67, 0xab, 0x9d, 0x00, 0x0a,
                            0x09, 0x10, 0x50, 0x8c, 0xe5, 0xe6, 0x66, 0x53, 0x4c, 0x2b, 0x6b, 0x83, 0x00, 0xa0, 0x9e, 0x1c,
                            0x4e, 0x19, 0xd5, 0x78, 0x37, 0x53, 0x61, 0x8a, 0x34, 0x4b, 0x40, 0x91, 0xfb, 0xc1, 0x24, 0xbb,
                            0x1c, 0x62, 0x00, 0x9f, 0x50, 0xe6, 0x5c, 0x34, 0x72, 0xa6, 0x54, 0x70, 0x45, 0xce, 0xe9, 0xec,
                            0x02, 0x6b, 0x98, 0xfa, 0x45, 0x72, 0x8f, 0xca]),
                        SHA1Header "f675d79bff344f6ccc2d4e71ffbb8a9c689badec"
                        ]
            headerSectionHeader h == expSH && headerTags h == expT && BS.length (headerStore h) == 228