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") } }