Files
Richard Courtman 139ee65b25 Stop test binaries reporting to the production telemetry endpoint
pkg/server tests boot the real server through Run() with the version
literal "test-version", which internal/updates normalizes to
0.0.0-test-version. Each test runs against its own t.TempDir(), so every
run minted a fresh install ID. The startup ping waits two minutes and so
never fired inside a short test, but the service-health failure reporter
added on 2026-08-29 sends synchronously from a deferred handler as soon
as Run() returns an error, so every CI shard containing pkg/server posted
one ping.

The licence server recorded 317 single-ping installs between 2026-08-29
and 2026-09-03 - 311 from linux/amd64 CI runners, 3 from a maintainer
workstation - still arriving at roughly 60 a day. The canonical clean
denominator excludes single-ping installs and was unaffected, but raw
install counts and the operator-evidence blocked-cause read counted them
as real installations.

A test binary is not an installation, which is the same reason mock mode
already suppresses pings, so the guard belongs beside it in the telemetry
package rather than at the four call sites: send() now refuses the
production endpoint whenever testing.Testing() reports true. The check
compares against productionPingEndpoint, so telemetry's own tests keep
asserting on real ping content through a redirected endpoint, and the
server tests additionally opt out at the config layer to say so locally.
2026-09-03 23:54:37 +01:00

1428 lines
51 KiB
Go

