Cover cloud tenant registry queries, unified views and slowlog wrappers

Three new branch-coverage tests taking eighteen previously unreached functions
from 0.0% to covered.

internal/cloudcp/registry: the workspace limit error message on both the nil
and populated receiver, the active workspace count per account, the tenant
lookup across owning account, foreign account and missing rows, the invitation
listing by email including case and whitespace normalization, and the
invitation delete for both an existing pair and a pair that never existed.

internal/unifiedresources: the Docker container and Kubernetes node typed
views, the presentation listing and the metrics target, each asserted on the
nil receiver, the nil nested payload and the populated case, with returned
slices proven independent of the store.

pkg/db: the tracing wrappers around BeginTx, Query, QueryRow and their context
variants plus the pool setters, with exact histogram deltas proving the
observe wiring rather than that SQLite works.

All three files are new; no source or existing test was touched.

Contract-Neutral: test-only: new Go branch-coverage tests, no source or contract change
This commit is contained in:
rcourtman
2026-07-22 21:41:02 +01:00
parent bdebc0242f
commit 82083378a2
3 changed files with 1296 additions and 0 deletions
@@ -0,0 +1,303 @@
package registry
import (
"strings"
"testing"
"time"
)
func newCovAccount(t *testing.T, reg *TenantRegistry) string {
t.Helper()
id, err := GenerateAccountID()
if err != nil {
t.Fatalf("GenerateAccountID: %v", err)
}
if err := reg.CreateAccount(&Account{ID: id, Kind: AccountKindIndividual, DisplayName: "cov account"}); err != nil {
t.Fatalf("CreateAccount: %v", err)
}
return id
}
func TestBranchcov0722PM_WorkspaceLimitExceededErrorError(t *testing.T) {
t.Run("nil receiver falls back to generic message", func(t *testing.T) {
var nilErr *WorkspaceLimitExceededError
got := nilErr.Error()
const want = "workspace limit exceeded"
if got != want {
t.Fatalf("nil receiver Error() = %q, want %q", got, want)
}
})
t.Run("populated message embeds account current and limit", func(t *testing.T) {
err := &WorkspaceLimitExceededError{AccountID: "a_cov123456", Current: 7, Limit: 3}
got := err.Error()
for _, want := range []string{`"a_cov123456"`, "7", "3"} {
if !strings.Contains(got, want) {
t.Errorf("Error() = %q, missing substring %q", got, want)
}
}
})
}
func TestBranchcov0722PM_CountActiveByAccountID(t *testing.T) {
reg := newTestRegistry(t)
const targetAccount = "a_count_tgt1"
const otherAccount = "a_count_other"
got, err := reg.CountActiveByAccountID(targetAccount)
if err != nil {
t.Fatalf("CountActiveByAccountID on empty account: %v", err)
}
if got != 0 {
t.Fatalf("empty account count = %d, want 0", got)
}
countedStates := []struct {
id string
state TenantState
}{
{"t-CNTACTV01", TenantStateActive},
{"t-CNTPRVN01", TenantStateProvisioning},
{"t-CNTSPN001", TenantStateSuspended},
{"t-CNTFLD001", TenantStateFailed},
}
excludedStates := []struct {
id string
state TenantState
}{
{"t-CNTDLT001", TenantStateDeleted},
{"t-CNTCNC001", TenantStateCanceled},
{"t-CNTDLG001", TenantStateDeleting},
}
for _, s := range countedStates {
if err := reg.Create(&Tenant{ID: s.id, AccountID: targetAccount, State: s.state}); err != nil {
t.Fatalf("Create %s (%s): %v", s.id, s.state, err)
}
}
for _, s := range excludedStates {
if err := reg.Create(&Tenant{ID: s.id, AccountID: targetAccount, State: s.state}); err != nil {
t.Fatalf("Create %s (%s): %v", s.id, s.state, err)
}
}
if err := reg.Create(&Tenant{ID: "t-CNTOTH001", AccountID: otherAccount, State: TenantStateActive}); err != nil {
t.Fatalf("Create other-account tenant: %v", err)
}
got, err = reg.CountActiveByAccountID(targetAccount)
if err != nil {
t.Fatalf("CountActiveByAccountID after inserts: %v", err)
}
if want := len(countedStates); got != want {
t.Fatalf("CountActiveByAccountID = %d, want %d (excluded states and other accounts must not count)", got, want)
}
gotOther, err := reg.CountActiveByAccountID(otherAccount)
if err != nil {
t.Fatalf("CountActiveByAccountID otherAccount: %v", err)
}
if gotOther != 1 {
t.Fatalf("CountActiveByAccountID otherAccount = %d, want 1", gotOther)
}
}
func TestBranchcov0722PM_GetTenantForAccount(t *testing.T) {
reg := newTestRegistry(t)
const ownerAccount = "a_owner_get1"
const otherAccount = "a_other_get1"
const tenantID = "t-GETOWN001"
const foreignTenantID = "t-GETOTH001"
if err := reg.Create(&Tenant{ID: tenantID, AccountID: ownerAccount, State: TenantStateActive, DisplayName: "Owned"}); err != nil {
t.Fatalf("Create owned tenant: %v", err)
}
if err := reg.Create(&Tenant{ID: foreignTenantID, AccountID: otherAccount, State: TenantStateActive, DisplayName: "Foreign"}); err != nil {
t.Fatalf("Create foreign tenant: %v", err)
}
t.Run("found for owning account", func(t *testing.T) {
got, err := reg.GetTenantForAccount(ownerAccount, tenantID)
if err != nil {
t.Fatalf("GetTenantForAccount: %v", err)
}
if got == nil {
t.Fatal("expected tenant, got nil")
}
if got.ID != tenantID {
t.Fatalf("got.ID = %q, want %q", got.ID, tenantID)
}
if got.AccountID != ownerAccount {
t.Fatalf("got.AccountID = %q, want %q", got.AccountID, ownerAccount)
}
})
t.Run("exists but belongs to a different account returns nil nil", func(t *testing.T) {
got, err := reg.GetTenantForAccount(ownerAccount, foreignTenantID)
if err != nil {
t.Fatalf("GetTenantForAccount foreign: %v", err)
}
if got != nil {
t.Fatalf("expected nil tenant for foreign-owned workspace, got %+v", got)
}
})
t.Run("missing tenant returns nil nil", func(t *testing.T) {
got, err := reg.GetTenantForAccount(ownerAccount, "t-GETMS001")
if err != nil {
t.Fatalf("GetTenantForAccount missing: %v", err)
}
if got != nil {
t.Fatalf("expected nil tenant for missing id, got %+v", got)
}
})
}
func TestBranchcov0722PM_ListInvitationsByEmail(t *testing.T) {
reg := newTestRegistry(t)
account1 := newCovAccount(t, reg)
account2 := newCovAccount(t, reg)
account3 := newCovAccount(t, reg)
mixedAccount := newCovAccount(t, reg)
for _, acc := range []string{account1, account2} {
if err := reg.UpsertInvitation(&AccountInvitation{
AccountID: acc,
Email: "shared@example.com",
Role: MemberRoleTech,
}); err != nil {
t.Fatalf("UpsertInvitation shared for %s: %v", acc, err)
}
}
if err := reg.UpsertInvitation(&AccountInvitation{
AccountID: account3,
Email: "solo@example.com",
Role: MemberRoleAdmin,
}); err != nil {
t.Fatalf("UpsertInvitation solo: %v", err)
}
if _, err := reg.db.Exec(
`INSERT INTO account_invitations (id, account_id, email, role, invited_by, invited_at) VALUES (?, ?, ?, ?, ?, ?)`,
"ainv_RAWLWR01", mixedAccount, "rawinsert@example.com", "tech", "", time.Now().UTC().Unix(),
); err != nil {
t.Fatalf("raw insert lowercase invitation: %v", err)
}
t.Run("no matches returns empty", func(t *testing.T) {
got, err := reg.ListInvitationsByEmail("nobody@example.com")
if err != nil {
t.Fatalf("ListInvitationsByEmail no-match: %v", err)
}
if len(got) != 0 {
t.Fatalf("expected 0 invitations, got %d (%+v)", len(got), got)
}
})
t.Run("single match returns the one invitation", func(t *testing.T) {
got, err := reg.ListInvitationsByEmail("solo@example.com")
if err != nil {
t.Fatalf("ListInvitationsByEmail solo: %v", err)
}
if len(got) != 1 {
t.Fatalf("expected 1 invitation, got %d", len(got))
}
if got[0].AccountID != account3 {
t.Fatalf("got[0].AccountID = %q, want %q", got[0].AccountID, account3)
}
if got[0].Email != "solo@example.com" {
t.Fatalf("got[0].Email = %q, want %q", got[0].Email, "solo@example.com")
}
if got[0].Role != MemberRoleAdmin {
t.Fatalf("got[0].Role = %q, want %q", got[0].Role, MemberRoleAdmin)
}
})
t.Run("several matches across accounts", func(t *testing.T) {
got, err := reg.ListInvitationsByEmail("shared@example.com")
if err != nil {
t.Fatalf("ListInvitationsByEmail shared: %v", err)
}
if len(got) != 2 {
t.Fatalf("expected 2 invitations, got %d", len(got))
}
seen := map[string]bool{}
for _, inv := range got {
seen[inv.AccountID] = true
if inv.Email != "shared@example.com" {
t.Errorf("inv.Email = %q, want %q", inv.Email, "shared@example.com")
}
}
if !seen[account1] || !seen[account2] {
t.Fatalf("expected both accounts in results, seen=%v", seen)
}
})
t.Run("query normalizes case and surrounding whitespace", func(t *testing.T) {
got, err := reg.ListInvitationsByEmail(" RAWINSERT@EXAMPLE.COM ")
if err != nil {
t.Fatalf("ListInvitationsByEmail normalized: %v", err)
}
if len(got) != 1 {
t.Fatalf("expected 1 invitation after normalization, got %d", len(got))
}
if got[0].ID != "ainv_RAWLWR01" {
t.Fatalf("got[0].ID = %q, want %q", got[0].ID, "ainv_RAWLWR01")
}
})
}
func TestBranchcov0722PM_DeleteInvitationByAccountAndEmail(t *testing.T) {
reg := newTestRegistry(t)
account := newCovAccount(t, reg)
const email = "deletable@example.com"
if err := reg.UpsertInvitation(&AccountInvitation{
AccountID: account,
Email: email,
Role: MemberRoleTech,
}); err != nil {
t.Fatalf("UpsertInvitation: %v", err)
}
before, err := reg.ListInvitationsByEmail(email)
if err != nil {
t.Fatalf("ListInvitationsByEmail before delete: %v", err)
}
if len(before) != 1 {
t.Fatalf("expected 1 invitation before delete, got %d", len(before))
}
t.Run("deletes existing invitation then it is gone via list", func(t *testing.T) {
if err := reg.DeleteInvitationByAccountAndEmail(account, email); err != nil {
t.Fatalf("DeleteInvitationByAccountAndEmail: %v", err)
}
after, err := reg.ListInvitationsByEmail(email)
if err != nil {
t.Fatalf("ListInvitationsByEmail after delete: %v", err)
}
if len(after) != 0 {
t.Fatalf("expected 0 invitations after delete, got %d (%+v)", len(after), after)
}
})
t.Run("deleting a non-existent pair returns nil", func(t *testing.T) {
beforeNoOp, err := reg.ListInvitationsByEmail(email)
if err != nil {
t.Fatalf("ListInvitationsByEmail before no-op: %v", err)
}
if err := reg.DeleteInvitationByAccountAndEmail(account, "never@existed.com"); err != nil {
t.Fatalf("DeleteInvitationByAccountAndEmail non-existent = %v, want nil", err)
}
got, err := reg.ListInvitationsByEmail(email)
if err != nil {
t.Fatalf("ListInvitationsByEmail after no-op: %v", err)
}
if len(got) != len(beforeNoOp) {
t.Fatalf("no-op delete changed invitation count: before %d, after %d", len(beforeNoOp), len(got))
}
})
}
@@ -0,0 +1,499 @@
package unifiedresources
import (
"testing"
"time"
)
// TestBranchcov0722PM mechanically raises branch/function coverage on four
// previously-uncovered *ResourceRegistry accessors declared in registry.go:
// - ListForPresentation() []Resource
// - MetricsTarget(resourceID string) *MetricsTarget
// - DockerContainers() []*DockerContainerView
// - K8sNodes() []*K8sNodeView
//
// Each subtest seeds a ResourceRegistry through the public IngestResources /
// IngestRecords paths (or, where the function semantics require an unmatched
// resource with no source mapping, via the in-package rr.resources map — the
// same pattern used by the sibling TestBuildMetricsTargetForRegistryFallsBackToStoredMetricsTarget)
// and asserts the concrete returned value, driving both arms of every
// conditional (empty vs populated, hit vs miss, nil vs non-nil, kind match vs
// kind mismatch). No source file or pre-existing test is modified.
func TestBranchcov0722PM(t *testing.T) {
now := time.Date(2026, 7, 22, 14, 0, 0, 0, time.UTC)
// ---------------------------------------------------------------------------
// ListForPresentation
// ---------------------------------------------------------------------------
t.Run("ListForPresentation/EmptyRegistryReturnsNonNilEmptySlice", func(t *testing.T) {
rr := NewRegistry(nil)
got := rr.ListForPresentation()
// ListForPresentation delegates to List() (which always allocates a
// non-nil slice) and then to CoalescePresentationHostResourcesWithExclusions,
// whose len==0 early-return hands that empty slice straight back.
if got == nil {
t.Fatal("expected non-nil empty slice for empty registry, got nil")
}
if len(got) != 0 {
t.Fatalf("expected 0 resources for empty registry, got %d: %+v", len(got), got)
}
})
t.Run("ListForPresentation/PreservesAllNonHostKindsSortedByName", func(t *testing.T) {
rr := NewRegistry(nil)
rr.IngestResources([]Resource{
{ID: "vm-zeta", Type: ResourceTypeVM, Name: "zeta-vm", Status: StatusOnline, LastSeen: now},
{ID: "k8snode-alpha", Type: ResourceTypeK8sNode, Name: "alpha-node", Status: StatusOnline, LastSeen: now},
{ID: "container-beta", Type: ResourceTypeAppContainer, Name: "beta-app", Status: StatusOnline, LastSeen: now},
{ID: "storage-gamma", Type: ResourceTypeStorage, Name: "gamma-pool", Status: StatusOnline, LastSeen: now},
})
got := rr.ListForPresentation()
// None of the seeded resources are ResourceTypeAgent, so the host
// coalesce step is a no-op and every input kind survives.
if len(got) != 4 {
t.Fatalf("expected all 4 non-host resources preserved, got %d: %+v", len(got), got)
}
// ListForPresentation sorts by canonical name (lowercased name, then
// type, then id) via the List() it composes on top of.
wantNames := []string{"alpha-node", "beta-app", "gamma-pool", "zeta-vm"}
gotNames := make([]string, 0, len(got))
for _, r := range got {
gotNames = append(gotNames, r.Name)
}
assertStringSlice(t, gotNames, wantNames)
// Every requested kind must be reachable in the output — the
// function must not silently drop non-agent kinds.
wantTypes := map[ResourceType]bool{
ResourceTypeVM: true,
ResourceTypeK8sNode: true,
ResourceTypeAppContainer: true,
ResourceTypeStorage: true,
}
seenTypes := make(map[ResourceType]int, len(got))
for _, r := range got {
seenTypes[CanonicalResourceType(r.Type)]++
}
for wantType := range wantTypes {
if seenTypes[wantType] != 1 {
t.Fatalf("expected exactly one %q in presentation output, got %d (seen=%v)", wantType, seenTypes[wantType], seenTypes)
}
}
})
t.Run("ListForPresentation/CoalescesSplitAgentHostViews", func(t *testing.T) {
// Two ResourceTypeAgent records that share a canonical short hostname
// — one sourced from Proxmox (platform/runtime view), one from the
// Pulse agent — must collapse into a single presentation host while
// List() still returns both raw records. This is the only filtering
// the function performs: it never drops a kind outright, it only
// merges qualifying agent views.
rr := NewRegistry(nil)
rr.IngestResources([]Resource{
{
ID: "agent-proxmox-tower",
Type: ResourceTypeAgent,
Name: "tower",
Status: StatusWarning,
LastSeen: now.Add(-1 * time.Minute),
Sources: []DataSource{SourceProxmox},
Identity: ResourceIdentity{Hostnames: []string{"tower"}},
Proxmox: &ProxmoxData{
NodeName: "tower",
ClusterName: "homelab",
},
},
{
ID: "agent-runtime-tower",
Type: ResourceTypeAgent,
Name: "tower",
Status: StatusOnline,
LastSeen: now,
Sources: []DataSource{SourceAgent},
Identity: ResourceIdentity{
MachineID: "agent-machine-tower",
Hostnames: []string{"tower"},
},
Agent: &AgentData{
AgentID: "agent-machine-tower",
Hostname: "tower",
OSName: "Proxmox VE",
},
},
// A non-agent resource rides along to prove the coalesce only
// touches host views — the VM count is unchanged.
{ID: "vm-bystander", Type: ResourceTypeVM, Name: "bystander-vm", Status: StatusOnline, LastSeen: now},
})
raw := rr.List()
if len(raw) != 3 {
t.Fatalf("List() should still expose all 3 raw records, got %d", len(raw))
}
got := rr.ListForPresentation()
if len(got) != 2 {
t.Fatalf("expected split agent host views to coalesce into 1 (plus the VM = 2 total), got %d: %+v", len(got), got)
}
// The merged host must carry both agent and proxmox facets, and the
// agent-source record wins as primary (its ID is the canonical one
// the metrics API writes under).
var mergedHost *Resource
var vmCount int
for i := range got {
if CanonicalResourceType(got[i].Type) == ResourceTypeAgent {
mergedHost = &got[i]
}
if CanonicalResourceType(got[i].Type) == ResourceTypeVM {
vmCount++
}
}
if mergedHost == nil {
t.Fatalf("expected a coalesced agent host in output, got %+v", got)
}
if vmCount != 1 {
t.Fatalf("expected the bystander VM to survive coalesce untouched, got %d", vmCount)
}
if mergedHost.ID != "agent-runtime-tower" {
t.Fatalf("expected agent-source record to win as primary, got id=%q", mergedHost.ID)
}
if mergedHost.Agent == nil || mergedHost.Proxmox == nil {
t.Fatalf("expected merged host to carry both agent and proxmox facets, got agent=%+v proxmox=%+v", mergedHost.Agent, mergedHost.Proxmox)
}
if !containsDataSource(mergedHost.Sources, SourceAgent) || !containsDataSource(mergedHost.Sources, SourceProxmox) {
t.Fatalf("expected merged host sources to include agent+proxmox, got %+v", mergedHost.Sources)
}
})
t.Run("ListForPresentation/ExclusionBlocksHostCoalesce", func(t *testing.T) {
// The closure passed to CoalescePresentationHostResourcesWithExclusions
// consults rr.exclusions: if the two candidate IDs are listed there,
// the merge is skipped and both host views survive in the output.
// We seed the exclusion in-package (the same way loadOverrides
// populates it from the store) and assert the otherwise-coalescing
// pair stays split.
rr := NewRegistry(nil)
rr.IngestResources([]Resource{
{
ID: "agent-proxmox-pve1",
Type: ResourceTypeAgent,
Name: "pve1",
Status: StatusOnline,
LastSeen: now,
Sources: []DataSource{SourceProxmox},
Identity: ResourceIdentity{Hostnames: []string{"pve1"}},
Proxmox: &ProxmoxData{NodeName: "pve1", ClusterName: "homelab"},
},
{
ID: "agent-runtime-pve1",
Type: ResourceTypeAgent,
Name: "pve1",
Status: StatusOnline,
LastSeen: now,
Sources: []DataSource{SourceAgent},
Identity: ResourceIdentity{Hostnames: []string{"pve1"}},
Agent: &AgentData{AgentID: "agent-machine-pve1", Hostname: "pve1"},
},
})
// Sanity: without an exclusion they coalesce.
if got := rr.ListForPresentation(); len(got) != 1 {
t.Fatalf("precondition: expected pair to coalesce into 1, got %d", len(got))
}
// Register the exclusion between the two IDs and re-run; the
// closure's lookup must now return true and block the merge.
rr.exclusions[exclusionKey("agent-proxmox-pve1", "agent-runtime-pve1")] = struct{}{}
got := rr.ListForPresentation()
if len(got) != 2 {
t.Fatalf("expected exclusion to keep both host views split, got %d: %+v", len(got), got)
}
// The defensive "blank ID" arm of the exclusion closure
// (leftID=="" || rightID == "" -> return false) is only reachable
// when the coalesce step actually compares two host views and one
// has an empty canonical ID. Such a resource cannot enter via the
// public Ingest* APIs (they reject empty IDs), so we insert it
// in-package sharing a hostname with the real agent above; the
// coalesce step then invokes the closure with the blank ID, the
// blank-ID arm fires, and the lookup short-circuits without panic.
rr.resources[""] = &Resource{
ID: "",
Type: ResourceTypeAgent,
Name: "pve1",
Status: StatusOnline,
LastSeen: now,
Sources: []DataSource{SourceProxmox},
Identity: ResourceIdentity{Hostnames: []string{"pve1"}},
Proxmox: &ProxmoxData{NodeName: "pve1", ClusterName: "homelab"},
}
// Must not panic; the blank-ID arm short-circuits the exclusion lookup.
_ = rr.ListForPresentation()
})
t.Run("ListForPresentation/ResultIsIndependentOfRegistryStorage", func(t *testing.T) {
rr := NewRegistry(nil)
rr.IngestResources([]Resource{
{ID: "vm-independent", Type: ResourceTypeVM, Name: "original-name", Status: StatusOnline, LastSeen: now},
})
first := rr.ListForPresentation()
if len(first) != 1 {
t.Fatalf("expected 1 resource, got %d", len(first))
}
origName := first[0].Name
// Mutate the returned slice in place — List() deep-clones each
// resource and the coalesce step operates on those clones, so this
// must not leak back into the registry's internal storage.
first[0].Name = "MUTATED-NAME"
first[0].Status = StatusOffline
second := rr.ListForPresentation()
if len(second) != 1 {
t.Fatalf("expected 1 resource on re-call, got %d", len(second))
}
if second[0].Name != origName {
t.Fatalf("mutation of returned slice leaked into registry: name=%q want %q", second[0].Name, origName)
}
if second[0].Status != StatusOnline {
t.Fatalf("mutation of returned slice leaked into registry: status=%q want %q", second[0].Status, StatusOnline)
}
})
// ---------------------------------------------------------------------------
// MetricsTarget — covers (rr *ResourceRegistry) MetricsTarget and the
// private metricsTargetForResourceLocked helper it delegates to.
// ---------------------------------------------------------------------------
t.Run("MetricsTarget/UnknownIDReturnsNil", func(t *testing.T) {
rr := NewRegistry(nil)
// An empty registry has no resources at all, so any ID misses.
if got := rr.MetricsTarget("does-not-exist"); got != nil {
t.Fatalf("expected nil MetricsTarget for unknown id on empty registry, got %+v", got)
}
// A populated registry must still return nil for an id that is
// simply not present — the resource lookup is the second arm of
// metricsTargetForResourceLocked.
rr.IngestResources([]Resource{
{ID: "vm-real", Type: ResourceTypeVM, Name: "real-vm", Status: StatusOnline, LastSeen: now},
})
if got := rr.MetricsTarget("also-does-not-exist"); got != nil {
t.Fatalf("expected nil MetricsTarget for unknown id, got %+v", got)
}
})
t.Run("MetricsTarget/ResolvedFromSourceMapping", func(t *testing.T) {
// An AppContainer ingested via SourceDocker gets a host-scoped
// docker source mapping, so BuildMetricsTarget succeeds and
// MetricsTarget returns the resolved target rather than falling
// back to the stored MetricsTarget field.
rr := NewRegistry(nil)
rr.IngestRecords(SourceDocker, []IngestRecord{{
SourceID: "host-1/container/web-1",
Resource: Resource{
Type: ResourceTypeAppContainer,
Name: "web",
Status: StatusOnline,
LastSeen: now,
Docker: &DockerData{
HostSourceID: "host-1",
ContainerID: "web-1",
ContainerState: "running",
},
},
}})
resources := rr.ListByType(ResourceTypeAppContainer)
if len(resources) != 1 {
t.Fatalf("expected 1 seeded app-container, got %d", len(resources))
}
got := rr.MetricsTarget(resources[0].ID)
if got == nil {
t.Fatal("expected non-nil MetricsTarget resolved from source mapping")
}
// BuildMetricsTarget prefers the canonical ContainerID for
// app-containers because that is the key the metrics writer uses.
if got.ResourceType != "app-container" {
t.Fatalf("ResourceType = %q, want %q", got.ResourceType, "app-container")
}
if got.ResourceID != "web-1" {
t.Fatalf("ResourceID = %q, want %q", got.ResourceID, "web-1")
}
// Mutating the returned target must not mutate the registry's
// in-flight computation (clone isolation on the success path).
got.ResourceID = "MUTATED"
again := rr.MetricsTarget(resources[0].ID)
if again == nil || again.ResourceID != "web-1" {
t.Fatalf("expected clone isolation on MetricsTarget success path, got %+v", again)
}
})
t.Run("MetricsTarget/FallsBackToStoredMetricsTargetField", func(t *testing.T) {
// When BuildMetricsTarget returns nil (no source mapping), the
// function falls back to cloneMetricsTarget(resource.MetricsTarget).
// We seed directly into rr.resources — the same in-package pattern
// the sibling TestBuildMetricsTargetForRegistryFallsBackToStoredMetricsTarget
// uses — so bySource stays empty for this id.
rr := NewRegistry(nil)
const resourceID = "app-stored-target"
rr.resources[resourceID] = &Resource{
ID: resourceID,
Type: ResourceTypeAppContainer,
Name: "stored",
Status: StatusOnline,
LastSeen: now,
MetricsTarget: &MetricsTarget{ResourceType: "dockerContainer", ResourceID: "nextcloud-web-1"},
}
got := rr.MetricsTarget(resourceID)
if got == nil {
t.Fatal("expected fallback MetricsTarget from stored field")
}
if got.ResourceType != "dockerContainer" || got.ResourceID != "nextcloud-web-1" {
t.Fatalf("fallback MetricsTarget = %+v, want dockerContainer/nextcloud-web-1", got)
}
})
t.Run("MetricsTarget/ExistsButHasNoTargetReturnsNil", func(t *testing.T) {
// A resource that exists, has no source mapping (so BuildMetricsTarget
// returns nil) AND has no stored MetricsTarget field must yield nil.
// This covers the final `return cloneMetricsTarget(resource.MetricsTarget)`
// arm with a nil stored field.
rr := NewRegistry(nil)
const resourceID = "orphan-no-target"
rr.resources[resourceID] = &Resource{
ID: resourceID,
Type: ResourceTypeAppContainer,
Name: "orphan",
Status: StatusOnline,
LastSeen: now,
// MetricsTarget intentionally left nil.
}
if got := rr.MetricsTarget(resourceID); got != nil {
t.Fatalf("expected nil MetricsTarget for resource with no source mapping and no stored target, got %+v", got)
}
})
// ---------------------------------------------------------------------------
// DockerContainers
// ---------------------------------------------------------------------------
t.Run("DockerContainers/EmptyRegistryReturnsEmpty", func(t *testing.T) {
rr := NewRegistry(nil)
got := rr.DockerContainers()
if len(got) != 0 {
t.Fatalf("expected 0 docker container views for empty registry, got %d", len(got))
}
})
t.Run("DockerContainers/PopulatedReturnsViewsSortedByName", func(t *testing.T) {
rr := NewRegistry(nil)
rr.IngestResources([]Resource{
{ID: "dc-zeta", Type: ResourceTypeAppContainer, Name: "zeta-svc", Status: StatusOnline, LastSeen: now, Docker: &DockerData{ContainerID: "cid-zeta"}},
{ID: "dc-alpha", Type: ResourceTypeAppContainer, Name: "alpha-svc", Status: StatusOnline, LastSeen: now, Docker: &DockerData{ContainerID: "cid-alpha"}},
{ID: "dc-mid", Type: ResourceTypeAppContainer, Name: "mid-svc", Status: StatusOnline, LastSeen: now, Docker: &DockerData{ContainerID: "cid-mid"}},
})
got := rr.DockerContainers()
if len(got) != 3 {
t.Fatalf("expected 3 docker container views, got %d", len(got))
}
// rebuildViews sorts cachedDockerContainers by canonical name.
wantNames := []string{"alpha-svc", "mid-svc", "zeta-svc"}
gotNames := make([]string, 0, len(got))
for _, v := range got {
gotNames = append(gotNames, v.Name())
}
assertStringSlice(t, gotNames, wantNames)
// The view wraps the cloned resource, so its container id comes
// straight from the Docker facet.
if got[0].ContainerID() != "cid-alpha" {
t.Fatalf("first view ContainerID = %q, want cid-alpha", got[0].ContainerID())
}
if got[0].ID() != "dc-alpha" {
t.Fatalf("first view ID = %q, want dc-alpha", got[0].ID())
}
})
t.Run("DockerContainers/ReturnsEmptyWhenOnlyOtherKindSeeded", func(t *testing.T) {
// A registry seeded exclusively with K8sNode resources must return
// zero docker container views — never the wrong kind.
rr := NewRegistry(nil)
rr.IngestResources([]Resource{
{ID: "k8s-node-only-1", Type: ResourceTypeK8sNode, Name: "node-a", Status: StatusOnline, LastSeen: now, Kubernetes: &K8sData{NodeName: "node-a"}},
{ID: "k8s-node-only-2", Type: ResourceTypeK8sNode, Name: "node-b", Status: StatusOnline, LastSeen: now, Kubernetes: &K8sData{NodeName: "node-b"}},
})
if got := rr.DockerContainers(); len(got) != 0 {
t.Fatalf("expected 0 docker container views when only K8sNodes seeded, got %d: %+v", len(got), got)
}
// Sanity: the k8s node cache DID build, proving the rebuild ran
// and the docker cache stayed empty by kind, not by accident.
if got := rr.K8sNodes(); len(got) != 2 {
t.Fatalf("expected 2 k8s node views as a control, got %d", len(got))
}
})
// ---------------------------------------------------------------------------
// K8sNodes
// ---------------------------------------------------------------------------
t.Run("K8sNodes/EmptyRegistryReturnsEmpty", func(t *testing.T) {
rr := NewRegistry(nil)
got := rr.K8sNodes()
if len(got) != 0 {
t.Fatalf("expected 0 k8s node views for empty registry, got %d", len(got))
}
})
t.Run("K8sNodes/PopulatedReturnsViewsSortedByName", func(t *testing.T) {
rr := NewRegistry(nil)
rr.IngestResources([]Resource{
{ID: "k8s-n-zeta", Type: ResourceTypeK8sNode, Name: "zeta-node", Status: StatusOnline, LastSeen: now, Kubernetes: &K8sData{NodeUID: "uid-zeta", NodeName: "zeta-node", ClusterName: "cluster-a"}},
{ID: "k8s-n-alpha", Type: ResourceTypeK8sNode, Name: "alpha-node", Status: StatusOnline, LastSeen: now, Kubernetes: &K8sData{NodeUID: "uid-alpha", NodeName: "alpha-node", ClusterName: "cluster-a"}},
{ID: "k8s-n-mid", Type: ResourceTypeK8sNode, Name: "mid-node", Status: StatusOnline, LastSeen: now, Kubernetes: &K8sData{NodeUID: "uid-mid", NodeName: "mid-node", ClusterName: "cluster-b"}},
})
got := rr.K8sNodes()
if len(got) != 3 {
t.Fatalf("expected 3 k8s node views, got %d", len(got))
}
// rebuildViews sorts cachedK8sNodes by canonical name.
wantNames := []string{"alpha-node", "mid-node", "zeta-node"}
gotNames := make([]string, 0, len(got))
for _, v := range got {
gotNames = append(gotNames, v.Name())
}
assertStringSlice(t, gotNames, wantNames)
// View accessors surface the underlying K8sData fields.
if got[0].NodeUID() != "uid-alpha" {
t.Fatalf("first view NodeUID = %q, want uid-alpha", got[0].NodeUID())
}
if got[0].NodeName() != "alpha-node" {
t.Fatalf("first view NodeName = %q, want alpha-node", got[0].NodeName())
}
if got[0].ClusterName() != "cluster-a" {
t.Fatalf("first view ClusterName = %q, want cluster-a", got[0].ClusterName())
}
if got[0].ID() != "k8s-n-alpha" {
t.Fatalf("first view ID = %q, want k8s-n-alpha", got[0].ID())
}
})
t.Run("K8sNodes/ReturnsEmptyWhenOnlyOtherKindSeeded", func(t *testing.T) {
// A registry seeded exclusively with AppContainer resources must
// return zero k8s node views — never the wrong kind.
rr := NewRegistry(nil)
rr.IngestResources([]Resource{
{ID: "app-only-1", Type: ResourceTypeAppContainer, Name: "app-a", Status: StatusOnline, LastSeen: now, Docker: &DockerData{ContainerID: "cid-a"}},
{ID: "app-only-2", Type: ResourceTypeAppContainer, Name: "app-b", Status: StatusOnline, LastSeen: now, Docker: &DockerData{ContainerID: "cid-b"}},
})
if got := rr.K8sNodes(); len(got) != 0 {
t.Fatalf("expected 0 k8s node views when only AppContainers seeded, got %d: %+v", len(got), got)
}
// Control: the docker cache DID build.
if got := rr.DockerContainers(); len(got) != 2 {
t.Fatalf("expected 2 docker container views as a control, got %d", len(got))
}
})
}
+494
View File
@@ -0,0 +1,494 @@
package db
import (
"context"
"database/sql"
"errors"
"strings"
"testing"
"time"
_ "modernc.org/sqlite"
)
// TestBranchcov0722PM raises branch coverage on the Context/BeginTx/setter
// wrappers in slowlog.go that slowlog_test.go does not reach:
//
// - InstrumentedDB: ExecContext, QueryContext, QueryRowContext, BeginTx,
// SetMaxOpenConns, SetMaxIdleConns, SetConnMaxLifetime
// - InstrumentedTx: ExecContext, Query, QueryContext, QueryRow
// - InstrumentedStmt: ExecContext, Query, QueryRow
//
// Each wrapper is driven through both its success arm (asserting the concrete
// return value AND that the per-operation Prometheus histogram sample count
// grew, proving observe() instrumentation actually ran) and its error arm
// (invalid SQL, wrong arg count, or a cancelled context — asserting the
// error is propagated to the caller). Unique db names per subtest isolate
// the global Prometheus counters.
//
// TIMING NOTE: SlowQueryThreshold is 100ms but the spec caps test sleeps at
// 50ms, so no wrapper call here can deterministically exceed the threshold
// without a >50ms sleep. The slow-query branch of observe() is already 100%
// covered by TestSlowQueryDetection / TestQueryTruncation in slowlog_test.go
// (which call observe() directly with a backdated start time). These tests
// therefore cover pass-through + instrumentation-counter behaviour + error
// propagation only; slow-query detection via a wrapper call is intentionally
// not asserted here.
func TestBranchcov0722PM(t *testing.T) {
ctx := context.Background()
// ---------------- InstrumentedDB.ExecContext --------------------------
t.Run("DB_ExecContext", func(t *testing.T) {
const name = "branchcov0722pm_db_execctx"
idb := Wrap(openTestDB(t), name)
before := histogramSampleCount(t, name, "exec")
res, err := idb.ExecContext(ctx,
"CREATE TABLE t (id INTEGER PRIMARY KEY, val TEXT)")
if err != nil {
t.Fatalf("ExecContext CREATE: unexpected error: %v", err)
}
if res == nil {
t.Fatal("ExecContext: expected non-nil Result, got nil")
}
// Error path: invalid SQL. observe() runs unconditionally, so the
// histogram must still advance.
_, err = idb.ExecContext(ctx,
"INSERT INTO no_such_table (val) VALUES (?)", "x")
if err == nil {
t.Fatal("ExecContext: expected error for invalid SQL, got nil")
}
if !strings.Contains(strings.ToLower(err.Error()), "no such table") {
t.Fatalf("ExecContext: expected 'no such table' in error, got %q", err.Error())
}
if got := histogramSampleCount(t, name, "exec") - before; got != 2 {
t.Fatalf("exec histogram delta = %d, want 2", got)
}
})
// ---------------- InstrumentedDB.QueryContext -------------------------
t.Run("DB_QueryContext", func(t *testing.T) {
const name = "branchcov0722pm_db_queryctx"
idb := Wrap(openTestDB(t), name)
setupTable(t, idb.DB, "ctx")
before := histogramSampleCount(t, name, "query")
rows, err := idb.QueryContext(ctx, "SELECT val FROM t")
if err != nil {
t.Fatalf("QueryContext: unexpected error: %v", err)
}
var vals []string
for rows.Next() {
var v string
if err := rows.Scan(&v); err != nil {
rows.Close()
t.Fatalf("Scan: %v", err)
}
vals = append(vals, v)
}
rows.Close()
if len(vals) != 1 || vals[0] != "ctx" {
t.Fatalf("QueryContext: rows = %v, want [ctx]", vals)
}
// Error path.
_, err = idb.QueryContext(ctx, "SELECT * FROM no_such_table")
if err == nil {
t.Fatal("QueryContext: expected error for invalid SQL, got nil")
}
if got := histogramSampleCount(t, name, "query") - before; got != 2 {
t.Fatalf("query histogram delta = %d, want 2", got)
}
})
// ---------------- InstrumentedDB.QueryRowContext ----------------------
t.Run("DB_QueryRowContext", func(t *testing.T) {
const name = "branchcov0722pm_db_queryrowctx"
idb := Wrap(openTestDB(t), name)
setupTable(t, idb.DB, "rowctx")
before := histogramSampleCount(t, name, "query_row")
var val string
if err := idb.QueryRowContext(ctx,
"SELECT val FROM t WHERE id = ?", 1).Scan(&val); err != nil {
t.Fatalf("QueryRowContext.Scan: unexpected error: %v", err)
}
if val != "rowctx" {
t.Fatalf("QueryRowContext: val = %q, want rowctx", val)
}
// Error path: no matching row -> sql.ErrNoRows from Scan.
var missing string
err := idb.QueryRowContext(ctx,
"SELECT val FROM t WHERE id = ?", 9999).Scan(&missing)
if !errors.Is(err, sql.ErrNoRows) {
t.Fatalf("QueryRowContext: expected sql.ErrNoRows, got %v", err)
}
if got := histogramSampleCount(t, name, "query_row") - before; got != 2 {
t.Fatalf("query_row histogram delta = %d, want 2", got)
}
})
// ---------------- InstrumentedDB.BeginTx ------------------------------
t.Run("DB_BeginTx", func(t *testing.T) {
const name = "branchcov0722pm_db_begintx"
idb := Wrap(openTestDB(t), name)
// Success path: returns a fully-populated InstrumentedTx and bumps
// the "begin" histogram.
before := histogramSampleCount(t, name, "begin")
tx, err := idb.BeginTx(ctx, nil)
if err != nil {
t.Fatalf("BeginTx: unexpected error: %v", err)
}
if tx == nil || tx.Tx == nil {
t.Fatal("BeginTx: expected non-nil InstrumentedTx wrapping a *sql.Tx")
}
if tx.name != name {
t.Fatalf("BeginTx: tx.name = %q, want %q", tx.name, name)
}
if got := histogramSampleCount(t, name, "begin") - before; got != 1 {
t.Fatalf("begin histogram delta = %d, want 1", got)
}
if err := tx.Rollback(); err != nil {
t.Fatalf("Rollback: %v", err)
}
// Error path: a pre-cancelled context must surface as
// "context canceled" and a nil tx (the wrapper's
// `if err != nil { return nil, err }` arm).
cctx, cancel := context.WithCancel(ctx)
cancel()
ntx, err := idb.BeginTx(cctx, nil)
if err == nil {
t.Fatal("BeginTx: expected error from cancelled context, got nil")
}
if !errors.Is(err, context.Canceled) {
t.Fatalf("BeginTx: expected context.Canceled, got %v", err)
}
if ntx != nil {
t.Fatalf("BeginTx: expected nil tx on error, got %T", ntx)
}
})
// ---------------- InstrumentedDB setters ------------------------------
t.Run("DB_Setters", func(t *testing.T) {
idb := Wrap(openTestDB(t), "branchcov0722pm_db_setters")
// SetMaxOpenConns: database/sql exposes the configured value via
// Stats().MaxOpenConnections, so assert it round-trips — both the
// bounded value and 0 (unlimited).
idb.SetMaxOpenConns(7)
if got := idb.DB.Stats().MaxOpenConnections; got != 7 {
t.Fatalf("SetMaxOpenConns(7): Stats().MaxOpenConnections = %d, want 7", got)
}
idb.SetMaxOpenConns(0)
if got := idb.DB.Stats().MaxOpenConnections; got != 0 {
t.Fatalf("SetMaxOpenConns(0): Stats().MaxOpenConnections = %d, want 0", got)
}
// SetMaxIdleConns / SetConnMaxLifetime: database/sql exposes no
// getter for the configured value, so the only observable behaviour
// is that the delegation returns without panicking. Exercise both
// with a positive value and with zero to cover the call sites.
idb.SetMaxIdleConns(4)
idb.SetMaxIdleConns(0)
idb.SetConnMaxLifetime(5 * time.Second)
idb.SetConnMaxLifetime(0)
})
// ---------------- InstrumentedTx.ExecContext --------------------------
t.Run("Tx_ExecContext", func(t *testing.T) {
const name = "branchcov0722pm_tx_execctx"
idb := Wrap(openTestDB(t), name)
setupTable(t, idb.DB, "")
tx := beginTx(t, idb)
defer rollbackQuiet(tx)
before := histogramSampleCount(t, name, "tx_exec")
res, err := tx.ExecContext(ctx,
"INSERT INTO t (val) VALUES (?)", "a")
if err != nil {
t.Fatalf("tx.ExecContext: unexpected error: %v", err)
}
if res == nil {
t.Fatal("tx.ExecContext: expected non-nil Result")
}
// Error path.
_, err = tx.ExecContext(ctx,
"INSERT INTO no_such_table (val) VALUES (?)", "a")
if err == nil {
t.Fatal("tx.ExecContext: expected error for invalid SQL, got nil")
}
if got := histogramSampleCount(t, name, "tx_exec") - before; got != 2 {
t.Fatalf("tx_exec histogram delta = %d, want 2", got)
}
})
// ---------------- InstrumentedTx.Query --------------------------------
t.Run("Tx_Query", func(t *testing.T) {
const name = "branchcov0722pm_tx_query"
idb := Wrap(openTestDB(t), name)
setupTable(t, idb.DB, "tq")
tx := beginTx(t, idb)
defer rollbackQuiet(tx)
before := histogramSampleCount(t, name, "tx_query")
rows, err := tx.Query("SELECT val FROM t")
if err != nil {
t.Fatalf("tx.Query: unexpected error: %v", err)
}
var got []string
for rows.Next() {
var v string
if err := rows.Scan(&v); err != nil {
rows.Close()
t.Fatalf("Scan: %v", err)
}
got = append(got, v)
}
rows.Close()
if len(got) != 1 || got[0] != "tq" {
t.Fatalf("tx.Query: rows = %v, want [tq]", got)
}
// Error path.
_, err = tx.Query("SELECT * FROM no_such_table")
if err == nil {
t.Fatal("tx.Query: expected error for invalid SQL, got nil")
}
if delta := histogramSampleCount(t, name, "tx_query") - before; delta != 2 {
t.Fatalf("tx_query histogram delta = %d, want 2", delta)
}
})
// ---------------- InstrumentedTx.QueryContext -------------------------
t.Run("Tx_QueryContext", func(t *testing.T) {
const name = "branchcov0722pm_tx_queryctx"
idb := Wrap(openTestDB(t), name)
setupTable(t, idb.DB, "tqc")
tx := beginTx(t, idb)
defer rollbackQuiet(tx)
before := histogramSampleCount(t, name, "tx_query")
rows, err := tx.QueryContext(ctx, "SELECT val FROM t WHERE id = ?", 1)
if err != nil {
t.Fatalf("tx.QueryContext: unexpected error: %v", err)
}
if !rows.Next() {
rows.Close()
t.Fatal("tx.QueryContext: expected one row")
}
var v string
if err := rows.Scan(&v); err != nil {
rows.Close()
t.Fatalf("Scan: %v", err)
}
rows.Close()
if v != "tqc" {
t.Fatalf("tx.QueryContext: val = %q, want tqc", v)
}
// Error path.
_, err = tx.QueryContext(ctx, "SELECT * FROM no_such_table")
if err == nil {
t.Fatal("tx.QueryContext: expected error for invalid SQL, got nil")
}
if delta := histogramSampleCount(t, name, "tx_query") - before; delta != 2 {
t.Fatalf("tx_query histogram delta = %d, want 2", delta)
}
})
// ---------------- InstrumentedTx.QueryRow -----------------------------
t.Run("Tx_QueryRow", func(t *testing.T) {
const name = "branchcov0722pm_tx_queryrow"
idb := Wrap(openTestDB(t), name)
setupTable(t, idb.DB, "tqr")
tx := beginTx(t, idb)
defer rollbackQuiet(tx)
before := histogramSampleCount(t, name, "tx_query_row")
var v string
if err := tx.QueryRow("SELECT val FROM t WHERE id = ?", 1).Scan(&v); err != nil {
t.Fatalf("tx.QueryRow.Scan: unexpected error: %v", err)
}
if v != "tqr" {
t.Fatalf("tx.QueryRow: val = %q, want tqr", v)
}
// Error path: no matching row -> sql.ErrNoRows.
var missing string
err := tx.QueryRow("SELECT val FROM t WHERE id = ?", 9999).Scan(&missing)
if !errors.Is(err, sql.ErrNoRows) {
t.Fatalf("tx.QueryRow: expected sql.ErrNoRows, got %v", err)
}
if delta := histogramSampleCount(t, name, "tx_query_row") - before; delta != 2 {
t.Fatalf("tx_query_row histogram delta = %d, want 2", delta)
}
})
// ---------------- InstrumentedStmt.ExecContext ------------------------
t.Run("Stmt_ExecContext", func(t *testing.T) {
const name = "branchcov0722pm_stmt_execctx"
idb := Wrap(openTestDB(t), name)
setupTable(t, idb.DB, "")
stmt, err := idb.Prepare("INSERT INTO t (val) VALUES (?)")
if err != nil {
t.Fatalf("Prepare: %v", err)
}
defer stmt.Close()
before := histogramSampleCount(t, name, "stmt_exec")
res, err := stmt.ExecContext(ctx, "x")
if err != nil {
t.Fatalf("stmt.ExecContext: unexpected error: %v", err)
}
if res == nil {
t.Fatal("stmt.ExecContext: expected non-nil Result")
}
// Error path: the statement was prepared with one placeholder, so
// calling it with no args is rejected by the driver at exec time.
_, err = stmt.ExecContext(ctx)
if err == nil {
t.Fatal("stmt.ExecContext: expected error for wrong arg count, got nil")
}
if delta := histogramSampleCount(t, name, "stmt_exec") - before; delta != 2 {
t.Fatalf("stmt_exec histogram delta = %d, want 2", delta)
}
})
// ---------------- InstrumentedStmt.Query ------------------------------
t.Run("Stmt_Query", func(t *testing.T) {
const name = "branchcov0722pm_stmt_query"
idb := Wrap(openTestDB(t), name)
setupTable(t, idb.DB, "sq")
stmt, err := idb.Prepare("SELECT val FROM t WHERE id = ?")
if err != nil {
t.Fatalf("Prepare: %v", err)
}
defer stmt.Close()
before := histogramSampleCount(t, name, "stmt_query")
rows, err := stmt.Query(1)
if err != nil {
t.Fatalf("stmt.Query: unexpected error: %v", err)
}
if !rows.Next() {
rows.Close()
t.Fatal("stmt.Query: expected one row")
}
var v string
if err := rows.Scan(&v); err != nil {
rows.Close()
t.Fatalf("Scan: %v", err)
}
rows.Close()
if v != "sq" {
t.Fatalf("stmt.Query: val = %q, want sq", v)
}
// Error path: wrong arg count (prepared statement needs 1, given 0).
r2, err := stmt.Query()
if err == nil {
if r2 != nil {
r2.Close()
}
t.Fatal("stmt.Query: expected error for wrong arg count, got nil")
}
if r2 != nil {
t.Fatalf("stmt.Query: expected nil rows on error, got non-nil")
}
if delta := histogramSampleCount(t, name, "stmt_query") - before; delta != 2 {
t.Fatalf("stmt_query histogram delta = %d, want 2", delta)
}
})
// ---------------- InstrumentedStmt.QueryRow ---------------------------
t.Run("Stmt_QueryRow", func(t *testing.T) {
const name = "branchcov0722pm_stmt_queryrow"
idb := Wrap(openTestDB(t), name)
setupTable(t, idb.DB, "sqr")
stmt, err := idb.Prepare("SELECT val FROM t WHERE id = ?")
if err != nil {
t.Fatalf("Prepare: %v", err)
}
defer stmt.Close()
before := histogramSampleCount(t, name, "stmt_query_row")
// Success: Scan a real value.
var v string
if err := stmt.QueryRow(1).Scan(&v); err != nil {
t.Fatalf("stmt.QueryRow.Scan: unexpected error: %v", err)
}
if v != "sqr" {
t.Fatalf("stmt.QueryRow: val = %q, want sqr", v)
}
// Error path: no matching row -> sql.ErrNoRows from Scan. QueryRow
// has no error-return arm itself; this is its real failure mode.
var missing string
err = stmt.QueryRow(9999).Scan(&missing)
if !errors.Is(err, sql.ErrNoRows) {
t.Fatalf("stmt.QueryRow: expected sql.ErrNoRows, got %v", err)
}
if delta := histogramSampleCount(t, name, "stmt_query_row") - before; delta != 2 {
t.Fatalf("stmt_query_row histogram delta = %d, want 2", delta)
}
})
}
// setupTable creates table t(id INTEGER PRIMARY KEY, val TEXT) and, when val
// is non-empty, inserts one row with that val — the common precondition for
// the query/queryrow subtests.
func setupTable(t *testing.T, db *sql.DB, val string) {
t.Helper()
if _, err := db.Exec("CREATE TABLE t (id INTEGER PRIMARY KEY, val TEXT)"); err != nil {
t.Fatalf("setup CREATE TABLE: %v", err)
}
if val != "" {
if _, err := db.Exec("INSERT INTO t (val) VALUES (?)", val); err != nil {
t.Fatalf("setup INSERT: %v", err)
}
}
}
// beginTx starts an InstrumentedTx (via the already-covered Begin) for the
// Tx/Stmt subtests. Kept separate from the BeginTx target test so those
// subtests do not depend on the function under test.
func beginTx(t *testing.T, idb *InstrumentedDB) *InstrumentedTx {
t.Helper()
tx, err := idb.Begin()
if err != nil {
t.Fatalf("Begin: %v", err)
}
return tx
}
// rollbackQuiet rolls the tx back, ignoring the (benign) error that arises
// when the subtest already committed/rolled back. Used only in deferred
// cleanup; real assertions go through the wrappers under test.
func rollbackQuiet(tx *InstrumentedTx) {
_ = tx.Rollback()
}