
LoadScenarioTest.pudu
Pudu88 lines4.7 KB
1/** @Test.Integration.LoadScenario.Suite — a project loaded end to end */2module Integration.LoadScenarioTest34import Std.App.Secret as Secret5import Std.Env as Env6import Std.Fs as Fs7import Std.Io as Io8import Std.Map as Map9import Std.Path as Path10import Std.Process as Process11import Std.Result as Result12import Std.Test as Test13import PuduLangEnvironment.Binding as Binding14import PuduLangEnvironment as Environment15import PuduLangEnvironment.Loader as Loader16import PuduLangEnvironment.Options as Options17import PuduLangEnvironment.Settings as Settings18import PuduLangEnvironment.Variables as Variables19import PuduLangValidator.Rule as Rule20import PuduLangValidator.Rules.Number as NumberRule21import PuduLangValidator.Validator as Validator222324const SECRET: Str = "whsec_9f8e7d6c5b4a"2526/** @Test.Integration.App — the settings the scenario's program reads */27type App = {28 port: Int,29 publicUrl: Str,30 @secret webhookSecret: Secret.Secret,31 @default("2") workers: Int,32 debug: Option[Bool]33} derives Binding.Bind, Binding.Redacted343536fn rules() -> Validator.Validator[App] {37 Validator.add(Validator.create(), Rule.build(NumberRule.greaterThan(Rule.ruleFor("port", |given: App| given.port), 1023)))38}394041fn main() -> Int {42 let made = Result.unwrapOr(Fs.temporaryDirectoryIn(Env.temporaryDirectory(), "pudu-env-scenario-"), ".")43 let root = Result.unwrapOr(Fs.canonical(made), made)44 let service = Path.join(root, "services/api")45 let _services = Io.makeDirectory(Path.join(root, "services"))46 let _api = Io.makeDirectory(service)47 let _shared = Io.write(Path.join(root, ".env"), "export PORT=8080\nHOST=localhost\nPUBLIC_URL=\"https://$\{HOST\}:$\{PORT\}\"\nWEBHOOK_SECRET='" + SECRET + "'\n")48 let _staging = Io.write(Path.join(root, ".env.staging"), "HOST=staging.example.org\nPUBLIC_URL=\"https://$\{HOST\}\"\n")49 let _mine = Io.write(Path.join(root, ".env.staging.local"), "DEBUG=true # only on this machine\n")50 let process = Map.fromPairs(&[("PUDU_ENV", "staging"), ("PORT", "9443")])51 let options = Options.defaults().inDirectory(service).withCascade().withProbe().withExportSyntax().withExpansion().withoutOverwrite()52 let loaded = Loader.loadOver(&options, &process)53 let app = match loaded {54 case Ok(variables) => Settings.bind(&variables, &rules())55 case Err(problem) => Err(problem)56 }57 let rendered = match app {58 case Ok(held) => held.redacted()59 case Err(_) => ""60 }61 let launch = match loaded {62 case Ok(variables) => Variables.apply(&variables, &Process.launch("tool", &[]).isolated())63 case Err(_) => Process.launch("none", &[])64 }65 let checks = Test.suite("Integration.LoadScenario", &[66 Test.equals("the cascade is found two levels up and read in order", &Result.map(loaded, |view: Variables.Variables| view.sources.map(|path: Str| Path.nameOf(path))), &Ok([".env", ".env.staging", ".env.staging.local"])),67 Test.equals("the process keeps its own port", &Result.map(app, |held: App| held.port), &Ok(9443)),68 Test.equals("without overwriting the first public URL is kept, expanded with the port the process keeps", &Result.map(app, |held: App| held.publicUrl), &Ok("https://localhost:9443")),69 Test.equals("the secret is read", &Result.map(app, |held: App| Secret.reveal(&held.webhookSecret)), &Ok(SECRET)),70 Test.equals("a default fills an unset field", &Result.map(app, |held: App| held.workers), &Ok(2)),71 Test.equals("an optional field is read from the machine's own layer", &Result.map(app, |held: App| held.debug), &Ok(Some(true))),72 Test.that("the redacted rendering hides the secret", !rendered.contains(SECRET) && rendered.contains("webhookSecret: [REDACTED]")),73 Test.that("the loaded view hides the secret", match loaded {74 case Ok(view) => !show(view).contains(SECRET) && !Variables.describe(&view).contains(SECRET)75 case Err(_) => false76 }),77 Test.that("a child receives the applied keys but not the process port", launch.variables.contains(("WEBHOOK_SECRET", SECRET)) && !launch.variables.contains(("PORT", "8080"))),78 Test.equals("a port the validator refuses stops the program", &Result.map(Loader.loadOver(&options, &Map.fromPairs(&[("PUDU_ENV", "staging"), ("PORT", "80")])), |view: Variables.Variables| Result.map(Settings.bind(&view, &rules()), |held: App| held.port)), &Ok(Err(Environment.Invalid(["port (GreaterThan)"])))),79 Test.equals("an unsafe environment name stops the load", &Result.map(Loader.loadOver(&options, &Map.fromPairs(&[("PUDU_ENV", "../../etc")])), |view: Variables.Variables| view.applied), &Err(Environment.UnsafeEnvironmentName("../../etc")))80 ])81 let _cleared = Fs.removeTree(root)82 let ran = Test.run(&checks)83 for failure in Test.failuresOf(&ran) {84 let _reported = Io.writeErrorLine(failure)85 }86 Test.report(&ran)87}88