package telemetry
import (
"encoding/json"
"errors"
"fmt"
"io"
"io/fs"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"reflect"
"regexp"
"strings"
"sync/atomic"
"testing"
"time"
"context"
"github.com/google/uuid"
"github.com/rcourtman/pulse-go-rewrite/internal/testutil"
"github.com/rcourtman/pulse-go-rewrite/internal/updates"
)
func TestGetOrCreateInstallID_CreatesNew(t *testing.T) {
dir := t.TempDir()
now := time.Date(2026, 3, 28, 12, 0, 0, 0, time.UTC)
id := getOrCreateInstallIDAt(dir, now)
if id == "" {
t.Fatal("expected non-empty install ID")
}
// Verify file was persisted.
data, err := os.ReadFile(filepath.Join(dir, installIDFile))
if err != nil {
t.Fatal(err)
}
record := decodeInstallIDRecord(t, data)
if record.InstallID != id {
t.Fatalf("persisted install ID = %q, want %q", record.InstallID, id)
}
if !record.IssuedAt.Equal(now) {
t.Fatalf("persisted issued_at = %v, want %v", record.IssuedAt, now)
}
}
func TestGetOrCreateInstallID_ReusesExisting(t *testing.T) {
dir := t.TempDir()
now := time.Date(2026, 3, 28, 12, 0, 0, 0, time.UTC)
// Create first ID.
id1 := getOrCreateInstallIDAt(dir, now)
// Second call should return the same ID.
id2 := getOrCreateInstallIDAt(dir, now.Add(7*24*time.Hour))
if id1 != id2 {
t.Fatalf("expected same ID across calls, got %q and %q", id1, id2)
}
}
func TestGetOrCreateInstallID_RegeneratesInvalid(t *testing.T) {
dir := t.TempDir()
// Write garbage.
if err := os.WriteFile(filepath.Join(dir, installIDFile), []byte("not-a-uuid\n"), 0600); err != nil {
t.Fatalf("write install id file: %v", err)
}
id := getOrCreateInstallIDAt(dir, time.Date(2026, 3, 28, 12, 0, 0, 0, time.UTC))
if id == "" || id == "not-a-uuid" {
t.Fatalf("expected new valid UUID, got %q", id)
}
}
func TestGetOrCreateInstallID_RotatesExpiredRecord(t *testing.T) {
dir := t.TempDir()
start := time.Date(2026, 1, 1, 12, 0, 0, 0, time.UTC)
first := getOrCreateInstallIDAt(dir, start)
second := getOrCreateInstallIDAt(dir, start.Add(installIDRotationWindow+time.Hour))
if first == second {
t.Fatalf("expected install ID rotation after %v, got same value %q", installIDRotationWindow, first)
}
}
func TestGetOrCreateInstallID_RotatesLegacyPlaintextID(t *testing.T) {
dir := t.TempDir()
legacyID := uuid.New().String()
if err := os.WriteFile(filepath.Join(dir, installIDFile), []byte(legacyID+"\n"), 0600); err != nil {
t.Fatalf("WriteFile: %v", err)
}
now := time.Date(2026, 3, 28, 12, 0, 0, 0, time.UTC)
rotated := getOrCreateInstallIDAt(dir, now)
if rotated == legacyID {
t.Fatalf("expected legacy plaintext install ID to rotate, got same value %q", rotated)
}
record := decodeInstallIDRecordFile(t, filepath.Join(dir, installIDFile))
if record.InstallID != rotated {
t.Fatalf("persisted install ID = %q, want %q", record.InstallID, rotated)
}
}
func TestResetInstallID_RewritesRecordImmediately(t *testing.T) {
dir := t.TempDir()
start := time.Date(2026, 3, 1, 12, 0, 0, 0, time.UTC)
original := getOrCreateInstallIDAt(dir, start)
resetAt := start.Add(12 * time.Hour)
rotated, err := resetInstallIDAt(dir, resetAt)
if err != nil {
t.Fatalf("resetInstallIDAt: %v", err)
}
if rotated == "" {
t.Fatal("expected non-empty rotated install ID")
}
if rotated == original {
t.Fatalf("expected reset to rotate install ID, got same value %q", rotated)
}
record := decodeInstallIDRecordFile(t, filepath.Join(dir, installIDFile))
if record.InstallID != rotated {
t.Fatalf("persisted install ID = %q, want %q", record.InstallID, rotated)
}
if !record.IssuedAt.Equal(resetAt) {
t.Fatalf("persisted issued_at = %v, want %v", record.IssuedAt, resetAt)
}
}
func TestIsEnabled(t *testing.T) {
tests := []struct {
value string
want bool
}{
{"true", true},
{"1", true},
{"false", false},
{"", true}, // enabled by default when env var is not set
{"0", false},
{"yes", false}, // only "true"/"1" are truthy; unknown values disable
}
for _, tt := range tests {
t.Run(tt.value, func(t *testing.T) {
t.Setenv("PULSE_TELEMETRY", tt.value)
if got := IsEnabled(); got != tt.want {
t.Fatalf("IsEnabled() = %v for PULSE_TELEMETRY=%q, want %v", got, tt.value, tt.want)
}
})
}
}
func TestClassifyPulseIntelligenceProActivationProof(t *testing.T) {
tests := []struct {
name string
input PulseIntelligenceProActivationProofInput
want PulseIntelligenceProActivationProof
}{
{
name: "not started",
want: PulseIntelligenceProActivationProof{
ValueProofState: PulseIntelligenceProActivationValueProofNotStarted,
},
},
{
name: "starter only stays in progress",
input: PulseIntelligenceProActivationProofInput{
ProActivationStarterCount: 1,
},
want: PulseIntelligenceProActivationProof{
ValueProofState: PulseIntelligenceProActivationValueProofInProgress,
},
},
{
name: "approved verified outcome resolves without external agent readiness",
input: PulseIntelligenceProActivationProofInput{
ProActivationStarterCount: 1,
PatrolIssueEvidenceCount: 1,
ContextualCollaborationCount: 1,
ApprovedDecisionCount: 1,
VerifiedOutcomeCount: 1,
},
want: PulseIntelligenceProActivationProof{
Completed: true,
Resolved: true,
ValueProofState: PulseIntelligenceProActivationValueProofVerified,
},
},
{
name: "rejected governed decision records proof state before full completion",
input: PulseIntelligenceProActivationProofInput{
ProActivationStarterCount: 1,
RejectedDecisionCount: 1,
},
want: PulseIntelligenceProActivationProof{
ValueProofState: PulseIntelligenceProActivationValueProofGovernedDecisionRecorded,
},
},
{
name: "rejected governed decision completes without resolving",
input: PulseIntelligenceProActivationProofInput{
ProActivationStarterCount: 1,
PatrolIssueEvidenceCount: 1,
ContextualCollaborationCount: 1,
RejectedDecisionCount: 1,
},
want: PulseIntelligenceProActivationProof{
Completed: true,
ValueProofState: PulseIntelligenceProActivationValueProofGovernedDecisionRecorded,
},
},
{
name: "approved verified outcome resolves",
input: PulseIntelligenceProActivationProofInput{
ProActivationStarterCount: 1,
PatrolIssueEvidenceCount: 1,
ContextualCollaborationCount: 1,
ApprovedDecisionCount: 1,
VerifiedOutcomeCount: 1,
ExternalAgentReady: true,
},
want: PulseIntelligenceProActivationProof{
Completed: true,
Resolved: true,
ValueProofState: PulseIntelligenceProActivationValueProofVerified,
},
},
{
name: "approved decision without verified outcome stays in progress",
input: PulseIntelligenceProActivationProofInput{
ProActivationStarterCount: 1,
PatrolIssueEvidenceCount: 1,
ContextualCollaborationCount: 1,
ApprovedDecisionCount: 1,
ExternalAgentReady: true,
},
want: PulseIntelligenceProActivationProof{
ValueProofState: PulseIntelligenceProActivationValueProofInProgress,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := ClassifyPulseIntelligenceProActivationProof(tt.input); got != tt.want {
t.Fatalf("ClassifyPulseIntelligenceProActivationProof() = %#v, want %#v", got, tt.want)
}
})
}
}
func TestClassifyPulseIntelligencePatrolControlProof(t *testing.T) {
tests := []struct {
name string
input PulseIntelligencePatrolControlProofInput
want PulseIntelligencePatrolControlProof
}{
{
name: "not started",
want: PulseIntelligencePatrolControlProof{
ValueProofState: PulseIntelligenceProActivationValueProofNotStarted,
},
},
{
name: "starter only stays in progress",
input: PulseIntelligencePatrolControlProofInput{
PatrolControlStarterCount: 1,
},
want: PulseIntelligencePatrolControlProof{
ValueProofState: PulseIntelligenceProActivationValueProofInProgress,
},
},
{
name: "rejected governed decision completes without resolving",
input: PulseIntelligencePatrolControlProofInput{
PatrolControlStarterCount: 1,
PatrolIssueEvidenceCount: 1,
ContextualCollaborationCount: 1,
RejectedDecisionCount: 1,
},
want: PulseIntelligencePatrolControlProof{
Completed: true,
ValueProofState: PulseIntelligenceProActivationValueProofGovernedDecisionRecorded,
},
},
{
name: "approved verified outcome resolves",
input: PulseIntelligencePatrolControlProofInput{
PatrolControlStarterCount: 1,
PatrolIssueEvidenceCount: 1,
ContextualCollaborationCount: 1,
ApprovedDecisionCount: 1,
VerifiedOutcomeCount: 1,
ExternalAgentReady: true,
},
want: PulseIntelligencePatrolControlProof{
Completed: true,
Resolved: true,
ValueProofState: PulseIntelligenceProActivationValueProofVerified,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := ClassifyPulseIntelligencePatrolControlProof(tt.input); got != tt.want {
t.Fatalf("ClassifyPulseIntelligencePatrolControlProof() = %#v, want %#v", got, tt.want)
}
})
}
legacy := ClassifyPulseIntelligencePatrolAutonomyProof(PulseIntelligencePatrolAutonomyProofInput{
PatrolAutonomyStarterCount: 1,
PatrolIssueEvidenceCount: 1,
ContextualCollaborationCount: 1,
ApprovedDecisionCount: 1,
VerifiedOutcomeCount: 1,
})
if legacy != (PulseIntelligencePatrolAutonomyProof{
Completed: true,
Resolved: true,
ValueProofState: PulseIntelligenceProActivationValueProofVerified,
}) {
t.Fatalf("legacy Patrol autonomy classifier alias drifted: %#v", legacy)
}
}
func TestApplySnapshot(t *testing.T) {
base := Ping{
InstallID: "test-id",
Version: "6.0.0",
Platform: "docker",
OS: "linux",
Arch: "amd64",
}
snap := func() Snapshot {
return Snapshot{
PVENodes: 3,
VMs: 10,
Containers: 5,
AgentHosts: 2,
DockerContainers: 12,
KubernetesPods: 18,
StoragePools: 4,
PhysicalDisks: 9,
TrueNASSystems: 1,
TrueNASApps: 3,
VMwareHosts: 2,
AvailabilityTargets: 6,
AIEnabled: true,
PatrolEnabled: true,
DiscoveryEnabled: true,
NotificationsEnabled: true,
AIActionsEnabled: true,
AlertAIEnabled: true,
ActiveAlerts: 2,
PaidLicense: true,
HasAPITokens: true,
RBACCustomRoles: 3,
RBACUserAssignments: 7,
AuditReads30d: 41,
ReportSchedules: 5,
ReportSchedulesEnabled: 4,
ReportSchedulesRun30d: 2,
AgentProfiles: 9,
UpdateAttempts30d: 4,
UpdateSuccesses30d: 2,
UpdateFailures30d: 1,
UpdateLastFailureCategory: "download",
ActiveAlertsCritical: 2,
ActiveAlertsAge1h24h: 2,
AlertsFiredCritical30d: 5,
AlertsResolvedCritical30d: 3,
AlertsResolution1h24h30d: 3,
AlertsRepeatOccurrences30d: 2,
AlertsSnoozedOccurrences30d: 2,
AlertsResolvedWhileSnoozed30d: 1,
AlertManagerTenants: 2,
AlertDeliveryActiveTenants: 1,
AlertFlappingEnabledTenants: 2,
AlertIntentPolicyConfiguredTenants: 1,
AlertEventHistoryAuthoritativeTenants: 2,
AlertActiveStateAuthoritativeTenants: 2,
PulseIntelligenceLoopConfigured: true,
PulseIntelligenceLoopActive30d: true,
PulseIntelligenceCompleteOperationsLoop30d: true,
PulseIntelligenceApprovedExecutionLoop30d: true,
PulseIntelligenceResolvedOperationsLoop30d: true,
PulseIntelligencePatrolControlCompletedOperationsLoop30d: true,
PulseIntelligencePatrolControlResolvedOperationsLoop30d: true,
PulseIntelligencePatrolControlPaidCompletedOperationsLoop30d: true,
PulseIntelligencePatrolControlPaidResolvedOperationsLoop30d: true,
PulseIntelligenceProActivationCompletedOperationsLoop30d: true,
PulseIntelligenceProActivationResolvedOperationsLoop30d: true,
PulseIntelligenceProActivationPaidCompletedOperationsLoop30d: true,
PulseIntelligenceProActivationPaidResolvedOperationsLoop30d: true,
PulseIntelligenceGovernedActionActive30d: true,
PulseIntelligenceAssistantOperationsLoop30d: true,
PulseIntelligenceAssistantApprovedExecutionLoop30d: true,
PulseIntelligenceAssistantApprovedActionSuccessLoop30d: true,
PulseIntelligenceAssistantResolvedOperationsLoop30d: true,
PulseIntelligenceExternalAgentOperationsLoop30d: true,
PulseIntelligenceExternalAgentApprovedExecutionLoop30d: true,
PulseIntelligenceExternalAgentApprovedActionSuccessLoop30d: true,
PulseIntelligenceExternalAgentResolvedOperationsLoop30d: true,
PulseIntelligenceMCPAdapterOperationsLoop30d: true,
PulseIntelligenceMCPAdapterApprovedExecutionLoop30d: true,
PulseIntelligenceMCPAdapterApprovedActionSuccessLoop30d: true,
PulseIntelligenceMCPAdapterResolvedOperationsLoop30d: true,
PulseIntelligenceOperationsLoopStarterRequests30d: 5,
PulseIntelligenceAssistantOperationsLoopStarterRequests30d: 2,
PulseIntelligencePatrolOperationsLoopStarterRequests30d: 1,
PulseIntelligencePatrolControlOperationsLoopStarterRequests30d: 3,
PulseIntelligenceProActivationOperationsLoopStarterRequests30d: 1,
PulseIntelligenceMCPOperationsLoopStarterRequests30d: 1,
PulseIntelligenceAssistantAICalls30d: 7,
PulseIntelligenceAssistantContextAICalls30d: 4,
PulseIntelligenceAssistantToolCalls30d: 9,
PulseIntelligencePatrolAICalls30d: 3,
PulseIntelligencePatrolRuns30d: 4,
PulseIntelligencePatrolNewFindings30d: 5,
PulseIntelligencePatrolInvestigations30d: 6,
PulseIntelligencePatrolResolvedFindings30d: 3,
PulseIntelligencePatrolBlockedCause: "provider_not_configured",
PulseIntelligencePatrolAutonomyLevel: "approval",
PulseIntelligencePatrolInputTokensBucket30d: "5m_20m",
PulseIntelligencePatrolOutputTokensBucket30d: "100k_500k",
PulseIntelligencePatrolInvestigationOutcomeCannotFix30d: 4,
AIProviderClass: "local",
PulseIntelligenceExternalAgentEnabled: true,
PulseIntelligenceExternalAgentUsed30d: true,
PulseIntelligenceMCPAdapterUsed30d: true,
PulseIntelligenceExternalAgentContextRequests30d: 8,
PulseIntelligenceExternalAgentEventStreamRequests30d: 2,
PulseIntelligenceExternalAgentProvisioningRequests30d: 1,
PulseIntelligenceExternalAgentOperatorStateRequests30d: 3,
PulseIntelligenceExternalAgentFindingRequests30d: 5,
PulseIntelligenceExternalAgentActionRequests30d: 4,
PulseIntelligenceActionPlans30d: 6,
PulseIntelligenceApprovalRequests30d: 2,
PulseIntelligenceRejectedActionDecisions30d: 1,
PulseIntelligenceApprovedActionDecisions30d: 1,
PulseIntelligenceApprovedActionAttempts30d: 1,
PulseIntelligenceApprovedActionSuccesses30d: 1,
PulseIntelligenceApprovedActionInFlight30d: 2,
PulseIntelligenceApprovedActionUnclassified30d: 3,
PulseIntelligenceApprovedActionRefusalsPlanStale30d: 4,
PulseIntelligenceApprovedActionRefusalsPolicy30d: 5,
PulseIntelligenceApprovedActionRefusalsCapability30d: 6,
PulseIntelligenceApprovedActionRefusalsTargetChanged30d: 7,
PulseIntelligenceApprovedActionRefusalsPrerequisite30d: 8,
PulseIntelligenceApprovedActionRefusalsContract30d: 9,
PulseIntelligenceApprovedActionRefusalsOther30d: 10,
PulseIntelligenceVerifiedFindingResolutions30d: 8,
}
}
ping := applySnapshot(base, snap)
if ping.InstallID != "test-id" {
t.Fatal("base fields should be preserved")
}
if ping.PVENodes != 3 {
t.Fatalf("PVENodes = %d, want 3", ping.PVENodes)
}
if ping.VMs != 10 {
t.Fatalf("VMs = %d, want 10", ping.VMs)
}
if ping.AgentHosts != 2 || ping.DockerContainers != 12 || ping.KubernetesPods != 18 {
t.Fatalf("expanded workload counts not applied: %#v", ping)
}
if ping.StoragePools != 4 || ping.PhysicalDisks != 9 {
t.Fatalf("expanded storage counts not applied: %#v", ping)
}
if ping.TrueNASSystems != 1 || ping.TrueNASApps != 3 || ping.VMwareHosts != 2 || ping.AvailabilityTargets != 6 {
t.Fatalf("expanded platform counts not applied: %#v", ping)
}
if !ping.AIEnabled {
t.Fatal("AIEnabled should be true")
}
if !ping.PatrolEnabled || !ping.DiscoveryEnabled || !ping.NotificationsEnabled || !ping.AIActionsEnabled {
t.Fatalf("expanded feature flags not applied: %#v", ping)
}
if !ping.AlertAIEnabled {
t.Fatal("AlertAIEnabled should be true")
}
if !ping.PaidLicense {
t.Fatal("PaidLicense should be true")
}
if !ping.HasAPITokens {
t.Fatal("HasAPITokens should be true")
}
if ping.RBACCustomRoles != 3 || ping.RBACUserAssignments != 7 {
t.Fatalf("RBAC adoption counts not applied: %#v", ping)
}
if ping.AuditReads30d != 41 {
t.Fatalf("audit read count not applied: %#v", ping)
}
if ping.ReportSchedules != 5 || ping.ReportSchedulesEnabled != 4 || ping.ReportSchedulesRun30d != 2 {
t.Fatalf("report schedule adoption counts not applied: %#v", ping)
}
if ping.AgentProfiles != 9 {
t.Fatalf("agent profile count not applied: %#v", ping)
}
if ping.UpdateAttempts30d != 4 || ping.UpdateSuccesses30d != 2 || ping.UpdateFailures30d != 1 ||
ping.UpdateLastFailureCategory != "download" {
t.Fatalf("update telemetry counters not applied: %#v", ping)
}
if ping.ActiveAlertsCritical != 2 || ping.ActiveAlertsAge1h24h != 2 ||
ping.AlertsFiredCritical30d != 5 || ping.AlertsResolvedCritical30d != 3 ||
ping.AlertsResolution1h24h30d != 3 || ping.AlertsRepeatOccurrences30d != 2 ||
ping.AlertsSnoozedOccurrences30d != 2 || ping.AlertsResolvedWhileSnoozed30d != 1 ||
ping.AlertManagerTenants != 2 || ping.AlertDeliveryActiveTenants != 1 ||
ping.AlertFlappingEnabledTenants != 2 || ping.AlertIntentPolicyConfiguredTenants != 1 ||
ping.AlertEventHistoryAuthoritativeTenants != 2 || ping.AlertActiveStateAuthoritativeTenants != 2 {
t.Fatalf("alert quality snapshot not applied: %#v", ping)
}
if !ping.PulseIntelligenceLoopConfigured || !ping.PulseIntelligenceLoopActive30d ||
!ping.PulseIntelligenceCompleteOperationsLoop30d ||
!ping.PulseIntelligenceApprovedExecutionLoop30d ||
!ping.PulseIntelligenceResolvedOperationsLoop30d ||
!ping.PulseIntelligencePatrolControlCompletedOperationsLoop30d ||
!ping.PulseIntelligencePatrolControlResolvedOperationsLoop30d ||
!ping.PulseIntelligencePatrolControlPaidCompletedOperationsLoop30d ||
!ping.PulseIntelligencePatrolControlPaidResolvedOperationsLoop30d ||
!ping.PulseIntelligenceProActivationCompletedOperationsLoop30d ||
!ping.PulseIntelligenceProActivationResolvedOperationsLoop30d ||
!ping.PulseIntelligenceProActivationPaidCompletedOperationsLoop30d ||
!ping.PulseIntelligenceProActivationPaidResolvedOperationsLoop30d ||
!ping.PulseIntelligenceGovernedActionActive30d ||
!ping.PulseIntelligenceAssistantOperationsLoop30d ||
!ping.PulseIntelligenceAssistantApprovedExecutionLoop30d ||
!ping.PulseIntelligenceAssistantApprovedActionSuccessLoop30d ||
!ping.PulseIntelligenceAssistantResolvedOperationsLoop30d ||
!ping.PulseIntelligenceExternalAgentOperationsLoop30d ||
!ping.PulseIntelligenceExternalAgentApprovedExecutionLoop30d ||
!ping.PulseIntelligenceExternalAgentApprovedActionSuccessLoop30d ||
!ping.PulseIntelligenceExternalAgentResolvedOperationsLoop30d ||
!ping.PulseIntelligenceMCPAdapterOperationsLoop30d ||
!ping.PulseIntelligenceMCPAdapterApprovedExecutionLoop30d ||
!ping.PulseIntelligenceMCPAdapterApprovedActionSuccessLoop30d ||
!ping.PulseIntelligenceMCPAdapterResolvedOperationsLoop30d {
t.Fatalf("Pulse Intelligence adoption state not applied: %#v", ping)
}
if ping.PulseIntelligenceAssistantAICalls30d != 7 ||
ping.PulseIntelligenceOperationsLoopStarterRequests30d != 5 ||
ping.PulseIntelligenceAssistantOperationsLoopStarterRequests30d != 2 ||
ping.PulseIntelligencePatrolOperationsLoopStarterRequests30d != 1 ||
ping.PulseIntelligencePatrolControlOperationsLoopStarterRequests30d != 3 ||
ping.PulseIntelligenceProActivationOperationsLoopStarterRequests30d != 1 ||
ping.PulseIntelligenceMCPOperationsLoopStarterRequests30d != 1 ||
ping.PulseIntelligenceAssistantContextAICalls30d != 4 ||
ping.PulseIntelligenceAssistantToolCalls30d != 9 ||
ping.PulseIntelligencePatrolAICalls30d != 3 ||
ping.PulseIntelligencePatrolRuns30d != 4 ||
ping.PulseIntelligencePatrolNewFindings30d != 5 ||
ping.PulseIntelligencePatrolInvestigations30d != 6 ||
ping.PulseIntelligencePatrolResolvedFindings30d != 3 ||
ping.PulseIntelligencePatrolBlockedCause != "provider_not_configured" ||
ping.PulseIntelligencePatrolAutonomyLevel != "approval" ||
ping.PulseIntelligencePatrolInputTokensBucket30d != "5m_20m" ||
ping.PulseIntelligencePatrolOutputTokensBucket30d != "100k_500k" ||
ping.PulseIntelligencePatrolInvestigationOutcomeCannotFix30d != 4 ||
ping.AIProviderClass != "local" ||
ping.PulseIntelligenceActionPlans30d != 6 ||
ping.PulseIntelligenceApprovalRequests30d != 2 ||
ping.PulseIntelligenceRejectedActionDecisions30d != 1 ||
ping.PulseIntelligenceApprovedActionDecisions30d != 1 ||
ping.PulseIntelligenceApprovedActionAttempts30d != 1 ||
ping.PulseIntelligenceApprovedActionSuccesses30d != 1 ||
ping.PulseIntelligenceApprovedActionInFlight30d != 2 ||
ping.PulseIntelligenceApprovedActionUnclassified30d != 3 ||
ping.PulseIntelligenceApprovedActionRefusalsPlanStale30d != 4 ||
ping.PulseIntelligenceApprovedActionRefusalsPolicy30d != 5 ||
ping.PulseIntelligenceApprovedActionRefusalsCapability30d != 6 ||
ping.PulseIntelligenceApprovedActionRefusalsTargetChanged30d != 7 ||
ping.PulseIntelligenceApprovedActionRefusalsPrerequisite30d != 8 ||
ping.PulseIntelligenceApprovedActionRefusalsContract30d != 9 ||
ping.PulseIntelligenceApprovedActionRefusalsOther30d != 10 ||
ping.PulseIntelligenceVerifiedFindingResolutions30d != 8 {
t.Fatalf("Pulse Intelligence counters not applied: %#v", ping)
}
if !ping.PulseIntelligenceExternalAgentEnabled || !ping.PulseIntelligenceExternalAgentUsed30d ||
!ping.PulseIntelligenceMCPAdapterUsed30d {
t.Fatalf("Pulse Intelligence external-agent booleans not applied: %#v", ping)
}
if ping.PulseIntelligenceExternalAgentContextRequests30d != 8 ||
ping.PulseIntelligenceExternalAgentEventStreamRequests30d != 2 ||
ping.PulseIntelligenceExternalAgentProvisioningRequests30d != 1 ||
ping.PulseIntelligenceExternalAgentOperatorStateRequests30d != 3 ||
ping.PulseIntelligenceExternalAgentFindingRequests30d != 5 ||
ping.PulseIntelligenceExternalAgentActionRequests30d != 4 {
t.Fatalf("Pulse Intelligence external-agent class counters not applied: %#v", ping)
}
}
func TestApplyUpdateTelemetrySnapshotSummarizesHistory(t *testing.T) {
history, err := updates.NewUpdateHistory(t.TempDir())
if err != nil {
t.Fatalf("NewUpdateHistory: %v", err)
}
now := time.Date(2026, 6, 28, 14, 0, 0, 0, time.UTC)
ctx := context.Background()
entries := []updates.UpdateHistoryEntry{
{
EventID: "old-success",
Timestamp: now.Add(-(installIDRotationWindow + time.Hour)),
Action: "update",
Status: updates.StatusSuccess,
},
{
EventID: "recent-success",
Timestamp: now.Add(-2 * time.Hour),
Action: "update",
Status: updates.StatusSuccess,
},
{
EventID: "recent-failed-download",
Timestamp: now.Add(-time.Hour),
Action: "update",
Status: updates.StatusFailed,
Error: &updates.UpdateError{
Message: "failed to download update: upstream timed out",
Details: "raw details stay local",
},
},
{
EventID: "recent-check",
Timestamp: now.Add(-30 * time.Minute),
Action: "check",
Status: updates.StatusSuccess,
},
}
for _, entry := range entries {
if _, err := history.CreateEntry(ctx, entry); err != nil {
t.Fatalf("CreateEntry(%s): %v", entry.EventID, err)
}
}
var snap Snapshot
ApplyUpdateTelemetrySnapshot(&snap, history, now)
if snap.UpdateAttempts30d != 2 {
t.Fatalf("UpdateAttempts30d = %d, want 2", snap.UpdateAttempts30d)
}
if snap.UpdateSuccesses30d != 1 {
t.Fatalf("UpdateSuccesses30d = %d, want 1", snap.UpdateSuccesses30d)
}
if snap.UpdateFailures30d != 1 {
t.Fatalf("UpdateFailures30d = %d, want 1", snap.UpdateFailures30d)
}
if snap.UpdateLastFailureCategory != "download" {
t.Fatalf("UpdateLastFailureCategory = %q, want download", snap.UpdateLastFailureCategory)
}
}
func TestApplyUpdateTelemetrySnapshotDoesNotExposeRawFailureText(t *testing.T) {
history, err := updates.NewUpdateHistory(t.TempDir())
if err != nil {
t.Fatalf("NewUpdateHistory: %v", err)
}
now := time.Date(2026, 6, 28, 14, 0, 0, 0, time.UTC)
if _, err := history.CreateEntry(context.Background(), updates.UpdateHistoryEntry{
EventID: "recent-sensitive-failure",
Timestamp: now.Add(-time.Minute),
Action: "update",
Status: updates.StatusFailed,
Error: &updates.UpdateError{
Code: "UPSTREAM_503",
Message: "failed for https://updates.example.test/private/build?token=secret",
Details: "/home/alice/pulse/update.log contained local command output",
},
}); err != nil {
t.Fatalf("CreateEntry: %v", err)
}
var snap Snapshot
ApplyUpdateTelemetrySnapshot(&snap, history, now)
if snap.UpdateLastFailureCategory != "unknown" {
t.Fatalf("UpdateLastFailureCategory = %q, want unknown", snap.UpdateLastFailureCategory)
}
for _, disallowed := range []string{"https://", "updates.example", "/home/alice", "token=", "command output"} {
if strings.Contains(snap.UpdateLastFailureCategory, disallowed) {
t.Fatalf("UpdateLastFailureCategory leaked raw failure text %q in %q", disallowed, snap.UpdateLastFailureCategory)
}
}
}
func TestAllTelemetryFieldsAreDisclosed(t *testing.T) {
pingType := reflect.TypeOf(Ping{})
fieldLabels := make([]string, 0)
for i := 0; i < pingType.NumField(); i++ {
jsonName := strings.Split(pingType.Field(i).Tag.Get("json"), ",")[0]
fieldLabels = append(fieldLabels, normalizedTelemetryDisclosureLabel(jsonName))
}
if len(fieldLabels) == 0 {
t.Fatal("expected telemetry fields on Ping")
}
for _, relativePath := range []string{
filepath.Join("..", "..", "docs", "PRIVACY.md"),
filepath.Join("..", "..", "frontend-modern", "public", "docs", "PRIVACY.md"),
} {
raw, err := os.ReadFile(relativePath)
if err != nil {
t.Fatalf("read %s: %v", relativePath, err)
}
doc := normalizedTelemetryDisclosureTableText(string(raw))
schemaDisclosure := fmt.Sprintf("schema version %d", TelemetrySchemaVersion)
if !strings.Contains(doc, schemaDisclosure) {
t.Errorf("%s must identify the current telemetry contract as %q", relativePath, schemaDisclosure)
}
for _, label := range fieldLabels {
if !strings.Contains(doc, label) {
t.Errorf("%s must disclose telemetry field %q", relativePath, label)
}
}
}
}
func TestTelemetryPrivacyDocsDisclosePseudonymousIdentityAndIPHandling(t *testing.T) {
anonymousTelemetryClaimPattern := regexp.MustCompile(`(?i)\banonymous\b[^\n]{0,120}\btelemetry\b|\btelemetry\b[^\n]{0,120}\banonymous\b`)
for _, relativePath := range []string{
filepath.Join("..", "..", "docs", "PRIVACY.md"),
filepath.Join("..", "..", "frontend-modern", "public", "docs", "PRIVACY.md"),
} {
raw, err := os.ReadFile(relativePath)
if err != nil {
t.Fatalf("read %s: %v", relativePath, err)
}
content := string(raw)
normalized := normalizedTelemetryDisclosureText(content)
for _, required := range []string{
"outbound usage telemetry",
"enabled by default",
"rotating pseudonymous install ID",
"Pulse does not send raw browser events or an event-level clickstream",
"Lifecycle and outcome signals are deliberately limited to closed buckets, booleans, and aggregate counts",
"PULSE_TELEMETRY=false",
"The license server uses request IP addresses transiently for abuse/rate limiting",
} {
if !strings.Contains(content, required) {
t.Errorf("%s must disclose %q", relativePath, required)
}
}
if !strings.Contains(normalized, "does not store ip addresses in telemetry rows") {
t.Errorf("%s must disclose that telemetry rows do not store IP addresses", relativePath)
}
if anonymousTelemetryClaimPattern.MatchString(content) {
t.Errorf("%s must not describe outbound usage telemetry as anonymous", relativePath)
}
}
}
func TestTelemetryPrivacyDocsKeepObserverProposalContentLocal(t *testing.T) {
for _, relativePath := range []string{
filepath.Join("..", "..", "docs", "PRIVACY.md"),
filepath.Join("..", "..", "frontend-modern", "public", "docs", "PRIVACY.md"),
} {
raw, err := os.ReadFile(relativePath)
if err != nil {
t.Fatalf("read %s: %v", relativePath, err)
}
content := string(raw)
for _, required := range []string{
"model-authored observer proposal",
"encrypts that artifact with the retained objective",
"excludes it from public objective reads and later prompt seeds",
"does not include its content in usage telemetry or audit messages",
} {
if !strings.Contains(content, required) {
t.Errorf("%s must disclose %q", relativePath, required)
}
}
}
}
func TestRepositoryDoesNotClaimTelemetryIsAnonymous(t *testing.T) {
repoRoot := filepath.Clean(filepath.Join("..", ".."))
anonymousTelemetryClaimPattern := regexp.MustCompile(`(?i)\banonymous\b[^\n]{0,120}\btelemetry\b|\btelemetry\b[^\n]{0,120}\banonymous\b`)
scannableExtensions := map[string]bool{
".go": true,
".html": true,
".json": true,
".md": true,
".py": true,
".sh": true,
".ts": true,
".tsx": true,
".yaml": true,
".yml": true,
}
err := filepath.WalkDir(repoRoot, func(path string, entry fs.DirEntry, walkErr error) error {
if walkErr != nil {
return walkErr
}
if entry.IsDir() {
switch entry.Name() {
case ".claude", ".git", "build", "dist", "node_modules", "tmp", "vendor":
return fs.SkipDir
default:
return nil
}
}
relativePath, err := filepath.Rel(repoRoot, path)
if err != nil {
return err
}
normalizedPath := filepath.ToSlash(relativePath)
baseName := entry.Name()
if normalizedPath == "docs/release-control/v6/internal/status.json" ||
strings.Contains(normalizedPath, "/__tests__/") ||
strings.Contains(normalizedPath, "/tests/") ||
strings.HasSuffix(baseName, "_test.go") ||
strings.HasSuffix(baseName, "_test.py") ||
strings.Contains(baseName, ".test.") {
return nil
}
if !scannableExtensions[strings.ToLower(filepath.Ext(baseName))] {
return nil
}
raw, err := os.ReadFile(path)
if err != nil {
return err
}
if match := anonymousTelemetryClaimPattern.Find(raw); match != nil {
t.Errorf("%s must describe telemetry as pseudonymous, not %q", normalizedPath, match)
}
return nil
})
if err != nil {
t.Fatalf("scan repository telemetry language: %v", err)
}
}
func normalizedTelemetryDisclosureTableText(value string) string {
tableLines := make([]string, 0)
for _, line := range strings.Split(value, "\n") {
if strings.HasPrefix(strings.TrimSpace(line), "|") {
tableLines = append(tableLines, line)
}
}
return normalizedTelemetryDisclosureText(strings.Join(tableLines, "\n"))
}
func normalizedTelemetryDisclosureLabel(jsonName string) string {
if strings.HasPrefix(jsonName, "pulse_intelligence_") {
label := strings.TrimPrefix(jsonName, "pulse_intelligence_")
return normalizedTelemetryDisclosureText("Pulse Intelligence " + strings.ReplaceAll(label, "_", " "))
}
return normalizedTelemetryDisclosureText(strings.ReplaceAll(jsonName, "_", " "))
}
func normalizedTelemetryDisclosureText(value string) string {
value = strings.ToLower(value)
replacer := strings.NewReplacer(
"`", " ",
"|", " ",
"_", " ",
"-", " ",
"/", " ",
",", " ",
".", " ",
":", " ",
";", " ",
"*", " ",
"(", " ",
")", " ",
"[", " ",
"]", " ",
)
value = replacer.Replace(value)
return strings.Join(strings.Fields(value), " ")
}
func TestApplySnapshot_NilFunc(t *testing.T) {
base := Ping{InstallID: "test-id", Version: "6.0.0"}
ping := applySnapshot(base, nil)
if ping.InstallID != "test-id" {
t.Fatal("should return base when func is nil")
}
if ping.PVENodes != 0 {
t.Fatal("snapshot fields should be zero when func is nil")
}
}
func TestBuildPreview_UsesCurrentHeartbeatPayload(t *testing.T) {
dir := t.TempDir()
preview, err := BuildPreview(Config{
Version: "v6.0.0-rc.1-45-gABCDEF",
DataDir: dir,
IsDocker: true,
GetSnapshot: func() Snapshot {
return Snapshot{
PVENodes: 3,
VMs: 10,
ActiveAlerts: 2,
AIEnabled: true,
NotificationFailures7d: 3,
NotificationFailuresAuthentication7d: 2,
NotificationFailuresServerError7d: 1,
AlertsRepeatOccurrences30d: 4,
AlertManagerTenants: 2,
}
},
})
if err != nil {
t.Fatalf("BuildPreview: %v", err)
}
if preview.Event != "heartbeat" {
t.Fatalf("preview event = %q, want heartbeat", preview.Event)
}
if preview.Platform != "docker" {
t.Fatalf("preview platform = %q, want docker", preview.Platform)
}
if preview.Version != "6.0.0-rc.1+git.45.gabcdef" {
t.Fatalf("preview version = %q, want normalized development version", preview.Version)
}
if preview.VersionRaw != "v6.0.0-rc.1-45-gABCDEF" {
t.Fatalf("preview raw version = %q, want original version string", preview.VersionRaw)
}
if preview.VersionChannel != "dev" {
t.Fatalf("preview version channel = %q, want dev", preview.VersionChannel)
}
if preview.VersionBuild != "git.45.gabcdef" {
t.Fatalf("preview version build = %q, want git.45.gabcdef", preview.VersionBuild)
}
if !preview.VersionDevelopment {
t.Fatal("expected preview to mark development build")
}
if preview.VersionPublished {
t.Fatal("development preview must not be marked as published release")
}
if preview.PVENodes != 3 || preview.VMs != 10 || preview.ActiveAlerts != 2 {
t.Fatalf("preview snapshot = %#v", preview)
}
if preview.NotificationFailures7d != 3 ||
preview.NotificationFailuresAuthentication7d != 2 ||
preview.NotificationFailuresServerError7d != 1 {
t.Fatalf("preview notification failure classes = %#v", preview)
}
if preview.AlertsRepeatOccurrences30d != 4 || preview.AlertManagerTenants != 2 {
t.Fatalf("preview alert-quality fields = %#v", preview)
}
if preview.InstallID == "" {
t.Fatal("expected preview install ID")
}
if preview.SchemaVersion != TelemetrySchemaVersion || preview.SentAt == "" {
t.Fatalf("preview schema identity = version %d sent_at %q", preview.SchemaVersion, preview.SentAt)
}
if preview.DeploymentMethod != "container_other" {
t.Fatalf("preview deployment method = %q, want container_other", preview.DeploymentMethod)
}
if preview.ActivationStage != "outcome_observed" || !preview.MonitoringActive {
t.Fatalf("preview lifecycle = stage %q active %v, want outcome_observed/true", preview.ActivationStage, preview.MonitoringActive)
}
if preview.EstateSizeBucket != "11_50" {
t.Fatalf("preview estate bucket = %q, want 11_50", preview.EstateSizeBucket)
}
if preview.TimeToFirstMonitoredResourceBucket != "present_at_first_observation" {
t.Fatalf("preview time-to-monitoring bucket = %q, want present_at_first_observation", preview.TimeToFirstMonitoredResourceBucket)
}
record := decodeInstallIDRecordFile(t, filepath.Join(dir, installIDFile))
if record.InstallID != preview.InstallID {
t.Fatalf("persisted install ID = %q, want %q", record.InstallID, preview.InstallID)
}
}
func TestAlertQualityPayloadContainsOnlyAggregateFields(t *testing.T) {
payload, err := json.Marshal(Ping{
AlertsRepeatOccurrences30d: 7,
AlertsResolvedWhileSnoozed30d: 5,
AlertIntentPolicyConfiguredTenants: 3,
AlertActiveStatePersistenceDegradedTenants: 1,
})
if err != nil {
t.Fatalf("marshal ping: %v", err)
}
text := strings.ToLower(string(payload))
for _, forbidden := range []string{
"resource_id", "hostname", "alert_text", "alert_message", "rule_content",
"destination", "resolved_at", "start_time", "error_text", "click",
} {
if strings.Contains(text, forbidden) {
t.Fatalf("alert-quality payload contains forbidden key fragment %q: %s", forbidden, text)
}
}
}
func TestBuildPingAt_RotatesIdentifierDuringLongRunningSession(t *testing.T) {
dir := t.TempDir()
start := time.Date(2026, 1, 1, 12, 0, 0, 0, time.UTC)
cfg := Config{DataDir: dir}
first, err := buildPingAt(cfg, "startup", start)
if err != nil {
t.Fatalf("build first ping: %v", err)
}
second, err := buildPingAt(cfg, "heartbeat", start.Add(installIDRotationWindow+time.Hour))
if err != nil {
t.Fatalf("build second ping: %v", err)
}
if first.InstallID == second.InstallID {
t.Fatalf("expected per-event install ID rotation, got %q twice", first.InstallID)
}
}
func TestDeploymentMethodRejectsFreeFormValues(t *testing.T) {
t.Setenv("PULSE_DEPLOYMENT_METHOD", "/home/alice/private-install")
if got := deploymentMethod(Config{IsDocker: true}); got != "container_other" {
t.Fatalf("docker invalid deployment value = %q, want container_other", got)
}
if got := deploymentMethod(Config{}); got != "binary_other" {
t.Fatalf("binary invalid deployment value = %q, want binary_other", got)
}
t.Setenv("PULSE_DEPLOYMENT_METHOD", "systemd")
if got := deploymentMethod(Config{}); got != "systemd" {
t.Fatalf("closed deployment value = %q, want systemd", got)
}
}
func TestBuildPingAt_PersistsOnlyCoarseLifecycleMilestones(t *testing.T) {
dir := t.TempDir()
start := time.Date(2026, 1, 1, 12, 0, 0, 0, time.UTC)
snapshot := Snapshot{AuthConfigured: true}
cfg := Config{
DataDir: dir,
GetSnapshot: func() Snapshot {
return snapshot
},
}
first, err := buildPingAt(cfg, "heartbeat", start)
if err != nil {
t.Fatalf("build first ping: %v", err)
}
if first.ActivationStage != "secured" || first.TimeToFirstMonitoredResourceBucket != "not_observed" {
t.Fatalf("first lifecycle = %#v", first)
}
snapshot = Snapshot{
AuthConfigured: true,
ConfiguredConnections: 1,
PVENodes: 1,
VMs: 4,
AlertsFired30d: 1,
AlertsResolved30d: 1,
}
second, err := buildPingAt(cfg, "heartbeat", start.Add(2*time.Hour))
if err != nil {
t.Fatalf("build second ping: %v", err)
}
if second.ActivationStage != "outcome_observed" || !second.MonitoringActive || !second.OutcomeObserved30d {
t.Fatalf("second lifecycle = %#v", second)
}
if second.TimeToFirstMonitoredResourceBucket != "1_6h" {
t.Fatalf("time-to-monitoring bucket = %q, want 1_6h", second.TimeToFirstMonitoredResourceBucket)
}
snapshot = Snapshot{}
third, err := buildPingAt(cfg, "heartbeat", start.Add(8*24*time.Hour))
if err != nil {
t.Fatalf("build third ping: %v", err)
}
if third.ActivationStage != "outcome_observed" || third.MonitoringActive {
t.Fatalf("historical/current lifecycle split = stage %q active %v", third.ActivationStage, third.MonitoringActive)
}
if third.KnownInstallAgeBucket != "8_30d" {
t.Fatalf("known install age bucket = %q, want 8_30d", third.KnownInstallAgeBucket)
}
data, err := os.ReadFile(filepath.Join(dir, lifecycleStateFile))
if err != nil {
t.Fatalf("read lifecycle state: %v", err)
}
var stored map[string]any
if err := json.Unmarshal(data, &stored); err != nil {
t.Fatalf("decode lifecycle state: %v", err)
}
for key := range stored {
switch key {
case "first_observed_at", "first_monitored_resource_at", "highest_observed_activation":
default:
t.Fatalf("unexpected lifecycle state field %q", key)
}
}
}
func TestSend_Success(t *testing.T) {
var received atomic.Int32
var lastPing Ping
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
body, _ := io.ReadAll(r.Body)
_ = json.Unmarshal(body, &lastPing)
received.Add(1)
w.WriteHeader(http.StatusNoContent)
}))
defer ts.Close()
// Override the endpoint for testing.
origEndpoint := pingEndpoint
pingEndpoint = ts.URL
defer func() { pingEndpoint = origEndpoint }()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
ping := Ping{
InstallID: "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
Version: "6.0.0-rc.1",
VersionRaw: "v6.0.0-rc.1",
VersionChannel: "rc",
VersionDevelopment: false,
VersionPublished: true,
Event: "startup",
Platform: "docker",
OS: "linux",
Arch: "amd64",
}
if err := send(ctx, ping); err != nil {
t.Fatalf("send: %v", err)
}
if received.Load() != 1 {
t.Fatalf("expected 1 request to reach server, got %d", received.Load())
}
if lastPing.InstallID != ping.InstallID {
t.Errorf("install_id = %q, want %q", lastPing.InstallID, ping.InstallID)
}
if lastPing.Event != "startup" {
t.Errorf("event = %q, want %q", lastPing.Event, "startup")
}
if lastPing.Version != "6.0.0-rc.1" {
t.Errorf("version = %q, want %q", lastPing.Version, "6.0.0-rc.1")
}
if lastPing.VersionRaw != "v6.0.0-rc.1" {
t.Errorf("version_raw = %q, want %q", lastPing.VersionRaw, "v6.0.0-rc.1")
}
if lastPing.VersionChannel != "rc" {
t.Errorf("version_channel = %q, want rc", lastPing.VersionChannel)
}
if !lastPing.VersionPublished {
t.Error("expected version_is_published_release to be true")
}
}
func TestSend_UsesReducedCommercialSignals(t *testing.T) {
var rawBody []byte
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
rawBody, _ = io.ReadAll(r.Body)
w.WriteHeader(http.StatusNoContent)
}))
defer ts.Close()
origEndpoint := pingEndpoint
pingEndpoint = ts.URL
defer func() { pingEndpoint = origEndpoint }()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := send(ctx, Ping{
InstallID: "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
Version: "6.0.0",
Event: "heartbeat",
Platform: "binary",
OS: "linux",
Arch: "amd64",
PaidLicense: true,
HasAPITokens: true,
}); err != nil {
t.Fatalf("send: %v", err)
}
var payload map[string]any
if err := json.Unmarshal(rawBody, &payload); err != nil {
t.Fatalf("Unmarshal: %v", err)
}
if _, ok := payload["license_tier"]; ok {
t.Fatal("legacy license_tier field should not be sent")
}
if _, ok := payload["api_tokens"]; ok {
t.Fatal("legacy api_tokens field should not be sent")
}
if got, ok := payload["paid_license"].(bool); !ok || !got {
t.Fatalf("paid_license = %#v, want true", payload["paid_license"])
}
if got, ok := payload["has_api_tokens"].(bool); !ok || !got {
t.Fatalf("has_api_tokens = %#v, want true", payload["has_api_tokens"])
}
}
func TestSend_ReturnsNonSuccessStatus(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
http.Error(w, "rejected", http.StatusUnprocessableEntity)
}))
defer ts.Close()
origEndpoint := pingEndpoint
pingEndpoint = ts.URL
defer func() { pingEndpoint = origEndpoint }()
err := send(context.Background(), Ping{InstallID: uuid.New().String()})
if err == nil || !strings.Contains(err.Error(), "HTTP 422") {
t.Fatalf("send error = %v, want HTTP 422", err)
}
}
func TestJitteredHeartbeat_WithinBounds(t *testing.T) {
min := heartbeatInterval - maxHeartbeatJitter
max := heartbeatInterval + maxHeartbeatJitter
for i := 0; i < 1000; i++ {
d := jitteredHeartbeat()
if d < min || d > max {
t.Fatalf("jitteredHeartbeat() = %v, want [%v, %v]", d, min, max)
}
}
}
func TestJitteredHeartbeat_NotConstant(t *testing.T) {
seen := make(map[time.Duration]bool)
for i := 0; i < 100; i++ {
seen[jitteredHeartbeat()] = true
}
if len(seen) < 2 {
t.Fatal("jitteredHeartbeat() returned the same value 100 times — jitter is not working")
}
}
func TestSendEvent_SuppressedWhileMockModeEnabled(t *testing.T) {
var received atomic.Int32
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
received.Add(1)
w.WriteHeader(http.StatusNoContent)
}))
defer ts.Close()
origEndpoint := pingEndpoint
pingEndpoint = ts.URL
defer func() { pingEndpoint = origEndpoint }()
testutil.SetMockMode(t, true)
cfg := Config{DataDir: t.TempDir()}
sendEvent(context.Background(), cfg, "startup")
sendEvent(context.Background(), cfg, "heartbeat")
if got := received.Load(); got != 0 {
t.Fatalf("expected no telemetry pings while mock mode is enabled, got %d", got)
}
}
func TestSendEvent_SendsWhenMockModeDisabled(t *testing.T) {
var received atomic.Int32
var lastPing Ping
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
body, _ := io.ReadAll(r.Body)
_ = json.Unmarshal(body, &lastPing)
received.Add(1)
w.WriteHeader(http.StatusNoContent)
}))
defer ts.Close()
origEndpoint := pingEndpoint
pingEndpoint = ts.URL
defer func() { pingEndpoint = origEndpoint }()
testutil.SetMockMode(t, false)
sendEvent(context.Background(), Config{DataDir: t.TempDir()}, "heartbeat")
if got := received.Load(); got != 1 {
t.Fatalf("expected 1 telemetry ping with mock mode disabled, got %d", got)
}
if lastPing.Event != "heartbeat" {
t.Errorf("event = %q, want %q", lastPing.Event, "heartbeat")
}
}
func TestStartStop_DisabledByDefault(t *testing.T) {
// Start should be a no-op when Enabled is false (the default).
Start(context.Background(), Config{
Version: "6.0.0",
DataDir: t.TempDir(),
Enabled: false,
})
// Stop should also be safe when nothing was started.
Stop()
}
func decodeInstallIDRecordFile(t *testing.T, path string) installIDRecord {
t.Helper()
data, err := os.ReadFile(path)
if err != nil {
t.Fatalf("ReadFile(%s): %v", path, err)
}
return decodeInstallIDRecord(t, data)
}
func decodeInstallIDRecord(t *testing.T, data []byte) installIDRecord {
t.Helper()
var record installIDRecord
if err := json.Unmarshal(data, &record); err != nil {
t.Fatalf("Unmarshal install ID record: %v", err)
}
return record
}
// The node connection test counts are only useful if they leave the install, so
// assert the snapshot-to-ping mapping rather than trusting field order.
func TestBuildPingCarriesNodeTestCounts(t *testing.T) {
ping := BuildPingForSnapshot(Snapshot{
NodeTestAttempts30d: 9,
NodeTestFailures30d: 4,
})
if ping.NodeTestAttempts30d != 9 {
t.Fatalf("node_test_attempts_30d = %d, want 9", ping.NodeTestAttempts30d)
}
if ping.NodeTestFailures30d != 4 {
t.Fatalf("node_test_failures_30d = %d, want 4", ping.NodeTestFailures30d)
}
}
// TestTelemetryPrivacyDocsDiscloseSetupChoiceAndPayloadChanges pins the two
// disclosure surfaces that replaced the in-app payload-update banner: the
// first-run setup choice and the dated payload changelog. The changelog must
// carry a row for the current schema so a bump cannot land undisclosed.
func TestTelemetryPrivacyDocsDiscloseSetupChoiceAndPayloadChanges(t *testing.T) {
for _, relativePath := range []string{
filepath.Join("..", "..", "docs", "PRIVACY.md"),
filepath.Join("..", "..", "frontend-modern", "public", "docs", "PRIVACY.md"),
} {
raw, err := os.ReadFile(relativePath)
if err != nil {
t.Fatalf("read %s: %v", relativePath, err)
}
content := string(raw)
for _, required := range []string{
"During first-run setup, switch off **Usage statistics** on the admin-account step",
"The first startup ping is sent about two minutes after Pulse starts",
"#### Payload changes",
"Every change to the payload bumps the schema version, is listed here with its date, and appears in the release notes",
"An in-app notice is reserved for a change in kind",
"#### What it is not used for",
"It is not sold, licensed, or shared with anyone else",
"It is not linked to a Pulse account, license key, purchase, or email address",
fmt.Sprintf("| %d | 2026-", TelemetrySchemaVersion),
} {
if !strings.Contains(content, required) {
t.Errorf("%s must disclose %q", relativePath, required)
}
}
}
}
// The production telemetry receiver must be unreachable from a Go test binary.
// A test that boots the real server runs against a throwaway data directory,
// so it mints a fresh install ID on every run and lands at the receiver as a
// distinct live installation. Regression guard for the 317 single-ping
// 0.0.0-test-version installs that pkg/server tests reported between
// 2026-08-29 and 2026-09-03.
func TestSendRefusesProductionEndpointUnderTest(t *testing.T) {
if pingEndpoint != productionPingEndpoint {
t.Fatalf("pingEndpoint = %q, want the production endpoint by default", pingEndpoint)
}
err := send(context.Background(), Ping{Event: "startup"})
if !errors.Is(err, errProductionEndpointUnderTest) {
t.Fatalf("send() to the production endpoint = %v, want errProductionEndpointUnderTest", err)
}
}
// SendServiceHealthEvent is the path pkg/server.Run takes when startup fails,
// and it sends synchronously rather than after the two-minute startup delay,
// which is why the failing server tests reported and the passing ones did not.
func TestSendServiceHealthEventRefusesProductionEndpointUnderTest(t *testing.T) {
cfg := Config{
Version: "test-version",
DataDir: t.TempDir(),
Enabled: true,
}
err := SendServiceHealthEvent(context.Background(), cfg, "startup", ServiceHealthObservation{
Observed: true,
FailureCategory: ServiceHealthFailureListener,
})
if !errors.Is(err, errProductionEndpointUnderTest) {
t.Fatalf("SendServiceHealthEvent() = %v, want errProductionEndpointUnderTest", err)
}
}
// A redirected endpoint is how telemetry's own tests assert on ping content,
// so the guard must not block it.
func TestSendAllowsRedirectedEndpointUnderTest(t *testing.T) {
var received atomic.Int32
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
received.Add(1)
w.WriteHeader(http.StatusOK)
}))
defer ts.Close()
origEndpoint := pingEndpoint
pingEndpoint = ts.URL
defer func() { pingEndpoint = origEndpoint }()
if err := send(context.Background(), Ping{Event: "startup"}); err != nil {
t.Fatalf("send() to a redirected endpoint: %v", err)
}
if got := received.Load(); got != 1 {
t.Fatalf("redirected endpoint received %d pings, want 1", got)
}
}