Pudu programming language
Menu
Package

@chrismichaelps / pudu-lang-mcp

Model Context Protocol server for Pudu language documentation and compiler tools

0.1.1Apache-2.01

InstallClose

ArgumentsTest.pudu

Pudu81 lines5.4 KB

GitHub ↗
1/** @Test.Domain.Catalog.ArgumentsTest.Suite — tool argument validation */2module PuduLangMcp.Domain.Catalog.ArgumentsTest34import Std.Io as Io5import Std.Json as Json6import Std.Test as Test7import PuduLangMcp.Domain.Catalog.Arguments as Arguments8import PuduLangMcp.Domain.Catalog.ToolSpec as ToolSpec9import PuduLangMcp.Errors.ToolError as ToolError1011/// A spec exercising every rule: a required text, a bounded integer, and an exclusive pair.12fn sample() -> ToolSpec.ToolSpec {13  ToolSpec.ToolSpec {14    name: "sample",15    title: "Sample",16    description: "A tool for tests.",17    properties: [18      ToolSpec.text("query", "What to find.", true),19      ToolSpec.integer("limit", "How many.", false, 1, 10),20      ToolSpec.text("source", "Code.", false),21      ToolSpec.text("path", "A file.", false)22    ],23    exactlyOne: ["source", "path"],24    annotations: ToolSpec.Annotations{readOnly: true, destructive: false, idempotent: true, openWorld: false}25  }26}2728/// A spec whose exclusive group has three names.29fn triple() -> ToolSpec.ToolSpec {30  ToolSpec.ToolSpec{..sample(), properties: [ToolSpec.text("a", "A.", false), ToolSpec.text("b", "B.", false), ToolSpec.text("c", "C.", false)], exactlyOne: ["a", "b", "c"]}31}3233/// Whether arguments are accepted by the three-way spec.34fn tripleAccepts(members: &Array[(Str, Json.Json)]) -> Bool {35  Arguments.validate(&triple(), &Json.object(members)) != Err(ToolError.ExactlyOne(["a", "b", "c"]))36}3738/// The failure validating `members` answers, if any.39fn failureOf(members: &Array[(Str, Json.Json)]) -> Option[ToolError.ToolFailure] {40  match Arguments.validate(&sample(), &Json.object(members)) {41    case Ok(_) => None42    case Err(failure) => Some(failure)43  }44}4546/// Runs the suite.47fn main() -> Int {48  let valid = [("query", Json.Text("x")), ("source", Json.Text("module Main"))]49  let accepted = Arguments.validate(&sample(), &Json.object(&valid.push(("limit", Json.Number(3)))))50  let checks = Test.suite("Domain.Catalog.Arguments", &[51      Test.succeeded("valid arguments are accepted", &accepted),52      Test.equals("a text argument is read", &match accepted { case Ok(args) => Arguments.text(&args, "query") case Err(_) => None }, &Some("x")),53      Test.equals("an integer argument is read", &match accepted { case Ok(args) => Arguments.integerOr(&args, "limit", 0) case Err(_) => 0 }, &3),54      Test.equals("an absent integer takes its fallback", &match Arguments.validate(&sample(), &Json.object(&valid)) { case Ok(args) => Arguments.integerOr(&args, "limit", 7) case Err(_) => 0 }, &7),55      Test.equals("null arguments are an empty object", &match Arguments.validate(&sample(), &Json.Null) { case Err(failure) => Some(failure) case Ok(_) => None }, &Some(ToolError.MissingArgument("query"))),56      Test.equals("a non-object is refused", &match Arguments.validate(&sample(), &Json.list(&[])) { case Err(failure) => Some(failure) case Ok(_) => None }, &Some(ToolError.NotAnObject)),57      Test.equals("an undeclared argument is refused", &failureOf(&valid.push(("limt", Json.Number(3)))), &Some(ToolError.UnknownArgument("limt"))),58      Test.equals("a missing required argument is refused", &failureOf(&[("source", Json.Text("m"))]), &Some(ToolError.MissingArgument("query"))),59      Test.equals("a blank required text is refused", &failureOf(&[("query", Json.Text("   ")), ("source", Json.Text("m"))]), &Some(ToolError.WrongType("query", "a non-empty string"))),60      Test.equals("a number for a text is refused", &failureOf(&[("query", Json.Number(1)), ("source", Json.Text("m"))]), &Some(ToolError.WrongType("query", "a string"))),61      Test.equals("text for an integer is refused", &failureOf(&valid.push(("limit", Json.Text("3")))), &Some(ToolError.WrongType("limit", "an integer"))),62      Test.equals("a fraction for an integer is refused", &failureOf(&valid.push(("limit", Json.Fractional("2.5")))), &Some(ToolError.WrongType("limit", "an integer"))),63      Test.equals("null for an argument is the wrong kind", &failureOf(&valid.push(("limit", Json.Null))), &Some(ToolError.WrongType("limit", "an integer"))),64      Test.equals("an integer below its minimum is refused", &failureOf(&valid.push(("limit", Json.Number(0)))), &Some(ToolError.OutOfRange("limit", "between 1 and 10"))),65      Test.equals("an integer above its maximum is refused", &failureOf(&valid.push(("limit", Json.Number(11)))), &Some(ToolError.OutOfRange("limit", "between 1 and 10"))),66      Test.equals("the minimum itself is accepted", &failureOf(&valid.push(("limit", Json.Number(1)))), &None),67      Test.equals("the maximum itself is accepted", &failureOf(&valid.push(("limit", Json.Number(10)))), &None),68      Test.equals("neither of an exclusive pair is refused", &failureOf(&[("query", Json.Text("x"))]), &Some(ToolError.ExactlyOne(["source", "path"]))),69      Test.equals("both of an exclusive pair are refused", &failureOf(&[("query", Json.Text("x")), ("source", Json.Text("m")), ("path", Json.Text("a.pudu"))]), &Some(ToolError.ExactlyOne(["source", "path"]))),70      Test.that("one of three exclusive names is accepted", tripleAccepts(&[("b", Json.Text("x"))])),71      Test.not("two of three exclusive names are refused", tripleAccepts(&[("a", Json.Text("x")), ("c", Json.Text("y"))])),72      Test.not("none of three exclusive names is refused", tripleAccepts(&[])),73      Test.equals("a blank optional text is accepted", &failureOf(&[("query", Json.Text("x")), ("path", Json.Text(""))]), &None)74    ])75  let ran = Test.run(&checks)76  for failure in Test.failuresOf(&ran) {77    let _reported = Io.writeErrorLine(failure)78  }79  Test.report(&ran)80}81