yao/cmd/doc/doc_test.go
Max efc84fbb97 feat(doc): add yao doc CLI commands and YAML documentation for all packages
Implement yao doc process list/inspect/validate and yao doc runtime
list/inspect/validate commands. Validate uses engine addressing logic
(process.Of) and checks dynamic-ID group registries (model, store, fs,
task, schedule) to verify resources actually exist.

- cmd/doc/: CLI command tree with process and runtime subcommands
- cmd/root.go: wire docCmd into rootCmd
- 27 process doc.yml + doc.go pairs across yao packages
- cmd/doc/doc_test.go: integration tests

Made-with: Cursor
2026-04-23 21:37:43 +08:00

345 lines
6.7 KiB
Go

package doc
import (
"testing"
"github.com/yaoapp/gou/doc"
)
const sampleProcessYAML = `
group: test
type: process
entries:
- name: test.hello
desc: Greet someone
args:
- name: name
type: string
required: true
return:
type: string
- name: test.add
desc: Add two numbers
args:
- name: a
type: number
required: true
- name: b
type: number
required: true
return:
type: number
`
const sampleShortNameYAML = `
group: http
type: process
entries:
- name: get
desc: Send HTTP GET
args:
- name: url
type: string
required: true
return:
type: object
- name: post
desc: Send HTTP POST
args:
- name: url
type: string
required: true
return:
type: object
`
const sampleSubGroupYAML = `
group: utils
type: process
entries:
- name: throw.Forbidden
desc: Throw 403
return:
type: void
`
const sampleRuntimeYAML = `
group: objects
type: js_object
entries:
- name: log
desc: Logging utility
methods:
- name: Info
desc: Log info
args:
- name: msg
type: string
required: true
return:
type: void
`
const sampleClassYAML = `
group: objects
type: js_class
entries:
- name: FS
desc: File system operations
args:
- name: name
type: string
methods:
- name: ReadFile
desc: Read file
args:
- name: path
type: string
required: true
return:
type: string
`
const sampleFunctionYAML = `
group: functions
type: js_function
entries:
- name: Process
desc: Execute a Yao process
args:
- name: name
type: string
required: true
return:
type: any
`
func setupTestData(t *testing.T) {
t.Helper()
doc.Reset()
for _, yaml := range []string{
sampleProcessYAML, sampleShortNameYAML, sampleSubGroupYAML,
sampleRuntimeYAML, sampleClassYAML, sampleFunctionYAML,
} {
if err := doc.LoadYAML([]byte(yaml)); err != nil {
t.Fatalf("LoadYAML failed: %v", err)
}
}
}
func TestNormalisedNames(t *testing.T) {
setupTestData(t)
// "test.hello" already has prefix → stays "test.hello"
e, ok := doc.Get(doc.TypeProcess, "test.hello")
if !ok {
t.Fatal("test.hello not found")
}
if e.Name != "test.hello" {
t.Errorf("name = %q, want test.hello", e.Name)
}
// short name "get" with group "http" → normalised to "http.get"
e, ok = doc.Get(doc.TypeProcess, "http.get")
if !ok {
t.Fatal("http.get not found — short name was not normalised")
}
if e.Name != "http.get" {
t.Errorf("name = %q, want http.get", e.Name)
}
// sub-group name "throw.Forbidden" with group "utils" → "utils.throw.Forbidden"
e, ok = doc.Get(doc.TypeProcess, "utils.throw.Forbidden")
if !ok {
t.Fatal("utils.throw.Forbidden not found")
}
if e.Name != "utils.throw.Forbidden" {
t.Errorf("name = %q, want utils.throw.Forbidden", e.Name)
}
}
func TestProcessListCommand(t *testing.T) {
setupTestData(t)
entries := doc.List(doc.TypeProcess)
if len(entries) != 5 {
t.Errorf("expected 5 process entries, got %d", len(entries))
}
}
func TestProcessListWithGroupFilter(t *testing.T) {
setupTestData(t)
entries := doc.List(doc.TypeProcess, doc.ListOption{Group: "http"})
if len(entries) != 2 {
t.Errorf("expected 2 http entries, got %d", len(entries))
}
entries = doc.List(doc.TypeProcess, doc.ListOption{Group: "nonexistent"})
if len(entries) != 0 {
t.Errorf("expected 0, got %d", len(entries))
}
}
func TestProcessListWithSearch(t *testing.T) {
setupTestData(t)
entries := doc.List(doc.TypeProcess, doc.ListOption{Search: "hello"})
if len(entries) != 1 {
t.Fatalf("expected 1, got %d", len(entries))
}
if entries[0].Name != "test.hello" {
t.Errorf("expected test.hello, got %s", entries[0].Name)
}
}
func TestProcessGet_FullName(t *testing.T) {
setupTestData(t)
e, ok := doc.Get(doc.TypeProcess, "http.post")
if !ok {
t.Fatal("http.post not found")
}
if e.Desc != "Send HTTP POST" {
t.Errorf("desc = %q", e.Desc)
}
}
func TestProcessValidate_OK(t *testing.T) {
setupTestData(t)
r := doc.Validate(doc.TypeProcess, "test.hello")
if !r.Valid {
t.Fatal("expected valid")
}
if r.Status != "ok" {
t.Errorf("status = %q", r.Status)
}
}
func TestProcessValidate_FullName(t *testing.T) {
setupTestData(t)
r := doc.Validate(doc.TypeProcess, "http.get")
if !r.Valid {
t.Fatal("expected valid for http.get")
}
}
func TestProcessValidate_NotFound(t *testing.T) {
setupTestData(t)
r := doc.Validate(doc.TypeProcess, "does.not.exist")
if r.Valid {
t.Fatal("expected not valid")
}
}
func TestProcessValidate_Suggestions(t *testing.T) {
setupTestData(t)
r := doc.Validate(doc.TypeProcess, "test.hell")
if r.Valid {
t.Fatal("expected not valid")
}
found := false
for _, s := range r.Suggestion {
if s == "test.hello" {
found = true
}
}
if !found {
t.Errorf("expected test.hello in suggestions: %v", r.Suggestion)
}
}
func TestRuntimeList(t *testing.T) {
setupTestData(t)
objects := doc.List(doc.TypeJSObject)
if len(objects) != 1 {
t.Errorf("expected 1 object, got %d", len(objects))
}
classes := doc.List(doc.TypeJSClass)
if len(classes) != 1 {
t.Errorf("expected 1 class, got %d", len(classes))
}
functions := doc.List(doc.TypeJSFunction)
if len(functions) != 1 {
t.Errorf("expected 1 function, got %d", len(functions))
}
}
func TestRuntimeValidate_Object(t *testing.T) {
setupTestData(t)
r := doc.Validate(doc.TypeJSObject, "log")
if !r.Valid {
t.Fatal("expected valid")
}
}
func TestRuntimeValidate_Class(t *testing.T) {
setupTestData(t)
r := doc.Validate(doc.TypeJSClass, "FS")
if !r.Valid {
t.Fatal("expected valid")
}
}
func TestRuntimeValidate_Function(t *testing.T) {
setupTestData(t)
r := doc.Validate(doc.TypeJSFunction, "Process")
if !r.Valid {
t.Fatal("expected valid")
}
}
func TestFindRuntime(t *testing.T) {
setupTestData(t)
e := findRuntime("log")
if e == nil {
t.Fatal("expected to find log")
}
e = findRuntime("FS")
if e == nil {
t.Fatal("expected to find FS")
}
e = findRuntime("nonexistent")
if e != nil {
t.Fatal("expected nil")
}
}
func TestFormatArgs(t *testing.T) {
args := []doc.TypeValue{
{Name: "name", Type: "string", Required: true},
{Name: "options", Type: "object"},
}
s := formatArgs(args)
if s != "(name string, options object?)" {
t.Errorf("formatArgs = %q", s)
}
s = formatArgs(nil)
if s != "()" {
t.Errorf("formatArgs nil = %q", s)
}
}
func TestFormatReturn(t *testing.T) {
if formatReturn(nil) != "void" {
t.Error("nil should return void")
}
tv := &doc.TypeValue{Type: "string"}
if formatReturn(tv) != "string" {
t.Error("string type mismatch")
}
}