mirror of
https://github.com/larksuite/cli.git
synced 2026-07-03 14:02:43 +08:00
141 lines
4.3 KiB
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)
|
|
}
|
|
}
|