
ClefTest.pudu
Pudu84 lines5.1 KB
1/** @Test.Domain.Clef.Suite — compact JSON lines read back into events */2module PuduLangLog.Domain.ClefTest34import Std.Io as Io5import Std.Option as Option6import Std.Test as Test7import PuduLangLog.Domain.Clef as Clef8import PuduLangLog as Log9import PuduLangLog.Value as Value101112fn read(text: Str) -> Option[Log.Event] {13 match Clef.line(text) {14 case Ok(event) => Some(event)15 case Err(_) => None16 }17}181920fn refusal(text: Str) -> Str {21 match Clef.line(text) {22 case Ok(_) => "read"23 case Err(problem) => problem24 }25}262728fn failureIn(text: Str) -> Option[Log.Failure] { Option.andThen(read(text), |event: Log.Event| event.failure) }293031fn main() -> Int {32 let minimal = read("\{\"@t\":\"2026-09-28T14:05:09.1230000Z\",\"@mt\":\"Hello \{Name\}\",\"Name\":\"Ada\"\}")33 let offset = read("\{\"@t\":\"2026-09-28T16:05:09.123+02:00\",\"@mt\":\"x\"\}")34 let behind = read("\{\"@t\":\"2026-09-28T09:35:09.123-04:30\",\"@mt\":\"x\"\}")35 let rendered = read("\{\"@t\":\"2026-09-28T14:05:09Z\",\"@m\":\"Set \{a\} to 1\",\"@i\":\"abc\"\}")36 let bare = read("\{\"@t\":\"2026-09-28T14:05:09Z\"\}")37 let values = read("\{\"@t\":\"2026-09-28T14:05:09Z\",\"@mt\":\"v\",\"@r\":[\"1\"],\"n\":null,\"b\":true,\"i\":-4,\"d\":1.50,\"s\":\"x\",\"l\":[1,\"a\"],\"o\":\{\"A\":1,\"$type\":\"User\"\},\"u\":\{\"B\":2\},\"@@odd\":1\}")38 let checks = Test.suite("Domain.Clef", &[39 Test.equals("the timestamp is read in UTC", &Option.map(minimal, |event: Log.Event| event.timestamp), &Some(Log.Timestamp{millis: 1790604309123, offset: 0})),40 Test.equals("a positive offset is kept in minutes", &Option.map(offset, |event: Log.Event| event.timestamp), &Some(Log.Timestamp{millis: 1790604309123, offset: 120})),41 Test.equals("a negative offset is kept in minutes", &Option.map(behind, |event: Log.Event| event.timestamp), &Some(Log.Timestamp{millis: 1790604309123, offset: -270})),42 Test.equals("the template and its properties", &Option.map(minimal, |event: Log.Event| (event.template.text, event.properties, event.level)), &Some(("Hello \{Name\}", [Value.property("Name", Value.text("Ada"))], Log.Information))),43 Test.equals("a rendered message becomes an escaped template", &Option.map(rendered, |event: Log.Event| (event.template.text, event.template.holes.length(), event.properties.length())), &Some(("Set \{\{a\}\} to 1", 0, 0))),44 Test.equals("no template reads as empty", &Option.map(bare, |event: Log.Event| event.template.text), &Some("")),45 Test.equals("values of every kind", &Option.map(values, |event: Log.Event| event.properties), &Some([46 Value.property("n", Value.nothing()),47 Value.property("b", Value.bool(true)),48 Value.property("i", Value.int(-4)),49 Value.property("d", Value.decimal(1.50d)),50 Value.property("s", Value.text("x")),51 Value.property("l", Value.list([Value.int(1), Value.text("a")])),52 Value.property("o", Value.structure("User", [("A", Value.int(1))])),53 Value.property("u", Value.object([("B", Value.int(2))])),54 Value.property("@odd", Value.int(1))55 ])),56 Test.equals("levels, trace, and span", &Option.map(read("\{\"@t\":\"2026-09-28T14:05:09Z\",\"@l\":\"warning\",\"@tr\":\"t1\",\"@sp\":\"s1\"\}"), |event: Log.Event| (event.level, event.traceId, event.spanId)), &Some((Log.Warning, Some("t1"), Some("s1")))),57 Test.equals("a failure with a kind and message", &failureIn("\{\"@t\":\"2026-09-28T14:05:09Z\",\"@x\":\"IoError: disk: full\"\}"), &Some(Log.Failure{kind: "IoError", message: "disk: full", trace: [], causes: []})),58 Test.equals("a failure with only a kind", &failureIn("\{\"@t\":\"2026-09-28T14:05:09Z\",\"@x\":\"Timeout\"\}"), &Some(Log.Failure{kind: "Timeout", message: "", trace: [], causes: []})),59 Test.equals("a failure with message lines, a trace, and a chain of causes", &failureIn("\{\"@t\":\"2026-09-28T14:05:09Z\",\"@x\":\"Outer: first\\nsecond\\n at a\\n at b\\n ---> Middle: m\\n at c\\n ---> Inner: i\"\}"), &Some(Log.Failure{kind: "Outer", message: "first\nsecond", trace: ["a", "b"], causes: [Log.Failure{kind: "Middle", message: "m", trace: ["c"], causes: [Log.Failure{kind: "Inner", message: "i", trace: [], causes: []}]}]})),60 Test.equals("refusals", &[61 refusal("not json"),62 refusal("[1]"),63 refusal("\{\"@mt\":\"x\"\}"),64 refusal("\{\"@t\":\"yesterday\"\}"),65 refusal("\{\"@t\":\"2026-09-28T14:05:09Z\",\"@l\":\"Loud\"\}"),66 refusal("\{\"@t\":\"2026-09-28T14:05:09Z\",\"@mt\":3\}"),67 refusal("\{\"@t\":1\}")68 ], &[69 "The line is not JSON: expected a value at 0",70 "The event is not a JSON object",71 "The event has no `@t` timestamp",72 "The timestamp `yesterday` is not RFC 3339",73 "Unknown level `Loud`",74 "The `@mt` member is not text",75 "The `@t` member is not text"76 ])77 ])78 let ran = Test.run(&checks)79 for failure in Test.failuresOf(&ran) {80 let _reported = Io.writeErrorLine(failure)81 }82 Test.report(&ran)83}84