Add Go branch-coverage tests for pure helpers across six packages

Test-only wave, contract-neutral. New *_branchcov0719pm_test.go files cover
previously-uncovered pure value-in/value-out helpers, each verified to move
its target functions from 0% to full coverage:

- internal/agentcontext: formatKubernetesServicePorts (empty/single/cap/overflow
  arms) and addMetricFact (nil-metric, percent/value/ratio arms) now 100%.
- pkg/reporting: reportLogoTypeFromPath 0->100, reportLogoTypeFromData 28.6->100,
  scaledLogoSize 70->90 (extension and aspect branches).
- internal/cloudcp/email: RenderMagicLinkEmail 0->80 (render success path; the
  compile-time template-error arm is unreachable and left uncovered).
- internal/recovery: recoveryDetailString (nil map, missing key, non-string,
  string arms) and recoveryPointObservedAt 40->100.
- internal/ai/tools: ErrStrictResolution/ErrRoutingMismatch ToToolResponse 100.
- internal/ai/providers: every NormalizeCollections receiver 0->100.

No source or existing test modified.
This commit is contained in:
rcourtman
2026-07-19 14:09:26 +01:00
parent 260073c27e
commit ff93d99fa1
6 changed files with 1180 additions and 0 deletions
@@ -0,0 +1,284 @@
package agentcontext
import (
"testing"
"time"
unified "github.com/rcourtman/pulse-go-rewrite/internal/unifiedresources"
)
// TestFormatKubernetesServicePortsBranchCov exercises every branch of
// formatKubernetesServicePorts: the empty-input early return, the regular
// formatting arm in the bounded loop, and the truncation ("N more") arm
// triggered once the loop index reaches the internal cap of 5.
func TestFormatKubernetesServicePortsBranchCov(t *testing.T) {
five := []unified.K8sServicePort{
{Port: 1, TargetPort: "a", Protocol: "TCP"},
{Port: 2, TargetPort: "b", Protocol: "TCP"},
{Port: 3, TargetPort: "c", Protocol: "TCP"},
{Port: 4, TargetPort: "d", Protocol: "TCP"},
{Port: 5, TargetPort: "e", Protocol: "TCP"},
}
tests := []struct {
name string
ports []unified.K8sServicePort
want string
}{
{
name: "nil slice returns empty string",
ports: nil,
want: "",
},
{
name: "empty slice returns empty string",
ports: []unified.K8sServicePort{},
want: "",
},
{
name: "single port formats as port:targetPort/protocol",
ports: []unified.K8sServicePort{
{Name: "http", Port: 80, TargetPort: "http", Protocol: "TCP", NodePort: 30080},
},
want: "80:http/TCP",
},
{
name: "two ports joined with comma-space",
ports: []unified.K8sServicePort{
{Port: 80, TargetPort: "http", Protocol: "TCP"},
{Port: 443, TargetPort: "https", Protocol: "TCP"},
},
want: "80:http/TCP, 443:https/TCP",
},
{
name: "exactly five ports renders every entry without truncation",
ports: five,
want: "1:a/TCP, 2:b/TCP, 3:c/TCP, 4:d/TCP, 5:e/TCP",
},
{
name: "six ports truncates to five plus one more suffix",
ports: append(append([]unified.K8sServicePort{}, five...),
unified.K8sServicePort{Port: 6, TargetPort: "f", Protocol: "TCP"},
),
want: "1:a/TCP, 2:b/TCP, 3:c/TCP, 4:d/TCP, 5:e/TCP, 1 more",
},
{
name: "eight ports truncates to five plus three more suffix",
ports: append(append([]unified.K8sServicePort{}, five...),
unified.K8sServicePort{Port: 6, TargetPort: "f", Protocol: "TCP"},
unified.K8sServicePort{Port: 7, TargetPort: "g", Protocol: "TCP"},
unified.K8sServicePort{Port: 8, TargetPort: "h", Protocol: "TCP"},
),
want: "1:a/TCP, 2:b/TCP, 3:c/TCP, 4:d/TCP, 5:e/TCP, 3 more",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got := formatKubernetesServicePorts(tc.ports)
if got != tc.want {
t.Fatalf("formatKubernetesServicePorts(%+v) = %q, want %q", tc.ports, got, tc.want)
}
})
}
}
// TestAddMetricFactBranchCov exercises every branch of addMetricFact and the
// downstream empty-value skip in addAgentContextFact: the nil-metric early
// return, each arm of the metric switch (Percent, Value, Used/Total), the
// empty-value skip when no arm matches, and propagation of observedAt
// through both its nil and non-nil forms.
func TestAddMetricFactBranchCov(t *testing.T) {
observedAt := time.Date(2026, 7, 19, 12, 0, 0, 0, time.UTC)
t.Run("nil_metric_does_not_append", func(t *testing.T) {
facts := []Fact{{Label: "preexisting", Value: "v"}}
before := make([]Fact, len(facts))
copy(before, facts)
addMetricFact(&facts, "CPU", nil, &observedAt)
if len(facts) != len(before) {
t.Fatalf("nil metric appended: before=%d after=%d (%+v)", len(before), len(facts), facts)
}
if facts[0].Label != "preexisting" || facts[0].Value != "v" {
t.Fatalf("existing slice mutated by nil metric: %+v", facts)
}
})
t.Run("nil_slice_unchanged_when_metric_nil", func(t *testing.T) {
var facts []Fact
addMetricFact(&facts, "CPU", nil, &observedAt)
if facts != nil {
t.Fatalf("nil slice promoted to non-nil by nil metric: %+v", facts)
}
})
t.Run("percent_arm_appends_percentage_with_non_nil_observedAt", func(t *testing.T) {
facts := []Fact{}
metric := &unified.MetricValue{Percent: 42.5}
addMetricFact(&facts, "CPU", metric, &observedAt)
if len(facts) != 1 {
t.Fatalf("expected exactly 1 fact, got %d: %+v", len(facts), facts)
}
fact := facts[0]
if fact.Label != "CPU" {
t.Fatalf("Label = %q, want CPU", fact.Label)
}
if fact.Value != "42.5%" {
t.Fatalf("Value = %q, want 42.5%%", fact.Value)
}
if fact.Source != agentContextSourceUnifiedResource {
t.Fatalf("Source = %q, want %q", fact.Source, agentContextSourceUnifiedResource)
}
if fact.TrustTier != agentContextTrustRuntimeObserved {
t.Fatalf("TrustTier = %q, want %q", fact.TrustTier, agentContextTrustRuntimeObserved)
}
if fact.ObservedAt == nil || !fact.ObservedAt.Equal(observedAt) {
t.Fatalf("ObservedAt = %v, want %v", fact.ObservedAt, observedAt)
}
if fact.Redacted {
t.Fatalf("Redacted = true, want false")
}
})
t.Run("value_arm_appends_value_unit_with_nil_observedAt", func(t *testing.T) {
facts := []Fact{}
metric := &unified.MetricValue{Value: 1.5, Unit: "GB"}
addMetricFact(&facts, "Memory", metric, nil)
if len(facts) != 1 {
t.Fatalf("expected exactly 1 fact, got %d: %+v", len(facts), facts)
}
fact := facts[0]
if fact.Label != "Memory" {
t.Fatalf("Label = %q, want Memory", fact.Label)
}
if fact.Value != "1.5 GB" {
t.Fatalf("Value = %q, want \"1.5 GB\"", fact.Value)
}
if fact.ObservedAt != nil {
t.Fatalf("ObservedAt = %v, want nil when caller passes nil", fact.ObservedAt)
}
})
t.Run("value_arm_trims_surrounding_whitespace_in_unit", func(t *testing.T) {
facts := []Fact{}
metric := &unified.MetricValue{Value: 4.0, Unit: " MiB "}
addMetricFact(&facts, "Memory", metric, &observedAt)
if len(facts) != 1 {
t.Fatalf("expected exactly 1 fact, got %d: %+v", len(facts), facts)
}
if got := facts[0].Value; got != "4.0 MiB" {
t.Fatalf("Value = %q, want \"4.0 MiB\" (unit trimmed)", got)
}
})
t.Run("used_total_arm_appends_ratio_when_total_positive", func(t *testing.T) {
facts := []Fact{}
used := int64(100)
total := int64(200)
metric := &unified.MetricValue{Used: &used, Total: &total}
addMetricFact(&facts, "Disk", metric, &observedAt)
if len(facts) != 1 {
t.Fatalf("expected exactly 1 fact, got %d: %+v", len(facts), facts)
}
if got := facts[0].Value; got != "100/200" {
t.Fatalf("Value = %q, want \"100/200\"", got)
}
if got := facts[0].Label; got != "Disk" {
t.Fatalf("Label = %q, want Disk", got)
}
})
t.Run("used_total_arm_skipped_when_total_zero", func(t *testing.T) {
facts := []Fact{}
used := int64(50)
total := int64(0)
metric := &unified.MetricValue{Used: &used, Total: &total}
addMetricFact(&facts, "Disk", metric, &observedAt)
if len(facts) != 0 {
t.Fatalf("expected 0 facts when Total<=0, got %d: %+v", len(facts), facts)
}
})
t.Run("used_total_arm_skipped_when_total_negative", func(t *testing.T) {
facts := []Fact{}
used := int64(50)
total := int64(-1)
metric := &unified.MetricValue{Used: &used, Total: &total}
addMetricFact(&facts, "Disk", metric, &observedAt)
if len(facts) != 0 {
t.Fatalf("expected 0 facts when Total<0, got %d: %+v", len(facts), facts)
}
})
t.Run("used_total_arm_skipped_when_used_nil", func(t *testing.T) {
facts := []Fact{}
total := int64(100)
metric := &unified.MetricValue{Total: &total}
addMetricFact(&facts, "Disk", metric, &observedAt)
if len(facts) != 0 {
t.Fatalf("expected 0 facts when Used is nil, got %d: %+v", len(facts), facts)
}
})
t.Run("zero_metric_produces_empty_value_and_skips_append", func(t *testing.T) {
facts := []Fact{}
metric := &unified.MetricValue{}
addMetricFact(&facts, "Network in", metric, &observedAt)
if len(facts) != 0 {
t.Fatalf("expected 0 facts for zero metric, got %d: %+v", len(facts), facts)
}
})
t.Run("percent_arm_takes_precedence_over_value_and_used_total", func(t *testing.T) {
facts := []Fact{}
used := int64(100)
total := int64(200)
metric := &unified.MetricValue{Percent: 75.0, Value: 9.9, Unit: "GB", Used: &used, Total: &total}
addMetricFact(&facts, "CPU", metric, &observedAt)
if len(facts) != 1 {
t.Fatalf("expected exactly 1 fact, got %d: %+v", len(facts), facts)
}
if got := facts[0].Value; got != "75.0%" {
t.Fatalf("Value = %q, want \"75.0%%\" (Percent wins)", got)
}
})
t.Run("value_arm_takes_precedence_over_used_total", func(t *testing.T) {
facts := []Fact{}
used := int64(100)
total := int64(200)
metric := &unified.MetricValue{Value: 5.0, Unit: "GiB", Used: &used, Total: &total}
addMetricFact(&facts, "Memory", metric, &observedAt)
if len(facts) != 1 {
t.Fatalf("expected exactly 1 fact, got %d: %+v", len(facts), facts)
}
if got := facts[0].Value; got != "5.0 GiB" {
t.Fatalf("Value = %q, want \"5.0 GiB\" (Value wins over Used/Total)", got)
}
})
t.Run("negative_value_takes_value_arm", func(t *testing.T) {
facts := []Fact{}
metric := &unified.MetricValue{Value: -3.5, Unit: "MB/s"}
addMetricFact(&facts, "Network out", metric, &observedAt)
if len(facts) != 1 {
t.Fatalf("expected exactly 1 fact for negative Value, got %d: %+v", len(facts), facts)
}
if got := facts[0].Value; got != "-3.5 MB/s" {
t.Fatalf("Value = %q, want \"-3.5 MB/s\"", got)
}
})
}
@@ -0,0 +1,117 @@
package providers
import (
"reflect"
"testing"
"github.com/stretchr/testify/assert"
)
// TestToolProgressEventNormalizeCollections_BranchCov0719pm exercises every
// branch of (ToolProgressEvent).NormalizeCollections (provider.go:211):
// - the `if e.Input == nil` true arm, which substitutes a non-nil empty map
// - the implicit else, where a populated Input map is preserved unchanged
//
// Both cases also assert that the non-Input scalar/slice fields are passed
// through verbatim so the normalizer is confirmed to be a no-op outside of the
// nil-collection fix-up.
func TestToolProgressEventNormalizeCollections_BranchCov0719pm(t *testing.T) {
tests := []struct {
name string
event ToolProgressEvent
// wantInputIsNil reports the expected nil-ness of the returned Input
// map. It is always false after normalization; documented per-case for
// clarity.
wantInputIsNil bool
// wantInput reports the expected contents of the returned Input map
// (compared with reflect.DeepEqual so an empty non-nil map is
// distinguishable from a nil one).
wantInput map[string]interface{}
}{
{
name: "nil input map is replaced with non-nil empty map",
event: ToolProgressEvent{
ID: "tool_42",
Name: "get_time",
Input: nil,
RawInput: `{"tz":"UTC"}`,
Phase: "streaming",
Message: "Receiving tool input.",
},
wantInputIsNil: false,
wantInput: map[string]interface{}{},
},
{
name: "populated input map is preserved unchanged",
event: ToolProgressEvent{
ID: "tool_43",
Name: "search",
Input: map[string]interface{}{"q": "pulse", "limit": float64(10)},
Phase: "ready",
},
wantInputIsNil: false,
wantInput: map[string]interface{}{"q": "pulse", "limit": float64(10)},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Snapshot the input so we can prove the scalar/slice fields are
// preserved on the populated case. Input is asserted separately.
inID, inName := tt.event.ID, tt.event.Name
inRaw, inPhase, inMsg := tt.event.RawInput, tt.event.Phase, tt.event.Message
got := tt.event.NormalizeCollections()
// Input map: must never be nil after normalization, and contents
// must equal the expected map (empty for the nil case, the
// original entries for the populated case).
if got.Input == nil {
t.Fatalf("NormalizeCollections returned nil Input map; want non-nil")
}
if tt.wantInputIsNil {
t.Fatalf("test data error: wantInputIsNil must be false after normalization")
}
if !reflect.DeepEqual(got.Input, tt.wantInput) {
t.Fatalf("NormalizeCollections Input = %#v, want %#v", got.Input, tt.wantInput)
}
// On the populated case, the original map object identity may be
// preserved or a new map allocated with the same contents — the
// contract is contents-equality, asserted above via DeepEqual.
// Here we additionally assert the non-nil invariant explicitly so
// a future refactor that drops the fix-up fails loudly.
assert.NotNil(t, got.Input, "normalized Input map must be non-nil")
// All other fields must round-trip unchanged.
assert.Equal(t, inID, got.ID, "ID must be preserved")
assert.Equal(t, inName, got.Name, "Name must be preserved")
assert.Equal(t, inRaw, got.RawInput, "RawInput must be preserved")
assert.Equal(t, inPhase, got.Phase, "Phase must be preserved")
assert.Equal(t, inMsg, got.Message, "Message must be preserved")
})
}
}
// TestToolProgressEventNormalizeCollections_ZeroValue0719pm covers the literal
// zero value of ToolProgressEvent, which is the most common shape that reaches
// NormalizeCollections when a provider constructs an event before populating
// any field. Every field — including Input — starts nil and Input must end up a
// non-nil empty map.
func TestToolProgressEventNormalizeCollections_ZeroValue0719pm(t *testing.T) {
var zero ToolProgressEvent
got := zero.NormalizeCollections()
if got.Input == nil {
t.Fatalf("NormalizeCollections on zero-value event left Input nil; want non-nil empty map")
}
if len(got.Input) != 0 {
t.Fatalf("NormalizeCollections on zero-value event Input len = %d, want 0", len(got.Input))
}
// Zero scalars are unchanged.
assert.Equal(t, "", got.ID, "ID must remain zero value")
assert.Equal(t, "", got.Name, "Name must remain zero value")
assert.Equal(t, "", got.RawInput, "RawInput must remain zero value")
assert.Equal(t, "", got.Phase, "Phase must remain zero value")
assert.Equal(t, "", got.Message, "Message must remain zero value")
}
@@ -0,0 +1,218 @@
package tools
import (
"testing"
)
// This test file raises branch/function coverage on the two pure
// ToToolResponse() methods declared in tools_query.go:
// - (*ErrStrictResolution).ToToolResponse() (tools_query.go:48)
// - (*ErrRoutingMismatch).ToToolResponse() (tools_query.go:93)
//
// It deliberately exercises both arms of the conditional inside
// ErrRoutingMismatch.ToToolResponse() (MoreSpecificIDs populated vs empty),
// since the existing strict_resolution_test.go only hits the empty branch.
// TestErrStrictResolutionToToolResponse_BranchCov0719pm drives the single
// return path of (*ErrStrictResolution).ToToolResponse() and asserts the
// returned ToolResponse carries the STRICT_RESOLUTION code, the original
// human-readable message, the blocked flag, and the exact metadata map the
// policy contract publishes (resource_id / action / policy_boundary).
func TestErrStrictResolutionToToolResponse_BranchCov0719pm(t *testing.T) {
const (
wantResourceID = "vm:101"
wantAction = "restart"
wantMessage = "resource 'vm:101' has not been discovered; discovery is required before restart"
wantPolicy = "Resource discovery is required before resource-specific actions."
)
err := &ErrStrictResolution{
ResourceID: wantResourceID,
Action: wantAction,
Message: wantMessage,
}
resp := err.ToToolResponse()
if resp.OK {
t.Fatalf("ToToolResponse().OK = true; blocked operations must report OK=false")
}
if resp.Error == nil {
t.Fatalf("ToToolResponse().Error = nil; STRICT_RESOLUTION must populate Error envelope")
}
if resp.Error.Code != ErrCodeStrictResolution {
t.Fatalf("Error.Code = %q, want %q", resp.Error.Code, ErrCodeStrictResolution)
}
if !resp.Error.Blocked {
t.Errorf("Error.Blocked = false, want true (STRICT_RESOLUTION is a policy block)")
}
if resp.Error.Failed {
t.Errorf("Error.Failed = true, want false (block, not execution failure)")
}
if resp.Error.Message != wantMessage {
t.Errorf("Error.Message = %q, want %q", resp.Error.Message, wantMessage)
}
details := resp.Error.Details
if details == nil {
t.Fatalf("Error.Details = nil; expected populated policy metadata")
}
if got, ok := details["resource_id"].(string); !ok || got != wantResourceID {
t.Errorf("Details[resource_id] = %v, want %q", details["resource_id"], wantResourceID)
}
if got, ok := details["action"].(string); !ok || got != wantAction {
t.Errorf("Details[action] = %v, want %q", details["action"], wantAction)
}
if got, ok := details["policy_boundary"].(string); !ok || got != wantPolicy {
t.Errorf("Details[policy_boundary] = %v, want %q", details["policy_boundary"], wantPolicy)
}
if _, present := details["more_specific_resources"]; present {
t.Errorf("Details unexpectedly carries more_specific_resources (strict-resolution payload)")
}
}
// TestErrRoutingMismatchToToolResponse_BranchCov0719pm is table-driven over
// both branches of the `len(e.MoreSpecificIDs) > 0` conditional in
// (*ErrRoutingMismatch).ToToolResponse(): the canonical-ID arm (which adds
// the more_specific_resource_ids fact) and the empty arm (which omits it).
// Each case asserts the ROUTING_MISMATCH code, the blocked flag, the echoed
// message, and the precise set of metadata keys/values the function emits.
func TestErrRoutingMismatchToToolResponse_BranchCov0719pm(t *testing.T) {
const wantPolicy = "A more specific child resource exists on the requested host; choose the intended resource before retrying a scoped action."
type expect struct {
name string
targetHost string
moreSpecificResources []string
moreSpecificIDs []string
childKinds []string
message string
wantIDsKey bool // true → expects more_specific_resource_ids
wantIDsValue []string
wantResourcesAssertionType bool // true → assert Details[more_specific_resources] type-asserts as []string
}
cases := []expect{
{
name: "with_canonical_ids_populates_more_specific_resource_ids",
targetHost: "pve-node",
moreSpecificResources: []string{"homepage-docker", "jellyfin"},
moreSpecificIDs: []string{"system-container:proxmox:141", "vm:proxmox:100"},
childKinds: []string{"system-container", "vm"},
message: "target_host 'pve-node' has more specific children: [homepage-docker jellyfin]",
wantIDsKey: true,
wantIDsValue: []string{"system-container:proxmox:141", "vm:proxmox:100"},
wantResourcesAssertionType: true,
},
{
name: "without_canonical_ids_omits_more_specific_resource_ids",
targetHost: "pve-node",
moreSpecificResources: []string{"homepage-docker"},
moreSpecificIDs: nil, // forces the else branch
childKinds: nil,
message: "target_host 'pve-node' has more specific children: [homepage-docker]",
wantIDsKey: false,
wantResourcesAssertionType: true,
},
{
name: "empty_canonical_ids_slice_also_takes_else_branch",
targetHost: "prox97",
moreSpecificResources: []string{},
moreSpecificIDs: []string{},
childKinds: []string{},
message: "target_host 'prox97' has more specific children: []",
wantIDsKey: false,
wantResourcesAssertionType: true,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
err := &ErrRoutingMismatch{
TargetHost: tc.targetHost,
MoreSpecificResources: tc.moreSpecificResources,
MoreSpecificIDs: tc.moreSpecificIDs,
ChildKinds: tc.childKinds,
Message: tc.message,
}
resp := err.ToToolResponse()
if resp.OK {
t.Fatalf("ToToolResponse().OK = true; routing mismatch must report OK=false")
}
if resp.Error == nil {
t.Fatalf("ToToolResponse().Error = nil; ROUTING_MISMATCH must populate Error envelope")
}
if resp.Error.Code != ErrCodeRoutingMismatch {
t.Fatalf("Error.Code = %q, want %q", resp.Error.Code, ErrCodeRoutingMismatch)
}
if !resp.Error.Blocked {
t.Errorf("Error.Blocked = false, want true (ROUTING_MISMATCH is a policy block)")
}
if resp.Error.Failed {
t.Errorf("Error.Failed = true, want false (block, not execution failure)")
}
if resp.Error.Message != tc.message {
t.Errorf("Error.Message = %q, want %q", resp.Error.Message, tc.message)
}
details := resp.Error.Details
if details == nil {
t.Fatalf("Error.Details = nil; expected populated routing metadata")
}
if got, ok := details["target_host"].(string); !ok || got != tc.targetHost {
t.Errorf("Details[target_host] = %v, want %q", details["target_host"], tc.targetHost)
}
if tc.wantResourcesAssertionType {
resources, ok := details["more_specific_resources"].([]string)
if !ok {
t.Fatalf("Details[more_specific_resources] type = %T, want []string", details["more_specific_resources"])
}
if len(resources) != len(tc.moreSpecificResources) {
t.Fatalf("Details[more_specific_resources] = %v, want %v", resources, tc.moreSpecificResources)
}
for i := range resources {
if resources[i] != tc.moreSpecificResources[i] {
t.Errorf("Details[more_specific_resources][%d] = %q, want %q", i, resources[i], tc.moreSpecificResources[i])
}
}
}
idsValue, idsPresent := details["more_specific_resource_ids"]
if tc.wantIDsKey {
if !idsPresent {
t.Fatalf("Details[more_specific_resource_ids] missing; expected to be present when MoreSpecificIDs is non-empty")
}
idsSlice, ok := idsValue.([]string)
if !ok {
t.Fatalf("Details[more_specific_resource_ids] type = %T, want []string", idsValue)
}
if len(idsSlice) != len(tc.wantIDsValue) {
t.Fatalf("Details[more_specific_resource_ids] = %v, want %v", idsSlice, tc.wantIDsValue)
}
for i := range idsSlice {
if idsSlice[i] != tc.wantIDsValue[i] {
t.Errorf("Details[more_specific_resource_ids][%d] = %q, want %q", i, idsSlice[i], tc.wantIDsValue[i])
}
}
} else {
if idsPresent {
t.Errorf("Details[more_specific_resource_ids] = %v; expected to be absent when MoreSpecificIDs is empty", idsValue)
}
}
if got, ok := details["policy_boundary"].(string); !ok || got != wantPolicy {
t.Errorf("Details[policy_boundary] = %v, want %q", details["policy_boundary"], wantPolicy)
}
if _, present := details["auto_recoverable"]; present {
t.Errorf("Details[auto_recoverable] present; routing mismatch must not surface auto-recovery hint")
}
})
}
}
@@ -0,0 +1,92 @@
package email
import (
"strings"
"testing"
)
// TestRenderMagicLinkEmail covers RenderMagicLinkEmail in templates.go.
//
// The function's only error arm comes from magicLinkTemplate.Execute, which is
// unreachable at runtime: magicLinkTemplate is a package-level template.Must of
// a constant parse string, so Execute cannot fail for the single string field
// MagicLinkData.MagicLinkURL. That arm is therefore not fabricated here.
func TestRenderMagicLinkEmail(t *testing.T) {
t.Run("populated URL substituted into html and text", func(t *testing.T) {
const url = "https://pulse.example.com/auth/magic?token=abc123XYZ"
htmlBody, textBody, err := RenderMagicLinkEmail(MagicLinkData{MagicLinkURL: url})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if htmlBody == "" {
t.Fatal("expected non-empty html body")
}
if textBody == "" {
t.Fatal("expected non-empty text body")
}
if !strings.Contains(htmlBody, url) {
t.Errorf("html body does not contain magic link URL;\nwant substring: %s\ngot: %s", url, htmlBody)
}
if !strings.Contains(htmlBody, "Sign in to Pulse") {
t.Errorf("html body does not contain expected heading text;\ngot: %s", htmlBody)
}
if !strings.Contains(htmlBody, `href="`+url+`"`) {
t.Errorf("html body does not contain href with the URL;\ngot: %s", htmlBody)
}
if !strings.Contains(textBody, url) {
t.Errorf("text body does not contain magic link URL;\nwant substring: %s\ngot: %s", url, textBody)
}
if !strings.Contains(textBody, "Sign in to Pulse") {
t.Errorf("text body does not contain expected greeting;\ngot: %s", textBody)
}
if !strings.Contains(textBody, "expires in 15 minutes") {
t.Errorf("text body does not contain expiry note;\ngot: %s", textBody)
}
})
t.Run("html template escapes ampersands in URL, text body does not", func(t *testing.T) {
const rawURL = "https://pulse.example.com/auth/magic?token=abc&uid=42"
htmlBody, textBody, err := RenderMagicLinkEmail(MagicLinkData{MagicLinkURL: rawURL})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
const escapedURL = "https://pulse.example.com/auth/magic?token=abc&amp;uid=42"
if !strings.Contains(htmlBody, escapedURL) {
t.Errorf("html body should contain HTML-escaped URL %q;\ngot: %s", escapedURL, htmlBody)
}
if strings.Contains(htmlBody, `href="`+rawURL+`"`) {
t.Errorf("html body must not contain the raw unescaped URL inside href;\ngot: %s", htmlBody)
}
if !strings.Contains(textBody, rawURL) {
t.Errorf("text body should contain the raw URL %q (no HTML escaping);\ngot: %s", rawURL, textBody)
}
if strings.Contains(textBody, escapedURL) {
t.Errorf("text body must not contain HTML-escaped ampersands;\ngot: %s", textBody)
}
})
t.Run("empty URL still renders without error", func(t *testing.T) {
htmlBody, textBody, err := RenderMagicLinkEmail(MagicLinkData{MagicLinkURL: ""})
if err != nil {
t.Fatalf("unexpected error for empty URL: %v", err)
}
if htmlBody == "" {
t.Fatal("expected non-empty html body even with empty URL")
}
if textBody == "" {
t.Fatal("expected non-empty text body even with empty URL")
}
if !strings.Contains(htmlBody, "Sign in to Pulse") {
t.Errorf("html body missing heading for empty URL;\ngot: %s", htmlBody)
}
if !strings.Contains(textBody, "Sign in to Pulse") {
t.Errorf("text body missing greeting for empty URL;\ngot: %s", textBody)
}
})
}
@@ -0,0 +1,185 @@
package recovery
import (
"testing"
"time"
)
func TestRecoveryDetailStringBranch0719pm(t *testing.T) {
t.Parallel()
tests := []struct {
name string
point RecoveryPoint
key string
want string
}{
{
name: "nil details returns empty",
point: RecoveryPoint{},
key: "instance",
want: "",
},
{
name: "populated details but key absent returns empty",
point: RecoveryPoint{
Details: map[string]any{
"instance": "pve-main",
},
},
key: "missing-key",
want: "",
},
{
name: "key present but value is an int returns empty",
point: RecoveryPoint{
Details: map[string]any{
"vmid": 100,
},
},
key: "vmid",
want: "",
},
{
name: "key present but value is nil returns empty",
point: RecoveryPoint{
Details: map[string]any{
"connectionId": nil,
},
},
key: "connectionId",
want: "",
},
{
name: "key present with bare string value returns value",
point: RecoveryPoint{
Details: map[string]any{
"instance": "pve-main",
},
},
key: "instance",
want: "pve-main",
},
{
name: "key present with surrounding whitespace returns trimmed value",
point: RecoveryPoint{
Details: map[string]any{
"k8sClusterId": " prod-eks ",
},
},
key: "k8sClusterId",
want: "prod-eks",
},
{
name: "key present with whitespace-only string returns empty",
point: RecoveryPoint{
Details: map[string]any{
"instance": " ",
},
},
key: "instance",
want: "",
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()
got := recoveryDetailString(test.point, test.key)
if got != test.want {
t.Fatalf("recoveryDetailString(...) = %q, want %q", got, test.want)
}
})
}
}
func TestRecoveryPointObservedAtBranch0719pm(t *testing.T) {
t.Parallel()
completedAt := time.Date(2026, 7, 19, 10, 0, 0, 0, time.UTC)
startedAt := time.Date(2026, 7, 19, 9, 0, 0, 0, time.UTC)
// Construct a non-UTC time so we can assert the result is normalised to UTC.
completedNonUTC := time.Date(
2026, 7, 19, 8, 0, 0, 0, time.FixedZone("PVE", -2*3600),
)
zeroTime := time.Time{}
tests := []struct {
name string
point RecoveryPoint
want time.Time
}{
{
name: "nil completed and nil started returns zero",
point: RecoveryPoint{},
want: time.Time{},
},
{
name: "completed present returns completed in UTC",
point: RecoveryPoint{
CompletedAt: &completedAt,
},
want: completedAt.UTC(),
},
{
name: "completed nil but started present returns started in UTC",
point: RecoveryPoint{
StartedAt: &startedAt,
},
want: startedAt.UTC(),
},
{
name: "both completed and started present prefers completed",
point: RecoveryPoint{
CompletedAt: &completedAt,
StartedAt: &startedAt,
},
want: completedAt.UTC(),
},
{
name: "non-nil completed pointer holding zero value falls through to started",
point: RecoveryPoint{
CompletedAt: &zeroTime,
StartedAt: &startedAt,
},
want: startedAt.UTC(),
},
{
name: "non-nil started pointer holding zero value returns zero",
point: RecoveryPoint{
StartedAt: &zeroTime,
},
want: time.Time{},
},
{
name: "non-nil completed and started both zero returns zero",
point: RecoveryPoint{
CompletedAt: &zeroTime,
StartedAt: &zeroTime,
},
want: time.Time{},
},
{
name: "non-UTC completed is normalised to UTC equivalent",
point: RecoveryPoint{
CompletedAt: &completedNonUTC,
},
want: completedNonUTC.UTC(),
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()
got := recoveryPointObservedAt(test.point)
if !got.Equal(test.want) {
t.Fatalf(
"recoveryPointObservedAt(...) = %v (loc %s), want %v (loc %s)",
got, got.Location(), test.want, test.want.Location(),
)
}
})
}
}
@@ -0,0 +1,284 @@
package reporting
import (
"bytes"
"image"
"image/color"
"image/png"
"math"
"testing"
"github.com/go-pdf/fpdf"
)
func approxEqual(a, b float64) bool {
const eps = 1e-6
return math.Abs(a-b) <= eps
}
// encodeLogoTestPNG returns a deterministic black-opaque RGBA PNG of the
// requested pixel dimensions. Used to drive scaledLogoSize with a real
// fpdf-registered image so the math runs against actual Width()/Height()
// values rather than a fabricated stub.
func encodeLogoTestPNG(t *testing.T, w, h int) []byte {
t.Helper()
img := image.NewRGBA(image.Rect(0, 0, w, h))
for y := 0; y < h; y++ {
for x := 0; x < w; x++ {
img.SetRGBA(x, y, color.RGBA{R: 0, G: 0, B: 0, A: 255})
}
}
var buf bytes.Buffer
if err := png.Encode(&buf, img); err != nil {
t.Fatalf("encode png %dx%d: %v", w, h, err)
}
return buf.Bytes()
}
// registerLogoTestImage encodes a PNG of the given pixel size and registers
// it with a fresh fpdf instance, returning the resulting ImageInfoType.
func registerLogoTestImage(t *testing.T, w, h int, name string) *fpdf.ImageInfoType {
t.Helper()
pdf := fpdf.New("P", "mm", "A4", "")
info := pdf.RegisterImageOptionsReader(
name,
fpdf.ImageOptions{ImageType: "png", ReadDpi: true},
bytes.NewReader(encodeLogoTestPNG(t, w, h)),
)
if info == nil {
t.Fatalf("RegisterImageOptionsReader returned nil info for %dx%d", w, h)
}
if info.Width() <= 0 || info.Height() <= 0 {
t.Fatalf("registered image %dx%d reported non-positive extent w=%v h=%v",
w, h, info.Width(), info.Height())
}
return info
}
func TestReportLogoTypeFromData_BranchCov(t *testing.T) {
pngMagic8 := []byte{0x89, 'P', 'N', 'G', '\r', '\n', 0x1a, '\n'}
pngTail := []byte{0, 0, 0, 13, 'I', 'H', 'D', 'R'}
pngFull := append(append([]byte{}, pngMagic8...), pngTail...)
jpgMagic3 := []byte{0xff, 0xd8, 0xff}
jpgFull := append(append([]byte{}, jpgMagic3...), 0xe0, 0, 0x10, 0, 0x4a, 0x46, 0x49, 0x46)
gif87a := []byte("GIF87a")
gif89a := []byte("GIF89a")
unknown := []byte("not an image magic at all")
tests := []struct {
name string
data []byte
configured string
want string
}{
// --- configured short-circuit arm (normalizeLogoFormat != "") ---
{"configured png wins over jpg data", jpgFull, "png", "png"},
{"configured jpg wins over png data", pngFull, "jpg", "jpg"},
{"configured jpeg normalizes to jpg", pngFull, "jpeg", "jpg"},
{"configured gif wins over png data", pngFull, "gif", "gif"},
{"configured uppercase is normalized", jpgFull, "PNG", "png"},
{"configured with whitespace trimmed", jpgFull, " jpg ", "jpg"},
{"configured invalid falls through to png data", pngFull, "svg", "png"},
{"configured invalid falls through to jpg data", jpgFull, "bmp", "jpg"},
{"configured invalid falls through to gif data", gif87a, "weird", "gif"},
// --- data-detection arms with empty configured ---
{"empty configured + full png magic", pngFull, "", "png"},
{"empty configured + exactly 8 png magic bytes", pngMagic8, "", "png"},
{"empty configured + full jpg magic", jpgFull, "", "jpg"},
{"empty configured + exactly 3 jpg magic bytes", jpgMagic3, "", "jpg"},
{"empty configured + gif87a", gif87a, "", "gif"},
{"empty configured + gif89a", gif89a, "", "gif"},
{"empty configured + exactly 6 gif bytes (87a)", []byte("GIF87a"), "", "gif"},
{"empty configured + exactly 6 gif bytes (89a)", []byte("GIF89a"), "", "gif"},
// --- default arm (no match) ---
{"empty configured + unknown bytes", unknown, "", ""},
{"empty configured + nil data", nil, "", ""},
{"empty configured + empty data", []byte{}, "", ""},
{"png prefix too short (7 bytes)", pngMagic8[:7], "", ""},
{"jpg prefix too short (2 bytes)", jpgMagic3[:2], "", ""},
{"gif prefix too short (5 bytes)", gif87a[:5], "", ""},
{"gif89a prefix wrong last char", []byte("GIF89b"), "", ""},
{"gif87a prefix wrong last char", []byte("GIF87b"), "", ""},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got := reportLogoTypeFromData(tc.data, tc.configured)
if got != tc.want {
t.Fatalf("reportLogoTypeFromData(%v, %q) = %q, want %q",
tc.data, tc.configured, got, tc.want)
}
})
}
}
func TestReportLogoTypeFromPath_BranchCov(t *testing.T) {
tests := []struct {
name string
path string
configured string
want string
}{
// --- configured short-circuit arm (normalizeLogoFormat != "") ---
{"configured png wins regardless of path", "/any/thing.xyz", "png", "png"},
{"configured jpg wins regardless of path", "/any/thing", "jpg", "jpg"},
{"configured jpeg normalizes to jpg", "/any/thing", "jpeg", "jpg"},
{"configured gif wins regardless of path", "/any/thing", "gif", "gif"},
{"configured uppercase normalized to png", "/any/thing", "PNG", "png"},
{"configured whitespace trimmed to jpg", "/any/thing", " jpg ", "jpg"},
{"configured invalid falls through to extension png", "/a.png", "svg", "png"},
{"configured invalid falls through to default arm", "/a.svg", "svg", ""},
// --- extension switch arms with empty configured ---
{"png extension lowercase", "logo.png", "", "png"},
{"png extension uppercase", "LOGO.PNG", "", "png"},
{"png extension mixed case", "Logo.PnG", "", "png"},
{"jpg extension", "logo.jpg", "", "jpg"},
{"jpeg extension", "logo.jpeg", "", "jpg"},
{"JPG extension uppercase", "LOGO.JPG", "", "jpg"},
{"JPEG extension uppercase", "LOGO.JPEG", "", "jpg"},
{"gif extension", "logo.gif", "", "gif"},
{"GIF extension uppercase", "LOGO.GIF", "", "gif"},
// --- default arm (unknown / no extension) ---
{"unknown svg extension", "logo.svg", "", ""},
{"unknown bmp extension", "logo.bmp", "", ""},
{"unknown webp extension", "logo.webp", "", ""},
{"unknown txt extension", "logo.txt", "", ""},
{"no extension at all", "README", "", ""},
{"trailing dot only", "logo.", "", ""},
{"trailing dot uppercased", "LOGO.", "", ""},
{"png with trailing wrong ext", "logo.png.bak", "", ""},
// --- realistic full paths ---
{"absolute path png", "/var/lib/pulse/assets/brand.png", "", "png"},
{"url-style path jpg", "https://example.com/img/logo.jpg", "", "jpg"},
{"windows-style path gif", `C:\assets\logo.gif`, "", "gif"},
{"empty path with empty configured", "", "", ""},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got := reportLogoTypeFromPath(tc.path, tc.configured)
if got != tc.want {
t.Fatalf("reportLogoTypeFromPath(%q, %q) = %q, want %q",
tc.path, tc.configured, got, tc.want)
}
})
}
}
func TestScaledLogoSize_BranchCov(t *testing.T) {
const eps = 1e-6
t.Run("nil_info_returns_zero_zero", func(t *testing.T) {
w, h := scaledLogoSize(nil, 100, 100)
if w != 0 || h != 0 {
t.Fatalf("scaledLogoSize(nil, 100, 100) = (%v, %v), want (0, 0)", w, h)
}
})
t.Run("zero_maxW_hits_scale_zero_arm", func(t *testing.T) {
info := registerLogoTestImage(t, 8, 4, "zero-maxw")
w, h := scaledLogoSize(info, 0, 100)
if w != 0 || h != 0 {
t.Fatalf("scaledLogoSize(info, 0, 100) = (%v, %v), want (0, 0)", w, h)
}
})
t.Run("zero_maxH_hits_scale_zero_arm", func(t *testing.T) {
info := registerLogoTestImage(t, 8, 4, "zero-maxh")
w, h := scaledLogoSize(info, 100, 0)
if w != 0 || h != 0 {
t.Fatalf("scaledLogoSize(info, 100, 0) = (%v, %v), want (0, 0)", w, h)
}
})
t.Run("negative_maxW_hits_scale_zero_arm", func(t *testing.T) {
info := registerLogoTestImage(t, 8, 4, "neg-maxw")
w, h := scaledLogoSize(info, -10, 100)
if w != 0 || h != 0 {
t.Fatalf("scaledLogoSize(info, -10, 100) = (%v, %v), want (0, 0)", w, h)
}
})
t.Run("negative_maxH_hits_scale_zero_arm", func(t *testing.T) {
info := registerLogoTestImage(t, 8, 4, "neg-maxh")
w, h := scaledLogoSize(info, 100, -10)
if w != 0 || h != 0 {
t.Fatalf("scaledLogoSize(info, 100, -10) = (%v, %v), want (0, 0)", w, h)
}
})
t.Run("square_image_square_box_fills_both_axes", func(t *testing.T) {
info := registerLogoTestImage(t, 4, 4, "square")
const maxW, maxH = 100.0, 100.0
w, h := scaledLogoSize(info, maxW, maxH)
if !approxEqual(w, 100) || !approxEqual(h, 100) {
t.Fatalf("scaledLogoSize(square 4x4, 100, 100) = (%v, %v), want (100, 100)", w, h)
}
})
t.Run("wide_image_in_square_box_width_binds", func(t *testing.T) {
// 8x4 aspect 2:1 in a 1:1 box: width binds, output is (100, 50).
info := registerLogoTestImage(t, 8, 4, "wide-sqbox")
const maxW, maxH = 100.0, 100.0
w, h := scaledLogoSize(info, maxW, maxH)
if !approxEqual(w, 100) || !approxEqual(h, 50) {
t.Fatalf("scaledLogoSize(8x4, 100, 100) = (%v, %v), want (100, 50)", w, h)
}
if w > maxW+eps || h > maxH+eps {
t.Fatalf("output (%v, %v) exceeds box (%v, %v)", w, h, maxW, maxH)
}
})
t.Run("tall_image_in_square_box_height_binds", func(t *testing.T) {
// 4x8 aspect 1:2 in a 1:1 box: height binds, output is (50, 100).
info := registerLogoTestImage(t, 4, 8, "tall-sqbox")
const maxW, maxH = 100.0, 100.0
w, h := scaledLogoSize(info, maxW, maxH)
if !approxEqual(w, 50) || !approxEqual(h, 100) {
t.Fatalf("scaledLogoSize(4x8, 100, 100) = (%v, %v), want (50, 100)", w, h)
}
if w > maxW+eps || h > maxH+eps {
t.Fatalf("output (%v, %v) exceeds box (%v, %v)", w, h, maxW, maxH)
}
})
t.Run("wide_image_in_matched_aspect_box_fills_both", func(t *testing.T) {
// 8x4 aspect 2:1 in a 100x50 box (also 2:1): both axes bind at once.
info := registerLogoTestImage(t, 8, 4, "wide-matched")
const maxW, maxH = 100.0, 50.0
w, h := scaledLogoSize(info, maxW, maxH)
if !approxEqual(w, 100) || !approxEqual(h, 50) {
t.Fatalf("scaledLogoSize(8x4, 100, 50) = (%v, %v), want (100, 50)", w, h)
}
})
t.Run("tall_image_in_matched_aspect_box_fills_both", func(t *testing.T) {
// 4x8 aspect 1:2 in a 50x100 box (also 1:2): both axes bind at once.
info := registerLogoTestImage(t, 4, 8, "tall-matched")
const maxW, maxH = 50.0, 100.0
w, h := scaledLogoSize(info, maxW, maxH)
if !approxEqual(w, 50) || !approxEqual(h, 100) {
t.Fatalf("scaledLogoSize(4x8, 50, 100) = (%v, %v), want (50, 100)", w, h)
}
})
t.Run("image_smaller_than_box_uses_uniform_scale", func(t *testing.T) {
// Verify aspect ratio is preserved across a non-square box where the
// image is upscaling rather than downscaling. 6x2 aspect 3:1 in a
// 300x100 box (also 3:1): both axes bind.
info := registerLogoTestImage(t, 6, 2, "upscale")
const maxW, maxH = 300.0, 100.0
w, h := scaledLogoSize(info, maxW, maxH)
if !approxEqual(w, 300) || !approxEqual(h, 100) {
t.Fatalf("scaledLogoSize(6x2, 300, 100) = (%v, %v), want (300, 100)", w, h)
}
if w > maxW+eps || h > maxH+eps {
t.Fatalf("output (%v, %v) exceeds box (%v, %v)", w, h, maxW, maxH)
}
})
}