github.com/netdata/go.d.plugin@v0.58.1/modules/pihole/pihole_test.go (about)

     1  // SPDX-License-Identifier: GPL-3.0-or-later
     2  
     3  package pihole
     4  
     5  import (
     6  	"fmt"
     7  	"net/http"
     8  	"net/http/httptest"
     9  	"os"
    10  	"testing"
    11  
    12  	"github.com/netdata/go.d.plugin/pkg/web"
    13  
    14  	"github.com/stretchr/testify/assert"
    15  	"github.com/stretchr/testify/require"
    16  )
    17  
    18  const (
    19  	pathSetupVarsOK    = "testdata/setupVars.conf"
    20  	pathSetupVarsWrong = "testdata/wrong.conf"
    21  )
    22  
    23  var (
    24  	dataEmptyResp                     = []byte("[]")
    25  	dataSummaryRawResp, _             = os.ReadFile("testdata/summaryRaw.json")
    26  	dataGetQueryTypesResp, _          = os.ReadFile("testdata/getQueryTypes.json")
    27  	dataGetForwardDestinationsResp, _ = os.ReadFile("testdata/getForwardDestinations.json")
    28  )
    29  
    30  func TestPihole_Init(t *testing.T) {
    31  	tests := map[string]struct {
    32  		wantFail bool
    33  		config   Config
    34  	}{
    35  		"success with default": {
    36  			wantFail: false,
    37  			config:   New().Config,
    38  		},
    39  		"fail when URL not set": {
    40  			wantFail: true,
    41  			config: Config{
    42  				HTTP: web.HTTP{
    43  					Request: web.Request{URL: ""},
    44  				},
    45  			},
    46  		},
    47  	}
    48  
    49  	for name, test := range tests {
    50  		t.Run(name, func(t *testing.T) {
    51  			p := New()
    52  			p.Config = test.config
    53  
    54  			if test.wantFail {
    55  				assert.False(t, p.Init())
    56  			} else {
    57  				assert.True(t, p.Init())
    58  			}
    59  		})
    60  	}
    61  }
    62  
    63  func TestPihole_Check(t *testing.T) {
    64  	tests := map[string]struct {
    65  		wantFail bool
    66  		prepare  func(t *testing.T) (p *Pihole, cleanup func())
    67  	}{
    68  		"success with web password": {
    69  			wantFail: false,
    70  			prepare:  caseSuccessWithWebPassword,
    71  		},
    72  		"fail without web password": {
    73  			wantFail: true,
    74  			prepare:  caseFailNoWebPassword,
    75  		},
    76  		"fail on unsupported version": {
    77  			wantFail: true,
    78  			prepare:  caseFailUnsupportedVersion,
    79  		},
    80  	}
    81  
    82  	for name, test := range tests {
    83  		t.Run(name, func(t *testing.T) {
    84  			p, cleanup := test.prepare(t)
    85  			defer cleanup()
    86  
    87  			if test.wantFail {
    88  				assert.False(t, p.Check())
    89  			} else {
    90  				assert.True(t, p.Check())
    91  			}
    92  		})
    93  	}
    94  }
    95  
    96  func TestPihole_Charts(t *testing.T) {
    97  	assert.NotNil(t, New().Charts())
    98  }
    99  
   100  func TestPihole_Collect(t *testing.T) {
   101  	tests := map[string]struct {
   102  		prepare       func(t *testing.T) (p *Pihole, cleanup func())
   103  		wantMetrics   map[string]int64
   104  		wantNumCharts int
   105  	}{
   106  		"success with web password": {
   107  			prepare:       caseSuccessWithWebPassword,
   108  			wantNumCharts: len(baseCharts) + 2,
   109  			wantMetrics: map[string]int64{
   110  				"A":                        1229,
   111  				"AAAA":                     1229,
   112  				"ANY":                      100,
   113  				"PTR":                      7143,
   114  				"SOA":                      100,
   115  				"SRV":                      100,
   116  				"TXT":                      100,
   117  				"ads_blocked_today":        1,
   118  				"ads_blocked_today_perc":   33333,
   119  				"ads_percentage_today":     100,
   120  				"blocking_status_disabled": 0,
   121  				"blocking_status_enabled":  1,
   122  				"blocklist_last_update":    106273651,
   123  				"destination_blocked":      220,
   124  				"destination_cached":       8840,
   125  				"destination_other":        940,
   126  				"dns_queries_today":        1,
   127  				"domains_being_blocked":    1,
   128  				"queries_cached":           1,
   129  				"queries_cached_perc":      33333,
   130  				"queries_forwarded":        1,
   131  				"queries_forwarded_perc":   33333,
   132  				"unique_clients":           1,
   133  			},
   134  		},
   135  		"fail without web password": {
   136  			prepare:     caseFailNoWebPassword,
   137  			wantMetrics: nil,
   138  		},
   139  		"fail on unsupported version": {
   140  			prepare:     caseFailUnsupportedVersion,
   141  			wantMetrics: nil,
   142  		},
   143  	}
   144  
   145  	for name, test := range tests {
   146  		t.Run(name, func(t *testing.T) {
   147  			p, cleanup := test.prepare(t)
   148  			defer cleanup()
   149  
   150  			mx := p.Collect()
   151  
   152  			copyBlockListLastUpdate(mx, test.wantMetrics)
   153  			require.Equal(t, test.wantMetrics, mx)
   154  			if len(test.wantMetrics) > 0 {
   155  				assert.Len(t, *p.Charts(), test.wantNumCharts)
   156  			}
   157  		})
   158  	}
   159  }
   160  
   161  func caseSuccessWithWebPassword(t *testing.T) (*Pihole, func()) {
   162  	p, srv := New(), mockPiholeServer{}.newPiholeHTTPServer()
   163  
   164  	p.SetupVarsPath = pathSetupVarsOK
   165  	p.URL = srv.URL
   166  
   167  	require.True(t, p.Init())
   168  
   169  	return p, srv.Close
   170  }
   171  
   172  func caseFailNoWebPassword(t *testing.T) (*Pihole, func()) {
   173  	p, srv := New(), mockPiholeServer{}.newPiholeHTTPServer()
   174  
   175  	p.SetupVarsPath = pathSetupVarsWrong
   176  	p.URL = srv.URL
   177  
   178  	require.True(t, p.Init())
   179  
   180  	return p, srv.Close
   181  }
   182  
   183  func caseFailUnsupportedVersion(t *testing.T) (*Pihole, func()) {
   184  	p, srv := New(), mockPiholeServer{unsupportedVersion: true}.newPiholeHTTPServer()
   185  
   186  	p.SetupVarsPath = pathSetupVarsOK
   187  	p.URL = srv.URL
   188  
   189  	require.True(t, p.Init())
   190  
   191  	return p, srv.Close
   192  }
   193  
   194  type mockPiholeServer struct {
   195  	unsupportedVersion bool
   196  	errOnAPIVersion    bool
   197  	errOnSummary       bool
   198  	errOnQueryTypes    bool
   199  	errOnGetForwardDst bool
   200  	errOnTopClients    bool
   201  	errOnTopItems      bool
   202  }
   203  
   204  func (m mockPiholeServer) newPiholeHTTPServer() *httptest.Server {
   205  	return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   206  		if r.URL.Path != urlPathAPI || len(r.URL.Query()) == 0 {
   207  			w.WriteHeader(http.StatusBadRequest)
   208  		}
   209  
   210  		if r.URL.Query().Get(urlQueryKeyAuth) == "" {
   211  			_, _ = w.Write(dataEmptyResp)
   212  			return
   213  		}
   214  
   215  		if r.URL.Query().Has(urlQueryKeyAPIVersion) {
   216  			if m.errOnAPIVersion {
   217  				w.WriteHeader(http.StatusNotFound)
   218  			} else if m.unsupportedVersion {
   219  				_, _ = w.Write([]byte(fmt.Sprintf(`{"version": %d}`, wantAPIVersion+1)))
   220  			} else {
   221  				_, _ = w.Write([]byte(fmt.Sprintf(`{"version": %d}`, wantAPIVersion)))
   222  			}
   223  			return
   224  		}
   225  
   226  		if r.URL.Query().Has(urlQueryKeySummaryRaw) {
   227  			if m.errOnSummary {
   228  				w.WriteHeader(http.StatusNotFound)
   229  			} else {
   230  				_, _ = w.Write(dataSummaryRawResp)
   231  			}
   232  			return
   233  		}
   234  
   235  		data := dataEmptyResp
   236  		isErr := false
   237  		switch {
   238  		case r.URL.Query().Has(urlQueryKeyGetQueryTypes):
   239  			data, isErr = dataGetQueryTypesResp, m.errOnQueryTypes
   240  		case r.URL.Query().Has(urlQueryKeyGetForwardDestinations):
   241  			data, isErr = dataGetForwardDestinationsResp, m.errOnGetForwardDst
   242  		}
   243  
   244  		if isErr {
   245  			w.WriteHeader(http.StatusNotFound)
   246  		} else {
   247  			_, _ = w.Write(data)
   248  		}
   249  	}))
   250  }
   251  
   252  func copyBlockListLastUpdate(dst, src map[string]int64) {
   253  	k := "blocklist_last_update"
   254  	if v, ok := src[k]; ok {
   255  		if _, ok := dst[k]; ok {
   256  			dst[k] = v
   257  		}
   258  	}
   259  }