sourcegraph/internal/database/telemetry_export_store_test.go
Robert Lin fce0faf66f
lib/telemetrygateway: publish all non-Sourcegraph-specific Telemetry Gateway bindings (#62061)
Migrates the Telemetry Gateway:

1. Service specification
2. Generated Go bindings
3. UUID constructor

into an exported `lib/telemetrygateway` package for internal and external consumption. See https://github.com/sourcegraph/sourcegraph/issues/61489 for use cases. This allows MSP services to more easily start publishing events for to Telemetry Gateway, and adds no new dependencies to `lib`.

Splits Sourcegraph-specific functionality that used to live in the `telemetrygateway/v1` package to:

1. `internal/telemetrygateway`: backcompat testing
2. `internal/telmeetrygateway/event`: event constructors (collapsing into parent caused import cycle)

I've left README + a stub service spec in the old package to redirect visitors from outdated links.

Closes https://github.com/sourcegraph/sourcegraph/issues/61489

## Test plan

```
sg start
```

watch for successful export logs from `telemetrygatewayexporter`
2024-04-22 14:36:46 -07:00

236 lines
6.8 KiB
Go

package database
import (
"context"
"testing"
"time"
"github.com/hexops/autogold/v2"
"github.com/sourcegraph/log/logtest"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/structpb"
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/sourcegraph/sourcegraph/internal/database/dbtest"
"github.com/sourcegraph/sourcegraph/internal/licensing"
telemetrygatewayv1 "github.com/sourcegraph/sourcegraph/lib/telemetrygateway/v1"
)
func TestTelemetryEventsExportQueue_QueueForExport(t *testing.T) {
for _, tc := range []struct {
name string
mode licensing.TelemetryEventsExportMode
events []*telemetrygatewayv1.Event
expectQueued autogold.Value
}{{
name: "disabled",
mode: licensing.TelemetryEventsExportDisabled,
events: []*telemetrygatewayv1.Event{{
Id: "1",
Feature: "foobar",
Action: "baz",
}},
expectQueued: autogold.Expect([]string{}),
}, {
name: "enabled: export all",
mode: licensing.TelemetryEventsExportAll,
events: []*telemetrygatewayv1.Event{{
Id: "1",
Feature: "foobar",
Action: "baz",
}},
expectQueued: autogold.Expect([]string{"1"}),
}, {
name: "cody-only: drop some",
mode: licensing.TelemetryEventsExportCodyOnly,
events: []*telemetrygatewayv1.Event{{
Id: "1",
Feature: "foobar",
Action: "baz",
}, {
Id: "2",
Feature: "cody.foobar",
Action: "baz",
}, {
Id: "3",
Feature: "cody",
Action: "bar",
}},
expectQueued: autogold.Expect([]string{"2", "3"}),
}} {
t.Run(tc.name, func(t *testing.T) {
ctx := context.Background()
logger := logtest.Scoped(t)
db := NewDB(logger, dbtest.NewDB(t))
store := TelemetryEventsExportQueueWith(logger, db)
// Set a mock mode to test enabled exports
store.(MockExportModeSetterTelemetryEventsExportQueueStore).
SetMockExportMode(tc.mode)
require.NoError(t, store.QueueForExport(context.Background(), tc.events))
queued, err := store.ListForExport(ctx, 999)
require.NoError(t, err)
var ids []string
for _, e := range queued {
ids = append(ids, e.Id)
}
tc.expectQueued.Equal(t, ids)
})
}
}
func TestTelemetryEventsExportQueueLifecycle(t *testing.T) {
ctx := context.Background()
logger := logtest.Scoped(t)
db := NewDB(logger, dbtest.NewDB(t))
store := TelemetryEventsExportQueueWith(logger, db)
// Set a mock mode to test enabled exports
store.(MockExportModeSetterTelemetryEventsExportQueueStore).
SetMockExportMode(licensing.TelemetryEventsExportAll)
events := []*telemetrygatewayv1.Event{{
Id: "1",
Feature: "Feature",
Action: "View",
Timestamp: timestamppb.New(time.Date(2022, 11, 3, 1, 0, 0, 0, time.UTC)),
Parameters: &telemetrygatewayv1.EventParameters{
Metadata: map[string]float64{"public": 1},
},
}, {
Id: "2",
Feature: "Feature",
Action: "Click",
Timestamp: timestamppb.New(time.Date(2022, 11, 3, 2, 0, 0, 0, time.UTC)),
Parameters: &telemetrygatewayv1.EventParameters{
PrivateMetadata: &structpb.Struct{
Fields: map[string]*structpb.Value{"sensitive": structpb.NewStringValue("sensitive")},
},
},
}, {
Id: "3",
Feature: "Feature",
Action: "Show",
Timestamp: timestamppb.New(time.Date(2022, 11, 3, 3, 0, 0, 0, time.UTC)),
}}
eventsToExport := []string{"1", "2"}
t.Run("QueueForExport", func(t *testing.T) {
require.NoError(t, store.QueueForExport(ctx, events))
})
t.Run("CountUnexported", func(t *testing.T) {
count, oldest, err := store.CountUnexported(ctx)
require.NoError(t, err)
assert.Equal(t, count, int64(3))
// First sample event is the oldest
assert.Equal(t, events[0].Timestamp.AsTime(), oldest)
})
t.Run("ListForExport", func(t *testing.T) {
limit := len(events) - 1
export, err := store.ListForExport(ctx, limit)
require.NoError(t, err)
assert.Len(t, export, limit)
// Check we got the exact event IDs we want to export
var gotIDs []string
for _, e := range export {
gotIDs = append(gotIDs, e.GetId())
}
assert.Equal(t, eventsToExport, gotIDs)
// Check integrity of first item
original, err := proto.Marshal(events[0])
require.NoError(t, err)
got, err := proto.Marshal(export[0])
require.NoError(t, err)
assert.Equal(t, string(original), string(got))
// Check second item's private meta is stripped
assert.NotNil(t, events[1].Parameters.PrivateMetadata) // original
assert.Nil(t, export[1].Parameters.PrivateMetadata) // got
})
t.Run("before export: DeleteExported", func(t *testing.T) {
affected, err := store.DeletedExported(ctx, time.Now())
require.NoError(t, err)
assert.Zero(t, affected)
})
t.Run("MarkAsExported", func(t *testing.T) {
require.NoError(t, store.MarkAsExported(ctx, eventsToExport))
})
t.Run("after export: CountRecentlyExported", func(t *testing.T) {
export, err := store.CountRecentlyExported(ctx)
require.NoError(t, err)
assert.Equal(t, export, int64(2))
})
t.Run("after export: ListRecentlyExported", func(t *testing.T) {
exported, err := store.ListRecentlyExported(ctx, 1, nil)
require.NoError(t, err)
require.Len(t, exported, 1)
// Most recent first
assert.Equal(t, "2", exported[0].ID)
assert.Equal(t, "2", exported[0].Payload.GetId())
assert.NotZero(t, exported[0].ExportedAt)
assert.NotZero(t, exported[0].Timestamp)
// Next "page"
cursor := exported[0].Timestamp
exported, err = store.ListRecentlyExported(ctx, 1, &cursor)
require.NoError(t, err)
require.Len(t, exported, 1)
assert.Equal(t, "1", exported[0].ID)
assert.Equal(t, "1", exported[0].Payload.GetId())
assert.NotZero(t, exported[0].ExportedAt)
assert.NotZero(t, exported[0].Timestamp)
})
t.Run("after export: QueueForExport", func(t *testing.T) {
export, err := store.ListForExport(ctx, len(events))
require.NoError(t, err)
assert.Len(t, export, 1)
// ID is exactly as expected
assert.Equal(t, "3", export[0].GetId())
})
t.Run("after export: CountUnexported", func(t *testing.T) {
count, oldest, err := store.CountUnexported(ctx)
require.NoError(t, err)
assert.Equal(t, count, int64(1))
// The third event is the only one left now
assert.Equal(t, events[len(events)-1].Timestamp.AsTime(), oldest)
})
t.Run("after export: DeleteExported", func(t *testing.T) {
affected, err := store.DeletedExported(ctx, time.Now())
require.NoError(t, err)
assert.Equal(t, int(affected), len(eventsToExport))
})
t.Run("mark all as exported", func(t *testing.T) {
// Only the third event is left
err := store.MarkAsExported(ctx, []string{"3"})
require.NoError(t, err)
})
t.Run("after all are exported: CountUnexported", func(t *testing.T) {
count, oldest, err := store.CountUnexported(ctx)
require.NoError(t, err)
// No events are lift
assert.Equal(t, count, int64(0))
assert.True(t, oldest.IsZero())
})
}