Pudu programming language
Menu
Package

@chrismichaelps / pudu-lang-environment

Environment files for Pudu: load .env variables safely with layered files, discovery, expansion, typed reads, and secrets kept out of every message

0.1.0Apache-2.01

InstallClose

DiscoveryTest.pudu

Pudu72 lines5.7 KB

GitHub ↗
1/** @Test.Discovery.Suite — given, probed, and cascaded files */2module PuduLangEnvironment.DiscoveryTest34import Std.Env as Env5import Std.Fs as Fs6import Std.Io as Io7import Std.Map as Map8import Std.Path as Path9import Std.Result as Result10import Std.Test as Test11import PuduLangEnvironment.Discovery as Discovery12import PuduLangEnvironment as Environment13import PuduLangEnvironment.Options as Options1415/// The paths discovery answers, or its problem.16fn paths(options: Options.Options, process: Array[(Str, Str)]) -> Result[Array[Str], Environment.Problem] {17  Ok(Discovery.discover(&options, &Map.fromPairs(&process)) ?.paths)18}1920/// What discovery skipped, or its problem.21fn skipped(options: Options.Options) -> Result[Array[Environment.Problem], Environment.Problem] {22  Ok(Discovery.discover(&options, &Map.empty()) ?.skipped)23}2425/// Runs the suite.26fn main() -> Int {27  let made = Result.unwrapOr(Fs.temporaryDirectoryIn(Env.temporaryDirectory(), "pudu-env-discovery-"), ".")28  let root = Result.unwrapOr(Fs.canonical(made), made)29  let project = Path.join(root, "project")30  let deep = Path.join(project, "a/b/c")31  let _made = Io.makeDirectory(project)32  let _a = Io.makeDirectory(Path.join(project, "a"))33  let _b = Io.makeDirectory(Path.join(project, "a/b"))34  let _c = Io.makeDirectory(deep)35  let _env = Io.write(Path.join(project, ".env"), "A=1")36  let _local = Io.write(Path.join(project, ".env.local"), "A=2")37  let _test = Io.write(Path.join(project, ".env.test.local"), "A=3")38  let base = Options.defaults().inDirectory(project)39  let empty = Path.join(project, "a")40  let checks = Test.suite("Discovery", &[41      Test.equals("a relative path is joined to the directory", &Discovery.locate("conf", "app.env"), &Path.join("conf", "app.env")),42      Test.equals("an absolute path is kept", &Discovery.locate("conf", "/etc/app.env"), &"/etc/app.env"),43      Test.equals("the working directory leaves a path as it is", &[Discovery.locate(".", "a.env"), Discovery.locate("", "a.env")], &["a.env", "a.env"]),44      Test.equals("given files are located in order", &paths(base.withFiles(&["x.env", "y.env"]), []), &Ok([Path.join(project, "x.env"), Path.join(project, "y.env")])),45      Test.equals("probing finds .env in the start directory", &paths(base.withProbe(), []), &Ok([Path.join(project, ".env")])),46      Test.equals("probing climbs to a parent", &paths(base.inDirectory(deep).withProbeLevels(3), []), &Ok([Path.join(project, ".env")])),47      Test.equals("probing stops at its level", &skipped(base.inDirectory(deep).withProbeLevels(2)), &Ok([Environment.NotFound([Path.join(deep, ".env"), Path.join(project, "a/b/.env"), Path.join(project, "a/.env")])])),48      Test.equals("probing that finds nothing reads nothing", &paths(base.inDirectory(deep).withProbeLevels(0), []), &Ok([])),49      Test.equals("strict probing that finds nothing is refused", &paths(base.inDirectory(deep).withProbeLevels(0).withStrict(), []), &Err(Environment.NotFound([Path.join(deep, ".env")]))),50      Test.equals("probe answers the directory holding a name", &Discovery.probe(deep, 4, &[".env.local"]), &Some(project)),51      Test.equals("probe answers none past its levels", &Discovery.probe(deep, 1, &[".env"]), &None),52      Test.equals("probe stops at the root", &Discovery.probe("/", 5, &["pudu-env-never-there"]), &None),53      Test.equals("the cascade reads the shared files that exist", &paths(base.withCascade(), []), &Ok([Path.join(project, ".env"), Path.join(project, ".env.local")])),54      Test.equals("the cascade adds the named layers that exist", &paths(base.withEnvironment("test"), []), &Ok([Path.join(project, ".env"), Path.join(project, ".env.local"), Path.join(project, ".env.test.local")])),55      Test.equals("the cascade takes its name from PUDU_ENV", &paths(base.withCascade(), [("PUDU_ENV", "test")]), &Ok([Path.join(project, ".env"), Path.join(project, ".env.local"), Path.join(project, ".env.test.local")])),56      Test.equals("the cascade takes its name from PROFILE", &paths(base.withCascade(), [("PROFILE", "test")]), &Ok([Path.join(project, ".env"), Path.join(project, ".env.local"), Path.join(project, ".env.test.local")])),57      Test.equals("an unsafe name is refused even when lenient", &paths(base.withEnvironment("../secrets"), []), &Err(Environment.UnsafeEnvironmentName("../secrets"))),58      Test.equals("an unsafe name from a variable is refused", &paths(base.withCascade(), [("PUDU_ENV", "a/b")]), &Err(Environment.UnsafeEnvironmentName("a/b"))),59      Test.equals("a cascade with no file records what it looked for", &skipped(base.inDirectory(empty).withCascade()), &Ok([Environment.NotFound([Path.join(empty, ".env"), Path.join(empty, ".env.local")])])),60      Test.equals("a strict cascade with no file is refused", &paths(base.inDirectory(empty).withCascade().withStrict(), []), &Err(Environment.NotFound([Path.join(empty, ".env"), Path.join(empty, ".env.local")]))),61      Test.equals("the cascade probes upward", &paths(base.inDirectory(deep).withEnvironment("test").withProbe(), []), &Ok([Path.join(project, ".env"), Path.join(project, ".env.local"), Path.join(project, ".env.test.local")])),62      Test.equals("a probing cascade that finds nothing lists every path", &skipped(base.inDirectory(deep).withCascade().withProbeLevels(1)), &Ok([Environment.NotFound([Path.join(deep, ".env"), Path.join(deep, ".env.local"), Path.join(project, "a/b/.env"), Path.join(project, "a/b/.env.local")])])),63      Test.equals("a strict probing cascade that finds nothing is refused", &paths(base.inDirectory(deep).withCascade().withProbeLevels(0).withStrict(), []), &Err(Environment.NotFound([Path.join(deep, ".env"), Path.join(deep, ".env.local")])))64    ])65  let _cleared = Fs.removeTree(root)66  let ran = Test.run(&checks)67  for failure in Test.failuresOf(&ran) {68    let _reported = Io.writeErrorLine(failure)69  }70  Test.report(&ran)71}72