packages feed

hnix-store-core-0.6.0.0: tests/Hash.hs

{-# language DataKinds           #-}
{-# language ScopedTypeVariables #-}
{-# language CPP #-}

module Hash where

import qualified Data.ByteString.Base16      as B16
import qualified System.Nix.Base32           as B32
import qualified Data.ByteString.Base64.Lazy as B64

import           Test.Hspec
import           Test.Tasty.QuickCheck

import           System.Nix.Hash
import           System.Nix.StorePath
import           Arbitrary
import           System.Nix.Internal.Base
import           Crypto.Hash                ( MD5
                                            , SHA1
                                            , SHA256
                                            , hash
                                            , Digest
                                            )

spec_hash :: Spec
spec_hash = do

  describe "hashing parity with nix-store" $ do

    cmp "produces (base32 . sha256) of \"nix-output:foo\" the same as Nix does at the moment for placeholder \"foo\""
      NixBase32 (hash @ByteString @SHA256) "nix-output:foo" "1x0ymrsy7yr7i9wdsqy9khmzc1yy7nvxw6rdp72yzn50285s67j5"
    cmp "produces (base16 . md5) of \"Hello World\" the same as the thesis"
      Base16 (hash @ByteString @MD5) "Hello World" "b10a8db164e0754105b7a99be72e3fe5"
    cmp "produces (base32 . sha1) of \"Hello World\" the same as the thesis"
      NixBase32 (hash @ByteString @SHA1) "Hello World" "s23c9fs0v32pf6bhmcph5rbqsyl5ak8a"

    -- The example in question:
    -- https://nixos.org/nixos/nix-pills/nix-store-paths.html
    it "produces same base32 as nix pill flat file example" $ do
      shouldBe (encodeWith NixBase32 $ coerce $ mkStorePathHashPart "source:sha256:2bfef67de873c54551d884fdab3055d84d573e654efa79db3c0d7b98883f9ee3:/nix/store:myfile")
        "xv2iccirbrvklck36f1g7vldn5v58vck"
 where
  cmp :: String -> BaseEncoding -> (ByteString -> Digest a) -> ByteString -> Text -> SpecWith ()
  cmp t b f s h =
    it t $
      shouldBe (encodeDigestWith b $ f s) h

-- | Test that Nix-like base32 encoding roundtrips
prop_nixBase32Roundtrip :: Property
prop_nixBase32Roundtrip = forAllShrink nonEmptyString genericShrink $
  \x -> pure (encodeUtf8 x) === (B32.decode . B32.encode . encodeUtf8 $ x)

-- | API variants
prop_nixBase16Roundtrip :: StorePathHashPart -> Property
prop_nixBase16Roundtrip x = pure (coerce x) === decodeWith Base16 (encodeWith Base16 $ coerce x)

-- | Hash encoding conversion ground-truth.
-- Similiar to nix/tests/hash.sh
spec_nixhash :: Spec
spec_nixhash = do

  describe "hashing parity with nix-nash" $ do

    cmp
      "b16 encoded . b32 decoded should equal original b16"
      B16.encode B32.decode b32s b16s

    cmp
      "b64 encoded . b32 decoded should equal original b64"
      (B64.encode . fromStrict) B32.decode b32s b64s

    cmp
      "b32 encoded . b64 decoded should equal original b32"
      (B32.encode . toStrict) B64.decode b64s b32s

    cmp
      "b16 encoded . b64 decoded should equal original b16"
      (B16.encode . toStrict) B64.decode b64s b16s

#if MIN_VERSION_base16_bytestring(1,0,0)
    cmp
      "b32 encoded . b16 decoded should equal original b32"
      B32.encode B16.decode b16s b32s

    cmp
      "b64 encoded . b16 decoded should equal original b64"
      (B64.encode . fromStrict) B16.decode b16s b64s
#else
    it "b32 encoded . b16 decoded should equal original b32" $
      traverse_ (\ b -> shouldBe (B32.encode $ fst $ B16.decode $ fst b) (snd b)) $ zip b16s b32s

    it "b64 encoded . b16 decoded should equal original b64" $
      traverse_ (\ b -> shouldBe (B64.encode . fromStrict $ fst $ B16.decode $ fst b) (snd b)) $ zip b16s b64s
#endif

 where
  cmp
    :: ( Eq b
       , Show b
       )
    => String
    -> (a -> b)
    -> (c -> Either String a)
    -> [c]
    -> [b]
    -> SpecWith ()
  cmp s f1 f2 b1 b2 = it s $ traverse_ (uncurry shouldBe . bimap (fmap f1 . f2) pure) $ zip b1 b2

  b16s = takeAxis (\(a,_,_) -> a)
  b32s = takeAxis (\(_,b,_) -> b)
  b64s = takeAxis (\(_,_,c) -> c)

  takeAxis f = fmap f samples

  samples =
    [ ( "800d59cfcd3c05e900cb4e214be48f6b886a08df"
      , "vw46m23bizj4n8afrc0fj19wrp7mj3c0"
      , "gA1Zz808BekAy04hS+SPa4hqCN8="
      )
    , ( "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
      , "1b8m03r63zqhnjf7l5wnldhh7c134ap5vpj0850ymkq1iyzicy5s"
      , "ungWv48Bz+pBQUDeXa4iI7ADYaOWF3qctBD/YfIAFa0="
      )
    , ( "204a8fc6dda82f0a0ced7beb8e08a41657c16ef468b228a8279be331a703c33596fd15c13b1b07f9aa1d3bea57789ca031ad85c7a71dd70354ec631238ca3445"
      , "12k9jiq29iyqm03swfsgiw5mlqs173qazm3n7daz43infy12pyrcdf30fkk3qwv4yl2ick8yipc2mqnlh48xsvvxl60lbx8vp38yji0"
      , "IEqPxt2oLwoM7XvrjgikFlfBbvRosiioJ5vjMacDwzWW/RXBOxsH+aodO+pXeJygMa2Fx6cd1wNU7GMSOMo0RQ=="
      )
    ]