Skip to content

Commit ab0abed

Browse files
committed
tests: add rudimentary tests for resolveIPs
This mocks net.LookupIP with a wrapper type. In future, this might be easier to accomplish with the help of the standard library (see discussion in golang/go#12503). With muety#1 in the pipeline, I might consider swapping the stdlib resolver with a full fledged DNS client (github.com/miekg/dns), as the stdlib resolver does not return TTL values.
1 parent 6dadc64 commit ab0abed

File tree

3 files changed

+119
-1
lines changed

3 files changed

+119
-1
lines changed

dns.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package caddy_remote_host
2+
3+
import (
4+
"context"
5+
"net"
6+
)
7+
8+
// A resolver is able to lookup IP addresses from a given host name.
9+
// The net.Resolver type (found at net.DefaultResolver) matches this
10+
// interface.
11+
//
12+
// This is intended for testing.
13+
type resolver interface {
14+
LookupIPAddr(ctx context.Context, host string) (addrs []net.IPAddr, err error)
15+
}
16+
17+
// lookupIP does the same thing as net.LookupIP, except it doesn't use
18+
// net.DefaultResolver when given an alternative resolver implementation
19+
// as first argument.
20+
// (Setting resolv to nil will fallback to net.DefaultResolver.)
21+
func lookupIP(resolv resolver, host string) ([]net.IP, error) {
22+
r := resolv
23+
if r == nil {
24+
r = net.DefaultResolver
25+
}
26+
27+
addrs, err := r.LookupIPAddr(context.Background(), host)
28+
if err != nil {
29+
return nil, err
30+
}
31+
ips := make([]net.IP, len(addrs))
32+
for i, ia := range addrs {
33+
ips[i] = ia.IP
34+
}
35+
return ips, nil
36+
}

plugin.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ type MatchRemoteHost struct {
4848

4949
logger *zap.Logger
5050
cache *cache.Cache
51+
52+
// resolver is set in tests to provide canned results
53+
resolver resolver
5154
}
5255

5356
// CaddyModule returns the Caddy module information.
@@ -160,7 +163,7 @@ func (m *MatchRemoteHost) resolveIPs() ([]net.IP, error) {
160163
allIPs := make([]net.IP, 0)
161164

162165
for _, h := range m.Hosts {
163-
ips, err := net.LookupIP(h)
166+
ips, err := lookupIP(m.resolver, h)
164167
if err != nil {
165168
return nil, err
166169
}

plugin_internals_test.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ package caddy_remote_host
44
// public API see package caddy_remote_host_test.
55

66
import (
7+
"context"
8+
"errors"
9+
"net"
710
"testing"
811

912
"github.com/caddyserver/caddy/v2"
@@ -19,5 +22,81 @@ func TestMatchRemoteHost_Provision(t *testing.T) {
1922
require.NoError(t, err)
2023
assert.NotNil(t, subject.logger)
2124
assert.NotNil(t, subject.cache)
25+
assert.Nil(t, subject.resolver)
2226
assert.NotNil(t, hostRegex)
2327
}
28+
29+
type lookupResult struct {
30+
ips []net.IPAddr
31+
err error
32+
}
33+
34+
func resolvesTo(ips ...string) lookupResult {
35+
r := lookupResult{ips: make([]net.IPAddr, len(ips))}
36+
for i, ip := range ips {
37+
r.ips[i] = net.IPAddr{IP: net.ParseIP(ip)}
38+
}
39+
return r
40+
}
41+
42+
type mockResolver struct {
43+
addrs map[string]lookupResult
44+
}
45+
46+
func (m *mockResolver) LookupIPAddr(_ context.Context, h string) ([]net.IPAddr, error) {
47+
if result, ok := m.addrs[h]; ok {
48+
return result.ips, result.err
49+
}
50+
return nil, errors.New("no suitable address found")
51+
}
52+
53+
func TestMatchRemoteHost_resolveIPs(t *testing.T) {
54+
mock := &mockResolver{
55+
addrs: map[string]lookupResult{
56+
"example.com": resolvesTo("127.0.0.1", "127.0.0.2"),
57+
"example.org": resolvesTo("::1", "fe80::1"),
58+
"nil.records.example": {},
59+
"no.records.example": {ips: make([]net.IPAddr, 0)},
60+
},
61+
}
62+
63+
subject := MatchRemoteHost{resolver: mock}
64+
for host := range mock.addrs {
65+
subject.Hosts = append(subject.Hosts, host)
66+
}
67+
68+
require.NoError(t, subject.Provision(caddy.Context{}))
69+
ips, err := subject.resolveIPs()
70+
require.NoError(t, err)
71+
72+
var haveIPs []string
73+
for _, result := range mock.addrs {
74+
for _, ip := range result.ips {
75+
haveIPs = append(haveIPs, ip.String())
76+
}
77+
}
78+
79+
wantIPs := make([]string, len(ips))
80+
for i, ip := range ips {
81+
wantIPs[i] = ip.String()
82+
}
83+
84+
assert.ElementsMatch(t, haveIPs, wantIPs)
85+
}
86+
87+
func TestMatchRemoteHost_resolveIPs_failure(t *testing.T) {
88+
subject := MatchRemoteHost{
89+
Hosts: []string{"example.com"},
90+
resolver: &mockResolver{map[string]lookupResult{
91+
"example.com": {err: &net.DNSError{Err: "no suitable host found"}},
92+
}},
93+
}
94+
95+
require.NoError(t, subject.Provision(caddy.Context{}))
96+
97+
ips, err := subject.resolveIPs()
98+
// XXX: the expected message is constructed within package net
99+
// and might change with future Go versions
100+
assert.EqualError(t, err, "lookup : no suitable host found")
101+
assert.Empty(t, ips)
102+
}

0 commit comments

Comments
 (0)