Files
cairnobs/enterprise/internal/groundingregistry/registry_test.go
T
jcoffey-dev 7d316f92db Phase 7: AI-assisted query authoring (autocomplete, explain, fix, optimize, NL translation)
Adds a self-hosted (Ollama, qwen2.5-coder) model provider abstraction
with a pluggable opt-in cloud adapter, schema grounding, and a shared
cost/safety guard every AI-suggested query is assessed against --
compiling to and executing through the same unchanged Phase 2 IR/
compiler and Phase 4 tenant scoping as a hand-written query, no
parallel execution path.

Track A (built into the query bar): inline ghost-text autocomplete,
"Explain this query", "Fix this query" with a diff view, and a
rule-based "Optimize" suggestion. Track B: natural-language-to-query
translation, always a separate review step from execution, with
`sentryctl query --nl` requiring explicit confirmation to run.
Every accepted/dismissed translate-fix-optimize interaction is logged
into the same append-only audit_log table Phase 4 built.

Two real product bugs were found and fixed via live browser
verification (a Svelte effect re-running on every keystroke that
silently cancelled the ghost-text debounce; a ghost-text widget
positioned at document offset 0 instead of the cursor), and a real
costguard logic bug (unbounded-aggregation vs. raw-row) was caught by
its own test suite. New integration tests wire a real Ollama client
through the real HTTP handler against a mock server matching Ollama's
wire contract (hack/mock-ollama), keeping model-quality verification
out of CI as a disclosed, periodic human-run check instead.

See /docs/phase-7-ai-design.md and /docs/phase-7-runbook.md.
2026-08-16 18:06:27 -07:00

75 lines
2.5 KiB
Go

package groundingregistry
import (
"context"
"testing"
"github.com/sentry/sentry/api/authz"
"github.com/sentry/sentry/api/querylang/executor"
)
// tenantAwareFakeRunner returns a service list keyed by the tenant
// identity RunSQL is called with -- confirms groundingregistry actually
// stamps a different tenant per Service.Refresh call, not the same
// context reused for everyone (chrunner.Registry's real RunSQL resolves
// tenant from ctx the same way, so this fake exercises the same
// contract).
type tenantAwareFakeRunner struct {
byTenant map[string][]string
}
func (f *tenantAwareFakeRunner) RunSQL(ctx context.Context, sql string) (*executor.Result, error) {
id, ok := authz.IdentityFromContext(ctx)
if !ok {
return &executor.Result{Columns: []string{"service"}, Rows: nil}, nil
}
services := f.byTenant[id.TenantID]
rows := make([][]any, len(services))
for i, s := range services {
rows[i] = []any{s}
}
return &executor.Result{Columns: []string{"service"}, Rows: rows}, nil
}
func TestRefreshAllScopesEachTenantIndependently(t *testing.T) {
runner := &tenantAwareFakeRunner{byTenant: map[string][]string{
"tenant-a": {"api-a"},
"tenant-b": {"api-b", "worker-b"},
}}
reg := New(runner)
lister := func(context.Context) ([]string, error) {
return []string{"tenant-a", "tenant-b"}, nil
}
reg.refreshAll(context.Background(), lister, nil)
a := reg.SchemaContextFor("tenant-a")
if len(a.Services) != 1 || a.Services[0] != "api-a" {
t.Errorf("tenant-a grounding = %v, want [api-a]", a.Services)
}
b := reg.SchemaContextFor("tenant-b")
if len(b.Services) != 2 {
t.Errorf("tenant-b grounding = %v, want 2 services", b.Services)
}
unknown := reg.SchemaContextFor("tenant-never-seen")
if len(unknown.Services) != 0 || len(unknown.Fields) != 0 {
t.Errorf("unseen tenant should get a zero-valued SchemaContext, got %+v", unknown)
}
}
func TestRefreshAllListerErrorLeavesExistingSnapshots(t *testing.T) {
runner := &tenantAwareFakeRunner{byTenant: map[string][]string{"tenant-a": {"api-a"}}}
reg := New(runner)
good := func(context.Context) ([]string, error) { return []string{"tenant-a"}, nil }
reg.refreshAll(context.Background(), good, nil)
failing := func(context.Context) ([]string, error) { return nil, context.DeadlineExceeded }
reg.refreshAll(context.Background(), failing, nil)
a := reg.SchemaContextFor("tenant-a")
if len(a.Services) != 1 {
t.Errorf("tenant-a grounding after a failed lister call = %v, want unchanged [api-a]", a.Services)
}
}