
ToolsTest.pudu
Pudu90 lines5.5 KB
1/** @Test.Domain.Catalog.ToolsTest.Suite — catalogue invariants and handler coverage */2module PuduLangMcp.Domain.Catalog.ToolsTest34import Std.Io as Io5import Std.Json as Json6import Std.List as List7import Std.Map as Map8import Std.Test as Test9import Std.Text as Text10import PuduLangMcp.App.Tools.Registry as Registry11import PuduLangMcp.Domain.Catalog.ToolSpec as ToolSpec12import PuduLangMcp.Domain.Catalog.Tools as Tools13import PuduLangMcp.Utils.JsonAccess as Access141516fn wellFormed(name: Str) -> Bool {17 name.length() >= 1 && name.length() <= 128 && Text.allChars(name, |c: Char| (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_' || c == '-' || c == '.')18}192021fn expected() -> Array[(Str, (Bool, Bool, Bool, Bool), Array[Str])] {22 let lookup = (true, false, true, false)23 let position = ["line", "character"]24 [25 ("pudu_docs_search", lookup, ["query"]),26 ("pudu_docs_read", lookup, ["slug"]),27 ("pudu_reference_search", lookup, ["query"]),28 ("pudu_module_reference", lookup, ["module"]),29 ("pudu_check", lookup, []),30 ("pudu_format", lookup, []),31 ("pudu_lint", lookup, []),32 ("pudu_run", (true, false, false, false), ["source"]),33 ("pudu_test", (false, false, false, true), []),34 ("pudu_hover", lookup, position),35 ("pudu_definition", lookup, position),36 ("pudu_references", lookup, position),37 ("pudu_completion", lookup, position),38 ("pudu_signature_help", lookup, position),39 ("pudu_code_actions", lookup, position),40 ("pudu_rename_preview", lookup, ["line", "character", "newName"]),41 ("pudu_document_symbols", lookup, []),42 ("pudu_workspace_symbols", lookup, ["query"]),43 ("pudu_toolchain", lookup, [])44 ]45}464748fn declared(spec: &ToolSpec.ToolSpec) -> (Str, (Bool, Bool, Bool, Bool), Array[Str]) {49 let hints = (spec.annotations.readOnly, spec.annotations.destructive, spec.annotations.idempotent, spec.annotations.openWorld)50 (spec.name, hints, spec.properties.filter(|held: ToolSpec.Property| held.required).map(|held: ToolSpec.Property| held.name))51}525354fn main() -> Int {55 let all = Tools.all()56 let names = all.map(|spec: ToolSpec.ToolSpec| spec.name)57 let described = all.map(|spec: ToolSpec.ToolSpec| ToolSpec.toJson(&spec))58 let handlers = Registry.handlers()59 let rename = Tools.find("pudu_rename_preview")60 let docs = described.filter(|tool: Json.Json| Access.text(&tool, "name") == Some("pudu_docs_search"))[0]61 let check = described.filter(|tool: Json.Json| Access.text(&tool, "name") == Some("pudu_check"))[0]62 let bare = described.filter(|tool: Json.Json| Access.text(&tool, "name") == Some("pudu_toolchain"))[0]63 let checks = Test.suite("Domain.Catalog.Tools", &[64 Test.equals("names are unique", &List.distinct(&names).length(), &names.length()),65 Test.all("names are well formed", &names, |name: Str| wellFormed(name)),66 Test.all("names carry the pudu_ prefix", &names, |name: Str| name.startsWith("pudu_")),67 Test.all("every tool has a handler", &names, |name: Str| Map.containsKey(&handlers, name)),68 Test.equals("every handler has a tool", &Map.size(&handlers), &names.length()),69 Test.all("every schema is a closed object", &described, |tool: Json.Json| Access.pathText(&tool, &["inputSchema", "type"]) == Some("object") && Json.path(&tool, &["inputSchema", "additionalProperties"]) == Some(Json.Boolean(false))),70 Test.all("every tool declares all four hints", &described, |tool: Json.Json| ["readOnlyHint", "destructiveHint", "idempotentHint", "openWorldHint"].filter(|hint: Str| Json.path(&tool, &["annotations", hint]) == None).isEmpty()),71 Test.all("no tool is destructive", &all, |spec: ToolSpec.ToolSpec| !spec.annotations.destructive),72 Test.all("a description is present", &all, |spec: ToolSpec.ToolSpec| !Text.isBlank(spec.description)),73 Test.all("no exclusive name is also required", &all, |spec: ToolSpec.ToolSpec| spec.properties.filter(|held: ToolSpec.Property| held.required && spec.exactlyOne.contains(held.name)).isEmpty()),74 Test.all("exclusive names are declared properties", &all, |spec: ToolSpec.ToolSpec| spec.exactlyOne.filter(|name: Str| !spec.properties.map(|held: ToolSpec.Property| held.name).contains(name)).isEmpty()),75 Test.present("a rename preview is offered", &rename),76 Test.equals("every tool declares its hints and required arguments", &all.map(|spec: ToolSpec.ToolSpec| declared(&spec)), &expected()),77 Test.equals("a tool without required arguments has no required member", &Json.path(&described.filter(|tool: Json.Json| Access.text(&tool, "name") == Some("pudu_test"))[0], &["inputSchema", "required"]), &None),78 Test.equals("required arguments reach the schema", &Json.path(&docs, &["inputSchema", "required"]), &Some(Access.texts(&["query"]))),79 Test.that("an exclusive pair is described as oneOf", Json.path(&check, &["inputSchema", "oneOf"]) != None),80 Test.equals("bounds reach the schema", &Access.pathInt(&docs, &["inputSchema", "properties", "limit", "maximum"]), &Some(25)),81 Test.absent("an unknown tool is not found", &Tools.find("pudu_nope")),82 Test.equals("a tool without arguments has no properties member", &Json.path(&bare, &["inputSchema", "properties"]), &None)83 ])84 let ran = Test.run(&checks)85 for failure in Test.failuresOf(&ran) {86 let _reported = Io.writeErrorLine(failure)87 }88 Test.report(&ran)89}90