mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2026-10-07 13:56:29 +00:00
- modelregistry: ModelInfo (description, purpose, tags, column docs) with external JSON loader, Describer fallback and gorm/bun/comment tag support - resolvemcp: surface descriptions in list_tables and describe_table, send a usage guide as MCP server instructions, add package docs - add BuildCatalog/ExportCatalog to write a JSON or Markdown API catalogue - document the descriptions map and catalogue in the README
69 lines
2.1 KiB
Go
69 lines
2.1 KiB
Go
package modelregistry
|
|
|
|
import (
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
type infoModel struct {
|
|
ID int `json:"id" gorm:"primaryKey;comment:Row id"`
|
|
Email string `json:"email" bun:"email,comment:Login address"`
|
|
Name string `json:"name" note:"Display name"`
|
|
Plain string `json:"plain"`
|
|
}
|
|
|
|
func (infoModel) ModelDescription() string { return " From the model " }
|
|
|
|
func TestFieldComment(t *testing.T) {
|
|
typ := reflect.TypeOf(infoModel{})
|
|
want := map[string]string{"ID": "Row id", "Email": "Login address", "Name": "Display name", "Plain": ""}
|
|
for field, exp := range want {
|
|
sf, _ := typ.FieldByName(field)
|
|
if got := FieldComment(sf); got != exp {
|
|
t.Errorf("%s = %q, want %q", field, got, exp)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestModelInfoPrecedence(t *testing.T) {
|
|
r := NewModelRegistry()
|
|
if err := r.RegisterModel("public.items", infoModel{}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := r.ResolveModelInfo("public.items").Description; got != "From the model" {
|
|
t.Errorf("describer fallback = %q", got)
|
|
}
|
|
|
|
n, err := r.LoadModelInfo(strings.NewReader(
|
|
`{"public.items":{"description":"From file","tags":["a"],"columns":{"email":"Mail"}},"public.later":{"purpose":"p"}}`))
|
|
if err != nil || n != 2 {
|
|
t.Fatalf("load n=%d err=%v", n, err)
|
|
}
|
|
info := r.ResolveModelInfo("public.items")
|
|
if info.Description != "From file" || info.Columns["email"] != "Mail" || len(info.Tags) != 1 {
|
|
t.Errorf("file info = %+v", info)
|
|
}
|
|
if _, ok := r.GetModelInfo("public.later"); !ok {
|
|
t.Error("info for a not-yet-registered model must be kept")
|
|
}
|
|
|
|
// returned info is a copy
|
|
info.Columns["email"] = "changed"
|
|
if got, _ := r.GetModelInfo("public.items"); got.Columns["email"] != "Mail" {
|
|
t.Error("GetModelInfo leaked internal map")
|
|
}
|
|
}
|
|
|
|
func TestLoadModelInfoRejectsBadInput(t *testing.T) {
|
|
r := NewModelRegistry()
|
|
for _, in := range []string{`not json`, `{"a":{"descripton":"typo"}}`} {
|
|
if _, err := r.LoadModelInfo(strings.NewReader(in)); err == nil {
|
|
t.Errorf("expected error for %q", in)
|
|
}
|
|
}
|
|
if _, err := r.LoadModelInfoFile("/nonexistent/x.json"); err == nil {
|
|
t.Error("expected error for missing file")
|
|
}
|
|
}
|