content: {-# LANGUAGE QuasiQuotes #-}
  
  module NixConfSpec where
  
  import Cachix.Client.Exception (CachixException (CircularInclude))
  import Cachix.Client.NixConf as NixConf
  import Cachix.Types.BinaryCache (BinaryCache (..), CompressionMethod (..))
  import Cachix.Types.Permission (Permission (..))
  import Data.String.Here
  import Protolude
  import System.FilePath ((</>))
  import System.IO.Temp (withTempDirectory)
  import Test.Hspec
  
  property :: Text -> Expectation
  property x = NixConf.render <$> parse x `shouldBe` Right x
  
  bc :: BinaryCache
  bc =
    BinaryCache
      { name = "name",
        uri = "https://name.cachix.org",
        isPublic = True,
        permission = Admin,
        publicSigningKeys = ["pub"],
        githubUsername = "foobar",
        preferredCompressionMethod = ZSTD
      }
  
  spec :: Spec
  spec = do
    describe "render . parse" $ do
      it "handles single value substituters" $
        property "substituters = a\n"
      it "handles multi value substituters" $
        property "substituters = a b c\n"
      it "handles all known keys" $
        property "substituters = a b c\ntrusted-users = him me\ntrusted-public-keys = a\n"
      it "handles includes" $
        property "include /etc/nix/nix.conf\n!include /etc/nix/nix.conf\n"
      it "random content" $
        property "blabla = foobar\nfoo = bar\n"
  
    describe "add" $ do
      it "merges binary caches from both files" $
        let globalConf =
              NixConf
                [ Substituters ["bc1"],
                  TrustedPublicKeys ["pub1"]
                ]
            localConf =
              NixConf
                [ Substituters ["bc2"],
                  TrustedPublicKeys ["pub2"]
                ]
            result =
              NixConf
                [ Substituters [defaultPublicURI, "bc1", "bc2", "https://name.cachix.org"],
                  TrustedPublicKeys [defaultSigningKey, "pub1", "pub2", "pub"]
                ]
         in add bc [globalConf, localConf] localConf `shouldBe` result
  
      it "is noop if binary cache exists in one file" $
        let globalConf =
              NixConf
                [ Substituters [defaultPublicURI, "https://name.cachix.org"],
                  TrustedPublicKeys [defaultSigningKey, "pub"]
                ]
            localConf = NixConf []
            result =
              NixConf
                [ Substituters [defaultPublicURI, "https://name.cachix.org"],
                  TrustedPublicKeys [defaultSigningKey, "pub"]
                ]
         in add bc [globalConf, localConf] localConf `shouldBe` result
  
      it "preserves other nixconf entries" $
        let globalConf =
              NixConf
                [ Substituters ["http"],
                  TrustedPublicKeys ["pub1"],
                  TrustedUsers ["user"],
                  Other "foo"
                ]
            localConf =
              NixConf
                [ TrustedUsers ["user2"],
                  Other "bar"
                ]
            result =
              NixConf
                [ TrustedUsers ["user2"],
                  Other "bar",
                  Substituters [defaultPublicURI, "http", "https://name.cachix.org"],
                  TrustedPublicKeys [defaultSigningKey, "pub1", "pub"]
                ]
         in add bc [globalConf, localConf] localConf `shouldBe` result
  
      it "removed duplicates" $
        let globalConf =
              NixConf
                [ Substituters ["bc1", "bc1"],
                  TrustedPublicKeys ["pub1", "pub1"]
                ]
            localConf =
              NixConf
                [ Substituters ["bc2", "bc2"],
                  TrustedPublicKeys ["pub2", "pub2"]
                ]
            result =
              NixConf
                [ Substituters [defaultPublicURI, "bc1", "bc2", "https://name.cachix.org"],
                  TrustedPublicKeys [defaultSigningKey, "pub1", "pub2", "pub"]
                ]
         in add bc [globalConf, localConf] localConf `shouldBe` result
  
      it "adds binary cache and defaults if no existing entries exist" $
        let globalConf = NixConf []
            localConf = NixConf []
            result =
              NixConf
                [ Substituters [defaultPublicURI, "https://name.cachix.org"],
                  TrustedPublicKeys [defaultSigningKey, "pub"]
                ]
         in add bc [globalConf, localConf] localConf `shouldBe` result
  
    describe "remove" $ do
      it "removes a binary cache" $
        let localConf =
              NixConf
                [ Substituters [defaultPublicURI, "https://name.cachix.org"],
                  TrustedPublicKeys [defaultSigningKey, "name.cachix.org-1:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa="]
                ]
            result =
              NixConf
                [ Substituters [defaultPublicURI],
                  TrustedPublicKeys [defaultSigningKey]
                ]
         in remove "https://cachix.org" "name" [localConf] localConf `shouldBe` (result, True)
  
      it "removes nothing if the binary cache is missing" $
        let globalConf =
              NixConf
                [ Substituters [defaultPublicURI],
                  TrustedPublicKeys [defaultSigningKey]
                ]
            localConf = globalConf
            result = localConf
         in remove "https://cachix.org" "name" [globalConf, localConf] localConf `shouldBe` (result, False)
  
    describe "parse" $ do
      it "parses substituters" $
        parse "substituters = a\n"
          `shouldBe` Right (NixConf [Substituters ["a"]])
  
      it "parses long key" $
        parse "binary-caches-parallel-connections = 40\n"
          `shouldBe` Right (NixConf [Other "binary-caches-parallel-connections = 40"])
  
      it "parses substituters with multiple values" $
        parse "substituters = a b c\n"
          `shouldBe` Right (NixConf [Substituters ["a", "b", "c"]])
  
      it "parses equal sign after the first key as literal" $
        parse "substituters = a b c= d\n"
          `shouldBe` Right (NixConf [Substituters ["a", "b", "c=", "d"]])
  
      it "parses with missing endline" $
        parse "allowed-users = *"
          `shouldBe` Right (NixConf [Other "allowed-users = *"])
  
      it "parses include" $
        parse "include /etc/nix/nix.conf\n"
          `shouldBe` Right (NixConf [Include (RequiredInclude "/etc/nix/nix.conf")])
  
      it "parses !include" $
        parse "!include /etc/nix/nix.conf\n"
          `shouldBe` Right (NixConf [Include (OptionalInclude "/etc/nix/nix.conf")])
  
      it "parses a complex example" $
        parse realExample
          `shouldBe` Right parsedRealExample
  
    describe "NixConfSource" $ do
      it "write . read" $ do
        withTempDirectory "/tmp" "nixconf" $ \temp -> do
          let confPath = temp </> "nix.conf"
          let subConfPath = temp </> "sub.conf"
          let confContents = "include " <> toS subConfPath <> "\n"
          let parsedConfContents = NixConf [Include (RequiredInclude (toS subConfPath))]
          writeFile confPath confContents
          writeFile subConfPath realExample
  
          Just conf <- NixConf.read (Custom temp)
          conf `shouldBe` NixConfSource confPath parsedConfContents
  
          NixConf.write conf
          readFile confPath `shouldReturn` confContents
  
      -- Test that missing optional includes do not throw errors
      it "resolves required and optional includes" $ do
        withTempDirectory "/tmp" "nixconf" $ \temp -> do
          let confPath = temp </> "nix.conf"
              requiredConfPath = temp </> "required.conf"
              optionalConfPath = temp </> "optional.conf"
              parsedConfContents =
                NixConf
                  [ Include (RequiredInclude (toS requiredConfPath)),
                    Include (OptionalInclude (toS optionalConfPath))
                  ]
          writeFile confPath $
            unlines
              [ "include " <> toS requiredConfPath <> "\n",
                "!include " <> toS optionalConfPath <> "\n"
              ]
          writeFile requiredConfPath realExample
  
          Just conf <- NixConf.read (Custom temp)
          NixConf.resolveIncludes conf
            `shouldReturn` [ NixConfSource confPath parsedConfContents,
                             NixConfSource requiredConfPath parsedRealExample
                           ]
  
      it "detects cycles" $ do
        withTempDirectory "/tmp" "nixconf" $ \temp -> do
          let confPath = temp </> "nix.conf"
              subConfPath = temp </> "sub.conf"
              confContents :: Text = "include " <> toS subConfPath <> "\n"
              subConfContents :: Text = "include " <> toS confPath <> "\n"
          writeFile confPath confContents
          writeFile subConfPath subConfContents
  
          Just conf <- NixConf.read (Custom temp)
  
          let isCircularInclude = \case
                CircularInclude _ -> True
                _ -> False
          NixConf.resolveIncludes conf `shouldThrow` isCircularInclude
  
      it "detects trusted-users through includes" $ do
        withTempDirectory "/tmp" "nixconf" $ \temp -> do
          let confPath = temp </> "nix.conf"
              subConfPath = temp </> "sub.conf"
              confContents :: Text = "include " <> toS subConfPath <> "\n"
              subConfContents :: Text = "trusted-users = @wheel"
          writeFile confPath confContents
          writeFile subConfPath subConfContents
  
          ncs <- NixConf.resolveIncludes =<< NixConf.readWithDefault (NixConf.Custom temp)
          concatMap (NixConf.readLines NixConf.isTrustedUsers) ncs `shouldBe` ["@wheel"]
  
  realExample :: Text
  realExample =
    [hereLit|
  substituters = a  b c
  trusted-users = him me
  trusted-public-keys  =  a
  blabla =  asd
  # comment
  
  
  |]
  
  parsedRealExample :: NixConf
  parsedRealExample =
    NixConf
      [ Other "",
        Substituters ["a", "b", "c"],
        TrustedUsers ["him", "me"],
        TrustedPublicKeys ["a"],
        Other "blabla =  asd",
        Other "# comment",
        Other "",
        Other ""
      ]
  