api/api_test.go

257 lines
7.3 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package api_test
import (
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"
"apigo.cc/go/api"
"apigo.cc/go/encoding"
)
// =========================================================================
// 1. 配置继承与合并测试
// =========================================================================
func TestConfigInheritance(t *testing.T) {
api.GlobalConfigs = map[string]any{} // 确保环境干净
api.GlobalConfigs["api"] = map[string]any{
"mockSvc": map[string]any{
"url": "https://api.mock.com",
"signer": "mockSigner",
"config": map[string]any{
"secretId": "globalId",
},
"actions": map[string]any{
"subSvc": map[string]any{
"config": map[string]any{
"service": "subSvc",
},
"actions": map[string]any{
"doAction": map[string]any{
"url": "/subSvc/doAction",
"config": map[string]any{
"secretId": "overrideId",
},
},
},
},
},
},
}
cfg := api.GetActionConfig("mockSvc.subSvc.doAction")
if cfg == nil {
t.Fatal("config not found")
}
if cfg["url"] != "/subSvc/doAction" {
t.Errorf("expected url /subSvc/doAction, got %v", cfg["url"])
}
if cfg["signer"] != "mockSigner" {
t.Errorf("expected signer mockSigner, got %v", cfg["signer"])
}
actionConfig, ok := cfg["config"].(map[string]any)
if !ok {
t.Fatal("action config not found or invalid type")
}
if actionConfig["secretId"] != "overrideId" {
t.Errorf("expected secretId overrideId, got %v", actionConfig["secretId"])
}
if actionConfig["service"] != "subSvc" {
t.Errorf("expected service subSvc, got %v", actionConfig["service"])
}
}
// =========================================================================
// 2. 签名器逻辑测试
// =========================================================================
func TestBuiltinSigners(t *testing.T) {
// 测试 Basic 签名器
req := &api.HttpRequest{Headers: make(map[string]string)}
config := map[string]any{
"username": "admin",
"password": "123",
}
signer := api.GetSigner("basic")
if signer == nil {
t.Fatal("basic signer not found")
}
if err := signer.Sign(req, config); err != nil {
t.Fatal(err)
}
expected := "Basic " + encoding.Base64ToString([]byte("admin:123"))
if req.Headers["Authorization"] != expected {
t.Errorf("expected %s, got %s", expected, req.Headers["Authorization"])
}
// 测试 Bearer 签名器
req = &api.HttpRequest{Headers: make(map[string]string)}
config = map[string]any{
"token": "secret-token",
}
signer = api.GetSigner("bearer")
if signer == nil {
t.Fatal("bearer signer not found")
}
signer.Sign(req, config)
if req.Headers["Authorization"] != "Bearer secret-token" {
t.Errorf("expected Bearer secret-token, got %s", req.Headers["Authorization"])
}
}
// =========================================================================
// 3. 完整调用与参数注入测试 (无状态网络环境)
// =========================================================================
// 模拟外部专有签名器
type mockExtSigner struct{}
func (s *mockExtSigner) Sign(req *api.HttpRequest, rawConfig map[string]any) error {
// 获取动态配置信息
service, _ := rawConfig["service"].(string)
// 签名器内部处理 URL 动态重写
if strings.Contains(req.Url, "{{.service}}") {
req.Url = strings.ReplaceAll(req.Url, "{{.service}}", service)
}
// 添加模拟签名
req.Headers["Authorization"] = "Mock-Signature-Pass"
return nil
}
func init() {
api.RegisterSigner("mockExtSigner", &mockExtSigner{})
}
// 模拟 Action 定义
type MockCallAction struct {
Name string
Age int
AppId string // 预期由引擎的 Fill 机制自动注入
Secret string // 预期由引擎的 Fill 机制自动注入
}
func (MockCallAction) ActionName() string { return "mockPlatform.mockAction" }
func (MockCallAction) SignerName() string { return "mockExtSigner" }
func (MockCallAction) Config() map[string]any {
return map[string]any{
"service": "mockService",
}
}
type MockResponse struct {
Status string
Echo string
}
func TestFullCallFlow(t *testing.T) {
// 启动本地截获 HTTP 的 Mock Server
var receivedURL string
var receivedAuth string
var receivedMethod string
var receivedBody []byte
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
receivedURL = r.URL.String()
receivedAuth = r.Header.Get("Authorization")
receivedMethod = r.Method
receivedBody, _ = io.ReadAll(r.Body)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"status": "success", "echo": "Hello from mock server"}`))
}))
defer ts.Close()
// 初始化隔离的全局配置
api.GlobalConfigs = map[string]any{}
api.GlobalConfigs["api"] = map[string]any{
"mockPlatform": map[string]any{
"url": "{{.service}}.example.com", // 带占位符的 host (会被 signer 替换)
"appId": "inject-app-id",
"actions": map[string]any{
"mockAction": map[string]any{
"url": ts.URL + "?host={{.service}}.example.com", // 为了让流量指向 ts我们在 query 模拟
"secret": "inject-secret",
},
},
},
}
action := &MockCallAction{
Name: "Alice",
Age: 30,
}
// 执行调度调用
resp, err := api.Call[MockResponse](action)
if err != nil {
t.Fatalf("Call failed: %v", err)
}
// 1. 验证 Fill 参数注入
if action.AppId != "inject-app-id" {
t.Errorf("expected injected AppId inject-app-id, got %s", action.AppId)
}
if action.Secret != "inject-secret" {
t.Errorf("expected injected Secret inject-secret, got %s", action.Secret)
}
if action.Name != "Alice" {
t.Errorf("Fill should not overwrite existing value, expected Alice, got %s", action.Name)
}
// 2. 验证响应绑定
if resp.Status != "success" || resp.Echo != "Hello from mock server" {
t.Errorf("Response bind failed, got %+v", resp)
}
// 3. 验证 HTTP 透传数据
if receivedMethod != "POST" {
t.Errorf("expected POST method, got %s", receivedMethod)
}
if receivedAuth != "Mock-Signature-Pass" {
t.Errorf("expected signature header, got %s", receivedAuth)
}
// 验证 Signer 中 URL 占位符已被替换
if !strings.Contains(receivedURL, "host=mockService.example.com") {
t.Errorf("expected Signer to replace URL placeholder, got URL: %s", receivedURL)
}
// 验证 Payload 的 JSON 序列化 (由底层 apigo.cc/go/http 处理)
var bodyMap map[string]any
json.Unmarshal(receivedBody, &bodyMap)
if bodyMap["name"] != "Alice" || bodyMap["appId"] != "inject-app-id" {
t.Errorf("Payload body serialization incorrect: %s", string(receivedBody))
}
}
// 性能测试:验证纯逻辑开销 (无网络)
func BenchmarkCallEngineLogic(b *testing.B) {
api.GlobalConfigs = map[string]any{
"api": map[string]any{
"benchPlatform": map[string]any{
"appId": "bench-id",
},
},
}
actionConfig := api.GetActionConfig("benchPlatform")
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
// 仅测试引擎准备阶段的开销 (合并、校验、注入),不走网络
api.MergeMap(map[string]any{}, actionConfig)
// 这里由于 fill 仅限于 package 内部,我们不能直接调 fill()
// 但我们实际上想衡量的是整个准备流程。
// 由于 Call 会触发网络,这里只压测 GetActionConfig (最核心合并解析逻辑)
_ = api.GetActionConfig("mockPlatform.mockAction")
}
}