Files
grafana/pkg/infra/usagestats/service/usage_stats_test.go
Torkel Ödegaard e31cb93ec0 NavTree: Make it possible to configure where in nav tree plugins live (#55484)
* NewIA: Plugin nav config

* progress

* Progress

* Things are working

* Add monitoring node

* Add alerts and incidents

* added experiment with standalone page

* Refactoring by adding a type for navtree root

* First test working

* More tests

* more tests

* Progress on richer config and sorting

* Sort weight working

* Path config

* Improving logic for not including admin or cfg nodes, making it the last step so that enterprise can add admin nodes without having to worry about the section not existing

* fixed index routes

* removed file

* Fixes

* Fixing tests

* Fixing more tests and adding support for weight config

* Updates

* Remove unused fake

* More fixes

* Minor tweak

* Minor fix

* Can now control position using sortweight even when existing items have no sortweight

* Added tests for frontend standalone page logic

* more tests

* Remove unused fake and fixed lint issue

* Moving reading settings to navtree impl package

* remove nav_id setting prefix

* Remove old test file

* Fix trailing newline

* Fixed bug with adding nil node

* fixing lint issue

* remove some code we have to rethink

* move read settings to PrivideService and switch to util.SplitString
2022-09-28 08:29:35 +02:00

226 lines
6.3 KiB
Go

package service
import (
"bytes"
"context"
"encoding/json"
"errors"
"io"
"net/http"
"net/http/httptest"
"runtime"
"testing"
"time"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/infra/kvstore"
"github.com/grafana/grafana/pkg/infra/usagestats"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/sqlstore/mockstore"
"github.com/grafana/grafana/pkg/setting"
)
// This is to ensure that the interface contract is held by the implementation
func Test_InterfaceContractValidity(t *testing.T) {
newUsageStats := func() usagestats.Service {
return &UsageStats{}
}
v, ok := newUsageStats().(*UsageStats)
assert.NotNil(t, v)
assert.True(t, ok)
}
func TestMetrics(t *testing.T) {
const metricName = "stats.test_metric.count"
sqlStore := mockstore.NewSQLStoreMock()
uss := createService(t, setting.Cfg{}, sqlStore, false)
uss.RegisterMetricsFunc(func(context.Context) (map[string]interface{}, error) {
return map[string]interface{}{metricName: 1}, nil
})
_, err := uss.sendUsageStats(context.Background())
require.NoError(t, err)
t.Run("Given reporting not enabled and sending usage stats", func(t *testing.T) {
origSendUsageStats := sendUsageStats
t.Cleanup(func() {
sendUsageStats = origSendUsageStats
})
statsSent := false
sendUsageStats = func(uss *UsageStats, ctx context.Context, b *bytes.Buffer) error {
statsSent = true
return nil
}
uss.Cfg.ReportingEnabled = false
_, err := uss.sendUsageStats(context.Background())
require.NoError(t, err)
require.False(t, statsSent)
})
t.Run("Given reporting enabled, stats should be gathered and sent to HTTP endpoint", func(t *testing.T) {
origCfg := uss.Cfg
t.Cleanup(func() {
uss.Cfg = origCfg
})
uss.Cfg = &setting.Cfg{
ReportingEnabled: true,
BuildVersion: "5.0.0",
AnonymousEnabled: true,
BasicAuthEnabled: true,
LDAPEnabled: true,
AuthProxyEnabled: true,
Packaging: "deb",
ReportingDistributor: "hosted-grafana",
}
ch := make(chan httpResp)
ticker := time.NewTicker(2 * time.Second)
ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
buf, err := io.ReadAll(r.Body)
if err != nil {
t.Logf("Fake HTTP handler received an error: %s", err.Error())
ch <- httpResp{
err: err,
}
return
}
require.NoError(t, err, "Failed to read response body, err=%v", err)
t.Logf("Fake HTTP handler received a response")
ch <- httpResp{
responseBuffer: bytes.NewBuffer(buf),
req: r,
}
}))
t.Cleanup(ts.Close)
t.Cleanup(func() {
close(ch)
})
usageStatsURL = ts.URL
go func() {
_, err := uss.sendUsageStats(context.Background())
require.NoError(t, err)
}()
// Wait for fake HTTP server to receive a request
var resp httpResp
select {
case resp = <-ch:
require.NoError(t, resp.err, "Fake server experienced an error")
case <-ticker.C:
t.Fatalf("Timed out waiting for HTTP request")
}
t.Logf("Received response from fake HTTP server: %+v\n", resp)
assert.NotNil(t, resp.req)
assert.Equal(t, http.MethodPost, resp.req.Method)
assert.Equal(t, "application/json", resp.req.Header.Get("Content-Type"))
require.NotNil(t, resp.responseBuffer)
j := make(map[string]interface{})
err = json.Unmarshal(resp.responseBuffer.Bytes(), &j)
require.NoError(t, err)
assert.Equal(t, "5_0_0", j["version"])
assert.Equal(t, runtime.GOOS, j["os"])
assert.Equal(t, runtime.GOARCH, j["arch"])
usageId := uss.GetUsageStatsId(context.Background())
assert.NotEmpty(t, usageId)
metrics, ok := j["metrics"].(map[string]interface{})
require.True(t, ok)
assert.EqualValues(t, 1, metrics[metricName])
})
}
func TestGetUsageReport_IncludesMetrics(t *testing.T) {
sqlStore := mockstore.NewSQLStoreMock()
uss := createService(t, setting.Cfg{}, sqlStore, true)
metricName := "stats.test_metric.count"
uss.RegisterMetricsFunc(func(context.Context) (map[string]interface{}, error) {
return map[string]interface{}{metricName: 1}, nil
})
report, err := uss.GetUsageReport(context.Background())
require.NoError(t, err, "Expected no error")
metric := report.Metrics[metricName]
assert.Equal(t, 1, metric)
}
func TestRegisterMetrics(t *testing.T) {
const goodMetricName = "stats.test_external_metric.count"
sqlStore := mockstore.NewSQLStoreMock()
uss := createService(t, setting.Cfg{}, sqlStore, false)
metrics := map[string]interface{}{"stats.test_metric.count": 1, "stats.test_metric_second.count": 2}
uss.RegisterMetricsFunc(func(context.Context) (map[string]interface{}, error) {
return map[string]interface{}{goodMetricName: 1}, nil
})
{
extMetrics, err := uss.externalMetrics[0](context.Background())
require.NoError(t, err)
assert.Equal(t, map[string]interface{}{goodMetricName: 1}, extMetrics)
}
uss.gatherMetrics(context.Background(), metrics)
assert.Equal(t, 1, metrics[goodMetricName])
metricsCount := len(metrics)
t.Run("do not add metrics that return an error when fetched", func(t *testing.T) {
const badMetricName = "stats.test_external_metric_error.count"
uss.RegisterMetricsFunc(func(context.Context) (map[string]interface{}, error) {
return map[string]interface{}{badMetricName: 1}, errors.New("some error")
})
uss.gatherMetrics(context.Background(), metrics)
extErrorMetric := metrics[badMetricName]
extMetric := metrics[goodMetricName]
require.Nil(t, extErrorMetric, "Invalid metric should not be added")
assert.Equal(t, 1, extMetric)
assert.Len(t, metrics, metricsCount, "Expected same number of metrics before and after collecting bad metric")
assert.EqualValues(t, 1, metrics["stats.usagestats.debug.collect.error.count"])
})
}
type httpResp struct {
req *http.Request
responseBuffer *bytes.Buffer
err error
}
func createService(t *testing.T, cfg setting.Cfg, sqlStore sqlstore.Store, withDB bool) *UsageStats {
t.Helper()
if withDB {
sqlStore = sqlstore.InitTestDB(t)
}
return ProvideService(
&cfg,
&plugins.FakePluginStore{},
kvstore.ProvideService(sqlStore),
routing.NewRouteRegister(),
tracing.InitializeTracerForTest(),
)
}