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

SettingsTest.pudu

Pudu69 lines3.5 KB

GitHub ↗
1/** @Test.Settings.Suite — bound settings checked by a validator */2module PuduLangEnvironment.SettingsTest34import Std.Io as Io5import Std.Map as Map6import Std.Result as Result7import Std.Test as Test8import PuduLangEnvironment.Binding as Binding9import PuduLangEnvironment as Environment10import PuduLangEnvironment.Settings as Settings11import PuduLangEnvironment.Variables as Variables12import PuduLangValidator.Result as Validated13import PuduLangValidator.Rule as Rule14import PuduLangValidator.Rules.Number as NumberRule15import PuduLangValidator.Rules.Text as TextRule16import PuduLangValidator.Validator as Validator1718/// The token every rendering check looks for.19const TOKEN: Str = "tok-abcdefghijklmnop"2021/** @Test.Settings.Server — settings a server reads at start-up */22type Server = { port: Int, token: Str } derives Binding.Bind2324/// Ports from 1 to 65535 and a token of at least sixteen characters, with a message quoting the value.25fn server() -> Validator.Validator[Server] {26  let port = Rule.build(NumberRule.inclusiveBetween(Rule.ruleFor("port", |given: Server| given.port), 1, 65535))27  let token = Rule.build(Rule.withMessage(TextRule.minimumLength(Rule.ruleFor("token", |given: Server| given.token), 16), "token \{PropertyValue\} is short"))28  Validator.add(Validator.add(Validator.create(), port), token)29}3031/// The same checks, the token one only a warning.32fn lenient() -> Validator.Validator[Server] {33  let token = Rule.build(Rule.withSeverity(TextRule.minimumLength(Rule.ruleFor("token", |given: Server| given.token), 64), Validated.Warning))34  Validator.add(Validator.create(), token)35}3637/// A view over the given variables.38fn over(pairs: Array[(Str, Str)]) -> Variables.Variables {39  Variables.settled(&Map.empty(), &Map.fromPairs(&pairs), true, [], [])40}4142/// Runs the suite.43fn main() -> Int {44  let accepted = Settings.bind(&over([("PORT", "8080"), ("TOKEN", TOKEN)]), &server())45  let refused = Settings.bind(&over([("PORT", "0"), ("TOKEN", "short")]), &server())46  let codes = match refused {47    case Err(Environment.Invalid(failures)) => failures48    case _ => []49  }50  let checks = Test.suite("Settings", &[51      Test.equals("accepted settings are answered", &Result.map(accepted, |given: Server| given.port), &Ok(8080)),52      Test.equals("a refusal names each property", &codes.map(|failure: Str| failure.split(" ")[0]), &["port", "token"]),53      Test.that("a refusal names each validator's code", codes.length() == 2 && codes.filter(|failure: Str| failure.contains(" (") && failure.endsWith(")")).length() == 2),54      Test.that("a refusal never quotes the value its message would", match refused {55          case Err(problem) => !Environment.describe(&problem).contains("short")56          case Ok(_) => false57        }),58      Test.equals("a missing variable stops before validation", &Result.map(Settings.bind(&over([("PORT", "8080")]), &server()), |given: Server| given.port), &Err(Environment.Missing(["TOKEN"]))),59      Test.equals("a malformed variable stops before validation", &Result.map(Settings.bind(&over([("PORT", "x"), ("TOKEN", TOKEN)]), &server()), |given: Server| given.port), &Err(Environment.Malformed("PORT", "a whole number"))),60      Test.equals("warnings do not refuse", &Result.map(Settings.check(Server{port: 1, token: "t"}, &lenient()), |given: Server| given.token), &Ok("t")),61      Test.equals("a validator without rules accepts", &Result.map(Settings.check(Server{port: 0, token: ""}, &Validator.create()), |given: Server| given.port), &Ok(0))62    ])63  let ran = Test.run(&checks)64  for failure in Test.failuresOf(&ran) {65    let _reported = Io.writeErrorLine(failure)66  }67  Test.report(&ran)68}69