summaryrefslogtreecommitdiffstats
path: root/src/go/plugin/go.d/modules/tor/tor_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/go/plugin/go.d/modules/tor/tor_test.go')
-rw-r--r--src/go/plugin/go.d/modules/tor/tor_test.go328
1 files changed, 328 insertions, 0 deletions
diff --git a/src/go/plugin/go.d/modules/tor/tor_test.go b/src/go/plugin/go.d/modules/tor/tor_test.go
new file mode 100644
index 000000000..35001c39a
--- /dev/null
+++ b/src/go/plugin/go.d/modules/tor/tor_test.go
@@ -0,0 +1,328 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package tor
+
+import (
+ "bufio"
+ "errors"
+ "fmt"
+ "io"
+ "net"
+ "os"
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+var (
+ dataConfigJSON, _ = os.ReadFile("testdata/config.json")
+ dataConfigYAML, _ = os.ReadFile("testdata/config.yaml")
+)
+
+func Test_testDataIsValid(t *testing.T) {
+ for name, data := range map[string][]byte{
+ "dataConfigJSON": dataConfigJSON,
+ "dataConfigYAML": dataConfigYAML,
+ } {
+ require.NotNil(t, data, name)
+ }
+}
+
+func TestTor_ConfigurationSerialize(t *testing.T) {
+ module.TestConfigurationSerialize(t, &Tor{}, dataConfigJSON, dataConfigYAML)
+}
+
+func TestTor_Init(t *testing.T) {
+ tests := map[string]struct {
+ config Config
+ wantFail bool
+ }{
+ "success with default config": {
+ wantFail: false,
+ config: New().Config,
+ },
+ "fails if address not set": {
+ wantFail: true,
+ config: func() Config {
+ conf := New().Config
+ conf.Address = ""
+ return conf
+ }(),
+ },
+ }
+
+ for name, test := range tests {
+ t.Run(name, func(t *testing.T) {
+ tor := New()
+ tor.Config = test.config
+
+ if test.wantFail {
+ assert.Error(t, tor.Init())
+ } else {
+ assert.NoError(t, tor.Init())
+ }
+ })
+ }
+}
+
+func TestTor_Charts(t *testing.T) {
+ assert.NotNil(t, New().Charts())
+}
+
+func TestTor_Check(t *testing.T) {
+ tests := map[string]struct {
+ prepare func() (*Tor, *mockTorDaemon)
+ wantFail bool
+ }{
+ "success on valid response": {
+ wantFail: false,
+ prepare: prepareCaseOk,
+ },
+ "fails on connection refused": {
+ wantFail: true,
+ prepare: prepareCaseConnectionRefused,
+ },
+ }
+ for name, test := range tests {
+ t.Run(name, func(t *testing.T) {
+ tor, daemon := test.prepare()
+
+ defer func() {
+ assert.NoError(t, daemon.Close(), "daemon.Close()")
+ }()
+ go func() {
+ assert.NoError(t, daemon.Run(), "daemon.Run()")
+ }()
+
+ select {
+ case <-daemon.started:
+ case <-time.After(time.Second * 3):
+ t.Errorf("mock tor daemon start timed out")
+ }
+
+ require.NoError(t, tor.Init())
+
+ if test.wantFail {
+ assert.Error(t, tor.Check())
+ } else {
+ assert.NoError(t, tor.Check())
+ }
+
+ tor.Cleanup()
+
+ select {
+ case <-daemon.stopped:
+ case <-time.After(time.Second * 3):
+ t.Errorf("mock tor daemon stop timed out")
+ }
+ })
+ }
+}
+
+func TestTor_Collect(t *testing.T) {
+ tests := map[string]struct {
+ prepare func() (*Tor, *mockTorDaemon)
+ wantMetrics map[string]int64
+ wantCharts int
+ }{
+ "success on valid response": {
+ prepare: prepareCaseOk,
+ wantCharts: len(charts),
+ wantMetrics: map[string]int64{
+ "traffic/read": 100,
+ "traffic/written": 100,
+ "uptime": 100,
+ },
+ },
+ "fails on connection refused": {
+ prepare: prepareCaseConnectionRefused,
+ wantCharts: len(charts),
+ },
+ }
+
+ for name, test := range tests {
+ t.Run(name, func(t *testing.T) {
+ tor, daemon := test.prepare()
+
+ defer func() {
+ assert.NoError(t, daemon.Close(), "daemon.Close()")
+ }()
+ go func() {
+ assert.NoError(t, daemon.Run(), "daemon.Run()")
+ }()
+
+ select {
+ case <-daemon.started:
+ case <-time.After(time.Second * 3):
+ t.Errorf("mock tor daemon start timed out")
+ }
+
+ require.NoError(t, tor.Init())
+
+ mx := tor.Collect()
+
+ require.Equal(t, test.wantMetrics, mx)
+
+ assert.Equal(t, test.wantCharts, len(*tor.Charts()), "want charts")
+
+ if len(test.wantMetrics) > 0 {
+ module.TestMetricsHasAllChartsDims(t, tor.Charts(), mx)
+ }
+
+ tor.Cleanup()
+
+ select {
+ case <-daemon.stopped:
+ case <-time.After(time.Second * 3):
+ t.Errorf("mock tordaemon stop timed out")
+ }
+ })
+ }
+}
+
+func prepareCaseOk() (*Tor, *mockTorDaemon) {
+ daemon := &mockTorDaemon{
+ addr: "127.0.0.1:65001",
+ started: make(chan struct{}),
+ stopped: make(chan struct{}),
+ }
+
+ tor := New()
+ tor.Address = daemon.addr
+
+ return tor, daemon
+}
+
+func prepareCaseConnectionRefused() (*Tor, *mockTorDaemon) {
+ ch := make(chan struct{})
+ close(ch)
+
+ daemon := &mockTorDaemon{
+ addr: "127.0.0.1:65001",
+ dontStart: true,
+ started: ch,
+ stopped: ch,
+ }
+
+ tor := New()
+ tor.Address = daemon.addr
+
+ return tor, daemon
+}
+
+type mockTorDaemon struct {
+ addr string
+ srv net.Listener
+ started chan struct{}
+ stopped chan struct{}
+ dontStart bool
+ authenticated bool
+}
+
+func (m *mockTorDaemon) Run() error {
+ if m.dontStart {
+ return nil
+ }
+
+ srv, err := net.Listen("tcp", m.addr)
+ if err != nil {
+ return err
+ }
+
+ m.srv = srv
+
+ close(m.started)
+ defer close(m.stopped)
+
+ return m.handleConnections()
+}
+
+func (m *mockTorDaemon) Close() error {
+ if m.srv != nil {
+ err := m.srv.Close()
+ m.srv = nil
+ return err
+ }
+ return nil
+}
+
+func (m *mockTorDaemon) handleConnections() error {
+ conn, err := m.srv.Accept()
+ if err != nil || conn == nil {
+ return errors.New("could not accept connection")
+ }
+ return m.handleConnection(conn)
+}
+
+func (m *mockTorDaemon) handleConnection(conn net.Conn) error {
+ defer func() { _ = conn.Close() }()
+
+ rw := bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn))
+ var line string
+ var err error
+
+ for {
+ if line, err = rw.ReadString('\n'); err != nil {
+ return fmt.Errorf("error reading from connection: %v", err)
+ }
+
+ line = strings.TrimSpace(line)
+
+ cmd, param, _ := strings.Cut(line, " ")
+
+ switch cmd {
+ case cmdQuit:
+ return m.handleQuit(conn)
+ case cmdAuthenticate:
+ err = m.handleAuthenticate(conn)
+ case cmdGetInfo:
+ err = m.handleGetInfo(conn, param)
+ default:
+ s := fmt.Sprintf("510 Unrecognized command \"%s\"\n", cmd)
+ _, _ = rw.WriteString(s)
+ return fmt.Errorf("unexpected command: %s", line)
+ }
+
+ _ = rw.Flush()
+
+ if err != nil {
+ return err
+ }
+ }
+}
+
+func (m *mockTorDaemon) handleQuit(conn io.Writer) error {
+ _, err := conn.Write([]byte("250 closing connection\n"))
+ return err
+}
+
+func (m *mockTorDaemon) handleAuthenticate(conn io.Writer) error {
+ m.authenticated = true
+ _, err := conn.Write([]byte("250 OK\n"))
+ return err
+}
+
+func (m *mockTorDaemon) handleGetInfo(conn io.Writer, keywords string) error {
+ if !m.authenticated {
+ _, _ = conn.Write([]byte("514 Authentication required\n"))
+ return errors.New("authentication required")
+ }
+
+ keywords = strings.Trim(keywords, "\"")
+
+ for _, k := range strings.Fields(keywords) {
+ s := fmt.Sprintf("250-%s=%d\n", k, 100)
+
+ if _, err := conn.Write([]byte(s)); err != nil {
+ return err
+ }
+ }
+
+ _, err := conn.Write([]byte("250 OK\n"))
+ return err
+}