github.com/netdata/go.d.plugin@v0.58.1/modules/dnsmasq/dnsmasq_test.go (about) 1 // SPDX-License-Identifier: GPL-3.0-or-later 2 3 package dnsmasq 4 5 import ( 6 "errors" 7 "fmt" 8 "testing" 9 "time" 10 11 "github.com/miekg/dns" 12 "github.com/stretchr/testify/assert" 13 "github.com/stretchr/testify/require" 14 ) 15 16 func TestNew(t *testing.T) { 17 assert.IsType(t, (*Dnsmasq)(nil), New()) 18 } 19 20 func TestDnsmasq_Init(t *testing.T) { 21 tests := map[string]struct { 22 config Config 23 wantFail bool 24 }{ 25 "success on default config": { 26 config: New().Config, 27 }, 28 "fails on unset 'address'": { 29 wantFail: true, 30 config: Config{ 31 Protocol: "udp", 32 Address: "", 33 }, 34 }, 35 "fails on unset 'protocol'": { 36 wantFail: true, 37 config: Config{ 38 Protocol: "", 39 Address: "127.0.0.1:53", 40 }, 41 }, 42 "fails on invalid 'protocol'": { 43 wantFail: true, 44 config: Config{ 45 Protocol: "http", 46 Address: "127.0.0.1:53", 47 }, 48 }, 49 } 50 51 for name, test := range tests { 52 t.Run(name, func(t *testing.T) { 53 ns := New() 54 ns.Config = test.config 55 56 if test.wantFail { 57 assert.False(t, ns.Init()) 58 } else { 59 assert.True(t, ns.Init()) 60 } 61 }) 62 } 63 } 64 65 func TestDnsmasq_Check(t *testing.T) { 66 tests := map[string]struct { 67 prepare func() *Dnsmasq 68 wantFail bool 69 }{ 70 "success on valid response": { 71 prepare: prepareOKDnsmasq, 72 }, 73 "fails on error on cache stats query": { 74 wantFail: true, 75 prepare: prepareErrorOnExchangeDnsmasq, 76 }, 77 "fails on response rcode is not success": { 78 wantFail: true, 79 prepare: prepareRcodeServerFailureOnExchangeDnsmasq, 80 }, 81 } 82 83 for name, test := range tests { 84 t.Run(name, func(t *testing.T) { 85 dnsmasq := test.prepare() 86 require.True(t, dnsmasq.Init()) 87 88 if test.wantFail { 89 assert.False(t, dnsmasq.Check()) 90 } else { 91 assert.True(t, dnsmasq.Check()) 92 } 93 }) 94 } 95 } 96 97 func TestDnsmasq_Charts(t *testing.T) { 98 dnsmasq := New() 99 require.True(t, dnsmasq.Init()) 100 assert.NotNil(t, dnsmasq.Charts()) 101 } 102 103 func TestDnsmasq_Cleanup(t *testing.T) { 104 assert.NotPanics(t, New().Cleanup) 105 } 106 107 func TestDnsmasq_Collect(t *testing.T) { 108 tests := map[string]struct { 109 prepare func() *Dnsmasq 110 wantCollected map[string]int64 111 }{ 112 "success on valid response": { 113 prepare: prepareOKDnsmasq, 114 wantCollected: map[string]int64{ 115 //"auth": 5, 116 "cachesize": 999, 117 "evictions": 5, 118 "failed_queries": 9, 119 "hits": 100, 120 "insertions": 10, 121 "misses": 50, 122 "queries": 17, 123 }, 124 }, 125 "fails on error on cache stats query": { 126 prepare: prepareErrorOnExchangeDnsmasq, 127 }, 128 "fails on response rcode is not success": { 129 prepare: prepareRcodeServerFailureOnExchangeDnsmasq, 130 }, 131 } 132 133 for name, test := range tests { 134 t.Run(name, func(t *testing.T) { 135 dnsmasq := test.prepare() 136 require.True(t, dnsmasq.Init()) 137 138 collected := dnsmasq.Collect() 139 140 assert.Equal(t, test.wantCollected, collected) 141 if len(test.wantCollected) > 0 { 142 ensureCollectedHasAllChartsDimsVarsIDs(t, dnsmasq, collected) 143 } 144 }) 145 } 146 } 147 148 func ensureCollectedHasAllChartsDimsVarsIDs(t *testing.T, dnsmasq *Dnsmasq, collected map[string]int64) { 149 for _, chart := range *dnsmasq.Charts() { 150 if chart.Obsolete { 151 continue 152 } 153 for _, dim := range chart.Dims { 154 _, ok := collected[dim.ID] 155 assert.Truef(t, ok, "chart '%s' dim '%s': no dim in collected", dim.ID, chart.ID) 156 } 157 for _, v := range chart.Vars { 158 _, ok := collected[v.ID] 159 assert.Truef(t, ok, "chart '%s' dim '%s': no dim in collected", v.ID, chart.ID) 160 } 161 } 162 } 163 164 func prepareOKDnsmasq() *Dnsmasq { 165 dnsmasq := New() 166 dnsmasq.newDNSClient = func(network string, timeout time.Duration) dnsClient { 167 return &mockDNSClient{} 168 } 169 return dnsmasq 170 } 171 172 func prepareErrorOnExchangeDnsmasq() *Dnsmasq { 173 dnsmasq := New() 174 dnsmasq.newDNSClient = func(network string, timeout time.Duration) dnsClient { 175 return &mockDNSClient{ 176 errOnExchange: true, 177 } 178 } 179 return dnsmasq 180 } 181 182 func prepareRcodeServerFailureOnExchangeDnsmasq() *Dnsmasq { 183 dnsmasq := New() 184 dnsmasq.newDNSClient = func(network string, timeout time.Duration) dnsClient { 185 return &mockDNSClient{ 186 rcodeServerFailureOnExchange: true, 187 } 188 } 189 return dnsmasq 190 } 191 192 type mockDNSClient struct { 193 errOnExchange bool 194 rcodeServerFailureOnExchange bool 195 } 196 197 func (m mockDNSClient) Exchange(msg *dns.Msg, _ string) (*dns.Msg, time.Duration, error) { 198 if m.errOnExchange { 199 return nil, 0, errors.New("'Exchange' error") 200 } 201 if m.rcodeServerFailureOnExchange { 202 resp := &dns.Msg{MsgHdr: dns.MsgHdr{Rcode: dns.RcodeServerFailure}} 203 return resp, 0, nil 204 } 205 206 var answers []dns.RR 207 for _, q := range msg.Question { 208 a, err := prepareDNSAnswer(q) 209 if err != nil { 210 return nil, 0, err 211 } 212 answers = append(answers, a) 213 } 214 215 resp := &dns.Msg{ 216 MsgHdr: dns.MsgHdr{ 217 Rcode: dns.RcodeSuccess, 218 }, 219 Answer: answers, 220 } 221 return resp, 0, nil 222 } 223 224 func prepareDNSAnswer(q dns.Question) (dns.RR, error) { 225 if want, got := dns.TypeToString[dns.TypeTXT], dns.TypeToString[q.Qtype]; want != got { 226 return nil, fmt.Errorf("unexpected Qtype, want=%s, got=%s", want, got) 227 } 228 if want, got := dns.ClassToString[dns.ClassCHAOS], dns.ClassToString[q.Qclass]; want != got { 229 return nil, fmt.Errorf("unexpected Qclass, want=%s, got=%s", want, got) 230 } 231 232 var txt []string 233 switch q.Name { 234 case "cachesize.bind.": 235 txt = []string{"999"} 236 case "insertions.bind.": 237 txt = []string{"10"} 238 case "evictions.bind.": 239 txt = []string{"5"} 240 case "hits.bind.": 241 txt = []string{"100"} 242 case "misses.bind.": 243 txt = []string{"50"} 244 case "auth.bind.": 245 txt = []string{"5"} 246 case "servers.bind.": 247 txt = []string{"10.0.0.1#53 10 5", "1.1.1.1#53 4 3", "1.0.0.1#53 3 1"} 248 default: 249 return nil, fmt.Errorf("unexpected question Name: %s", q.Name) 250 } 251 252 rr := &dns.TXT{ 253 Hdr: dns.RR_Header{ 254 Name: q.Name, 255 Rrtype: dns.TypeTXT, 256 Class: dns.ClassCHAOS, 257 }, 258 Txt: txt, 259 } 260 return rr, nil 261 }