Files
coder/coderd/telemetry/telemetry_test.go
Kyle Carberry 4cce969018 feat: Add anonymized telemetry to report product usage (#2273)
* feat: Add anonymized telemetry to report product usage

This adds a background service to report telemetry to a Coder
server for usage data. There will be realtime event data sent
in the future, but for now usage will report on a CRON.

* Fix flake and requested changes

* Add reporting options for setup

* Add reporting for workspaces

* Add resources as they are reported

* Track API key usage

* Ensure telemetry is tracked prior to exit
2022-06-17 00:26:40 -05:00

149 lines
4.4 KiB
Go

package telemetry_test
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"github.com/go-chi/chi"
"github.com/google/uuid"
"github.com/stretchr/testify/require"
"go.uber.org/goleak"
"cdr.dev/slog"
"cdr.dev/slog/sloggers/slogtest"
"github.com/coder/coder/buildinfo"
"github.com/coder/coder/coderd/database"
"github.com/coder/coder/coderd/database/databasefake"
"github.com/coder/coder/coderd/telemetry"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
func TestTelemetry(t *testing.T) {
t.Parallel()
t.Run("Snapshot", func(t *testing.T) {
t.Parallel()
db := databasefake.New()
ctx := context.Background()
_, err := db.InsertAPIKey(ctx, database.InsertAPIKeyParams{
ID: uuid.NewString(),
LastUsed: database.Now(),
})
require.NoError(t, err)
_, err = db.InsertParameterSchema(ctx, database.InsertParameterSchemaParams{
ID: uuid.New(),
CreatedAt: database.Now(),
})
require.NoError(t, err)
_, err = db.InsertProvisionerJob(ctx, database.InsertProvisionerJobParams{
ID: uuid.New(),
CreatedAt: database.Now(),
})
require.NoError(t, err)
_, err = db.InsertTemplate(ctx, database.InsertTemplateParams{
ID: uuid.New(),
CreatedAt: database.Now(),
})
require.NoError(t, err)
_, err = db.InsertTemplateVersion(ctx, database.InsertTemplateVersionParams{
ID: uuid.New(),
CreatedAt: database.Now(),
})
require.NoError(t, err)
_, err = db.InsertUser(ctx, database.InsertUserParams{
ID: uuid.New(),
CreatedAt: database.Now(),
})
require.NoError(t, err)
_, err = db.InsertWorkspace(ctx, database.InsertWorkspaceParams{
ID: uuid.New(),
CreatedAt: database.Now(),
})
require.NoError(t, err)
_, err = db.InsertWorkspaceApp(ctx, database.InsertWorkspaceAppParams{
ID: uuid.New(),
CreatedAt: database.Now(),
})
require.NoError(t, err)
_, err = db.InsertWorkspaceAgent(ctx, database.InsertWorkspaceAgentParams{
ID: uuid.New(),
CreatedAt: database.Now(),
})
require.NoError(t, err)
_, err = db.InsertWorkspaceBuild(ctx, database.InsertWorkspaceBuildParams{
ID: uuid.New(),
CreatedAt: database.Now(),
})
require.NoError(t, err)
_, err = db.InsertWorkspaceResource(ctx, database.InsertWorkspaceResourceParams{
ID: uuid.New(),
CreatedAt: database.Now(),
})
require.NoError(t, err)
snapshot := collectSnapshot(t, db)
require.Len(t, snapshot.ParameterSchemas, 1)
require.Len(t, snapshot.ProvisionerJobs, 1)
require.Len(t, snapshot.Templates, 1)
require.Len(t, snapshot.TemplateVersions, 1)
require.Len(t, snapshot.Users, 1)
require.Len(t, snapshot.Workspaces, 1)
require.Len(t, snapshot.WorkspaceApps, 1)
require.Len(t, snapshot.WorkspaceAgents, 1)
require.Len(t, snapshot.WorkspaceBuilds, 1)
require.Len(t, snapshot.WorkspaceResources, 1)
})
t.Run("HashedEmail", func(t *testing.T) {
t.Parallel()
db := databasefake.New()
_, err := db.InsertUser(context.Background(), database.InsertUserParams{
ID: uuid.New(),
Email: "kyle@coder.com",
CreatedAt: database.Now(),
})
require.NoError(t, err)
snapshot := collectSnapshot(t, db)
require.Len(t, snapshot.Users, 1)
require.Equal(t, snapshot.Users[0].EmailHashed, "bb44bf07cf9a2db0554bba63a03d822c927deae77df101874496df5a6a3e896d@coder.com")
})
}
func collectSnapshot(t *testing.T, db database.Store) *telemetry.Snapshot {
t.Helper()
deployment := make(chan struct{}, 64)
snapshot := make(chan *telemetry.Snapshot, 64)
r := chi.NewRouter()
r.Post("/deployment", func(w http.ResponseWriter, r *http.Request) {
require.Equal(t, buildinfo.Version(), r.Header.Get(telemetry.VersionHeader))
w.WriteHeader(http.StatusAccepted)
deployment <- struct{}{}
})
r.Post("/snapshot", func(w http.ResponseWriter, r *http.Request) {
require.Equal(t, buildinfo.Version(), r.Header.Get(telemetry.VersionHeader))
w.WriteHeader(http.StatusAccepted)
ss := &telemetry.Snapshot{}
err := json.NewDecoder(r.Body).Decode(ss)
require.NoError(t, err)
snapshot <- ss
})
server := httptest.NewServer(r)
t.Cleanup(server.Close)
serverURL, err := url.Parse(server.URL)
require.NoError(t, err)
reporter, err := telemetry.New(telemetry.Options{
Database: db,
Logger: slogtest.Make(t, nil).Leveled(slog.LevelDebug),
URL: serverURL,
DeploymentID: uuid.NewString(),
})
require.NoError(t, err)
t.Cleanup(reporter.Close)
<-deployment
return <-snapshot
}