1/*
2** Zabbix
3** Copyright (C) 2001-2021 Zabbix SIA
4**
5** This program is free software; you can redistribute it and/or modify
6** it under the terms of the GNU General Public License as published by
7** the Free Software Foundation; either version 2 of the License, or
8** (at your option) any later version.
9**
10** This program is distributed in the hope that it will be useful,
11** but WITHOUT ANY WARRANTY; without even the implied warranty of
12** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13** GNU General Public License for more details.
14**
15** You should have received a copy of the GNU General Public License
16** along with this program; if not, write to the Free Software
17** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18**/
19
20package memcached
21
22import (
23	"errors"
24	"io"
25	"reflect"
26	"testing"
27	"time"
28
29	"zabbix.com/pkg/zbxerr"
30
31	"zabbix.com/pkg/plugin"
32	"zabbix.com/plugins/memcached/mockserver"
33)
34
35func handleStat(req *mockserver.MCRequest, w io.Writer) (ret *mockserver.MCResponse) {
36	ret = &mockserver.MCResponse{}
37	ret.Status = mockserver.Success
38
39	switch string(req.Key) {
40	case statsTypeGeneral:
41		ret.Key = []byte("version")
42		ret.Body = []byte("1.4.15")
43		_, _ = ret.Transmit(w)
44	case statsTypeItems:
45		ret.Key = []byte("items:1:number")
46		ret.Body = []byte("1")
47		_, _ = ret.Transmit(w)
48	case statsTypeSlabs:
49		ret.Key = []byte("1:chunk_size")
50		ret.Body = []byte("96")
51		_, _ = ret.Transmit(w)
52		ret.Key = []byte("1:total_pages")
53		ret.Body = []byte("1")
54		_, _ = ret.Transmit(w)
55	case statsTypeSizes:
56		ret.Key = []byte("96")
57		ret.Body = []byte("1")
58		_, _ = ret.Transmit(w)
59	case statsTypeSettings:
60		ret.Key = []byte("maxconns")
61		ret.Body = []byte("1024")
62		_, _ = ret.Transmit(w)
63	default:
64		ret.Status = mockserver.UnknownCommand
65		return
66	}
67
68	ret.Key = nil
69	ret.Body = nil
70
71	return ret
72}
73
74func handleNoOp(_ *mockserver.MCRequest, _ io.Writer) *mockserver.MCResponse {
75	return &mockserver.MCResponse{}
76}
77
78func TestPlugin_Start(t *testing.T) {
79	p := Plugin{}
80
81	t.Run("Connection manager must be initialized", func(t *testing.T) {
82		p.Start()
83		if p.connMgr == nil {
84			t.Error("Connection manager is not initialized")
85		}
86	})
87}
88
89func TestPlugin_Export(t *testing.T) {
90	ms, err := mockserver.NewMockServer()
91	if err != nil {
92		panic(err)
93	}
94
95	ms.RegisterHandler(mockserver.Stat, handleStat)
96	ms.RegisterHandler(mockserver.Noop, handleNoOp)
97
98	go ms.ListenAndServe()
99
100	time.Sleep(1 * time.Second)
101
102	type args struct {
103		key    string
104		params []string
105		ctx    plugin.ContextProvider
106	}
107
108	p := Plugin{}
109	p.Init(pluginName)
110	p.Configure(&plugin.GlobalOptions{Timeout: 30}, nil)
111
112	p.Start()
113	defer p.Stop()
114
115	tests := []struct {
116		name       string
117		p          *Plugin
118		args       args
119		wantResult interface{}
120		wantErr    error
121	}{
122		{
123			name:       "Too many parameters",
124			p:          &p,
125			args:       args{keyPing, []string{"tcp://127.0.0.1", "", "", "excess_param"}, nil},
126			wantResult: nil,
127			wantErr:    zbxerr.ErrorTooManyParameters,
128		},
129		{
130			name:       "Must successfully get general stats",
131			p:          &p,
132			args:       args{keyStats, []string{"tcp://" + ms.GetAddr()}, nil},
133			wantResult: `{"version":"1.4.15"}`,
134			wantErr:    nil,
135		},
136		{
137			name:       "Type parameter must be passed correctly",
138			p:          &p,
139			args:       args{keyStats, []string{"tcp://" + ms.GetAddr(), "", "", statsTypeItems}, nil},
140			wantResult: `{"items:1:number":"1"}`,
141			wantErr:    nil,
142		},
143		{
144			name:       "Must fail if server is not working",
145			p:          &p,
146			args:       args{keyStats, []string{"tcp://127.0.0.1:1"}, nil},
147			wantResult: nil,
148			wantErr:    errors.New("Cannot fetch data: dial tcp 127.0.0.1:1: connect: connection refused."),
149		},
150		{
151			name:       "Must not fail if server is not working for " + keyPing,
152			p:          &p,
153			args:       args{keyPing, []string{"tcp://127.0.0.1:1"}, nil},
154			wantResult: pingFailed,
155			wantErr:    nil,
156		},
157		{
158			name:       "pingHandler must create connection to server and run NoOp command",
159			p:          &p,
160			args:       args{keyPing, []string{"tcp://" + ms.GetAddr()}, nil},
161			wantResult: pingOk,
162			wantErr:    nil,
163		},
164	}
165	for _, tt := range tests {
166		t.Run(tt.name, func(t *testing.T) {
167			gotResult, err := tt.p.Export(tt.args.key, tt.args.params, tt.args.ctx)
168			if err != nil && (tt.wantErr == nil || err.Error() != tt.wantErr.Error()) {
169				t.Errorf("Plugin.Export() error = %v, wantErr %v", err, tt.wantErr)
170				return
171			}
172			if !reflect.DeepEqual(gotResult, tt.wantResult) {
173				t.Errorf("Plugin.Export() = %v, want %v", gotResult, tt.wantResult)
174			}
175		})
176	}
177}
178
179func TestPlugin_Stop(t *testing.T) {
180	p := Plugin{}
181	p.Start()
182	t.Run("Connection manager must be deinitialized", func(t *testing.T) {
183		p.Stop()
184		if p.connMgr != nil {
185			t.Error("Connection manager is not deinitialized")
186		}
187	})
188}
189