mirror of
https://github.com/0xJacky/nginx-ui.git
synced 2026-07-01 03:04:26 +08:00
684 lines
20 KiB
Go
684 lines
20 KiB
Go
package upstream
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net"
|
|
"sort"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// MockDNSServer simulates DNS responses for testing
|
|
type MockDNSServer struct {
|
|
srvRecords map[string][]*net.SRV
|
|
aRecords map[string][]net.IPAddr
|
|
}
|
|
|
|
// NewMockDNSServer creates a mock DNS server for testing
|
|
func NewMockDNSServer() *MockDNSServer {
|
|
return &MockDNSServer{
|
|
srvRecords: make(map[string][]*net.SRV),
|
|
aRecords: make(map[string][]net.IPAddr),
|
|
}
|
|
}
|
|
|
|
// AddSRVRecord adds a SRV record to the mock DNS server
|
|
func (m *MockDNSServer) AddSRVRecord(domain string, priority, weight uint16, port uint16, target string) {
|
|
m.srvRecords[domain] = append(m.srvRecords[domain], &net.SRV{
|
|
Priority: priority,
|
|
Weight: weight,
|
|
Port: port,
|
|
Target: target,
|
|
})
|
|
}
|
|
|
|
// AddARecord adds an A record to the mock DNS server
|
|
func (m *MockDNSServer) AddARecord(domain string, ip string) {
|
|
m.aRecords[domain] = append(m.aRecords[domain], net.IPAddr{
|
|
IP: net.ParseIP(ip),
|
|
})
|
|
}
|
|
|
|
// MockResolver is a custom resolver that uses our mock DNS server
|
|
type MockResolver struct {
|
|
mockServer *MockDNSServer
|
|
}
|
|
|
|
// LookupSRV simulates SRV record lookup with proper priority sorting
|
|
func (mr *MockResolver) LookupSRV(ctx context.Context, service, proto, name string) (string, []*net.SRV, error) {
|
|
domain := name
|
|
if service != "" || proto != "" {
|
|
domain = fmt.Sprintf("_%s._%s.%s", service, proto, name)
|
|
}
|
|
|
|
if records, exists := mr.mockServer.srvRecords[domain]; exists {
|
|
// Sort SRV records by priority (lowest first), then by weight (highest first)
|
|
// This follows RFC 2782 and nginx behavior
|
|
sortedRecords := make([]*net.SRV, len(records))
|
|
copy(sortedRecords, records)
|
|
|
|
sort.Slice(sortedRecords, func(i, j int) bool {
|
|
if sortedRecords[i].Priority != sortedRecords[j].Priority {
|
|
return sortedRecords[i].Priority < sortedRecords[j].Priority
|
|
}
|
|
// For same priority, higher weight comes first (but this is simplified for testing)
|
|
return sortedRecords[i].Weight > sortedRecords[j].Weight
|
|
})
|
|
|
|
return "", sortedRecords, nil
|
|
}
|
|
return "", nil, fmt.Errorf("no SRV records for %s", domain)
|
|
}
|
|
|
|
// LookupIPAddr simulates A record lookup
|
|
func (mr *MockResolver) LookupIPAddr(ctx context.Context, host string) ([]net.IPAddr, error) {
|
|
if records, exists := mr.mockServer.aRecords[host]; exists {
|
|
return records, nil
|
|
}
|
|
return nil, fmt.Errorf("no A records for %s", host)
|
|
}
|
|
|
|
// TestParseServiceURL tests the parseServiceURL function with nginx compliance
|
|
func TestParseServiceURL(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
expectedErr bool
|
|
expected *ServiceInfo
|
|
}{
|
|
{
|
|
name: "Valid nginx service URL - simple service name",
|
|
input: "backend.example.com service=http resolve",
|
|
expected: &ServiceInfo{
|
|
Hostname: "backend.example.com",
|
|
ServiceName: "http",
|
|
},
|
|
},
|
|
{
|
|
name: "Valid nginx service URL - service name with underscores",
|
|
input: "backend.example.com service=_http._tcp resolve",
|
|
expected: &ServiceInfo{
|
|
Hostname: "backend.example.com",
|
|
ServiceName: "_http._tcp",
|
|
},
|
|
},
|
|
{
|
|
name: "Valid nginx service URL - service name with dots",
|
|
input: "example.com service=server1.backend resolve",
|
|
expected: &ServiceInfo{
|
|
Hostname: "example.com",
|
|
ServiceName: "server1.backend",
|
|
},
|
|
},
|
|
{
|
|
name: "Consul service example",
|
|
input: "service.consul service=web-service resolve",
|
|
expected: &ServiceInfo{
|
|
Hostname: "service.consul",
|
|
ServiceName: "web-service",
|
|
},
|
|
},
|
|
{
|
|
name: "Empty input",
|
|
input: "",
|
|
expectedErr: true,
|
|
},
|
|
{
|
|
name: "Missing resolve parameter",
|
|
input: "backend.example.com service=http",
|
|
expectedErr: true,
|
|
},
|
|
{
|
|
name: "Missing service parameter",
|
|
input: "backend.example.com resolve",
|
|
expectedErr: true,
|
|
},
|
|
{
|
|
name: "Empty service name",
|
|
input: "backend.example.com service= resolve",
|
|
expectedErr: true,
|
|
},
|
|
{
|
|
name: "Only hostname",
|
|
input: "backend.example.com",
|
|
expectedErr: true,
|
|
},
|
|
}
|
|
|
|
resolver := NewDynamicResolver("127.0.0.1:8600")
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result, err := resolver.parseServiceURL(tt.input)
|
|
|
|
if tt.expectedErr {
|
|
if err == nil {
|
|
t.Errorf("Expected error but got none")
|
|
}
|
|
return
|
|
}
|
|
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
return
|
|
}
|
|
|
|
if result.Hostname != tt.expected.Hostname {
|
|
t.Errorf("Expected hostname %s, got %s", tt.expected.Hostname, result.Hostname)
|
|
}
|
|
|
|
if result.ServiceName != tt.expected.ServiceName {
|
|
t.Errorf("Expected service name %s, got %s", tt.expected.ServiceName, result.ServiceName)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestConstructSRVDomain tests SRV domain construction according to nginx.org rules
|
|
func TestConstructSRVDomain(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input *ServiceInfo
|
|
expected string
|
|
rule string
|
|
}{
|
|
{
|
|
name: "Rule 1: Service name without dots - http",
|
|
input: &ServiceInfo{
|
|
Hostname: "backend.example.com",
|
|
ServiceName: "http",
|
|
},
|
|
expected: "_http._tcp.backend.example.com",
|
|
rule: "nginx rule 1: no dots, add TCP protocol",
|
|
},
|
|
{
|
|
name: "Rule 1: Service name without dots - https",
|
|
input: &ServiceInfo{
|
|
Hostname: "api.example.com",
|
|
ServiceName: "https",
|
|
},
|
|
expected: "_https._tcp.api.example.com",
|
|
rule: "nginx rule 1: no dots, add TCP protocol",
|
|
},
|
|
{
|
|
name: "Rule 1: Service name without dots - mysql",
|
|
input: &ServiceInfo{
|
|
Hostname: "db.example.com",
|
|
ServiceName: "mysql",
|
|
},
|
|
expected: "_mysql._tcp.db.example.com",
|
|
rule: "nginx rule 1: no dots, add TCP protocol",
|
|
},
|
|
{
|
|
name: "Rule 2: Service name with dots - _http._tcp",
|
|
input: &ServiceInfo{
|
|
Hostname: "backend.example.com",
|
|
ServiceName: "_http._tcp",
|
|
},
|
|
expected: "_http._tcp.backend.example.com",
|
|
rule: "nginx rule 2: contains dots, join directly",
|
|
},
|
|
{
|
|
name: "Rule 2: Service name with dots - server1.backend",
|
|
input: &ServiceInfo{
|
|
Hostname: "example.com",
|
|
ServiceName: "server1.backend",
|
|
},
|
|
expected: "server1.backend.example.com",
|
|
rule: "nginx rule 2: contains dots, join directly",
|
|
},
|
|
{
|
|
name: "Rule 2: Complex service name with underscores and dots",
|
|
input: &ServiceInfo{
|
|
Hostname: "dc1.consul",
|
|
ServiceName: "_api._tcp.production",
|
|
},
|
|
expected: "_api._tcp.production.dc1.consul",
|
|
rule: "nginx rule 2: contains dots, join directly",
|
|
},
|
|
{
|
|
name: "Consul example - simple service",
|
|
input: &ServiceInfo{
|
|
Hostname: "service.consul",
|
|
ServiceName: "web",
|
|
},
|
|
expected: "_web._tcp.service.consul",
|
|
rule: "nginx rule 1: no dots, add TCP protocol",
|
|
},
|
|
}
|
|
|
|
resolver := NewDynamicResolver("127.0.0.1:8600")
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := resolver.constructSRVDomain(tt.input)
|
|
if result != tt.expected {
|
|
t.Errorf("Expected SRV domain %s, got %s (rule: %s)", tt.expected, result, tt.rule)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestNginxOfficialExamples tests the exact examples from nginx.org documentation
|
|
func TestNginxOfficialExamples(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
nginxConfig string
|
|
expectedQuery string
|
|
description string
|
|
}{
|
|
{
|
|
name: "Official Example 1",
|
|
nginxConfig: "backend.example.com service=http resolve",
|
|
expectedQuery: "_http._tcp.backend.example.com",
|
|
description: "To look up _http._tcp.backend.example.com SRV record",
|
|
},
|
|
{
|
|
name: "Official Example 2",
|
|
nginxConfig: "backend.example.com service=_http._tcp resolve",
|
|
expectedQuery: "_http._tcp.backend.example.com",
|
|
description: "Service name already contains dots, join directly",
|
|
},
|
|
{
|
|
name: "Official Example 3",
|
|
nginxConfig: "example.com service=server1.backend resolve",
|
|
expectedQuery: "server1.backend.example.com",
|
|
description: "Service name contains dots, join directly",
|
|
},
|
|
}
|
|
|
|
resolver := NewDynamicResolver("127.0.0.1:8600")
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
serviceInfo, err := resolver.parseServiceURL(tt.nginxConfig)
|
|
if err != nil {
|
|
t.Fatalf("Failed to parse nginx config: %v", err)
|
|
}
|
|
|
|
result := resolver.constructSRVDomain(serviceInfo)
|
|
if result != tt.expectedQuery {
|
|
t.Errorf("nginx.org example failed: expected %s, got %s (%s)",
|
|
tt.expectedQuery, result, tt.description)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestSRVRecordResolutionWithMockDNS tests actual SRV record resolution using mock DNS
|
|
func TestSRVRecordResolutionWithMockDNS(t *testing.T) {
|
|
// Create mock DNS server
|
|
mockDNS := NewMockDNSServer()
|
|
|
|
// Add SRV records for _http._tcp.backend.example.com
|
|
mockDNS.AddSRVRecord("_http._tcp.backend.example.com", 10, 60, 8080, "web1.backend.example.com")
|
|
mockDNS.AddSRVRecord("_http._tcp.backend.example.com", 10, 40, 8080, "web2.backend.example.com")
|
|
mockDNS.AddSRVRecord("_http._tcp.backend.example.com", 20, 100, 8080, "web3.backend.example.com")
|
|
|
|
// Add A records for the targets
|
|
mockDNS.AddARecord("web1.backend.example.com", "192.168.1.10")
|
|
mockDNS.AddARecord("web2.backend.example.com", "192.168.1.11")
|
|
mockDNS.AddARecord("web3.backend.example.com", "192.168.1.12")
|
|
|
|
t.Run("SRV record resolution", func(t *testing.T) {
|
|
mockResolver := &MockResolver{mockServer: mockDNS}
|
|
|
|
// Test SRV lookup
|
|
_, srvRecords, err := mockResolver.LookupSRV(context.Background(), "", "", "_http._tcp.backend.example.com")
|
|
if err != nil {
|
|
t.Fatalf("SRV lookup failed: %v", err)
|
|
}
|
|
|
|
if len(srvRecords) != 3 {
|
|
t.Errorf("Expected 3 SRV records, got %d", len(srvRecords))
|
|
}
|
|
|
|
// Verify priority ordering (lowest priority first) and weight ordering (highest weight first within same priority)
|
|
expectedPriorities := []uint16{10, 10, 20}
|
|
expectedWeights := []uint16{60, 40, 100} // For priorities [10, 10, 20], weights should be [60, 40, 100]
|
|
expectedTargets := []string{"web1.backend.example.com", "web2.backend.example.com", "web3.backend.example.com"}
|
|
|
|
for i, srv := range srvRecords {
|
|
if srv.Priority != expectedPriorities[i] {
|
|
t.Errorf("Expected priority %d at index %d, got %d", expectedPriorities[i], i, srv.Priority)
|
|
}
|
|
if srv.Weight != expectedWeights[i] {
|
|
t.Errorf("Expected weight %d at index %d, got %d", expectedWeights[i], i, srv.Weight)
|
|
}
|
|
if srv.Target != expectedTargets[i] {
|
|
t.Errorf("Expected target %s at index %d, got %s", expectedTargets[i], i, srv.Target)
|
|
}
|
|
}
|
|
|
|
// Test A record resolution for each target
|
|
for _, srv := range srvRecords {
|
|
ips, err := mockResolver.LookupIPAddr(context.Background(), srv.Target)
|
|
if err != nil {
|
|
t.Errorf("A record lookup failed for %s: %v", srv.Target, err)
|
|
continue
|
|
}
|
|
|
|
if len(ips) != 1 {
|
|
t.Errorf("Expected 1 IP for %s, got %d", srv.Target, len(ips))
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
// TestSRVPriorityHandling tests nginx SRV priority handling as per nginx.org documentation
|
|
func TestSRVPriorityHandling(t *testing.T) {
|
|
// Create mock DNS server
|
|
mockDNS := NewMockDNSServer()
|
|
|
|
// Add SRV records with different priorities to test primary/backup server logic
|
|
// Priority 5 (highest priority / primary servers)
|
|
mockDNS.AddSRVRecord("_http._tcp.app.example.com", 5, 100, 8080, "primary1.app.example.com")
|
|
mockDNS.AddSRVRecord("_http._tcp.app.example.com", 5, 50, 8080, "primary2.app.example.com")
|
|
// Priority 10 (backup servers)
|
|
mockDNS.AddSRVRecord("_http._tcp.app.example.com", 10, 80, 8080, "backup1.app.example.com")
|
|
// Priority 15 (lower priority backup servers)
|
|
mockDNS.AddSRVRecord("_http._tcp.app.example.com", 15, 200, 8080, "backup2.app.example.com")
|
|
|
|
// Add A records
|
|
mockDNS.AddARecord("primary1.app.example.com", "10.0.1.1")
|
|
mockDNS.AddARecord("primary2.app.example.com", "10.0.1.2")
|
|
mockDNS.AddARecord("backup1.app.example.com", "10.0.2.1")
|
|
mockDNS.AddARecord("backup2.app.example.com", "10.0.3.1")
|
|
|
|
t.Run("SRV priority handling", func(t *testing.T) {
|
|
mockResolver := &MockResolver{mockServer: mockDNS}
|
|
|
|
// Test SRV lookup
|
|
_, srvRecords, err := mockResolver.LookupSRV(context.Background(), "", "", "_http._tcp.app.example.com")
|
|
if err != nil {
|
|
t.Fatalf("SRV lookup failed: %v", err)
|
|
}
|
|
|
|
if len(srvRecords) != 4 {
|
|
t.Errorf("Expected 4 SRV records, got %d", len(srvRecords))
|
|
}
|
|
|
|
// According to nginx.org: "Highest-priority SRV records (records with the same lowest-number priority value)
|
|
// are resolved as primary servers, the rest of SRV records are resolved as backup servers"
|
|
expectedOrder := []struct {
|
|
priority uint16
|
|
weight uint16
|
|
target string
|
|
serverType string
|
|
}{
|
|
{5, 100, "primary1.app.example.com", "primary"}, // Highest priority (lowest number)
|
|
{5, 50, "primary2.app.example.com", "primary"}, // Same priority, lower weight
|
|
{10, 80, "backup1.app.example.com", "backup"}, // Lower priority (backup)
|
|
{15, 200, "backup2.app.example.com", "backup"}, // Lowest priority (backup)
|
|
}
|
|
|
|
for i, srv := range srvRecords {
|
|
expected := expectedOrder[i]
|
|
if srv.Priority != expected.priority {
|
|
t.Errorf("Record %d: expected priority %d, got %d", i, expected.priority, srv.Priority)
|
|
}
|
|
if srv.Weight != expected.weight {
|
|
t.Errorf("Record %d: expected weight %d, got %d", i, expected.weight, srv.Weight)
|
|
}
|
|
if srv.Target != expected.target {
|
|
t.Errorf("Record %d: expected target %s, got %s", i, expected.target, srv.Target)
|
|
}
|
|
|
|
// Log the server type for documentation
|
|
t.Logf("Record %d: Priority %d, Weight %d, Target %s (%s server)",
|
|
i, srv.Priority, srv.Weight, srv.Target, expected.serverType)
|
|
}
|
|
|
|
// Verify primary servers come first (lowest priority numbers)
|
|
primaryCount := 0
|
|
for _, srv := range srvRecords {
|
|
if srv.Priority == 5 { // Primary servers have priority 5
|
|
primaryCount++
|
|
} else {
|
|
break // Once we hit a non-primary, all following should be backups
|
|
}
|
|
}
|
|
|
|
if primaryCount != 2 {
|
|
t.Errorf("Expected 2 primary servers at the beginning, got %d", primaryCount)
|
|
}
|
|
})
|
|
}
|
|
|
|
// TestARecordFallback tests A record fallback when SRV lookup fails
|
|
func TestARecordFallback(t *testing.T) {
|
|
mockDNS := NewMockDNSServer()
|
|
|
|
// Only add A record, no SRV record
|
|
mockDNS.AddARecord("_http._tcp.backend.example.com", "192.168.1.100")
|
|
|
|
t.Run("A record fallback", func(t *testing.T) {
|
|
mockResolver := &MockResolver{mockServer: mockDNS}
|
|
|
|
// SRV lookup should fail
|
|
_, srvRecords, err := mockResolver.LookupSRV(context.Background(), "", "", "_http._tcp.backend.example.com")
|
|
if err == nil {
|
|
t.Error("Expected SRV lookup to fail")
|
|
}
|
|
if len(srvRecords) != 0 {
|
|
t.Errorf("Expected 0 SRV records, got %d", len(srvRecords))
|
|
}
|
|
|
|
// A record lookup should succeed
|
|
ips, err := mockResolver.LookupIPAddr(context.Background(), "_http._tcp.backend.example.com")
|
|
if err != nil {
|
|
t.Fatalf("A record lookup failed: %v", err)
|
|
}
|
|
|
|
if len(ips) != 1 {
|
|
t.Errorf("Expected 1 IP, got %d", len(ips))
|
|
}
|
|
|
|
expectedIP := "192.168.1.100"
|
|
if ips[0].IP.String() != expectedIP {
|
|
t.Errorf("Expected IP %s, got %s", expectedIP, ips[0].IP.String())
|
|
}
|
|
})
|
|
}
|
|
|
|
// TestComplexNginxScenarios tests more complex real-world nginx scenarios
|
|
func TestComplexNginxScenarios(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
nginxLine string
|
|
expectedSRV string
|
|
scenario string
|
|
}{
|
|
{
|
|
name: "Load balancer with HTTP service",
|
|
nginxLine: "api.microservices.local service=http resolve",
|
|
expectedSRV: "_http._tcp.api.microservices.local",
|
|
scenario: "Microservices API load balancing",
|
|
},
|
|
{
|
|
name: "Database connection",
|
|
nginxLine: "db.cluster.local service=mysql resolve",
|
|
expectedSRV: "_mysql._tcp.db.cluster.local",
|
|
scenario: "Database cluster connection",
|
|
},
|
|
{
|
|
name: "WebSocket service",
|
|
nginxLine: "chat.app.local service=ws resolve",
|
|
expectedSRV: "_ws._tcp.chat.app.local",
|
|
scenario: "WebSocket service discovery",
|
|
},
|
|
{
|
|
name: "Custom protocol with dots",
|
|
nginxLine: "service.consul service=_grpc._tcp resolve",
|
|
expectedSRV: "_grpc._tcp.service.consul",
|
|
scenario: "gRPC service via Consul",
|
|
},
|
|
{
|
|
name: "Multi-level service hierarchy",
|
|
nginxLine: "consul.local service=api.v1.production resolve",
|
|
expectedSRV: "api.v1.production.consul.local",
|
|
scenario: "Multi-level service naming",
|
|
},
|
|
{
|
|
name: "Kubernetes style service",
|
|
nginxLine: "cluster.local service=_http._tcp.nginx.default resolve",
|
|
expectedSRV: "_http._tcp.nginx.default.cluster.local",
|
|
scenario: "Kubernetes service discovery",
|
|
},
|
|
}
|
|
|
|
resolver := NewDynamicResolver("127.0.0.1:8600")
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
serviceInfo, err := resolver.parseServiceURL(tt.nginxLine)
|
|
if err != nil {
|
|
t.Fatalf("Failed to parse nginx line: %v", err)
|
|
}
|
|
|
|
result := resolver.constructSRVDomain(serviceInfo)
|
|
if result != tt.expectedSRV {
|
|
t.Errorf("Scenario '%s' failed: expected %s, got %s",
|
|
tt.scenario, tt.expectedSRV, result)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestBackwardCompatibility tests backward compatibility with old format
|
|
func TestBackwardCompatibility(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
expected string
|
|
}{
|
|
{
|
|
name: "New nginx format should work",
|
|
input: "backend.example.com service=http resolve",
|
|
expected: "http",
|
|
},
|
|
{
|
|
name: "New nginx format with dots",
|
|
input: "example.com service=_http._tcp resolve",
|
|
expected: "_http._tcp",
|
|
},
|
|
{
|
|
name: "Old consul format should still work as fallback",
|
|
input: "test-service.service.consul",
|
|
expected: "test-service",
|
|
},
|
|
{
|
|
name: "Invalid format should return empty",
|
|
input: "invalid format without proper structure",
|
|
expected: "",
|
|
},
|
|
}
|
|
|
|
resolver := NewDynamicResolver("127.0.0.1:8600")
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := resolver.extractServiceName(tt.input)
|
|
if result != tt.expected {
|
|
t.Errorf("Expected %s, got %s", tt.expected, result)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestDynamicTargetsFunction tests the TestDynamicTargets function
|
|
func TestDynamicTargetsFunction(t *testing.T) {
|
|
t.Run("Valid dynamic targets", func(t *testing.T) {
|
|
targets := []ProxyTarget{
|
|
{
|
|
Host: "service.consul",
|
|
Port: "dynamic",
|
|
Type: "upstream",
|
|
Resolver: "127.0.0.1:8600",
|
|
IsConsul: true,
|
|
ServiceURL: "backend.example.com service=http resolve",
|
|
},
|
|
}
|
|
|
|
results := TestDynamicTargets(targets)
|
|
|
|
if len(results) != 1 {
|
|
t.Errorf("Expected 1 result, got %d", len(results))
|
|
}
|
|
|
|
key := "service.consul:dynamic"
|
|
if _, found := results[key]; !found {
|
|
t.Errorf("Expected result for key %s not found", key)
|
|
}
|
|
})
|
|
|
|
t.Run("Target without resolver should be offline", func(t *testing.T) {
|
|
targets := []ProxyTarget{
|
|
{
|
|
Host: "service.consul",
|
|
Port: "dynamic",
|
|
Type: "upstream",
|
|
IsConsul: true,
|
|
ServiceURL: "backend.example.com service=http resolve",
|
|
// No resolver specified
|
|
},
|
|
}
|
|
|
|
results := TestDynamicTargets(targets)
|
|
|
|
key := "service.consul:dynamic"
|
|
if status, found := results[key]; found {
|
|
if status.Online {
|
|
t.Error("Expected target without resolver to be offline")
|
|
}
|
|
if status.Latency != 0 {
|
|
t.Errorf("Expected latency 0 for offline target, got %.2f", status.Latency)
|
|
}
|
|
} else {
|
|
t.Errorf("Expected result for key %s", key)
|
|
}
|
|
})
|
|
}
|
|
|
|
// TestIntegrationWithProxyParser tests integration with the proxy parser
|
|
func TestIntegrationWithProxyParser(t *testing.T) {
|
|
config := `upstream web-backend {
|
|
zone upstream_web 128k;
|
|
resolver 127.0.0.1:8600 valid=5s;
|
|
resolver_timeout 2s;
|
|
server backend.example.com service=http resolve;
|
|
}
|
|
server {
|
|
listen 80;
|
|
server_name example.com;
|
|
location / {
|
|
proxy_pass http://web-backend;
|
|
}
|
|
}`
|
|
|
|
targets := ParseProxyTargetsFromRawContent(config)
|
|
|
|
// Should find the dynamic DNS target
|
|
found := false
|
|
for _, target := range targets {
|
|
if target.IsConsul && strings.Contains(target.ServiceURL, "service=http") {
|
|
found = true
|
|
|
|
// Verify the target is correctly parsed
|
|
if target.Resolver != "127.0.0.1:8600" {
|
|
t.Errorf("Expected resolver 127.0.0.1:8600, got %s", target.Resolver)
|
|
}
|
|
|
|
if target.ServiceURL != "backend.example.com service=http resolve" {
|
|
t.Errorf("Expected service URL 'backend.example.com service=http resolve', got %s", target.ServiceURL)
|
|
}
|
|
break
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
t.Error("Dynamic DNS target not found in parsed config")
|
|
}
|
|
}
|