// Licensed to Elasticsearch B.V. under one or more contributor // license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright // ownership. Elasticsearch B.V. licenses this file to you under // the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. // +build !integration package mb import ( "testing" "time" "github.com/elastic/beats/libbeat/common" "github.com/stretchr/testify/assert" ) type testModule struct { BaseModule hostParser func(string) (HostData, error) } func (m testModule) ParseHost(host string) (HostData, error) { return m.hostParser(host) } // EventFetcher type testMetricSet struct { BaseMetricSet } func (m *testMetricSet) Fetch() (common.MapStr, error) { return nil, nil } // EventsFetcher type testMetricSetEventsFetcher struct { BaseMetricSet } func (m *testMetricSetEventsFetcher) Fetch() ([]common.MapStr, error) { return nil, nil } // ReportingFetcher type testMetricSetReportingFetcher struct { BaseMetricSet } func (m *testMetricSetReportingFetcher) Fetch(r Reporter) {} // PushMetricSet type testPushMetricSet struct { BaseMetricSet } func (m *testPushMetricSet) Run(r PushReporter) {} func TestModuleConfig(t *testing.T) { tests := []struct { in interface{} out ModuleConfig err string }{ { in: map[string]interface{}{}, err: "missing required field accessing 'module'", }, { in: map[string]interface{}{ "module": "example", "metricsets": []string{"test"}, }, out: ModuleConfig{ Module: "example", MetricSets: []string{"test"}, Enabled: true, Period: time.Second * 10, Timeout: 0, }, }, { in: map[string]interface{}{ "module": "example", "metricsets": []string{"test"}, "period": -1, }, err: "negative value accessing 'period'", }, { in: map[string]interface{}{ "module": "example", "metricsets": []string{"test"}, "timeout": -1, }, err: "negative value accessing 'timeout'", }, } for i, test := range tests { c, err := common.NewConfigFrom(test.in) if err != nil { t.Fatal(err) } unpackedConfig := DefaultModuleConfig() err = c.Unpack(&unpackedConfig) if err != nil && test.err == "" { t.Errorf("unexpected error while unpacking in testcase %d: %v", i, err) continue } if test.err != "" { if err != nil { assert.Contains(t, err.Error(), test.err, "testcase %d", i) } else { t.Errorf("expected error '%v' in testcase %d", test.err, i) } continue } assert.Equal(t, test.out, unpackedConfig) } } // TestModuleConfigDefaults validates that the default values are not changed. // Any changes to this test case are probably indicators of non-backwards // compatible changes affect all modules (including community modules). func TestModuleConfigDefaults(t *testing.T) { c, err := common.NewConfigFrom(map[string]interface{}{ "module": "mymodule", "metricsets": []string{"mymetricset"}, }) if err != nil { t.Fatal(err) } mc := DefaultModuleConfig() err = c.Unpack(&mc) if err != nil { t.Fatal(err) } assert.Equal(t, true, mc.Enabled) assert.Equal(t, time.Second*10, mc.Period) assert.Equal(t, time.Second*0, mc.Timeout) assert.Empty(t, mc.Hosts) } // TestNewModulesDuplicateHosts verifies that an error is returned by // NewModules if any module configuration contains duplicate hosts. func TestNewModulesDuplicateHosts(t *testing.T) { r := newTestRegistry(t) c := newConfig(t, map[string]interface{}{ "module": moduleName, "metricsets": []string{metricSetName}, "hosts": []string{"a", "b", "a"}, }) _, _, err := NewModule(c, r) assert.Error(t, err) } // TestNewModulesWithDefaultMetricSet verifies that the default MetricSet is // instantiated when no metricsets are specified in the config. func TestNewModulesWithDefaultMetricSet(t *testing.T) { r := newTestRegistry(t, DefaultMetricSet()) c := newConfig(t, map[string]interface{}{ "module": moduleName, }) _, metricSets, err := NewModule(c, r) if err != nil { t.Fatal(err) } if assert.Len(t, metricSets, 1) { assert.Equal(t, metricSetName, metricSets[0].Name()) } } func TestNewModulesHostParser(t *testing.T) { const ( name = "HostParser" host = "example.com" uri = "http://" + host ) r := newTestRegistry(t) factory := func(base BaseMetricSet) (MetricSet, error) { return &testMetricSet{BaseMetricSet: base}, nil } hostParser := func(m Module, rawHost string) (HostData, error) { return HostData{URI: uri, Host: host}, nil } if err := r.AddMetricSet(moduleName, name, factory, hostParser); err != nil { t.Fatal(err) } t.Run("MetricSet without HostParser", func(t *testing.T) { ms := newTestMetricSet(t, r, map[string]interface{}{ "module": moduleName, "metricsets": []string{metricSetName}, "hosts": []string{uri}, }) // The URI is passed through in the Host() and HostData().URI. assert.Equal(t, uri, ms.Host()) assert.Equal(t, HostData{URI: uri}, ms.HostData()) }) t.Run("MetricSet with HostParser", func(t *testing.T) { ms := newTestMetricSet(t, r, map[string]interface{}{ "module": moduleName, "metricsets": []string{name}, "hosts": []string{uri}, }) // The URI is passed through in the Host() and HostData().URI. assert.Equal(t, host, ms.Host()) assert.Equal(t, HostData{URI: uri, Host: host}, ms.HostData()) }) } func TestNewModulesMetricSetTypes(t *testing.T) { r := newTestRegistry(t) factory := func(base BaseMetricSet) (MetricSet, error) { return &testMetricSet{base}, nil } name := "EventFetcher" if err := r.AddMetricSet(moduleName, name, factory); err != nil { t.Fatal(err) } t.Run(name+" MetricSet", func(t *testing.T) { ms := newTestMetricSet(t, r, map[string]interface{}{ "module": moduleName, "metricsets": []string{name}, }) _, ok := ms.(EventFetcher) assert.True(t, ok, name+" not implemented") }) factory = func(base BaseMetricSet) (MetricSet, error) { return &testMetricSetEventsFetcher{base}, nil } name = "EventsFetcher" if err := r.AddMetricSet(moduleName, name, factory); err != nil { t.Fatal(err) } t.Run(name+" MetricSet", func(t *testing.T) { ms := newTestMetricSet(t, r, map[string]interface{}{ "module": moduleName, "metricsets": []string{name}, }) _, ok := ms.(EventsFetcher) assert.True(t, ok, name+" not implemented") }) factory = func(base BaseMetricSet) (MetricSet, error) { return &testMetricSetReportingFetcher{base}, nil } name = "ReportingFetcher" if err := r.AddMetricSet(moduleName, name, factory); err != nil { t.Fatal(err) } t.Run(name+" MetricSet", func(t *testing.T) { ms := newTestMetricSet(t, r, map[string]interface{}{ "module": moduleName, "metricsets": []string{name}, }) _, ok := ms.(ReportingMetricSet) assert.True(t, ok, name+" not implemented") }) factory = func(base BaseMetricSet) (MetricSet, error) { return &testPushMetricSet{base}, nil } name = "Push" if err := r.AddMetricSet(moduleName, name, factory); err != nil { t.Fatal(err) } t.Run(name+" MetricSet", func(t *testing.T) { ms := newTestMetricSet(t, r, map[string]interface{}{ "module": moduleName, "metricsets": []string{name}, }) _, ok := ms.(PushMetricSet) assert.True(t, ok, name+" not implemented") }) } // TestNewBaseModuleFromModuleConfigStruct tests the creation a new BaseModule. func TestNewBaseModuleFromModuleConfigStruct(t *testing.T) { moduleConf := DefaultModuleConfig() moduleConf.Module = moduleName moduleConf.MetricSets = []string{metricSetName} c := newConfig(t, moduleConf) baseModule, err := newBaseModuleFromConfig(c) assert.NoError(t, err) assert.Equal(t, moduleName, baseModule.Name()) assert.Equal(t, moduleName, baseModule.Config().Module) assert.Equal(t, true, baseModule.Config().Enabled) assert.Equal(t, time.Second*10, baseModule.Config().Period) assert.Equal(t, time.Second*10, baseModule.Config().Timeout) assert.Empty(t, baseModule.Config().Hosts) } func newTestRegistry(t testing.TB, metricSetOptions ...MetricSetOption) *Register { r := NewRegister() if err := r.AddModule(moduleName, DefaultModuleFactory); err != nil { t.Fatal(err) } factory := func(base BaseMetricSet) (MetricSet, error) { return &testMetricSet{base}, nil } if err := r.addMetricSet(moduleName, metricSetName, factory, metricSetOptions...); err != nil { t.Fatal(err) } return r } func newTestMetricSet(t testing.TB, r *Register, config map[string]interface{}) MetricSet { _, metricsets, err := NewModule(newConfig(t, config), r) if err != nil { t.Fatal(err) } if !assert.Len(t, metricsets, 1) { assert.FailNow(t, "invalid number of metricsets") } return metricsets[0] } func newConfig(t testing.TB, moduleConfig interface{}) *common.Config { config, err := common.NewConfigFrom(moduleConfig) if err != nil { t.Fatal(err) } return config } func TestModuleConfigQueryParams(t *testing.T) { qp := QueryParams{ "stringKey": "value", "intKey": 10, "floatKey": 11.5, "boolKey": true, "nullKey": nil, "arKey": []interface{}{1, 2}, } res := qp.String() expectedValues := []string{"stringKey=value", "intKey=10", "floatKey=11.5", "boolKey=true", "nullKey=", "arKey=1", "arKey=2"} for _, expected := range expectedValues { assert.Contains(t, res, expected) } assert.NotContains(t, res, "?") assert.NotContains(t, res, "%") assert.NotEqual(t, "&", res[0]) assert.NotEqual(t, "&", res[len(res)-1]) }