maidn-cli/internal/cloudflare/client_test.go

119 lines
4.8 KiB
Go

package cloudflare
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
)
func TestClientTunnelAndDNSLifecycle(t *testing.T) {
requests := 0
server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
requests++
if request.Header.Get("Authorization") != "Bearer test-api-token" {
t.Error("Cloudflare request did not use the API token header")
writer.WriteHeader(http.StatusUnauthorized)
return
}
switch request.Method + " " + request.URL.Path {
case "GET /accounts":
_ = json.NewEncoder(writer).Encode(map[string]any{"success": true, "result": []map[string]string{{"id": "account-id"}}})
case "GET /accounts/account-id/cfd_tunnel":
if request.URL.Query().Get("name") != "maidn-demo" {
t.Error("tunnel lookup used the wrong name")
}
_ = json.NewEncoder(writer).Encode(map[string]any{"success": true, "result": []any{}})
case "POST /accounts/account-id/cfd_tunnel":
var body map[string]string
if err := json.NewDecoder(request.Body).Decode(&body); err != nil || body["name"] != "maidn-demo" || body["config_src"] != "local" {
t.Error("tunnel create request was invalid")
}
_ = json.NewEncoder(writer).Encode(map[string]any{"success": true, "result": map[string]string{"id": "tunnel-id", "tunnel_secret": "test-tunnel-secret"}})
case "GET /zones/zone-id/dns_records":
if request.URL.Query().Get("type") != "CNAME" || request.URL.Query().Get("name") != "app.example.test" {
t.Error("DNS lookup was invalid")
}
_ = json.NewEncoder(writer).Encode(map[string]any{"success": true, "result": []any{}})
case "POST /zones/zone-id/dns_records":
var body map[string]any
if err := json.NewDecoder(request.Body).Decode(&body); err != nil || body["content"] != "tunnel-id.cfargotunnel.com" || body["proxied"] != true {
t.Error("DNS create request was invalid")
}
_ = json.NewEncoder(writer).Encode(map[string]any{"success": true, "result": map[string]string{"id": "record-id"}})
default:
t.Errorf("unexpected request %s %s", request.Method, request.URL.Path)
writer.WriteHeader(http.StatusMethodNotAllowed)
}
}))
defer server.Close()
client := NewClient("test-api-token")
client.baseURL = server.URL
client.httpClient = server.Client()
accounts, err := client.ListAccounts(context.Background())
if err != nil || len(accounts) != 1 || accounts[0].ID != "account-id" {
t.Fatal("account lookup failed")
}
tunnels, err := client.ListTunnels(context.Background(), accounts[0].ID, "maidn-demo")
if err != nil || len(tunnels) != 0 {
t.Fatal("tunnel lookup failed")
}
tunnel, err := client.CreateTunnel(context.Background(), accounts[0].ID, "maidn-demo")
if err != nil || tunnel.ID != "tunnel-id" || tunnel.TunnelSecret != "test-tunnel-secret" {
t.Fatal("tunnel create failed")
}
if err := client.EnsureCNAME(context.Background(), "zone-id", "app.example.test", tunnel.ID); err != nil {
t.Fatal(err)
}
if requests != 5 {
t.Fatalf("expected five Cloudflare requests, got %d", requests)
}
}
func TestClientFailureDoesNotRevealToken(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, _ *http.Request) {
writer.WriteHeader(http.StatusInternalServerError)
}))
defer server.Close()
client := NewClient("test-api-token")
client.baseURL = server.URL
client.httpClient = server.Client()
_, err := client.ListAccounts(context.Background())
if err == nil || strings.Contains(err.Error(), "test-api-token") {
t.Fatal("Cloudflare API failure exposed the token")
}
}
func TestClientDeletesOnlyMatchingTunnelCNAME(t *testing.T) {
deleted := false
server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
if request.Header.Get("Authorization") != "Bearer test-api-token" {
t.Error("Cloudflare request did not use the API token header")
writer.WriteHeader(http.StatusUnauthorized)
return
}
switch request.Method + " " + request.URL.Path {
case "GET /zones/zone-id/dns_records":
_ = json.NewEncoder(writer).Encode(map[string]any{"success": true, "result": []map[string]any{{"id": "record-id", "name": "app.example.test", "content": "tunnel-id.cfargotunnel.com", "proxied": true}}})
case "DELETE /zones/zone-id/dns_records/record-id":
deleted = true
_ = json.NewEncoder(writer).Encode(map[string]any{"success": true, "result": map[string]string{"id": "record-id"}})
default:
t.Errorf("unexpected request %s %s", request.Method, request.URL.Path)
writer.WriteHeader(http.StatusMethodNotAllowed)
}
}))
defer server.Close()
client := NewClient("test-api-token")
client.baseURL = server.URL
client.httpClient = server.Client()
if err := client.DeleteCNAME(context.Background(), "zone-id", "app.example.test", "tunnel-id"); err != nil || !deleted {
t.Fatal("matching managed CNAME was not deleted")
}
}