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

BindingTest.pudu

Pudu79 lines5.2 KB

GitHub ↗
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 Variables1314/// The secret every rendering check looks for.15const 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.Redacted3132/// A view over the given variables.33fn over(pairs: Array[(Str, Str)]) -> Variables.Variables {34  Variables.settled(&Map.empty(), &Map.fromPairs(&pairs), true, [], [])35}3637/// The service the variables bind to.38fn bound(pairs: Array[(Str, Str)]) -> Result[Service, Environment.Problem] { Service.bind(&over(pairs)) }3940/// Every variable the service needs.41fn complete() -> Array[(Str, Str)] {42  [("PORT", "8080"), ("SERVICE_HOST", "db.local"), ("RATIO", "0.5"), ("PRICE", "1.25"), ("DEBUG", "false"), ("API_KEY", SECRET)]43}4445/// Runs the suite.46fn 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