Files
larksuite-cli/internal/meta/meta_test.go

141 lines
4.3 KiB
Go

// Copyright (c) 2026 Lark Technologies Pte. Ltd.
// SPDX-License-Identifier: MIT
package meta
import (
"reflect"
"testing"
)
const sampleJSON = `{
"version": "1.0.0",
"services": [
{
"name": "im",
"servicePath": "/open-apis/im/v1",
"resources": {
"chat.members": {
"methods": {
"create": {
"httpMethod": "POST",
"risk": "high-risk-write",
"parameters": {
"member_id_type": {"type": "string", "location": "query", "options": [{"value": "open_id"}, {"value": "user_id"}]},
"chat_id": {"type": "string", "location": "path", "required": true, "example": "oc_x"},
"x_header": {"type": "string", "location": "header"}
},
"requestBody": {
"id_list": {"type": "list", "required": true},
"avatar": {"type": "file"}
}
}
}
}
}
}
]
}`
func loadSample(t *testing.T) (Resource, Method) {
t.Helper()
reg, err := Parse([]byte(sampleJSON))
if err != nil {
t.Fatalf("Parse: %v", err)
}
res := reg.Services[0].ResourceList()
if len(res) != 1 {
t.Fatalf("want 1 resource, got %d", len(res))
}
methods := res[0].MethodList()
if len(methods) != 1 {
t.Fatalf("want 1 method, got %d", len(methods))
}
return res[0], methods[0]
}
func TestParse_TypedAndNameInjected(t *testing.T) {
res, m := loadSample(t)
if res.Name != "chat.members" {
t.Errorf("resource name = %q, want chat.members", res.Name)
}
if m.Name != "create" || m.HTTPMethod != "POST" || m.Risk != "high-risk-write" {
t.Errorf("method = %+v", m)
}
}
func TestMethod_AccessorsSortedByName(t *testing.T) {
_, m := loadSample(t)
// Params: path/query only (header dropped), sorted by name.
var params []string
for _, f := range m.Params() {
params = append(params, f.Name)
}
if want := []string{"chat_id", "member_id_type"}; !reflect.DeepEqual(params, want) {
t.Errorf("Params() = %v, want %v (sorted, header dropped)", params, want)
}
if d := m.Data(); len(d) != 1 || d[0].Name != "id_list" {
t.Errorf("Data() = %+v, want [id_list]", d)
}
if f := m.Files(); len(f) != 1 || f[0].Name != "avatar" {
t.Errorf("Files() = %+v, want [avatar]", f)
}
}
func TestField_FlagNameAndOptions(t *testing.T) {
_, m := loadSample(t)
by := make(map[string]Field)
for _, f := range m.Params() {
by[f.Name] = f
}
if got := by["chat_id"].FlagName(); got != "chat-id" {
t.Errorf("FlagName = %q, want chat-id", got)
}
if !by["chat_id"].Required || by["chat_id"].Example != "oc_x" {
t.Errorf("chat_id required/example wrong: %+v", by["chat_id"])
}
opts := by["member_id_type"].Options
if len(opts) != 2 || opts[0].Value != "open_id" || opts[1].Value != "user_id" {
t.Errorf("member_id_type options = %+v", opts)
}
}
// TestParse_TolerantOptionValue guards against whole-catalog blanking: a single
// options[].value that arrives as a JSON number (not the usual quoted string)
// must NOT fail the entire registry unmarshal. Option.Value is `any`, so it
// parses and coerces like Enum instead of returning an empty Registry.
func TestParse_TolerantOptionValue(t *testing.T) {
data := []byte(`{"services":[{"name":"im","servicePath":"/x","resources":{
"chat":{"methods":{"create":{"parameters":{
"flag":{"type":"integer","location":"query","options":[{"value":0,"description":"off"},{"value":1,"description":"on"}]}
}}}}}}]}`)
reg, err := Parse(data)
if err != nil {
t.Fatalf("Parse failed on numeric option value (would blank the catalog): %v", err)
}
if len(reg.Services) != 1 {
t.Fatalf("expected 1 service, got %d (catalog blanked)", len(reg.Services))
}
// The numeric option coerces into the typed enum as sorted int64 (not
// float64): the integer field's canonical type drives normalization.
m, _ := reg.Services[0].Resource("chat")
method, _ := m.Method("create")
by := map[string]Field{}
for _, f := range method.Params() {
by[f.Name] = f
}
if got := by["flag"].EnumValues(); !reflect.DeepEqual(got, []any{int64(0), int64(1)}) {
t.Errorf("numeric-valued enum did not coerce to sorted int64: %#v", got)
}
}
func TestParse_Empty(t *testing.T) {
reg, err := Parse(nil)
if err != nil || len(reg.Services) != 0 {
t.Fatalf("Parse(nil) = %+v, %v", reg, err)
}
}