
SettingsTest.pudu
Pudu135 lines8.5 KB
1/** @Test.Domain.Settings.Suite — key-value settings read into a plan */2module PuduLangLog.Domain.SettingsTest34import Std.Io as Io5import Std.Result as Result6import Std.Test as Test7import PuduLangLog.Domain.Settings as Settings8import PuduLangLog as Log91011fn planOf(pairs: Array[(Str, Str)]) -> Settings.Plan { Settings.read(&pairs).plan }121314fn problemsOf(pairs: Array[(Str, Str)]) -> Array[Str] { Settings.read(&pairs).problems }151617fn main() -> Int {18 let none: Array[Str] = []19 let full = Settings.read(&[20 ("MinimumLevel:Default", "Debug"),21 ("minimumlevel:override:App.Web", "Warning"),22 ("MinimumLevel:Override:App.Jobs", "$jobs"),23 ("LevelSwitches:$jobs", "Error"),24 ("LevelSwitches:$root", "Verbose"),25 ("MinimumLevel:ControlledBy", "$root"),26 ("Properties:Application", "Shop"),27 ("Properties:application", "Store"),28 ("Enrich:0", "MachineName"),29 ("Enrich:1:Name", "EnvironmentName"),30 ("Enrich:1:Args:variable", "APP_ENV"),31 ("WriteTo:0:Name", "Console"),32 ("WriteTo:0:Args:theme", "none"),33 ("WriteTo:File:Name", "File"),34 ("WriteTo:File:Args:path", "log.txt"),35 ("WriteTo:File:Args:levelSwitch", "$jobs"),36 ("WriteTo:0:Args:Theme", "code"),37 ("AuditTo:0", "Http"),38 ("Filter:0:ByExcluding", "RequestPath like '/health%'"),39 ("Filter:1:ByIncludingOnly", "@l <> 'Verbose'"),40 ("Destructure:MaximumDepth", "4"),41 ("Destructure:MaximumStringLength", "100"),42 ("Destructure:MaximumCollectionCount", "10"),43 ("Destructure:AsScalar:0", "Money")44 ])45 let plan = full.plan46 let checks = Test.suite("Domain.Settings", &[47 Test.equals("a full set of settings reads without problems", &full.problems, &none),48 Test.equals("levels", &(plan.minimum, plan.controlledBy), &(Some(Log.Debug), Some("root"))),49 Test.equals("level switches, their names without `$` and in lower case", &plan.switches, &[("jobs", Log.Error), ("root", Log.Verbose)]),50 Test.equals("overrides keep levels and switch references", &plan.overrides, &[("App.Web", "Warning"), ("App.Jobs", "$jobs")]),51 Test.equals("a later property replaces an earlier one ignoring case", &plan.properties, &[("application", "Store")]),52 Test.equals("enrichers in order, with arguments", &plan.enrichers, &[Settings.Target{name: "MachineName", arguments: []}, Settings.Target{name: "EnvironmentName", arguments: [("variable", "APP_ENV")]}]),53 Test.equals("sinks in first-seen order, arguments replaced ignoring case", &plan.sinks, &[Settings.Target{name: "Console", arguments: [("Theme", "code")]}, Settings.Target{name: "File", arguments: [("path", "log.txt"), ("levelSwitch", "$jobs")]}]),54 Test.equals("audit sinks", &plan.audits, &[Settings.Target{name: "Http", arguments: []}]),55 Test.equals("filters", &plan.filters, &[Settings.Filter{including: false, expression: "RequestPath like '/health%'"}, Settings.Filter{including: true, expression: "@l <> 'Verbose'"}]),56 Test.equals("capture limits and scalar tags", &(plan.maximumDepth, plan.maximumStringLength, plan.maximumCollectionCount, plan.scalars), &(Some(4), Some(100), Some(10), ["Money"])),57 Test.equals("a bare minimum level", &planOf([("MinimumLevel", "warning")]).minimum, &Some(Log.Warning)),58 Test.equals("nothing decided", &planOf([]), &Settings.empty()),59 Test.equals("malformed and unknown settings", &problemsOf([60 ("MinimumLevel", "Loud"),61 ("MinimumLevel:Override", "Debug"),62 ("MinimumLevel:Override:App", "Loud"),63 ("MinimumLevel:Other", "x"),64 ("MinimumLevel:Default:Extra", "Debug"),65 ("LevelSwitches:$a", "Loud"),66 ("LevelSwitches", "Debug"),67 ("Properties", "x"),68 ("Enrich", "x"),69 ("WriteTo:0:Args", "x"),70 ("WriteTo:0:Other", "x"),71 ("WriteTo:0:Args:", "x"),72 ("Filter:0:Somehow", "x"),73 ("Filter:0", "x"),74 ("Destructure:MaximumDepth", "-1"),75 ("Destructure:MaximumDepth", "deep"),76 ("Destructure:Other", "1"),77 ("Destructure", "1"),78 ("Destructure:Other:x", "1"),79 ("Destructure:AsScalar", "Money"),80 ("Using:0", "Something")81 ]), &[82 "Unknown or malformed setting `MinimumLevel`",83 "Unknown or malformed setting `MinimumLevel:Override`",84 "Unknown or malformed setting `MinimumLevel:Override:App`",85 "Unknown or malformed setting `MinimumLevel:Other`",86 "Unknown or malformed setting `MinimumLevel:Default:Extra`",87 "Unknown or malformed setting `LevelSwitches:$a`",88 "Unknown or malformed setting `LevelSwitches`",89 "Unknown or malformed setting `Properties`",90 "Unknown or malformed setting `Enrich`",91 "Unknown or malformed setting `WriteTo:0:Args`",92 "Unknown or malformed setting `WriteTo:0:Other`",93 "Unknown or malformed setting `WriteTo:0:Args:`",94 "Unknown or malformed setting `Filter:0:Somehow`",95 "Unknown or malformed setting `Filter:0`",96 "Unknown or malformed setting `Destructure:MaximumDepth`",97 "Unknown or malformed setting `Destructure:MaximumDepth`",98 "Unknown or malformed setting `Destructure:Other`",99 "Unknown or malformed setting `Destructure`",100 "Unknown or malformed setting `Destructure:Other:x`",101 "Unknown or malformed setting `Destructure:AsScalar`",102 "Unknown or malformed setting `Using:0`"103 ]),104 Test.equals("references to undeclared switches and unnamed entries", &problemsOf([105 ("MinimumLevel:ControlledBy", "$missing"),106 ("MinimumLevel:Override:App", "$other"),107 ("WriteTo:0:Args:levelSwitch", "$third"),108 ("AuditTo:0:Args:x", "1"),109 ("Enrich:0:Args:x", "1")110 ]), &[111 "An entry of `Enrich` names nothing",112 "An entry of `WriteTo` names nothing",113 "An entry of `AuditTo` names nothing",114 "The level switch `missing` is not declared",115 "The level switch `other` is not declared",116 "The level switch `third` is not declared"117 ]),118 Test.equals("JSON under a section", &Settings.fromJson("\{\"Other\":1,\"log\":\{\"MinimumLevel\":\{\"Default\":\"Debug\"\},\"WriteTo\":[\{\"Name\":\"Console\",\"Args\":\{\"buffered\":true,\"limit\":5,\"ratio\":0.5,\"none\":null\}\}]\}\}", "Log"), &Ok([("MinimumLevel:Default", "Debug"), ("WriteTo:0:Name", "Console"), ("WriteTo:0:Args:buffered", "true"), ("WriteTo:0:Args:limit", "5"), ("WriteTo:0:Args:ratio", "0.5"), ("WriteTo:0:Args:none", "")])),119 Test.equals("JSON lists are numbered from zero", &Settings.fromJson("\{\"Enrich\":[\"A\",\"B\",\"C\"]\}", ""), &Ok([("Enrich:0", "A"), ("Enrich:1", "B"), ("Enrich:2", "C")])),120 Test.equals("JSON without a section, or missing it", &(Settings.fromJson("\{\"Properties\":\{\"A\":false\}\}", ""), Settings.fromJson("\{\"A\":1\}", "Log"), Settings.fromJson("[1]", "Log")), &(Ok([("Properties:A", "false")]), Ok([]), Ok([]))),121 Test.that("text that is not JSON is refused", Result.isErr(&Settings.fromJson("\{", "Log"))),122 Test.equals("environment variable names", &[Settings.environmentKey("APP__MinimumLevel__Default", "APP"), Settings.environmentKey("app__WriteTo__0__Name", "App"), Settings.environmentKey("APP__", "APP"), Settings.environmentKey("OTHER__X", "APP"), Settings.environmentKey("APPX__Y", "APP")], &[Some("MinimumLevel:Default"), Some("WriteTo:0:Name"), None, None, None]),123 Test.equals("rolling intervals", &[Settings.intervalOf("day"), Settings.intervalOf(" Hour "), Settings.intervalOf("INFINITE"), Settings.intervalOf("week")], &[Some(Log.Day), Some(Log.Hour), Some(Log.Infinite), None]),124 Test.equals("counts", &[Settings.count("0"), Settings.count(" 12 "), Settings.count("-1"), Settings.count("x")], &[Some(0), Some(12), None, None]),125 Test.equals("flags", &[Settings.flag("TRUE"), Settings.flag(" false"), Settings.flag("yes")], &[Some(true), Some(false), None]),126 Test.equals("switch names", &[Settings.switchName(" $Jobs "), Settings.switchName("jobs")], &["jobs", "jobs"]),127 Test.equals("arguments by name ignoring case", &(Settings.argument(&Settings.Target{name: "x", arguments: [("Path", "a")]}, "path"), Settings.argument(&Settings.Target{name: "x", arguments: []}, "path")), &(Some("a"), None))128 ])129 let ran = Test.run(&checks)130 for failure in Test.failuresOf(&ran) {131 let _reported = Io.writeErrorLine(failure)132 }133 Test.report(&ran)134}135