
BindingTest.pudu
Pudu79 lines5.2 KB
1/** @Test.Binding.Suite — records bound from variables and redacted */2module PuduLangEnvironment.BindingTest34import Std.App.Secret as Secret5import Std.Decimal as Decimal6import Std.Io as Io7import Std.Map as Map8import Std.Result as Result9import Std.Test as Test10import PuduLangEnvironment.Binding as Binding11import PuduLangEnvironment as Environment12import PuduLangEnvironment.Variables as Variables131415const SECRET: Str = "sk-live-0123456789"1617/** @Test.Binding.Service — every kind a variable can hold */18type Service = {19 port: Int,20 @env("SERVICE_HOST") host: Str,21 @default("info") logLevel: Str,22 ratio: Float64,23 price: Decimal,24 debug: Bool,25 @secret apiKey: Secret.Secret,26 replicas: Option[Int]27} derives Binding.Bind, Binding.Redacted2829/** @Test.Binding.Leaky — secrets left unmarked */30type Leaky = { token: Secret.Secret, backup: Option[Secret.Secret], @secret password: Str, name: Str } derives Binding.Redacted313233fn over(pairs: Array[(Str, Str)]) -> Variables.Variables {34 Variables.settled(&Map.empty(), &Map.fromPairs(&pairs), true, [], [])35}363738fn bound(pairs: Array[(Str, Str)]) -> Result[Service, Environment.Problem] { Service.bind(&over(pairs)) }394041fn complete() -> Array[(Str, Str)] {42 [("PORT", "8080"), ("SERVICE_HOST", "db.local"), ("RATIO", "0.5"), ("PRICE", "1.25"), ("DEBUG", "false"), ("API_KEY", SECRET)]43}444546fn main() -> Int {47 let service = bound(complete())48 let rendered = match service {49 case Ok(held) => held.redacted()50 case Err(_) => ""51 }52 let checks = Test.suite("Binding", &[53 Test.equals("each field reads its variable", &Result.map(service, |held: Service| (held.port, held.host, held.ratio, held.debug)), &Ok((8080, "db.local", 0.5, false))),54 Test.equals("a decimal field is exact", &Result.ok(Result.map(service, |held: Service| held.price)), &Decimal.parse("1.25")),55 Test.equals("an unset field takes its default", &Result.map(service, |held: Service| held.logLevel), &Ok("info")),56 Test.equals("a set field ignores its default", &Result.map(bound(complete().push(("LOG_LEVEL", "debug"))), |held: Service| held.logLevel), &Ok("debug")),57 Test.equals("an unset optional field is none", &Result.map(service, |held: Service| held.replicas), &Ok(None)),58 Test.equals("a set optional field is read", &Result.map(bound(complete().push(("REPLICAS", "3"))), |held: Service| held.replicas), &Ok(Some(3))),59 Test.equals("a secret field keeps its value", &Result.map(service, |held: Service| Secret.reveal(&held.apiKey)), &Ok(SECRET)),60 Test.equals("every unset required variable is reported at once", &Result.map(bound([("PORT", "1")]), |held: Service| held.port), &Err(Environment.Missing(["SERVICE_HOST", "RATIO", "PRICE", "DEBUG", "API_KEY"]))),61 Test.equals("an empty variable is unset", &Result.map(bound(complete().push(("PORT", ""))), |held: Service| held.port), &Err(Environment.Missing(["PORT"]))),62 Test.equals("a malformed variable names its key and kind", &Result.map(bound(complete().push(("PORT", "eighty"))), |held: Service| held.port), &Err(Environment.Malformed("PORT", "a whole number"))),63 Test.equals("a malformed optional variable is refused", &Result.map(bound(complete().push(("REPLICAS", "many"))), |held: Service| held.replicas), &Err(Environment.Malformed("REPLICAS", "a whole number"))),64 Test.equals("a malformed truth value is refused", &Result.map(bound(complete().push(("DEBUG", "yes"))), |held: Service| held.debug), &Err(Environment.Malformed("DEBUG", "true or false"))),65 Test.equals("a malformed float and decimal are refused", &[Result.map(bound(complete().push(("RATIO", "half"))), |held: Service| held.port), Result.map(bound(complete().push(("PRICE", "x"))), |held: Service| held.port)], &[Err(Environment.Malformed("RATIO", "a floating-point number")), Err(Environment.Malformed("PRICE", "a decimal number"))]),66 Test.that("a redacted rendering hides secret fields", !rendered.contains(SECRET) && rendered.contains("apiKey: [REDACTED]")),67 Test.that("a redacted rendering shows other fields", rendered.startsWith("Service\{port: 8080, host: \"db.local\"") && rendered.contains("replicas: None")),68 Test.equals("a secret renders hidden without the attribute", &Leaky{token: Secret.secret(SECRET), backup: Some(Secret.secret(SECRET)), password: SECRET, name: "svc"}.redacted(), &"Leaky\{token: [REDACTED], backup: Some([REDACTED]), password: [REDACTED], name: \"svc\"\}"),69 Test.equals("an absent optional secret renders as none", &Leaky{token: Secret.secret(SECRET), backup: None, password: "", name: "svc"}.redacted(), &"Leaky\{token: [REDACTED], backup: None, password: [REDACTED], name: \"svc\"\}"),70 Test.that("every kind renders as written", rendered.contains("ratio: 0.5") && rendered.contains("price: 1.25") && rendered.contains("debug: false") && rendered.contains("logLevel: \"info\"")),71 Test.that("a problem never quotes a value", !Environment.describe(&Environment.Malformed("PORT", "a whole number")).contains("eighty"))72 ])73 let ran = Test.run(&checks)74 for failure in Test.failuresOf(&ran) {75 let _reported = Io.writeErrorLine(failure)76 }77 Test.report(&ran)78}79