Files
pulse/pkg/server/telemetry_licensed_features_test.go
rcourtman 54a312bebd Measure Pro feature adoption and drop the dead Patrol autofix counter
Six of the eight Pro-exclusive features had no telemetry field at all, so
there was no way to answer whether RBAC, audit logging, scheduled reporting,
agent profiles, alert-triggered AI, or Kubernetes AI were being used by the
installs paying for them. Schema v6 adds nine content-free adoption signals:

  alert_ai_enabled          AIConfig.IsAlertTriggeredAnalysisEnabled()
  rbac_custom_roles         non-built-in roles, per org
  rbac_user_assignments     user-to-role assignments, per org
  audit_logging_persistent  a persistent audit store is active, not console
  audit_events_30d          audit events retained inside the window
  report_schedules          configured scheduled reports
  report_schedules_enabled  scheduled reports switched on
  report_schedules_run_30d  schedules whose last run falls inside the window
  agent_profiles            configured agent profiles

Counts only. Role names, permissions, usernames, schedule names, delivery
recipients, report scope, profile names, and every audit event field stay on
the install. kubernetes_ai needs no field of its own: it is derivable at read
time from alert_ai_enabled combined with the existing kubernetes_clusters
count, and a dedicated field would be redundant.

Config-sourced signals are read through applyLicensedFeatureConfigSnapshot;
RBAC and audit live behind the router and are read through
Router.ApplyLicensedFeatureTelemetrySnapshot. The RBAC read goes through a new
TenantRBACProvider.PeekManager so a background telemetry read can never
provision an RBAC store for an org that has never used RBAC.

Also removes pulse_intelligence_patrol_autofixes_30d and the AutoFixCount
field behind it. patrol_run.go hardcoded AutoFixCount to 0 and no increment
site existed anywhere in the tree, so the counter was zero in all 233,364
retained production pings. That was a wiring bug, not evidence that nobody
uses Patrol fixes; governed fixes are delivered through the approved-action
pipeline, which is already instrumented. The field was plumbed through run
records, history persistence, the Assistant handoff, and telemetry while being
structurally incapable of holding a non-zero value.

Verified end to end against a running install rather than only in unit tests,
which is precisely the check the autofix counter never had: seeding three
report schedules (two enabled, one last run inside the window) and two agent
profiles produced report_schedules 3, report_schedules_enabled 2,
report_schedules_run_30d 1, agent_profiles 2 in the Settings telemetry
preview, and signing in moved audit_events_30d to 1.

The private receiver landed first in pulse-pro 78ff7dd so the new fields are
accepted on arrival.
2026-08-05 14:34:50 +01:00

70 lines
2.7 KiB
Go

package server
import (
"testing"
"time"
"github.com/rcourtman/pulse-go-rewrite/internal/config"
"github.com/rcourtman/pulse-go-rewrite/internal/models"
"github.com/rcourtman/pulse-go-rewrite/internal/telemetry"
)
func TestApplyLicensedFeatureConfigSnapshot_CountsScheduledReportingAndProfiles(t *testing.T) {
now := time.Date(2026, 8, 5, 12, 0, 0, 0, time.UTC)
recent := now.Add(-48 * time.Hour)
old := now.Add(-(telemetry.PulseIntelligenceTelemetryWindow + time.Hour))
persistence := config.NewConfigPersistence(t.TempDir())
if err := persistence.SaveReportScheduleStore(config.ReportScheduleStore{
Schedules: []config.ReportSchedule{
{ID: "a", Name: "weekly-capacity", Enabled: true, LastRunAt: &recent},
{ID: "b", Name: "monthly-uptime", Enabled: true, LastRunAt: &old},
{ID: "c", Name: "paused", Enabled: false, LastRunAt: &recent},
{ID: "d", Name: "never-run", Enabled: true},
},
}); err != nil {
t.Fatalf("SaveReportScheduleStore: %v", err)
}
if err := persistence.SaveAgentProfiles([]models.AgentProfile{
{ID: "profile-1", Name: "edge"},
{ID: "profile-2", Name: "core"},
}); err != nil {
t.Fatalf("SaveAgentProfiles: %v", err)
}
var snap telemetry.Snapshot
applyLicensedFeatureConfigSnapshot(&snap, persistence, now)
if snap.ReportSchedules != 4 {
t.Fatalf("report schedules = %d, want 4", snap.ReportSchedules)
}
if snap.ReportSchedulesEnabled != 3 {
t.Fatalf("enabled report schedules = %d, want 3", snap.ReportSchedulesEnabled)
}
// Only the two schedules whose last run falls inside the window count, and
// a disabled schedule that still ran recently is one of them.
if snap.ReportSchedulesRun30d != 2 {
t.Fatalf("report schedules run in window = %d, want 2", snap.ReportSchedulesRun30d)
}
if snap.AgentProfiles != 2 {
t.Fatalf("agent profiles = %d, want 2", snap.AgentProfiles)
}
}
func TestApplyLicensedFeatureConfigSnapshot_LeavesCountsZeroWhenNothingConfigured(t *testing.T) {
var snap telemetry.Snapshot
applyLicensedFeatureConfigSnapshot(&snap, config.NewConfigPersistence(t.TempDir()), time.Now().UTC())
if snap.ReportSchedules != 0 || snap.ReportSchedulesEnabled != 0 || snap.ReportSchedulesRun30d != 0 || snap.AgentProfiles != 0 {
t.Fatalf("unconfigured install must report zeroes: %#v", snap)
}
}
// A nil persistence must not panic: the telemetry snapshot runs on a timer and
// a failed persistence init cannot be allowed to take the process down.
func TestApplyLicensedFeatureConfigSnapshot_ToleratesNilInputs(t *testing.T) {
var snap telemetry.Snapshot
applyLicensedFeatureConfigSnapshot(&snap, nil, time.Now().UTC())
applyLicensedFeatureConfigSnapshot(nil, config.NewConfigPersistence(t.TempDir()), time.Now().UTC())
}