mirror of
				https://github.com/coredns/coredns.git
				synced 2025-10-31 02:03:20 -04:00 
			
		
		
		
	test(dnsserver): improve core/dnsserver test coverage (#7317)
Add comprehensive tests for multiple components including server blocks inspection, configuration handling, DoH/DoQ writers, and server startup functions. Increases overall test coverage from 27% to 38.4% with particular focus on register.go, https.go, quic.go, and config.go. Signed-off-by: Ville Vesilehto <ville@vesilehto.fi>
This commit is contained in:
		
							
								
								
									
										67
									
								
								core/dnsserver/config_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								core/dnsserver/config_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,67 @@ | |||||||
|  | package dnsserver | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"github.com/coredns/caddy" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestKeyForConfig(t *testing.T) { | ||||||
|  | 	tests := []struct { | ||||||
|  | 		name          string | ||||||
|  | 		blockIndex    int | ||||||
|  | 		blockKeyIndex int | ||||||
|  | 		expected      string | ||||||
|  | 	}{ | ||||||
|  | 		{"zero_indices", 0, 0, "0:0"}, | ||||||
|  | 		{"positive_indices", 1, 2, "1:2"}, | ||||||
|  | 		{"larger_indices", 10, 5, "10:5"}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, tc := range tests { | ||||||
|  | 		t.Run(tc.name, func(t *testing.T) { | ||||||
|  | 			result := keyForConfig(tc.blockIndex, tc.blockKeyIndex) | ||||||
|  | 			if result != tc.expected { | ||||||
|  | 				t.Errorf("Expected %s, got %s for blockIndex %d and blockKeyIndex %d", | ||||||
|  | 					tc.expected, result, tc.blockIndex, tc.blockKeyIndex) | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestGetConfig(t *testing.T) { | ||||||
|  | 	controller := caddy.NewTestController("dns", "") | ||||||
|  | 	initialCtx := controller.Context() | ||||||
|  | 	dnsCtx, ok := initialCtx.(*dnsContext) | ||||||
|  | 	if !ok { | ||||||
|  | 		t.Fatalf("controller.Context() did not return a *dnsContext, got %T", initialCtx) | ||||||
|  | 	} | ||||||
|  | 	if dnsCtx.keysToConfigs == nil { | ||||||
|  | 		t.Fatal("dnsCtx.keysToConfigs is nil; it should have been initialized by newContext") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	t.Run("returns and saves default config when config missing", func(t *testing.T) { | ||||||
|  | 		controller.ServerBlockIndex = 0 | ||||||
|  | 		controller.ServerBlockKeyIndex = 0 | ||||||
|  | 		key := keyForConfig(controller.ServerBlockIndex, controller.ServerBlockKeyIndex) | ||||||
|  |  | ||||||
|  | 		// Ensure config doesn't exist initially for this specific key | ||||||
|  | 		delete(dnsCtx.keysToConfigs, key) | ||||||
|  |  | ||||||
|  | 		cfg := GetConfig(controller) | ||||||
|  | 		if cfg == nil { | ||||||
|  | 			t.Fatal("GetConfig returned nil (should create and return a default)") | ||||||
|  | 		} | ||||||
|  | 		if len(cfg.ListenHosts) != 1 || cfg.ListenHosts[0] != "" { | ||||||
|  | 			t.Errorf("Expected default ListenHosts [\"\"] for auto-created config, got %v", cfg.ListenHosts) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		savedCfg, found := dnsCtx.keysToConfigs[key] | ||||||
|  | 		if !found { | ||||||
|  | 			t.Fatal("fallback did not save the default config into the context") | ||||||
|  | 		} | ||||||
|  | 		if savedCfg != cfg { | ||||||
|  | 			t.Fatal("config is not the same instance as the one saved in the context") | ||||||
|  | 		} | ||||||
|  | 	}) | ||||||
|  | } | ||||||
| @@ -88,3 +88,72 @@ func TestDoHWriter_Request(t *testing.T) { | |||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func TestDoHWriter_Write(t *testing.T) { | ||||||
|  | 	tests := []struct { | ||||||
|  | 		name    string | ||||||
|  | 		input   []byte | ||||||
|  | 		wantErr bool | ||||||
|  | 	}{ | ||||||
|  | 		{ | ||||||
|  | 			name: "valid DNS message", | ||||||
|  | 			// A minimal valid DNS query message | ||||||
|  | 			input: []byte{ | ||||||
|  | 				0x00, 0x01, /* ID */ | ||||||
|  | 				0x01, 0x00, /* Flags: query, recursion desired */ | ||||||
|  | 				0x00, 0x01, /* Questions: 1 */ | ||||||
|  | 				0x00, 0x00, /* Answer RRs: 0 */ | ||||||
|  | 				0x00, 0x00, /* Authority RRs: 0 */ | ||||||
|  | 				0x00, 0x00, /* Additional RRs: 0 */ | ||||||
|  | 				0x03, 'w', 'w', 'w', | ||||||
|  | 				0x07, 'e', 'x', 'a', 'm', 'p', 'l', 'e', | ||||||
|  | 				0x03, 'c', 'o', 'm', | ||||||
|  | 				0x00,       /* Null terminator for domain name */ | ||||||
|  | 				0x00, 0x01, /* Type: A */ | ||||||
|  | 				0x00, 0x01, /* Class: IN */ | ||||||
|  | 			}, | ||||||
|  | 			wantErr: false, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:    "empty message", | ||||||
|  | 			input:   []byte{}, | ||||||
|  | 			wantErr: true, // Expect an error because unpacking an empty message will fail | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:    "invalid DNS message", | ||||||
|  | 			input:   []byte{0x00, 0x01, 0x02}, // Truncated message | ||||||
|  | 			wantErr: true, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 	for _, tt := range tests { | ||||||
|  | 		t.Run(tt.name, func(t *testing.T) { | ||||||
|  | 			d := &DoHWriter{} | ||||||
|  | 			n, err := d.Write(tt.input) | ||||||
|  |  | ||||||
|  | 			if (err != nil) != tt.wantErr { | ||||||
|  | 				t.Errorf("Write() error = %v, wantErr %v", err, tt.wantErr) | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 			if !tt.wantErr && n != len(tt.input) { | ||||||
|  | 				t.Errorf("Write() bytes written = %v, want %v", n, len(tt.input)) | ||||||
|  | 			} | ||||||
|  | 			if !tt.wantErr && d.Msg == nil { | ||||||
|  | 				t.Errorf("Write() d.Msg is nil, expected a parsed message") | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestDoHWriter_Close(t *testing.T) { | ||||||
|  | 	d := &DoHWriter{} | ||||||
|  | 	if err := d.Close(); err != nil { | ||||||
|  | 		t.Errorf("Close() error = %v, want nil", err) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestDoHWriter_TsigStatus(t *testing.T) { | ||||||
|  | 	d := &DoHWriter{} | ||||||
|  | 	if err := d.TsigStatus(); err != nil { | ||||||
|  | 		t.Errorf("TsigStatus() error = %v, want nil", err) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|   | |||||||
| @@ -37,3 +37,110 @@ func TestRegex1035PrefSyntax(t *testing.T) { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func TestStartUpZones(t *testing.T) { | ||||||
|  | 	tests := []struct { | ||||||
|  | 		name           string | ||||||
|  | 		protocol       string | ||||||
|  | 		addr           string | ||||||
|  | 		zones          map[string][]*Config | ||||||
|  | 		expectedOutput string | ||||||
|  | 	}{ | ||||||
|  | 		{ | ||||||
|  | 			name:           "no zones", | ||||||
|  | 			protocol:       "dns://", | ||||||
|  | 			addr:           "127.0.0.1:53", | ||||||
|  | 			zones:          map[string][]*Config{}, | ||||||
|  | 			expectedOutput: "", | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:           "single zone valid syntax ip and port", | ||||||
|  | 			protocol:       "dns://", | ||||||
|  | 			addr:           "127.0.0.1:53", | ||||||
|  | 			zones:          map[string][]*Config{"example.com.": nil}, | ||||||
|  | 			expectedOutput: "dns://example.com.:53 on 127.0.0.1\n", | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:           "single zone valid syntax port only", | ||||||
|  | 			protocol:       "http://", | ||||||
|  | 			addr:           ":8080", | ||||||
|  | 			zones:          map[string][]*Config{"example.org.": nil}, | ||||||
|  | 			expectedOutput: "http://example.org.:8080\n", | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:     "single zone invalid syntax", | ||||||
|  | 			protocol: "tls://", | ||||||
|  | 			addr:     "10.0.0.1:853", | ||||||
|  | 			zones:    map[string][]*Config{"invalid-zone": nil}, | ||||||
|  | 			expectedOutput: "Warning: Domain \"invalid-zone\" does not follow RFC1035 preferred syntax\n" + | ||||||
|  | 				"tls://invalid-zone:853 on 10.0.0.1\n", | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:     "multiple zones sorted order", | ||||||
|  | 			protocol: "dns://", | ||||||
|  | 			addr:     "localhost:5353", | ||||||
|  | 			zones: map[string][]*Config{ | ||||||
|  | 				"c-zone.com.": nil, | ||||||
|  | 				"a-zone.org.": nil, | ||||||
|  | 				"b-zone.net.": nil, | ||||||
|  | 			}, | ||||||
|  | 			expectedOutput: "dns://a-zone.org.:5353 on localhost\n" + | ||||||
|  | 				"dns://b-zone.net.:5353 on localhost\n" + | ||||||
|  | 				"dns://c-zone.com.:5353 on localhost\n", | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:           "addr parse error", | ||||||
|  | 			protocol:       "grpc://", | ||||||
|  | 			addr:           "[::1]:8080:extra", // Malformed, should cause SplitProtocolHostPort to error | ||||||
|  | 			zones:          map[string][]*Config{"error.example.": nil}, | ||||||
|  | 			expectedOutput: "grpc://error.example.:[::1]:8080:extra\n", | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:           "root zone", | ||||||
|  | 			protocol:       "dns://", | ||||||
|  | 			addr:           "192.168.1.1:53", | ||||||
|  | 			zones:          map[string][]*Config{".": nil}, | ||||||
|  | 			expectedOutput: "dns://.:53 on 192.168.1.1\n", | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:           "reverse zone", | ||||||
|  | 			protocol:       "dns://", | ||||||
|  | 			addr:           ":53", | ||||||
|  | 			zones:          map[string][]*Config{"1.0.168.192.in-addr.arpa.": nil}, | ||||||
|  | 			expectedOutput: "dns://1.0.168.192.in-addr.arpa.:53\n", | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:     "multiple zones mixed syntax and addr handling", | ||||||
|  | 			protocol: "quic://", | ||||||
|  | 			addr:     "coolserver.local:784", | ||||||
|  | 			zones: map[string][]*Config{ | ||||||
|  | 				"valid.net.":         nil, | ||||||
|  | 				"_tcp.service.":      nil, // Invalid syntax | ||||||
|  | 				"another.valid.com.": nil, | ||||||
|  | 			}, | ||||||
|  | 			expectedOutput: "Warning: Domain \"_tcp.service.\" does not follow RFC1035 preferred syntax\n" + | ||||||
|  | 				"quic://_tcp.service.:784 on coolserver.local\n" + | ||||||
|  | 				"quic://another.valid.com.:784 on coolserver.local\n" + | ||||||
|  | 				"quic://valid.net.:784 on coolserver.local\n", | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:     "zone with leading dash invalid", | ||||||
|  | 			protocol: "dns://", | ||||||
|  | 			addr:     "127.0.0.1:53", | ||||||
|  | 			zones:    map[string][]*Config{"-leadingdash.com.": nil}, | ||||||
|  | 			expectedOutput: "Warning: Domain \"-leadingdash.com.\" does not follow RFC1035 preferred syntax\n" + | ||||||
|  | 				"dns://-leadingdash.com.:53 on 127.0.0.1\n", | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, tc := range tests { | ||||||
|  | 		t.Run(tc.name, func(t *testing.T) { | ||||||
|  | 			got := startUpZones(tc.protocol, tc.addr, tc.zones) | ||||||
|  | 			if got != tc.expectedOutput { | ||||||
|  | 				// Use %q for expected and got to make differences in whitespace/newlines visible. | ||||||
|  | 				t.Errorf("startUpZones(%q, %q, ...) mismatch for test '%s':\nGot:\n%q\nExpected:\n%q", | ||||||
|  | 					tc.protocol, tc.addr, tc.name, got, tc.expectedOutput) | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|   | |||||||
| @@ -1,7 +1,15 @@ | |||||||
| package dnsserver | package dnsserver | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"context" | ||||||
|  | 	"errors" | ||||||
|  | 	"net" | ||||||
| 	"testing" | 	"testing" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	"github.com/miekg/dns" | ||||||
|  | 	"github.com/quic-go/quic-go" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestDoQWriterAddPrefix(t *testing.T) { | func TestDoQWriterAddPrefix(t *testing.T) { | ||||||
| @@ -18,3 +26,239 @@ func TestDoQWriterAddPrefix(t *testing.T) { | |||||||
| 		t.Errorf("Expected prefixed size to be 3, got: %d", size) | 		t.Errorf("Expected prefixed size to be 3, got: %d", size) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func TestDoQWriter_ResponseWriterMethods(t *testing.T) { | ||||||
|  | 	localAddr := &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: 1234} | ||||||
|  | 	remoteAddr := &net.UDPAddr{IP: net.ParseIP("8.8.8.8"), Port: 53} | ||||||
|  |  | ||||||
|  | 	writer := &DoQWriter{ | ||||||
|  | 		localAddr:  localAddr, | ||||||
|  | 		remoteAddr: remoteAddr, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err := writer.TsigStatus(); err != nil { | ||||||
|  | 		t.Errorf("TsigStatus() returned an error: %v", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// this is a no-op, just call it | ||||||
|  | 	writer.TsigTimersOnly(true) | ||||||
|  | 	writer.TsigTimersOnly(false) | ||||||
|  |  | ||||||
|  | 	// this is a no-op, just call it | ||||||
|  | 	writer.Hijack() | ||||||
|  |  | ||||||
|  | 	if addr := writer.LocalAddr(); addr != localAddr { | ||||||
|  | 		t.Errorf("LocalAddr() = %v, want %v", addr, localAddr) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if addr := writer.RemoteAddr(); addr != remoteAddr { | ||||||
|  | 		t.Errorf("RemoteAddr() = %v, want %v", addr, remoteAddr) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // mockQuicStream is a mock implementation of quic.Stream for testing. | ||||||
|  | type mockQuicStream struct { | ||||||
|  | 	writer func(p []byte) (n int, err error) | ||||||
|  | 	closer func() error | ||||||
|  | 	closed bool | ||||||
|  | 	data   []byte | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (m *mockQuicStream) Write(p []byte) (n int, err error) { | ||||||
|  | 	m.data = append(m.data, p...) | ||||||
|  | 	if m.writer != nil { | ||||||
|  | 		return m.writer(p) | ||||||
|  | 	} | ||||||
|  | 	return len(p), nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (m *mockQuicStream) Close() error { | ||||||
|  | 	m.closed = true | ||||||
|  | 	if m.closer != nil { | ||||||
|  | 		return m.closer() | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Required by quic.Stream interface, but not used in these tests | ||||||
|  | func (m *mockQuicStream) Read(p []byte) (n int, err error)      { return 0, nil } | ||||||
|  | func (m *mockQuicStream) CancelRead(code quic.StreamErrorCode)  {} | ||||||
|  | func (m *mockQuicStream) CancelWrite(code quic.StreamErrorCode) {} | ||||||
|  | func (m *mockQuicStream) SetReadDeadline(t time.Time) error     { return nil } | ||||||
|  | func (m *mockQuicStream) SetWriteDeadline(t time.Time) error    { return nil } | ||||||
|  | func (m *mockQuicStream) SetDeadline(t time.Time) error         { return nil } | ||||||
|  | func (m *mockQuicStream) StreamID() quic.StreamID               { return 0 } | ||||||
|  | func (m *mockQuicStream) Context() context.Context              { return nil } | ||||||
|  |  | ||||||
|  | func TestDoQWriter_Write(t *testing.T) { | ||||||
|  | 	tests := []struct { | ||||||
|  | 		name         string | ||||||
|  | 		input        []byte | ||||||
|  | 		streamWriter func(p []byte) (n int, err error) | ||||||
|  | 		expectErr    bool | ||||||
|  | 		expectedData []byte | ||||||
|  | 		expectedN    int | ||||||
|  | 	}{ | ||||||
|  | 		{ | ||||||
|  | 			name:  "successful write", | ||||||
|  | 			input: []byte{0x1, 0x2, 0x3}, | ||||||
|  | 			streamWriter: func(p []byte) (n int, err error) { | ||||||
|  | 				return len(p), nil | ||||||
|  | 			}, | ||||||
|  | 			expectErr:    false, | ||||||
|  | 			expectedData: []byte{0x0, 0x3, 0x1, 0x2, 0x3}, // 3-byte length prefix | ||||||
|  | 			expectedN:    5, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:  "stream write error", | ||||||
|  | 			input: []byte{0x4, 0x5}, | ||||||
|  | 			streamWriter: func(p []byte) (n int, err error) { | ||||||
|  | 				return 0, errors.New("stream error") | ||||||
|  | 			}, | ||||||
|  | 			expectErr:    true, | ||||||
|  | 			expectedData: []byte{0x0, 0x2, 0x4, 0x5}, // 2-byte length prefix | ||||||
|  | 			expectedN:    0, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, tt := range tests { | ||||||
|  | 		t.Run(tt.name, func(t *testing.T) { | ||||||
|  | 			mockStream := &mockQuicStream{writer: tt.streamWriter} | ||||||
|  | 			writer := &DoQWriter{stream: mockStream} | ||||||
|  |  | ||||||
|  | 			n, err := writer.Write(tt.input) | ||||||
|  |  | ||||||
|  | 			if (err != nil) != tt.expectErr { | ||||||
|  | 				t.Errorf("Write() error = %v, expectErr %v", err, tt.expectErr) | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 			if n != tt.expectedN { | ||||||
|  | 				t.Errorf("Write() n = %v, want %v", n, tt.expectedN) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if !bytes.Equal(mockStream.data, tt.expectedData) { | ||||||
|  | 				t.Errorf("Write() data written to stream = %X, want %X", mockStream.data, tt.expectedData) | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestDoQWriter_WriteMsg(t *testing.T) { | ||||||
|  | 	newMsg := func() *dns.Msg { | ||||||
|  | 		m := new(dns.Msg) | ||||||
|  | 		m.SetQuestion("example.com.", dns.TypeA) | ||||||
|  | 		return m | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	tests := []struct { | ||||||
|  | 		name         string | ||||||
|  | 		msg          *dns.Msg | ||||||
|  | 		mockStream   *mockQuicStream | ||||||
|  | 		expectErr    bool | ||||||
|  | 		expectClosed bool | ||||||
|  | 		expectedData []byte // Expected data written to stream (packed msg with prefix) | ||||||
|  | 		packErr      bool   // Simulate error during msg.Pack() | ||||||
|  | 	}{ | ||||||
|  | 		{ | ||||||
|  | 			name:         "successful write and close", | ||||||
|  | 			msg:          newMsg(), | ||||||
|  | 			mockStream:   &mockQuicStream{}, | ||||||
|  | 			expectErr:    false, | ||||||
|  | 			expectClosed: true, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:         "msg.Pack() error", | ||||||
|  | 			msg:          new(dns.Msg), | ||||||
|  | 			mockStream:   &mockQuicStream{}, | ||||||
|  | 			expectErr:    true, | ||||||
|  | 			packErr:      true,  // We'll make msg.Pack() fail by corrupting the msg or using a mock | ||||||
|  | 			expectClosed: false, // Close should not be called if Pack fails | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name: "stream write error", | ||||||
|  | 			msg:  newMsg(), | ||||||
|  | 			mockStream: &mockQuicStream{ | ||||||
|  | 				writer: func(p []byte) (n int, err error) { | ||||||
|  | 					return 0, errors.New("stream write failed") | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			expectErr:    true, | ||||||
|  | 			expectClosed: false, // Close should not be called if Write fails | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name: "stream close error", | ||||||
|  | 			msg:  newMsg(), | ||||||
|  | 			mockStream: &mockQuicStream{ | ||||||
|  | 				closer: func() error { | ||||||
|  | 					return errors.New("stream close failed") | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			expectErr:    true, | ||||||
|  | 			expectClosed: true, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, tt := range tests { | ||||||
|  | 		t.Run(tt.name, func(t *testing.T) { | ||||||
|  | 			if tt.packErr { | ||||||
|  | 				// Intentionally make the message invalid to cause a pack error. | ||||||
|  | 				// Invalid Rcode to ensure Pack fails. | ||||||
|  | 				tt.msg.Rcode = 1337 | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			writer := &DoQWriter{stream: tt.mockStream, Msg: tt.msg} | ||||||
|  | 			err := writer.WriteMsg(tt.msg) | ||||||
|  |  | ||||||
|  | 			if (err != nil) != tt.expectErr { | ||||||
|  | 				t.Errorf("WriteMsg() error = %v, expectErr %v", err, tt.expectErr) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if tt.mockStream.closed != tt.expectClosed { | ||||||
|  | 				t.Errorf("WriteMsg() stream closed = %v, want %v", tt.mockStream.closed, tt.expectClosed) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if tt.packErr { | ||||||
|  | 				if len(tt.mockStream.data) != 0 { | ||||||
|  | 					t.Errorf("WriteMsg() data written to stream on pack error = %X, want empty", tt.mockStream.data) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestDoQWriter_Close(t *testing.T) { | ||||||
|  | 	tests := []struct { | ||||||
|  | 		name       string | ||||||
|  | 		mockStream *mockQuicStream | ||||||
|  | 		expectErr  bool | ||||||
|  | 	}{ | ||||||
|  | 		{ | ||||||
|  | 			name:       "successful close", | ||||||
|  | 			mockStream: &mockQuicStream{}, | ||||||
|  | 			expectErr:  false, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name: "stream close error", | ||||||
|  | 			mockStream: &mockQuicStream{ | ||||||
|  | 				closer: func() error { | ||||||
|  | 					return errors.New("stream close error") | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			expectErr: true, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, tt := range tests { | ||||||
|  | 		t.Run(tt.name, func(t *testing.T) { | ||||||
|  | 			writer := &DoQWriter{stream: tt.mockStream} | ||||||
|  | 			err := writer.Close() | ||||||
|  |  | ||||||
|  | 			if (err != nil) != tt.expectErr { | ||||||
|  | 				t.Errorf("Close() error = %v, expectErr %v", err, tt.expectErr) | ||||||
|  | 			} | ||||||
|  | 			if !tt.mockStream.closed { | ||||||
|  | 				t.Errorf("Close() stream not marked as closed") | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|   | |||||||
| @@ -2,6 +2,8 @@ package dnsserver | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
|  | 	"github.com/coredns/caddy/caddyfile" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestHandler(t *testing.T) { | func TestHandler(t *testing.T) { | ||||||
| @@ -118,3 +120,215 @@ func TestGroupingServers(t *testing.T) { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func TestInspectServerBlocks(t *testing.T) { | ||||||
|  | 	tests := []struct { | ||||||
|  | 		name                 string | ||||||
|  | 		serverBlocks         []caddyfile.ServerBlock | ||||||
|  | 		expectedServerBlocks []caddyfile.ServerBlock | ||||||
|  | 		expectedConfigsLen   int | ||||||
|  | 		expectedZoneAddrs    map[string]zoneAddr | ||||||
|  | 		wantErr              bool | ||||||
|  | 	}{ | ||||||
|  | 		{ | ||||||
|  | 			name: "simple dns", | ||||||
|  | 			serverBlocks: []caddyfile.ServerBlock{ | ||||||
|  | 				{Keys: []string{"example.org"}}, | ||||||
|  | 			}, | ||||||
|  | 			expectedServerBlocks: []caddyfile.ServerBlock{ | ||||||
|  | 				{Keys: []string{"dns://example.org.:53"}}, | ||||||
|  | 			}, | ||||||
|  | 			expectedConfigsLen: 1, | ||||||
|  | 			expectedZoneAddrs: map[string]zoneAddr{ | ||||||
|  | 				"dns://example.org.:53": {Zone: "example.org.", Port: "53", Transport: "dns"}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name: "dns with port", | ||||||
|  | 			serverBlocks: []caddyfile.ServerBlock{ | ||||||
|  | 				{Keys: []string{"example.org:1053"}}, | ||||||
|  | 			}, | ||||||
|  | 			expectedServerBlocks: []caddyfile.ServerBlock{ | ||||||
|  | 				{Keys: []string{"dns://example.org.:1053"}}, | ||||||
|  | 			}, | ||||||
|  | 			expectedConfigsLen: 1, | ||||||
|  | 			expectedZoneAddrs: map[string]zoneAddr{ | ||||||
|  | 				"dns://example.org.:1053": {Zone: "example.org.", Port: "1053", Transport: "dns"}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name: "tls", | ||||||
|  | 			serverBlocks: []caddyfile.ServerBlock{ | ||||||
|  | 				{Keys: []string{"tls://example.org"}}, | ||||||
|  | 			}, | ||||||
|  | 			expectedServerBlocks: []caddyfile.ServerBlock{ | ||||||
|  | 				{Keys: []string{"tls://example.org.:853"}}, | ||||||
|  | 			}, | ||||||
|  | 			expectedConfigsLen: 1, | ||||||
|  | 			expectedZoneAddrs: map[string]zoneAddr{ | ||||||
|  | 				"tls://example.org.:853": {Zone: "example.org.", Port: "853", Transport: "tls"}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name: "quic", | ||||||
|  | 			serverBlocks: []caddyfile.ServerBlock{ | ||||||
|  | 				{Keys: []string{"quic://example.org"}}, | ||||||
|  | 			}, | ||||||
|  | 			expectedServerBlocks: []caddyfile.ServerBlock{ | ||||||
|  | 				{Keys: []string{"quic://example.org.:853"}}, | ||||||
|  | 			}, | ||||||
|  | 			expectedConfigsLen: 1, | ||||||
|  | 			expectedZoneAddrs: map[string]zoneAddr{ | ||||||
|  | 				"quic://example.org.:853": {Zone: "example.org.", Port: "853", Transport: "quic"}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name: "grpc", | ||||||
|  | 			serverBlocks: []caddyfile.ServerBlock{ | ||||||
|  | 				{Keys: []string{"grpc://example.org"}}, | ||||||
|  | 			}, | ||||||
|  | 			expectedServerBlocks: []caddyfile.ServerBlock{ | ||||||
|  | 				{Keys: []string{"grpc://example.org.:443"}}, | ||||||
|  | 			}, | ||||||
|  | 			expectedConfigsLen: 1, | ||||||
|  | 			expectedZoneAddrs: map[string]zoneAddr{ | ||||||
|  | 				"grpc://example.org.:443": {Zone: "example.org.", Port: "443", Transport: "grpc"}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name: "https", | ||||||
|  | 			serverBlocks: []caddyfile.ServerBlock{ | ||||||
|  | 				{Keys: []string{"https://example.org."}}, | ||||||
|  | 			}, | ||||||
|  | 			expectedServerBlocks: []caddyfile.ServerBlock{ | ||||||
|  | 				{Keys: []string{"https://example.org.:443"}}, | ||||||
|  | 			}, | ||||||
|  | 			expectedConfigsLen: 1, | ||||||
|  | 			expectedZoneAddrs: map[string]zoneAddr{ | ||||||
|  | 				"https://example.org.:443": {Zone: "example.org.", Port: "443", Transport: "https"}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name: "multiple hosts same key", | ||||||
|  | 			serverBlocks: []caddyfile.ServerBlock{ | ||||||
|  | 				{Keys: []string{"example.org,example.com:1053"}}, | ||||||
|  | 			}, | ||||||
|  | 			expectedServerBlocks: []caddyfile.ServerBlock{ | ||||||
|  | 				{Keys: []string{"dns://example.org,example.com.:1053"}}, | ||||||
|  | 			}, | ||||||
|  | 			expectedConfigsLen: 1, | ||||||
|  | 			expectedZoneAddrs: map[string]zoneAddr{ | ||||||
|  | 				"dns://example.org,example.com.:1053": {Zone: "example.org,example.com.", Port: "1053", Transport: "dns"}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name: "multiple keys", | ||||||
|  | 			serverBlocks: []caddyfile.ServerBlock{ | ||||||
|  | 				{Keys: []string{"example.org", "example.com:1053"}}, | ||||||
|  | 			}, | ||||||
|  | 			expectedServerBlocks: []caddyfile.ServerBlock{ | ||||||
|  | 				{Keys: []string{"dns://example.org.:53", "dns://example.com.:1053"}}, | ||||||
|  | 			}, | ||||||
|  | 			expectedConfigsLen: 2, | ||||||
|  | 			expectedZoneAddrs: map[string]zoneAddr{ | ||||||
|  | 				"dns://example.org.:53":   {Zone: "example.org.", Port: "53", Transport: "dns"}, | ||||||
|  | 				"dns://example.com.:1053": {Zone: "example.com.", Port: "1053", Transport: "dns"}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name: "fqdn input", | ||||||
|  | 			serverBlocks: []caddyfile.ServerBlock{ | ||||||
|  | 				{Keys: []string{"example.org."}}, | ||||||
|  | 			}, | ||||||
|  | 			expectedServerBlocks: []caddyfile.ServerBlock{ | ||||||
|  | 				{Keys: []string{"dns://example.org.:53"}}, | ||||||
|  | 			}, | ||||||
|  | 			expectedConfigsLen: 1, | ||||||
|  | 			expectedZoneAddrs: map[string]zoneAddr{ | ||||||
|  | 				"dns://example.org.:53": {Zone: "example.org.", Port: "53", Transport: "dns"}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name: "multiple server blocks", | ||||||
|  | 			serverBlocks: []caddyfile.ServerBlock{ | ||||||
|  | 				{Keys: []string{"example.org"}}, | ||||||
|  | 				{Keys: []string{"sub.example.org:1054"}}, | ||||||
|  | 			}, | ||||||
|  | 			expectedServerBlocks: []caddyfile.ServerBlock{ | ||||||
|  | 				{Keys: []string{"dns://example.org.:53"}}, | ||||||
|  | 				{Keys: []string{"dns://sub.example.org.:1054"}}, | ||||||
|  | 			}, | ||||||
|  | 			expectedConfigsLen: 2, | ||||||
|  | 			expectedZoneAddrs: map[string]zoneAddr{ | ||||||
|  | 				"dns://example.org.:53":       {Zone: "example.org.", Port: "53", Transport: "dns"}, | ||||||
|  | 				"dns://sub.example.org.:1054": {Zone: "sub.example.org.", Port: "1054", Transport: "dns"}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, tc := range tests { | ||||||
|  | 		t.Run(tc.name, func(t *testing.T) { | ||||||
|  | 			ctx := newContext(nil).(*dnsContext) | ||||||
|  | 			processedBlocks, err := ctx.InspectServerBlocks("TestInspectServerBlocks", tc.serverBlocks) | ||||||
|  |  | ||||||
|  | 			if (err != nil) != tc.wantErr { | ||||||
|  | 				t.Fatalf("InspectServerBlocks() error = %v, wantErr %v", err, tc.wantErr) | ||||||
|  | 			} | ||||||
|  | 			if tc.wantErr { | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if len(processedBlocks) != len(tc.expectedServerBlocks) { | ||||||
|  | 				t.Fatalf("Expected %d processed blocks, got %d", len(tc.expectedServerBlocks), len(processedBlocks)) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			for i, block := range processedBlocks { | ||||||
|  | 				expectedBlock := tc.expectedServerBlocks[i] | ||||||
|  | 				if len(block.Keys) != len(expectedBlock.Keys) { | ||||||
|  | 					t.Errorf("Block %d: expected %d keys, got %d. Expected: %v, Got: %v", i, len(expectedBlock.Keys), len(block.Keys), expectedBlock.Keys, block.Keys) | ||||||
|  | 					continue | ||||||
|  | 				} | ||||||
|  | 				for j, key := range block.Keys { | ||||||
|  | 					if key != expectedBlock.Keys[j] { | ||||||
|  | 						t.Errorf("Block %d, Key %d: expected key '%s', got '%s'", i, j, expectedBlock.Keys[j], key) | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if len(ctx.configs) != tc.expectedConfigsLen { | ||||||
|  | 				t.Errorf("Expected %d configs to be created, got %d", tc.expectedConfigsLen, len(ctx.configs)) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if tc.expectedZoneAddrs != nil { | ||||||
|  | 				configIndex := 0 | ||||||
|  | 				for ib := range processedBlocks { | ||||||
|  | 					for ik, key := range processedBlocks[ib].Keys { | ||||||
|  | 						if configIndex >= len(ctx.configs) { | ||||||
|  | 							t.Fatalf("Not enough configs stored, expected at least %d, processed block %d key %d", configIndex+1, ib, ik) | ||||||
|  | 						} | ||||||
|  | 						cfg := ctx.configs[configIndex] | ||||||
|  | 						expectedZa, ok := tc.expectedZoneAddrs[key] | ||||||
|  | 						if !ok { | ||||||
|  | 							t.Errorf("No expected zoneAddr for processed key '%s'", key) | ||||||
|  | 							continue | ||||||
|  | 						} | ||||||
|  |  | ||||||
|  | 						if cfg.Zone != expectedZa.Zone { | ||||||
|  | 							t.Errorf("Config for key '%s': expected Zone '%s', got '%s'", key, expectedZa.Zone, cfg.Zone) | ||||||
|  | 						} | ||||||
|  | 						if cfg.Port != expectedZa.Port { | ||||||
|  | 							t.Errorf("Config for key '%s': expected Port '%s', got '%s'", key, expectedZa.Port, cfg.Port) | ||||||
|  | 						} | ||||||
|  | 						if cfg.Transport != expectedZa.Transport { | ||||||
|  | 							t.Errorf("Config for key '%s': expected Transport '%s', got '%s'", key, expectedZa.Transport, cfg.Transport) | ||||||
|  | 						} | ||||||
|  | 						if len(cfg.ListenHosts) != 1 || cfg.ListenHosts[0] != "" { | ||||||
|  | 							t.Errorf("Config for key '%s': expected ListenHosts [''], got %v", key, cfg.ListenHosts) | ||||||
|  | 						} | ||||||
|  | 						configIndex++ | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user