mirror of
https://github.com/larksuite/cli.git
synced 2026-07-07 09:11:44 +08:00
255 lines
7.9 KiB
Go
255 lines
7.9 KiB
Go
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package apps
|
|
|
|
import (
|
|
"reflect"
|
|
"testing"
|
|
)
|
|
|
|
func TestMaskAPIKey(t *testing.T) {
|
|
cases := map[string]string{
|
|
"": "****",
|
|
"abcd": "****",
|
|
"xxxxxxxxxxxx": "****xxxx",
|
|
}
|
|
for in, want := range cases {
|
|
if got := maskAPIKey(in); got != want {
|
|
t.Errorf("maskAPIKey(%q) = %q, want %q", in, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRedactKeyInfo_StripsRawKey(t *testing.T) {
|
|
in := map[string]interface{}{
|
|
"api_key_id": "k1",
|
|
"api_key": "xxxxxxxxxxxx",
|
|
"name": "partner-test",
|
|
"status": float64(1),
|
|
}
|
|
out := redactKeyInfo(in)
|
|
if _, ok := out["api_key"]; ok {
|
|
t.Fatalf("redactKeyInfo must strip api_key, got %v", out)
|
|
}
|
|
if out["key_preview"] != "****xxxx" {
|
|
t.Errorf("key_preview = %v, want ****xxxx", out["key_preview"])
|
|
}
|
|
if out["name"] != "partner-test" || out["api_key_id"] != "k1" {
|
|
t.Errorf("non-secret fields must be preserved, got %v", out)
|
|
}
|
|
// input not mutated
|
|
if _, ok := in["api_key"]; !ok {
|
|
t.Errorf("redactKeyInfo must not mutate input")
|
|
}
|
|
}
|
|
|
|
func TestParseScopeAPI(t *testing.T) {
|
|
t.Run("valid", func(t *testing.T) {
|
|
info, err := parseScopeAPI("GET /openapi/v1/orders")
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if info["http_method"] != "GET" {
|
|
t.Errorf("http_method = %v, want GET", info["http_method"])
|
|
}
|
|
if info["http_path"] != "/openapi/v1/orders" {
|
|
t.Errorf("http_path = %v, want /openapi/v1/orders", info["http_path"])
|
|
}
|
|
})
|
|
t.Run("lowercase method uppercased", func(t *testing.T) {
|
|
info, err := parseScopeAPI("post /openapi/x")
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if info["http_method"] != "POST" {
|
|
t.Errorf("http_method = %v, want POST", info["http_method"])
|
|
}
|
|
})
|
|
t.Run("too few fields", func(t *testing.T) {
|
|
if _, err := parseScopeAPI("GET"); err == nil {
|
|
t.Errorf("one-word input must error")
|
|
}
|
|
})
|
|
t.Run("too many fields", func(t *testing.T) {
|
|
if _, err := parseScopeAPI("GET /openapi/x extra"); err == nil {
|
|
t.Errorf("three-word input must error")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestBuildRequestScope(t *testing.T) {
|
|
t.Run("nothing set -> nil", func(t *testing.T) {
|
|
rs, err := buildRequestScope(false, nil, "")
|
|
if err != nil || rs != nil {
|
|
t.Fatalf("expected nil,nil got rs=%v err=%v", rs, err)
|
|
}
|
|
})
|
|
t.Run("scope-all only", func(t *testing.T) {
|
|
rs, err := buildRequestScope(true, nil, "")
|
|
if err != nil {
|
|
t.Fatalf("err = %v", err)
|
|
}
|
|
m := rs.(map[string]interface{})
|
|
if m["allow_all"] != true {
|
|
t.Errorf("allow_all = %v, want true", m["allow_all"])
|
|
}
|
|
if _, ok := m["http_infos"]; ok {
|
|
t.Errorf("http_infos should not appear when no scope-api provided")
|
|
}
|
|
})
|
|
t.Run("scope-api adds http_infos", func(t *testing.T) {
|
|
rs, err := buildRequestScope(false, []string{"GET /openapi/x"}, "")
|
|
if err != nil {
|
|
t.Fatalf("err = %v", err)
|
|
}
|
|
m := rs.(map[string]interface{})
|
|
if m["allow_all"] != false {
|
|
t.Errorf("allow_all = %v, want false", m["allow_all"])
|
|
}
|
|
infos := m["http_infos"].([]interface{})
|
|
if len(infos) != 1 {
|
|
t.Fatalf("http_infos len = %d, want 1", len(infos))
|
|
}
|
|
info := infos[0].(map[string]interface{})
|
|
if info["http_method"] != "GET" || info["http_path"] != "/openapi/x" {
|
|
t.Errorf("info = %v", info)
|
|
}
|
|
})
|
|
t.Run("raw scope passthrough", func(t *testing.T) {
|
|
rs, err := buildRequestScope(false, nil, `{"allow_all":true}`)
|
|
if err != nil {
|
|
t.Fatalf("err = %v", err)
|
|
}
|
|
m := rs.(map[string]interface{})
|
|
if m["allow_all"] != true {
|
|
t.Errorf("allow_all = %v, want true", m["allow_all"])
|
|
}
|
|
})
|
|
t.Run("raw + scope-all -> error", func(t *testing.T) {
|
|
if _, err := buildRequestScope(true, nil, `{"allow_all":true}`); err == nil {
|
|
t.Errorf("raw + scope-all must error")
|
|
}
|
|
})
|
|
t.Run("raw + scope-api -> error", func(t *testing.T) {
|
|
if _, err := buildRequestScope(false, []string{"GET /openapi/x"}, `{"allow_all":true}`); err == nil {
|
|
t.Errorf("raw + scope-api must error")
|
|
}
|
|
})
|
|
t.Run("invalid raw json -> error", func(t *testing.T) {
|
|
if _, err := buildRequestScope(false, nil, "{bad"); err == nil {
|
|
t.Errorf("invalid json must error")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestBuildKeyConfig(t *testing.T) {
|
|
t.Run("nothing set -> nil", func(t *testing.T) {
|
|
cfg, err := buildKeyConfig(false, nil, "", false, false)
|
|
if err != nil || cfg != nil {
|
|
t.Fatalf("empty -> nil, got cfg=%v err=%v", cfg, err)
|
|
}
|
|
})
|
|
t.Run("scope-all -> snake_case request_scope", func(t *testing.T) {
|
|
cfg, err := buildKeyConfig(true, nil, "", false, false)
|
|
if err != nil {
|
|
t.Fatalf("err = %v", err)
|
|
}
|
|
rs := cfg["request_scope"].(map[string]interface{})
|
|
if rs["allow_all"] != true {
|
|
t.Errorf("allow_all = %v, want true", rs["allow_all"])
|
|
}
|
|
if _, ok := cfg["is_allow_access_preview"]; ok {
|
|
t.Errorf("is_allow_access_preview should not appear")
|
|
}
|
|
})
|
|
t.Run("scope-api -> snake_case http_infos", func(t *testing.T) {
|
|
cfg, err := buildKeyConfig(false, []string{"GET /openapi/x"}, "", false, false)
|
|
if err != nil {
|
|
t.Fatalf("err = %v", err)
|
|
}
|
|
rs := cfg["request_scope"].(map[string]interface{})
|
|
if rs["allow_all"] != false {
|
|
t.Errorf("allow_all = %v, want false", rs["allow_all"])
|
|
}
|
|
infos := rs["http_infos"].([]interface{})
|
|
if len(infos) != 1 {
|
|
t.Fatalf("http_infos len = %d, want 1", len(infos))
|
|
}
|
|
info := infos[0].(map[string]interface{})
|
|
if info["http_method"] != "GET" || info["http_path"] != "/openapi/x" {
|
|
t.Errorf("info = %v", info)
|
|
}
|
|
})
|
|
t.Run("raw scope passthrough", func(t *testing.T) {
|
|
cfg, err := buildKeyConfig(false, nil, `{"allow_all":true}`, false, false)
|
|
if err != nil {
|
|
t.Fatalf("err = %v", err)
|
|
}
|
|
rs := cfg["request_scope"].(map[string]interface{})
|
|
if rs["allow_all"] != true {
|
|
t.Errorf("allow_all = %v", rs["allow_all"])
|
|
}
|
|
})
|
|
t.Run("allow-preview only -> is_allow_access_preview", func(t *testing.T) {
|
|
cfg, err := buildKeyConfig(false, nil, "", true, true)
|
|
if err != nil {
|
|
t.Fatalf("err = %v", err)
|
|
}
|
|
if _, ok := cfg["request_scope"]; ok {
|
|
t.Errorf("request_scope should not appear when not set")
|
|
}
|
|
if cfg["is_allow_access_preview"] != true {
|
|
t.Errorf("is_allow_access_preview = %v, want true", cfg["is_allow_access_preview"])
|
|
}
|
|
})
|
|
t.Run("scope-all + allow-preview -> both snake_case keys", func(t *testing.T) {
|
|
cfg, err := buildKeyConfig(true, nil, "", true, false)
|
|
if err != nil {
|
|
t.Fatalf("err = %v", err)
|
|
}
|
|
if _, ok := cfg["request_scope"]; !ok {
|
|
t.Errorf("request_scope missing")
|
|
}
|
|
if cfg["is_allow_access_preview"] != false {
|
|
t.Errorf("is_allow_access_preview = %v, want false", cfg["is_allow_access_preview"])
|
|
}
|
|
// ensure no camelCase keys
|
|
if _, ok := cfg["requestScope"]; ok {
|
|
t.Errorf("found camelCase key requestScope — must use snake_case")
|
|
}
|
|
if _, ok := cfg["isAllowAccessPreview"]; ok {
|
|
t.Errorf("found camelCase key isAllowAccessPreview — must use snake_case")
|
|
}
|
|
})
|
|
t.Run("raw + scope-all -> error", func(t *testing.T) {
|
|
if _, err := buildKeyConfig(true, nil, `{"allow_all":true}`, false, false); err == nil {
|
|
t.Errorf("raw + scope-all must error")
|
|
}
|
|
})
|
|
t.Run("invalid json -> error", func(t *testing.T) {
|
|
if _, err := buildKeyConfig(false, nil, "{bad", false, false); err == nil {
|
|
t.Errorf("invalid json must error")
|
|
}
|
|
})
|
|
t.Run("no camelCase keys emitted", func(t *testing.T) {
|
|
cfg, err := buildKeyConfig(false, []string{"GET /openapi/x"}, "", true, true)
|
|
if err != nil {
|
|
t.Fatalf("err = %v", err)
|
|
}
|
|
if _, ok := cfg["requestScope"]; ok {
|
|
t.Errorf("camelCase requestScope must not appear")
|
|
}
|
|
if _, ok := cfg["isAllowAccessPreview"]; ok {
|
|
t.Errorf("camelCase isAllowAccessPreview must not appear")
|
|
}
|
|
rs := cfg["request_scope"].(map[string]interface{})
|
|
infos := rs["http_infos"].([]interface{})
|
|
info := infos[0].(map[string]interface{})
|
|
wantInfo := map[string]interface{}{"http_method": "GET", "http_path": "/openapi/x"}
|
|
if !reflect.DeepEqual(info, wantInfo) {
|
|
t.Errorf("info = %v, want %v", info, wantInfo)
|
|
}
|
|
})
|
|
}
|