
SettingsTest.pudu
Pudu69 lines3.5 KB
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 Validator171819const TOKEN: Str = "tok-abcdefghijklmnop"2021/** @Test.Settings.Server — settings a server reads at start-up */22type Server = { port: Int, token: Str } derives Binding.Bind232425fn 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}303132fn 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}363738fn over(pairs: Array[(Str, Str)]) -> Variables.Variables {39 Variables.settled(&Map.empty(), &Map.fromPairs(&pairs), true, [], [])40}414243fn 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