encapsule-0.6: test/ConfigSpec.hs
-- SPDX-License-Identifier: Apache-2.0
module ConfigSpec (spec) where
import Control.Exception (bracket)
import qualified Data.Map.Strict as Map
import System.Directory (createDirectoryIfMissing, removeDirectoryRecursive)
import System.Environment (lookupEnv, setEnv, unsetEnv)
import System.FilePath ((</>), takeDirectory)
import System.Posix.Temp (mkdtemp)
import Test.Hspec
import Config (getCapabilities, loadConfig, resolveCapabilities)
import EncapsuleTest (encapsule)
spec :: Spec
spec = describe "config" $ do
it "returns Nothing when no config files exist, including bundled defaults" $
withConfigDirs $ \_ _ _ ->
loadConfig `shouldReturn` Nothing
it "loads bundled defaults when no user or system config exists" $
withConfigDirs $ \user _ _ -> do
writeBundledConfig user "[capabilities.bundled]\nenv = ['BUNDLED']\n"
resolve ["bundled"] `shouldReturn` ([], ["BUNDLED"], [], [], [])
out <- encapsule ["list-caps"]
out `shouldBe` "Available capabilities:\n bundled\n"
it "merges user overrides over bundled defaults without a system config" $
withConfigDirs $ \user _ _ -> do
writeBundledConfig user $ unlines
[ "[capabilities.shared]"
, "env = ['BUNDLED']"
, "path = ['/bundled/bin']"
, "[capabilities.bundled]"
, "volumes = ['bundled-volume']"
]
writeConfig user "[capabilities.shared]\nenv = ['USER']\n"
resolve ["shared", "bundled"] `shouldReturn`
(["bundled-volume"], ["USER"], ["/bundled/bin"], [], [])
it "ignores bundled defaults when a system config exists" $
withConfigDirs $ \user _ system -> do
writeBundledConfig user "[capabilities.bundled]\nenv = ['BUNDLED']\n"
writeConfig system "[capabilities.system]\nenv = ['SYSTEM']\n"
out <- encapsule ["list-caps"]
out `shouldBe` "Available capabilities:\n system\n"
it "lets an empty system config suppress bundled defaults" $
withConfigDirs $ \user system _ -> do
writeBundledConfig user "[capabilities.bundled]\nenv = ['BUNDLED']\n"
writeConfig system ""
(getCapabilities <$> loadConfig) `shouldReturn` Map.empty
it "reports malformed bundled defaults when no system config exists" $
withConfigDirs $ \user _ _ -> do
writeBundledConfig user "[broken\n"
out <- encapsule ["list-caps"]
out `shouldContain` "config parse error in"
out `shouldContain` (takeDirectory user </> "bundled" </> "data" </> "config.toml")
it "ignores malformed bundled defaults when a system config exists" $
withConfigDirs $ \user system _ -> do
writeBundledConfig user "[broken\n"
writeConfig system "[capabilities.system]\nenv = ['SYSTEM']\n"
resolve ["system"] `shouldReturn` ([], ["SYSTEM"], [], [], [])
it "loads a user config without a system config" $
withConfigDirs $ \user _ _ -> do
writeConfig user "[capabilities.user]\nenv = ['USER_ONLY']\n"
resolve ["user"] `shouldReturn` ([], ["USER_ONLY"], [], [], [])
it "loads a system config without a user config" $
withConfigDirs $ \_ system _ -> do
writeConfig system "[capabilities.system]\nenv = ['SYSTEM_ONLY']\n"
resolve ["system"] `shouldReturn` ([], ["SYSTEM_ONLY"], [], [], [])
it "merges capability fields and replaces arrays and scalar values" $
withConfigDirs $ \user system _ -> do
writeConfig system $ unlines
[ "[capabilities.shared]"
, "volumes = ['system-volume']"
, "env = ['SYSTEM_ENV']"
, "path = ['/system/bin']"
, "init = 'system-init'"
, "security_opts = ['label=disable']"
, "[capabilities.system]"
, "env = ['SYSTEM_ONLY']"
]
writeConfig user $ unlines
[ "[capabilities.shared]"
, "volumes = []"
, "env = ['USER_ENV']"
, "init = 'user-init'"
, "[capabilities.user]"
, "env = ['USER_ONLY']"
]
resolve ["shared"] `shouldReturn`
([], ["USER_ENV"], ["/system/bin"], ["user-init"], ["label=disable"])
resolve ["system", "user"] `shouldReturn`
([], ["SYSTEM_ONLY", "USER_ONLY"], [], [], [])
it "respects system directory order and user precedence" $
withConfigDirs $ \user first second -> do
writeConfig second "[capabilities.shared]\nenv = ['SECOND']\npath = ['/second/bin']\n"
writeConfig first "[capabilities.shared]\nenv = ['FIRST']\n"
resolve ["shared"] `shouldReturn` ([], ["FIRST"], ["/second/bin"], [], [])
writeConfig user "[capabilities.shared]\nenv = ['USER']\n"
resolve ["shared"] `shouldReturn` ([], ["USER"], ["/second/bin"], [], [])
it "lets a user value replace a system table" $
withConfigDirs $ \user system _ -> do
writeConfig system "[capabilities.shared]\nenv = ['SYSTEM']\n"
writeConfig user "capabilities = []\n"
(getCapabilities <$> loadConfig) `shouldReturn` Map.empty
it "reports the path of a malformed system config even with a user config" $
withConfigDirs $ \user system _ -> do
writeConfig user "[capabilities.user]\nenv = ['USER']\n"
writeConfig system "[broken\n"
out <- encapsule ["list-caps"]
out `shouldContain` "config parse error in"
out `shouldContain` (system </> "encapsule" </> "config.toml")
resolve :: [String] -> IO ([String], [String], [String], [String], [String])
resolve names = do
caps <- getCapabilities <$> loadConfig
resolveCapabilities caps names
writeConfig :: FilePath -> String -> IO ()
writeConfig dir contents = do
let appDir = dir </> "encapsule"
createDirectoryIfMissing True appDir
writeFile (appDir </> "config.toml") contents
writeBundledConfig :: FilePath -> String -> IO ()
writeBundledConfig user contents = do
let dataDir = takeDirectory user </> "bundled" </> "data"
createDirectoryIfMissing True dataDir
writeFile (dataDir </> "config.toml") contents
withConfigDirs :: (FilePath -> FilePath -> FilePath -> IO a) -> IO a
withConfigDirs action =
bracket (mkdtemp "/tmp/encapsule-config-test-XXXXXX") removeDirectoryRecursive $ \tmp -> do
let user = tmp </> "user"
first = tmp </> "system-first"
second = tmp </> "system-second"
withEnv "XDG_CONFIG_HOME" user $
withEnv "XDG_CONFIG_DIRS" (first ++ ":" ++ second) $
withEnv "encapsule_datadir" (tmp </> "bundled") $
action user first second
withEnv :: String -> String -> IO a -> IO a
withEnv key value action =
bracket (lookupEnv key) restore $ \_ -> do
setEnv key value
action
where
restore Nothing = unsetEnv key
restore (Just old) = setEnv key old