mirror of
https://github.com/sipeed/picoclaw.git
synced 2026-06-12 18:08:54 +00:00
a8d0b03515
Persist channel settings through the current channel_list schema, keeping common channel fields at the top level and channel-specific fields under settings. Return common fields and default config shapes from channel config endpoints, and add coverage for nested patches, missing channel defaults, and secret handling.
196 lines
5.7 KiB
Go
196 lines
5.7 KiB
Go
package api
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/sipeed/picoclaw/pkg/config"
|
|
)
|
|
|
|
func TestHandleGetChannelConfig_ReturnsSecretPresenceWithoutLeakingSecrets(t *testing.T) {
|
|
configPath, cleanup := setupOAuthTestEnv(t)
|
|
defer cleanup()
|
|
|
|
cfg, err := config.LoadConfig(configPath)
|
|
if err != nil {
|
|
t.Fatalf("LoadConfig() error = %v", err)
|
|
}
|
|
bc := cfg.Channels[config.ChannelFeishu]
|
|
bc.Enabled = true
|
|
decoded, err := bc.GetDecoded()
|
|
if err != nil {
|
|
t.Fatalf("GetDecoded() error = %v", err)
|
|
}
|
|
bcfg := decoded.(*config.FeishuSettings)
|
|
bcfg.AppID = "cli_test_app"
|
|
bcfg.AppSecret = *config.NewSecureString("feishu-secret-from-security")
|
|
bc.AllowFrom = config.FlexibleStringSlice{"ou_test_user"}
|
|
if err := config.SaveConfig(configPath, cfg); err != nil {
|
|
t.Fatalf("SaveConfig() error = %v", err)
|
|
}
|
|
|
|
h := NewHandler(configPath)
|
|
mux := http.NewServeMux()
|
|
h.RegisterRoutes(mux)
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/api/channels/feishu/config", nil)
|
|
rec := httptest.NewRecorder()
|
|
mux.ServeHTTP(rec, req)
|
|
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf(
|
|
"GET /api/channels/feishu/config status = %d, want %d, body=%s",
|
|
rec.Code,
|
|
http.StatusOK,
|
|
rec.Body.String(),
|
|
)
|
|
}
|
|
if strings.Contains(rec.Body.String(), "feishu-secret-from-security") {
|
|
t.Fatalf("response leaked secret value: %s", rec.Body.String())
|
|
}
|
|
|
|
var resp struct {
|
|
Config map[string]any `json:"config"`
|
|
ConfiguredSecrets []string `json:"configured_secrets"`
|
|
ConfigKey string `json:"config_key"`
|
|
Variant string `json:"variant"`
|
|
}
|
|
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
|
|
t.Fatalf("json.Unmarshal() error = %v", err)
|
|
}
|
|
|
|
if got := resp.ConfigKey; got != "feishu" {
|
|
t.Fatalf("config_key = %q, want %q", got, "feishu")
|
|
}
|
|
if got := resp.Config["app_id"]; got != "cli_test_app" {
|
|
t.Fatalf("config.app_id = %#v, want %q", got, "cli_test_app")
|
|
}
|
|
if got := resp.Config["enabled"]; got != true {
|
|
t.Fatalf("config.enabled = %#v, want true", got)
|
|
}
|
|
allowFrom, ok := resp.Config["allow_from"].([]any)
|
|
if !ok || len(allowFrom) != 1 || allowFrom[0] != "ou_test_user" {
|
|
t.Fatalf("config.allow_from = %#v, want [\"ou_test_user\"]", resp.Config["allow_from"])
|
|
}
|
|
if _, exists := resp.Config["app_secret"]; exists {
|
|
t.Fatalf("config should omit app_secret, got %#v", resp.Config["app_secret"])
|
|
}
|
|
if len(resp.ConfiguredSecrets) != 1 || resp.ConfiguredSecrets[0] != "app_secret" {
|
|
t.Fatalf("configured_secrets = %#v, want [\"app_secret\"]", resp.ConfiguredSecrets)
|
|
}
|
|
}
|
|
|
|
func TestHandleGetChannelConfig_ReturnsNotFoundForUnknownChannel(t *testing.T) {
|
|
configPath, cleanup := setupOAuthTestEnv(t)
|
|
defer cleanup()
|
|
|
|
h := NewHandler(configPath)
|
|
mux := http.NewServeMux()
|
|
h.RegisterRoutes(mux)
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/api/channels/not-a-channel/config", nil)
|
|
rec := httptest.NewRecorder()
|
|
mux.ServeHTTP(rec, req)
|
|
|
|
if rec.Code != http.StatusNotFound {
|
|
t.Fatalf("GET /api/channels/not-a-channel/config status = %d, want %d", rec.Code, http.StatusNotFound)
|
|
}
|
|
}
|
|
|
|
func TestHandleGetChannelConfig_ReturnsCommonFieldsWhenSettingsEmpty(t *testing.T) {
|
|
configPath, cleanup := setupOAuthTestEnv(t)
|
|
defer cleanup()
|
|
|
|
cfg, err := config.LoadConfig(configPath)
|
|
if err != nil {
|
|
t.Fatalf("LoadConfig() error = %v", err)
|
|
}
|
|
bc := cfg.Channels[config.ChannelFeishu]
|
|
bc.Enabled = true
|
|
bc.AllowFrom = config.FlexibleStringSlice{"ou_common_user"}
|
|
if err := config.SaveConfig(configPath, cfg); err != nil {
|
|
t.Fatalf("SaveConfig() error = %v", err)
|
|
}
|
|
|
|
h := NewHandler(configPath)
|
|
mux := http.NewServeMux()
|
|
h.RegisterRoutes(mux)
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/api/channels/feishu/config", nil)
|
|
rec := httptest.NewRecorder()
|
|
mux.ServeHTTP(rec, req)
|
|
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf(
|
|
"GET /api/channels/feishu/config status = %d, want %d, body=%s",
|
|
rec.Code,
|
|
http.StatusOK,
|
|
rec.Body.String(),
|
|
)
|
|
}
|
|
|
|
var resp struct {
|
|
Config map[string]any `json:"config"`
|
|
}
|
|
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
|
|
t.Fatalf("json.Unmarshal() error = %v", err)
|
|
}
|
|
if got := resp.Config["enabled"]; got != true {
|
|
t.Fatalf("config.enabled = %#v, want true", got)
|
|
}
|
|
allowFrom, ok := resp.Config["allow_from"].([]any)
|
|
if !ok || len(allowFrom) != 1 || allowFrom[0] != "ou_common_user" {
|
|
t.Fatalf("config.allow_from = %#v, want [\"ou_common_user\"]", resp.Config["allow_from"])
|
|
}
|
|
}
|
|
|
|
func TestHandleGetChannelConfig_ReturnsDefaultShapeForMissingChannel(t *testing.T) {
|
|
configPath, cleanup := setupOAuthTestEnv(t)
|
|
defer cleanup()
|
|
|
|
cfg, err := config.LoadConfig(configPath)
|
|
if err != nil {
|
|
t.Fatalf("LoadConfig() error = %v", err)
|
|
}
|
|
delete(cfg.Channels, config.ChannelIRC)
|
|
if err := config.SaveConfig(configPath, cfg); err != nil {
|
|
t.Fatalf("SaveConfig() error = %v", err)
|
|
}
|
|
|
|
h := NewHandler(configPath)
|
|
mux := http.NewServeMux()
|
|
h.RegisterRoutes(mux)
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/api/channels/irc/config", nil)
|
|
rec := httptest.NewRecorder()
|
|
mux.ServeHTTP(rec, req)
|
|
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf(
|
|
"GET /api/channels/irc/config status = %d, want %d, body=%s",
|
|
rec.Code,
|
|
http.StatusOK,
|
|
rec.Body.String(),
|
|
)
|
|
}
|
|
|
|
var resp struct {
|
|
Config map[string]any `json:"config"`
|
|
}
|
|
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
|
|
t.Fatalf("json.Unmarshal() error = %v", err)
|
|
}
|
|
if got := resp.Config["server"]; got != "" {
|
|
t.Fatalf("config.server = %#v, want empty string", got)
|
|
}
|
|
if got := resp.Config["nick"]; got != "picoclaw" {
|
|
t.Fatalf("config.nick = %#v, want %q", got, "picoclaw")
|
|
}
|
|
if got := resp.Config["enabled"]; got != false {
|
|
t.Fatalf("config.enabled = %#v, want false", got)
|
|
}
|
|
}
|