a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
188 lines
5.0 KiB
Go
188 lines
5.0 KiB
Go
package telemetry
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func fixedClock(day string) func() time.Time {
|
|
t, _ := time.Parse("2006-01-02", day)
|
|
return func() time.Time { return t }
|
|
}
|
|
|
|
func TestInstallIDStableAcrossCalls(t *testing.T) {
|
|
dir := t.TempDir()
|
|
a := InstallID(dir)
|
|
b := InstallID(dir)
|
|
if a == "" {
|
|
t.Fatal("InstallID returned empty")
|
|
}
|
|
if a != b {
|
|
t.Errorf("InstallID not stable: %q != %q", a, b)
|
|
}
|
|
// A fresh dir mints a different id.
|
|
if c := InstallID(t.TempDir()); c == a {
|
|
t.Errorf("two machines share an install id: %q", c)
|
|
}
|
|
}
|
|
|
|
func seedCompletedDay(t *testing.T, store *Store, day string) {
|
|
t.Helper()
|
|
r := &Rollup{Day: day, Counts: map[string]int{}}
|
|
r.Add("mcp_tool_call", "search_symbols")
|
|
if err := store.Save(r); err != nil {
|
|
t.Fatalf("seed %s: %v", day, err)
|
|
}
|
|
}
|
|
|
|
func TestMaybeSendTransmitsAndClearsCompletedDays(t *testing.T) {
|
|
dir := t.TempDir()
|
|
store := NewStore(dir)
|
|
seedCompletedDay(t, store, "2026-06-16")
|
|
seedCompletedDay(t, store, "2026-06-17")
|
|
// Today's open day must NOT be sent.
|
|
open := &Rollup{Day: "2026-06-18", Counts: map[string]int{}}
|
|
open.Add("cli_command", "review")
|
|
_ = store.Save(open)
|
|
|
|
var got Payload
|
|
var hits int32
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
atomic.AddInt32(&hits, 1)
|
|
body, _ := io.ReadAll(r.Body)
|
|
_ = json.Unmarshal(body, &got)
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}))
|
|
defer srv.Close()
|
|
|
|
s := NewSender(store, dir, "v9.9.9", func(k string) string {
|
|
if k == EnvEndpoint {
|
|
return srv.URL
|
|
}
|
|
return ""
|
|
})
|
|
s.now = fixedClock("2026-06-18")
|
|
|
|
s.MaybeSend(context.Background(), Consent{Enabled: true})
|
|
|
|
if atomic.LoadInt32(&hits) != 1 {
|
|
t.Fatalf("endpoint hit %d times, want 1", hits)
|
|
}
|
|
if got.SchemaVersion != sendSchemaVersion || got.GortexVersion != "v9.9.9" || got.InstallID == "" {
|
|
t.Errorf("payload envelope wrong: %+v", got)
|
|
}
|
|
if len(got.Days) != 2 {
|
|
t.Fatalf("payload carried %d days, want 2 (open day excluded)", len(got.Days))
|
|
}
|
|
// Sent days deleted; the open day survives.
|
|
days, _ := store.Days()
|
|
if len(days) != 1 || days[0] != "2026-06-18" {
|
|
t.Errorf("after send remaining days = %v, want [2026-06-18]", days)
|
|
}
|
|
}
|
|
|
|
func TestMaybeSendNoEndpointIsBlocked(t *testing.T) {
|
|
dir := t.TempDir()
|
|
store := NewStore(dir)
|
|
seedCompletedDay(t, store, "2026-06-17")
|
|
|
|
// No EnvEndpoint set → the live send is blocked.
|
|
s := NewSender(store, dir, "v1", func(string) string { return "" })
|
|
s.now = fixedClock("2026-06-18")
|
|
s.MaybeSend(context.Background(), Consent{Enabled: true})
|
|
|
|
// Nothing transmitted, nothing deleted, no last-send marker.
|
|
if days, _ := store.Days(); len(days) != 1 {
|
|
t.Errorf("blocked send deleted buffered days: %v", days)
|
|
}
|
|
if s.lastSendDay() != "" {
|
|
t.Error("blocked send wrote a last-send marker")
|
|
}
|
|
}
|
|
|
|
func TestMaybeSendConsentDisabled(t *testing.T) {
|
|
dir := t.TempDir()
|
|
store := NewStore(dir)
|
|
seedCompletedDay(t, store, "2026-06-17")
|
|
var hits int32
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
atomic.AddInt32(&hits, 1)
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}))
|
|
defer srv.Close()
|
|
|
|
s := NewSender(store, dir, "v1", func(k string) string {
|
|
if k == EnvEndpoint {
|
|
return srv.URL
|
|
}
|
|
return ""
|
|
})
|
|
s.now = fixedClock("2026-06-18")
|
|
s.MaybeSend(context.Background(), Consent{Enabled: false})
|
|
|
|
if atomic.LoadInt32(&hits) != 0 {
|
|
t.Errorf("disabled consent still transmitted (%d hits)", hits)
|
|
}
|
|
}
|
|
|
|
func TestMaybeSendOncePerDay(t *testing.T) {
|
|
dir := t.TempDir()
|
|
store := NewStore(dir)
|
|
seedCompletedDay(t, store, "2026-06-17")
|
|
var hits int32
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
atomic.AddInt32(&hits, 1)
|
|
w.WriteHeader(http.StatusOK)
|
|
}))
|
|
defer srv.Close()
|
|
|
|
mk := func() *Sender {
|
|
s := NewSender(store, dir, "v1", func(k string) string {
|
|
if k == EnvEndpoint {
|
|
return srv.URL
|
|
}
|
|
return ""
|
|
})
|
|
s.now = fixedClock("2026-06-18")
|
|
return s
|
|
}
|
|
mk().MaybeSend(context.Background(), Consent{Enabled: true})
|
|
// Re-seed a completed day and try again the same UTC day: must not re-send.
|
|
seedCompletedDay(t, store, "2026-06-16")
|
|
mk().MaybeSend(context.Background(), Consent{Enabled: true})
|
|
|
|
if atomic.LoadInt32(&hits) != 1 {
|
|
t.Errorf("sent %d times in one day, want 1", hits)
|
|
}
|
|
}
|
|
|
|
func TestMaybeSendServerErrorKeepsDays(t *testing.T) {
|
|
dir := t.TempDir()
|
|
store := NewStore(dir)
|
|
seedCompletedDay(t, store, "2026-06-17")
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
}))
|
|
defer srv.Close()
|
|
|
|
s := NewSender(store, dir, "v1", func(k string) string {
|
|
if k == EnvEndpoint {
|
|
return srv.URL
|
|
}
|
|
return ""
|
|
})
|
|
s.now = fixedClock("2026-06-18")
|
|
s.MaybeSend(context.Background(), Consent{Enabled: true}) // must not panic
|
|
|
|
// A 5xx is final (no retry) but the days are NOT deleted — they ship later.
|
|
if days, _ := store.Days(); len(days) != 1 {
|
|
t.Errorf("server error dropped buffered days: %v", days)
|
|
}
|
|
}
|