Files
MyGoNavi/internal/db/rabbitmq_impl_test.go
Syngnat d805f288ae feat(rabbitmq): 新增 RabbitMQ 数据源连接与测试发消息支持
- 新增 RabbitMQ 管理 API 数据源实现,支持 vhost、queue、exchange 浏览与队列预览

- 统一消息发送弹窗,支持 Kafka Topic 与 RabbitMQ Queue 的测试发送命令生成

- 补齐连接表单、能力矩阵、SQL 方言、图标与前后端回归测试覆盖
2026-06-14 10:49:11 +08:00

259 lines
8.4 KiB
Go

package db
import (
"encoding/json"
"net/http"
"net/http/httptest"
"reflect"
"strings"
"testing"
"GoNavi-Wails/internal/connection"
)
func TestNormalizeRabbitMQConfigParsesURIAndParams(t *testing.T) {
config := normalizeRabbitMQConfig(connection.ConnectionConfig{
URI: "rabbitmq://guest:secret@127.0.0.1:15672/%2F?tls=true&skip_verify=true",
ConnectionParams: "defaultQueue=orders.events&exchange=events.topic&pageSize=500",
})
if config.Host != "127.0.0.1" || config.Port != 15672 {
t.Fatalf("unexpected rabbitmq host/port: %#v", config)
}
if config.User != "guest" || config.Password != "secret" {
t.Fatalf("unexpected rabbitmq credentials: %#v", config)
}
if config.Database != "/" {
t.Fatalf("expected default vhost '/', got %q", config.Database)
}
if !config.UseSSL || config.SSLMode != "skip-verify" {
t.Fatalf("unexpected rabbitmq tls settings: %#v", config)
}
params := rabbitmqConnectionParams(config)
if params.Get("defaultQueue") != "orders.events" || params.Get("exchange") != "events.topic" {
t.Fatalf("unexpected rabbitmq params: %#v", params)
}
}
func TestRabbitMQQueryExecAndColumns(t *testing.T) {
var lastGetCount int
var lastPublishBody map[string]interface{}
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Content-Type", "application/json")
escapedPath := req.URL.EscapedPath()
switch {
case req.Method == http.MethodGet && escapedPath == "/api/vhosts":
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"items": []map[string]interface{}{{"name": "/", "tracing": false}},
"page": 1,
"page_count": 1,
})
case req.Method == http.MethodGet && escapedPath == "/api/queues/%2F":
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"items": []map[string]interface{}{
{
"vhost": "/",
"name": "orders.events.v1",
"durable": true,
"messages": 8,
"messages_ready": 5,
"messages_unacknowledged": 3,
"consumers": 2,
},
},
"page": 1,
"page_count": 1,
})
case req.Method == http.MethodGet && escapedPath == "/api/queues/%2F/orders.events.v1":
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"vhost": "/",
"name": "orders.events.v1",
"durable": true,
"messages": 8,
"messages_ready": 5,
"messages_unacknowledged": 3,
"consumers": 2,
"node": "rabbit@node1",
})
case req.Method == http.MethodGet && escapedPath == "/api/exchanges/%2F":
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"items": []map[string]interface{}{
{
"vhost": "/",
"name": "events.topic",
"type": "topic",
"durable": true,
"internal": false,
},
},
"page": 1,
"page_count": 1,
})
case req.Method == http.MethodPost && escapedPath == "/api/queues/%2F/orders.events.v1/get":
var body map[string]interface{}
if err := json.NewDecoder(req.Body).Decode(&body); err != nil {
t.Fatalf("decode get body failed: %v", err)
}
lastGetCount = intFromAny(body["count"], 0)
_ = json.NewEncoder(w).Encode([]map[string]interface{}{
{
"exchange": "events.topic",
"routing_key": "orders.events.v1",
"payload": `{"event":"created","meta":{"ip":"127.0.0.1"}}`,
"payload_bytes": 46,
"payload_encoding": "string",
"redelivered": false,
"message_count": 7,
"properties": map[string]interface{}{
"content_type": "application/json",
"headers": map[string]interface{}{
"x-env": "dev",
},
},
},
{
"exchange": "events.topic",
"routing_key": "orders.events.v1",
"payload": "plain-text",
"payload_bytes": 10,
"payload_encoding": "string",
"redelivered": true,
"message_count": 6,
"properties": map[string]interface{}{
"headers": map[string]interface{}{
"x-env": "qa",
},
},
},
})
case req.Method == http.MethodPut && escapedPath == "/api/exchanges/%2F/events.topic/publish":
if err := json.NewDecoder(req.Body).Decode(&lastPublishBody); err != nil {
t.Fatalf("decode publish body failed: %v", err)
}
_ = json.NewEncoder(w).Encode(map[string]interface{}{"routed": true})
default:
t.Fatalf("unexpected rabbitmq request: %s %s?%s", req.Method, escapedPath, req.URL.RawQuery)
}
}))
defer server.Close()
client := &RabbitMQDB{
client: server.Client(),
baseURL: server.URL,
defaultVHost: "/",
defaultQueue: "orders.events.v1",
defaultExchange: "events.topic",
pageSize: 50,
}
rows, columns, err := client.Query(`SHOW VHOSTS LIMIT 1`)
if err != nil {
t.Fatalf("SHOW VHOSTS failed: %v", err)
}
if len(rows) != 1 || rows[0]["vhost"] != "/" {
t.Fatalf("unexpected vhost rows: %#v", rows)
}
if !containsString(columns, "tracing") {
t.Fatalf("expected tracing column, got %v", columns)
}
rows, columns, err = client.Query(`SHOW QUEUES LIMIT 1`)
if err != nil {
t.Fatalf("SHOW QUEUES failed: %v", err)
}
if len(rows) != 1 || rows[0]["queue"] != "orders.events.v1" {
t.Fatalf("unexpected queue rows: %#v", rows)
}
if !containsString(columns, "messages_ready") {
t.Fatalf("expected messages_ready column, got %v", columns)
}
rows, _, err = client.Query(`DESCRIBE QUEUE "orders.events.v1"`)
if err != nil {
t.Fatalf("DESCRIBE QUEUE failed: %v", err)
}
if len(rows) != 1 || rows[0]["node"] != "rabbit@node1" {
t.Fatalf("unexpected describe queue rows: %#v", rows)
}
rows, columns, err = client.Query(`SHOW EXCHANGES LIMIT 1`)
if err != nil {
t.Fatalf("SHOW EXCHANGES failed: %v", err)
}
if len(rows) != 1 || rows[0]["exchange"] != "events.topic" {
t.Fatalf("unexpected exchange rows: %#v", rows)
}
if !containsString(columns, "type") {
t.Fatalf("expected exchange type column, got %v", columns)
}
rows, columns, err = client.Query(`SELECT * FROM "orders.events.v1" LIMIT 1 OFFSET 1`)
if err != nil {
t.Fatalf("SELECT queue failed: %v", err)
}
if lastGetCount != 2 {
t.Fatalf("expected fetch count 2 for offset emulation, got %d", lastGetCount)
}
if len(rows) != 1 || rows[0]["payload"] != "plain-text" || rows[0]["headers.x-env"] != "qa" {
t.Fatalf("unexpected rabbitmq message rows: %#v", rows)
}
if !containsString(columns, "headers.x-env") {
t.Fatalf("expected derived header column, got %v", columns)
}
affected, err := client.Exec(`{"queue":"orders.events.v1","exchange":"events.topic","routing_key":"orders.events.v1","payload":{"id":1},"headers":{"x-env":"dev"}}`)
if err != nil {
t.Fatalf("rabbitmq publish failed: %v", err)
}
if affected != 1 {
t.Fatalf("unexpected affected rows: %d", affected)
}
if lastPublishBody["routing_key"] != "orders.events.v1" {
t.Fatalf("unexpected publish routing key: %#v", lastPublishBody)
}
properties, ok := lastPublishBody["properties"].(map[string]interface{})
if !ok {
t.Fatalf("expected publish properties object, got %#v", lastPublishBody["properties"])
}
headers, ok := properties["headers"].(map[string]interface{})
if !ok || headers["x-env"] != "dev" {
t.Fatalf("unexpected publish headers: %#v", properties["headers"])
}
if lastPublishBody["payload_encoding"] != "string" || !strings.Contains(lastPublishBody["payload"].(string), `"id":1`) {
t.Fatalf("unexpected publish payload: %#v", lastPublishBody)
}
columnDefs, err := client.GetColumns("/", "orders.events.v1")
if err != nil {
t.Fatalf("GetColumns failed: %v", err)
}
names := make([]string, 0, len(columnDefs))
for _, col := range columnDefs {
names = append(names, col.Name)
}
joined := strings.Join(names, ",")
for _, want := range []string{"queue", "payload.meta.ip", "headers.x-env", "properties.content_type"} {
if !strings.Contains(joined, want) {
t.Fatalf("expected derived rabbitmq column %q in %s", want, joined)
}
}
databases, err := client.GetDatabases()
if err != nil {
t.Fatalf("GetDatabases failed: %v", err)
}
if !reflect.DeepEqual(databases, []string{"/"}) {
t.Fatalf("unexpected vhost list: %#v", databases)
}
tables, err := client.GetTables("/")
if err != nil {
t.Fatalf("GetTables failed: %v", err)
}
if !reflect.DeepEqual(tables, []string{"orders.events.v1"}) {
t.Fatalf("unexpected queue list: %#v", tables)
}
}