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  }