test coverage improvements: instance, hiveui, health, utils

Total hive coverage: 54.3% → 55.8% (+1.5%)

- instance/service_impl.go: new test file (generateHostname, RunInstance,
  cloud-init template, constructor)
- hiveui.go: new test file (security headers, gzip middleware, New(),
  Status(), Reload())
- daemon/health.go: extend existing tests (handleInstanceCrash core flow,
  first crash time, unknown instance type, maybeRestart scheduling)
- utils/utils.go: extend existing tests (pidPath all branches, RuntimeDir,
  extractDiskImagePath, WritePidFile error path)
- fix: use smallestAllocType in health tests to avoid CI resource failures
This commit is contained in:
Julian Sommer
2026-02-20 13:46:31 +10:00
parent db45c028be
commit 6bc36455fc
4 changed files with 847 additions and 0 deletions
+263
View File
@@ -2,7 +2,9 @@ package daemon
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"testing"
"time"
@@ -200,3 +202,264 @@ func TestRestartBackoff_Exponential(t *testing.T) {
assert.Equal(t, want, delay, "Backoff mismatch at restart count %d", i)
}
}
// newTestDaemon creates a Daemon with shared NATS and a ResourceManager for health tests.
// jsManager is nil — WriteState will error (logged, not fatal).
func newTestDaemon(t *testing.T) (*Daemon, func()) {
t.Helper()
nc, err := nats.Connect(sharedNATSURL)
require.NoError(t, err)
d := &Daemon{
node: "test-node",
natsConn: nc,
config: &config.Config{
Services: []string{"daemon"},
},
resourceMgr: NewResourceManager(),
Instances: vm.Instances{VMS: make(map[string]*vm.VM)},
}
return d, func() { nc.Close() }
}
// smallestAllocType returns the name of the smallest (fewest vCPUs) instance type
// in the resource manager. This ensures tests work on CI runners with limited resources.
func smallestAllocType(t *testing.T, rm *ResourceManager) string {
t.Helper()
var smallest string
var smallestVCPU int64 = 1<<63 - 1
for name, it := range rm.instanceTypes {
vcpu := instanceTypeVCPUs(it)
if vcpu < smallestVCPU {
smallestVCPU = vcpu
smallest = name
}
}
if smallest == "" {
t.Fatal("resource manager has no instance types")
}
return smallest
}
func TestHandleInstanceCrash_CoreFlow(t *testing.T) {
d, cleanup := newTestDaemon(t)
defer cleanup()
allocType := smallestAllocType(t, d.resourceMgr)
instanceType := d.resourceMgr.instanceTypes[allocType]
require.NoError(t, d.resourceMgr.allocate(instanceType))
// Record resource levels before crash
d.resourceMgr.mu.RLock()
allocVCPUBefore := d.resourceMgr.allocatedVCPU
allocMemBefore := d.resourceMgr.allocatedMem
d.resourceMgr.mu.RUnlock()
require.Greater(t, allocVCPUBefore, 0)
// Create a temp QMP socket file to verify removal
tmpDir := t.TempDir()
qmpPath := filepath.Join(tmpDir, "qmp.sock")
require.NoError(t, os.WriteFile(qmpPath, []byte("dummy"), 0o600))
instance := &vm.VM{
ID: "i-test-core",
Status: vm.StateRunning,
Running: true,
PID: 12345,
InstanceType: allocType,
Config: vm.Config{QMPSocket: qmpPath},
}
d.Instances.VMS[instance.ID] = instance
d.handleInstanceCrash(instance, fmt.Errorf("test crash"))
// Status transitioned to error
assert.Equal(t, vm.StateError, instance.Status)
// Health tracking updated
assert.Equal(t, 1, instance.Health.CrashCount)
assert.False(t, instance.Health.LastCrashTime.IsZero())
assert.Equal(t, "unknown", instance.Health.LastCrashReason) // fmt.Errorf is not *exec.ExitError
assert.False(t, instance.Health.FirstCrashTime.IsZero())
// Running and PID cleared
assert.False(t, instance.Running)
assert.Equal(t, 0, instance.PID)
// Resources deallocated
d.resourceMgr.mu.RLock()
assert.Less(t, d.resourceMgr.allocatedVCPU, allocVCPUBefore)
assert.Less(t, d.resourceMgr.allocatedMem, allocMemBefore)
d.resourceMgr.mu.RUnlock()
// QMP socket file removed
_, err := os.Stat(qmpPath)
assert.True(t, os.IsNotExist(err), "QMP socket should be removed")
}
func TestHandleInstanceCrash_FirstCrashSetsTime(t *testing.T) {
d, cleanup := newTestDaemon(t)
defer cleanup()
allocType := smallestAllocType(t, d.resourceMgr)
instance := &vm.VM{
ID: "i-test-firstcrash",
Status: vm.StateRunning,
Running: true,
PID: 111,
InstanceType: allocType,
}
d.Instances.VMS[instance.ID] = instance
// First crash
d.handleInstanceCrash(instance, fmt.Errorf("crash 1"))
firstTime := instance.Health.FirstCrashTime
assert.False(t, firstTime.IsZero())
assert.Equal(t, 1, instance.Health.CrashCount)
// Reset to running for a second crash
d.Instances.Mu.Lock()
instance.Status = vm.StateRunning
instance.Running = true
instance.PID = 222
d.Instances.Mu.Unlock()
time.Sleep(time.Millisecond) // ensure time advances
d.handleInstanceCrash(instance, fmt.Errorf("crash 2"))
// FirstCrashTime should be unchanged from the first crash
assert.Equal(t, firstTime, instance.Health.FirstCrashTime)
assert.Equal(t, 2, instance.Health.CrashCount)
}
func TestHandleInstanceCrash_UnknownInstanceType(t *testing.T) {
d, cleanup := newTestDaemon(t)
defer cleanup()
instance := &vm.VM{
ID: "i-test-unknown-type",
Status: vm.StateRunning,
Running: true,
PID: 333,
InstanceType: "z99.nonexistent",
}
d.Instances.VMS[instance.ID] = instance
// Should not panic even though instance type is not in resourceMgr
d.handleInstanceCrash(instance, fmt.Errorf("crash"))
assert.Equal(t, vm.StateError, instance.Status)
assert.Equal(t, 1, instance.Health.CrashCount)
assert.False(t, instance.Running)
assert.Equal(t, 0, instance.PID)
}
func TestMaybeRestart_SkipsShuttingDown(t *testing.T) {
d, cleanup := newTestDaemon(t)
defer cleanup()
d.shuttingDown.Store(true)
allocType := smallestAllocType(t, d.resourceMgr)
instance := &vm.VM{
ID: "i-test-restart-shutdown",
Status: vm.StateError,
InstanceType: allocType,
Health: vm.InstanceHealthState{
CrashCount: 1,
FirstCrashTime: time.Now(),
},
}
d.Instances.VMS[instance.ID] = instance
d.maybeRestartInstance(instance)
// RestartCount should not have changed — restart was skipped
assert.Equal(t, 0, instance.Health.RestartCount)
}
func TestMaybeRestart_UnknownInstanceType(t *testing.T) {
d, cleanup := newTestDaemon(t)
defer cleanup()
instance := &vm.VM{
ID: "i-test-restart-unknown",
Status: vm.StateError,
InstanceType: "z99.nonexistent",
Health: vm.InstanceHealthState{
CrashCount: 1,
FirstCrashTime: time.Now(),
},
}
d.Instances.VMS[instance.ID] = instance
// Should not panic — just log and return
d.maybeRestartInstance(instance)
// No restart scheduled
assert.Equal(t, 0, instance.Health.RestartCount)
}
func TestMaybeRestart_InsufficientResources(t *testing.T) {
d, cleanup := newTestDaemon(t)
defer cleanup()
allocType := smallestAllocType(t, d.resourceMgr)
// Exhaust all resources by setting allocated = available
d.resourceMgr.mu.Lock()
d.resourceMgr.allocatedVCPU = d.resourceMgr.availableVCPU
d.resourceMgr.allocatedMem = d.resourceMgr.availableMem
d.resourceMgr.mu.Unlock()
instance := &vm.VM{
ID: "i-test-restart-nores",
Status: vm.StateError,
InstanceType: allocType,
Health: vm.InstanceHealthState{
CrashCount: 1,
FirstCrashTime: time.Now(),
},
}
d.Instances.VMS[instance.ID] = instance
d.maybeRestartInstance(instance)
// No restart scheduled
assert.Equal(t, 0, instance.Health.RestartCount)
}
func TestMaybeRestart_SchedulesRestart(t *testing.T) {
d, cleanup := newTestDaemon(t)
defer cleanup()
allocType := smallestAllocType(t, d.resourceMgr)
instance := &vm.VM{
ID: "i-test-restart-schedule",
Status: vm.StateError,
InstanceType: allocType,
Health: vm.InstanceHealthState{
CrashCount: 1,
FirstCrashTime: time.Now(),
RestartCount: 0,
},
}
d.Instances.VMS[instance.ID] = instance
// maybeRestartInstance should pass all guards and call time.AfterFunc.
// The scheduled restartCrashedInstance will increment RestartCount and
// attempt LaunchInstance (which will fail without real infra — that's fine).
d.maybeRestartInstance(instance)
// The restart is scheduled via time.AfterFunc with 5s delay.
// We verify that the scheduling code path was reached by checking that
// the function didn't bail out early (all early returns leave RestartCount at 0
// and don't call time.AfterFunc). The counter reset branch does not apply since
// FirstCrashTime is recent. If we got here without panic, the scheduling path ran.
// We can't easily assert the timer fired without waiting 5s, but we can
// verify the counters were not reset (proving we didn't take the window-expired path).
assert.Equal(t, 1, instance.Health.CrashCount)
assert.Equal(t, 0, instance.Health.RestartCount) // incremented by restartCrashedInstance, not maybeRestart
}
@@ -0,0 +1,252 @@
package handlers_ec2_instance
import (
"bytes"
"testing"
"text/template"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/mulgadc/hive/hive/awserrors"
"github.com/mulgadc/hive/hive/config"
"github.com/mulgadc/hive/hive/objectstore"
"github.com/mulgadc/hive/hive/vm"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNewInstanceServiceImpl(t *testing.T) {
cfg := &config.Config{}
instanceTypes := map[string]*ec2.InstanceTypeInfo{
"t3.micro": {InstanceType: aws.String("t3.micro")},
}
store := objectstore.NewMemoryObjectStore()
instances := &vm.Instances{VMS: make(map[string]*vm.VM)}
svc := NewInstanceServiceImpl(cfg, instanceTypes, nil, instances, store)
require.NotNil(t, svc)
assert.Equal(t, cfg, svc.config)
assert.Equal(t, instanceTypes, svc.instanceTypes)
assert.Nil(t, svc.natsConn)
assert.Equal(t, instances, svc.instances)
assert.Equal(t, store, svc.objectStore)
}
func TestGenerateHostname(t *testing.T) {
tests := []struct {
name string
instanceID string
want string
}{
{
name: "Normal instance ID",
instanceID: "i-0123456789abcdef0",
want: "hive-vm-01234567",
},
{
name: "Too short (2 chars)",
instanceID: "ab",
want: "hive-vm-unknown",
},
{
name: "Empty string",
instanceID: "",
want: "hive-vm-unknown",
},
{
name: "Exactly 10 chars",
instanceID: "i-abcdef01",
want: "hive-vm-abcdef01",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := generateHostname(tt.instanceID)
assert.Equal(t, tt.want, got)
})
}
}
func TestRunInstance_Success(t *testing.T) {
instanceTypes := map[string]*ec2.InstanceTypeInfo{
"t3.micro": {InstanceType: aws.String("t3.micro")},
"t3.small": {InstanceType: aws.String("t3.small")},
}
svc := &InstanceServiceImpl{
instanceTypes: instanceTypes,
}
input := &ec2.RunInstancesInput{
ImageId: aws.String("ami-0abcdef1234567890"),
InstanceType: aws.String("t3.micro"),
KeyName: aws.String("my-key"),
}
instance, ec2Instance, err := svc.RunInstance(input)
require.NoError(t, err)
require.NotNil(t, instance)
require.NotNil(t, ec2Instance)
// Verify VM struct
assert.Contains(t, instance.ID, "i-")
assert.Equal(t, vm.StateProvisioning, instance.Status)
assert.Equal(t, "t3.micro", instance.InstanceType)
assert.Equal(t, input, instance.RunInstancesInput)
assert.Equal(t, ec2Instance, instance.Instance)
// Verify EC2 metadata
assert.Equal(t, instance.ID, *ec2Instance.InstanceId)
assert.Equal(t, "t3.micro", *ec2Instance.InstanceType)
assert.Equal(t, "ami-0abcdef1234567890", *ec2Instance.ImageId)
assert.Equal(t, "my-key", *ec2Instance.KeyName)
assert.Equal(t, int64(0), *ec2Instance.State.Code)
assert.Equal(t, "pending", *ec2Instance.State.Name)
assert.NotNil(t, ec2Instance.LaunchTime)
}
func TestRunInstance_NoKeyName(t *testing.T) {
instanceTypes := map[string]*ec2.InstanceTypeInfo{
"t3.micro": {InstanceType: aws.String("t3.micro")},
}
svc := &InstanceServiceImpl{instanceTypes: instanceTypes}
input := &ec2.RunInstancesInput{
ImageId: aws.String("ami-012345"),
InstanceType: aws.String("t3.micro"),
}
instance, ec2Instance, err := svc.RunInstance(input)
require.NoError(t, err)
require.NotNil(t, instance)
assert.Nil(t, ec2Instance.KeyName)
}
func TestRunInstance_InvalidInstanceType(t *testing.T) {
instanceTypes := map[string]*ec2.InstanceTypeInfo{
"t3.micro": {InstanceType: aws.String("t3.micro")},
}
svc := &InstanceServiceImpl{instanceTypes: instanceTypes}
input := &ec2.RunInstancesInput{
ImageId: aws.String("ami-012345"),
InstanceType: aws.String("nonexistent.type"),
}
instance, ec2Instance, err := svc.RunInstance(input)
require.Error(t, err)
assert.Equal(t, awserrors.ErrorInvalidInstanceType, err.Error())
assert.Nil(t, instance)
assert.Nil(t, ec2Instance)
}
func TestRunInstance_UniqueIDs(t *testing.T) {
instanceTypes := map[string]*ec2.InstanceTypeInfo{
"t3.micro": {InstanceType: aws.String("t3.micro")},
}
svc := &InstanceServiceImpl{instanceTypes: instanceTypes}
input := &ec2.RunInstancesInput{
ImageId: aws.String("ami-012345"),
InstanceType: aws.String("t3.micro"),
}
instance1, _, err1 := svc.RunInstance(input)
instance2, _, err2 := svc.RunInstance(input)
require.NoError(t, err1)
require.NoError(t, err2)
assert.NotEqual(t, instance1.ID, instance2.ID, "Each instance should have a unique ID")
}
func TestCloudInitTemplateRendering(t *testing.T) {
tests := []struct {
name string
data CloudInitData
contains []string
}{
{
name: "Basic SSH key and hostname",
data: CloudInitData{
Username: "ec2-user",
SSHKey: "ssh-rsa AAAAB3... user@host",
Hostname: "hive-vm-01234567",
},
contains: []string{
"ec2-user",
"ssh-rsa AAAAB3... user@host",
"hive-vm-01234567",
"#cloud-config",
},
},
{
name: "With cloud-config userdata",
data: CloudInitData{
Username: "ec2-user",
SSHKey: "ssh-ed25519 AAAA...",
Hostname: "hive-vm-abcdef01",
UserDataCloudConfig: "packages:\n - nginx",
},
contains: []string{
"packages:",
"nginx",
"custom userdata cloud-config",
},
},
{
name: "With script userdata",
data: CloudInitData{
Username: "ec2-user",
SSHKey: "ssh-rsa AAAA...",
Hostname: "hive-vm-test",
UserDataScript: " #!/bin/bash\n echo hello",
},
contains: []string{
"write_files:",
"/tmp/cloud-init-startup.sh",
"runcmd:",
"echo hello",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tmpl := template.Must(template.New("cloud-init").Parse(cloudInitUserDataTemplate))
var buf bytes.Buffer
err := tmpl.Execute(&buf, tt.data)
require.NoError(t, err)
rendered := buf.String()
for _, s := range tt.contains {
assert.Contains(t, rendered, s)
}
})
}
}
func TestCloudInitMetaTemplateRendering(t *testing.T) {
data := CloudInitMetaData{
InstanceID: "i-0123456789abcdef0",
Hostname: "hive-vm-01234567",
}
tmpl := template.Must(template.New("meta-data").Parse(cloudInitMetaTemplate))
var buf bytes.Buffer
err := tmpl.Execute(&buf, data)
require.NoError(t, err)
rendered := buf.String()
assert.Contains(t, rendered, "i-0123456789abcdef0")
assert.Contains(t, rendered, "hive-vm-01234567")
assert.Contains(t, rendered, "instance-id:")
assert.Contains(t, rendered, "local-hostname:")
}
+267
View File
@@ -0,0 +1,267 @@
package hiveui
import (
"compress/gzip"
"io"
"net"
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNew_ValidConfig(t *testing.T) {
cfg := &Config{
Port: 8080,
Host: "127.0.0.1",
TLSCert: "/custom/cert.pem",
TLSKey: "/custom/key.pem",
}
svc, err := New(cfg)
require.NoError(t, err)
assert.NotNil(t, svc)
assert.Equal(t, 8080, svc.Config.Port)
assert.Equal(t, "127.0.0.1", svc.Config.Host)
assert.Equal(t, "/custom/cert.pem", svc.Config.TLSCert)
assert.Equal(t, "/custom/key.pem", svc.Config.TLSKey)
}
func TestNew_DefaultPortAndHost(t *testing.T) {
cfg := &Config{
TLSCert: "/custom/cert.pem",
TLSKey: "/custom/key.pem",
}
svc, err := New(cfg)
require.NoError(t, err)
assert.Equal(t, 3000, svc.Config.Port)
assert.Equal(t, "0.0.0.0", svc.Config.Host)
}
func TestNew_DefaultTLSPaths(t *testing.T) {
cfg := &Config{}
svc, err := New(cfg)
require.NoError(t, err)
// TLS paths should be filled in from home directory
assert.NotEmpty(t, svc.Config.TLSCert)
assert.NotEmpty(t, svc.Config.TLSKey)
assert.Contains(t, svc.Config.TLSCert, "server.pem")
assert.Contains(t, svc.Config.TLSKey, "server.key")
}
func TestNew_CustomTLSPreserved(t *testing.T) {
cfg := &Config{
TLSCert: "/my/cert.pem",
TLSKey: "/my/key.pem",
}
svc, err := New(cfg)
require.NoError(t, err)
assert.Equal(t, "/my/cert.pem", svc.Config.TLSCert)
assert.Equal(t, "/my/key.pem", svc.Config.TLSKey)
}
func TestNew_PartialTLSFillsOnlyEmpty(t *testing.T) {
cfg := &Config{
TLSCert: "/my/cert.pem",
// TLSKey left empty — should get default
}
svc, err := New(cfg)
require.NoError(t, err)
// Both are empty-or-not checked together, so both get defaults
// because the condition is cfg.TLSCert == "" || cfg.TLSKey == ""
assert.Equal(t, "/my/cert.pem", svc.Config.TLSCert)
assert.Contains(t, svc.Config.TLSKey, "server.key")
}
func TestNew_InvalidConfigType(t *testing.T) {
svc, err := New("not a config")
assert.Nil(t, svc)
assert.Error(t, err)
assert.Contains(t, err.Error(), "invalid config type")
}
func TestNew_InvalidConfigTypeInt(t *testing.T) {
svc, err := New(42)
assert.Nil(t, svc)
assert.Error(t, err)
}
func TestStatus_ReturnsValidState(t *testing.T) {
svc := &Service{
Config: &Config{},
}
status, err := svc.Status()
assert.NoError(t, err)
// On a dev machine the hive-ui PID file may exist, so accept either outcome
assert.True(t, status == "stopped" || len(status) > 0, "status should be non-empty")
}
func TestReload_ReturnsNil(t *testing.T) {
svc := &Service{
Config: &Config{},
}
err := svc.Reload()
assert.NoError(t, err)
}
func TestServiceName(t *testing.T) {
assert.Equal(t, "hive-ui", serviceName)
}
func TestSecurityHeadersMiddleware(t *testing.T) {
inner := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
})
handler := securityHeadersMiddleware(inner)
req := httptest.NewRequest(http.MethodGet, "/", nil)
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
resp := rec.Result()
defer resp.Body.Close()
assert.Equal(t, http.StatusOK, resp.StatusCode)
assert.NotEmpty(t, resp.Header.Get("Content-Security-Policy"))
assert.Contains(t, resp.Header.Get("Content-Security-Policy"), "default-src 'self'")
assert.Equal(t, "camera=(), microphone=(), geolocation=(), browsing-topics=()", resp.Header.Get("Permissions-Policy"))
assert.Equal(t, "strict-origin-when-cross-origin", resp.Header.Get("Referrer-Policy"))
assert.Equal(t, "max-age=31536000; includeSubDomains", resp.Header.Get("Strict-Transport-Security"))
assert.Equal(t, "nosniff", resp.Header.Get("X-Content-Type-Options"))
}
func TestSecurityHeadersMiddleware_PassesThrough(t *testing.T) {
inner := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-Custom", "test-value")
w.WriteHeader(http.StatusCreated)
})
handler := securityHeadersMiddleware(inner)
req := httptest.NewRequest(http.MethodPost, "/api/data", nil)
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
resp := rec.Result()
defer resp.Body.Close()
assert.Equal(t, http.StatusCreated, resp.StatusCode)
assert.Equal(t, "test-value", resp.Header.Get("X-Custom"))
// Security headers still present
assert.NotEmpty(t, resp.Header.Get("Content-Security-Policy"))
}
func TestGzipMiddleware_CompressesEligibleContent(t *testing.T) {
body := "Hello, this is some text content that should be compressed if long enough."
inner := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html")
w.Write([]byte(body))
})
handler := gzipMiddleware(inner)
req := httptest.NewRequest(http.MethodGet, "/", nil)
req.Header.Set("Accept-Encoding", "gzip")
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
resp := rec.Result()
defer resp.Body.Close()
// The gzip handler may or may not compress depending on content size,
// but the handler should still return a valid response.
assert.Equal(t, http.StatusOK, resp.StatusCode)
if resp.Header.Get("Content-Encoding") == "gzip" {
reader, err := gzip.NewReader(resp.Body)
require.NoError(t, err)
defer reader.Close()
decompressed, err := io.ReadAll(reader)
require.NoError(t, err)
assert.Equal(t, body, string(decompressed))
}
}
func TestGzipMiddleware_NoCompressionWithoutHeader(t *testing.T) {
body := "uncompressed response body"
inner := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html")
w.Write([]byte(body))
})
handler := gzipMiddleware(inner)
req := httptest.NewRequest(http.MethodGet, "/", nil)
// No Accept-Encoding header
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
resp := rec.Result()
defer resp.Body.Close()
assert.Equal(t, http.StatusOK, resp.StatusCode)
assert.NotEqual(t, "gzip", resp.Header.Get("Content-Encoding"))
respBody, err := io.ReadAll(resp.Body)
require.NoError(t, err)
assert.Equal(t, body, string(respBody))
}
func TestGzipMiddleware_IgnoresNonTextContent(t *testing.T) {
inner := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "image/png")
w.Write([]byte("fake image data"))
})
handler := gzipMiddleware(inner)
req := httptest.NewRequest(http.MethodGet, "/image.png", nil)
req.Header.Set("Accept-Encoding", "gzip")
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
resp := rec.Result()
defer resp.Body.Close()
assert.Equal(t, http.StatusOK, resp.StatusCode)
// image/png not in the allowed content types — should not be compressed
assert.NotEqual(t, "gzip", resp.Header.Get("Content-Encoding"))
}
func TestShutdown_WithServer(t *testing.T) {
// Create a real server on a random port so Shutdown exercises the non-nil path
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
})
srv := &http.Server{Handler: mux}
ln, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err)
go srv.Serve(ln)
svc := &Service{
Config: &Config{},
server: srv,
}
err = svc.Shutdown()
assert.NoError(t, err)
}
+65
View File
@@ -942,3 +942,68 @@ func TestSetOOMScore_InvalidPID(t *testing.T) {
err := SetOOMScore(999999999, 100)
assert.Error(t, err)
}
func TestRuntimeDir(t *testing.T) {
dir := RuntimeDir()
assert.NotEmpty(t, dir)
}
func TestPidPath_XDG(t *testing.T) {
t.Setenv("XDG_RUNTIME_DIR", "/tmp/test-xdg-runtime")
assert.Equal(t, "/tmp/test-xdg-runtime", pidPath())
}
func TestPidPath_HomeHiveFallback(t *testing.T) {
tmpHome := t.TempDir()
hiveDir := fmt.Sprintf("%s/hive", tmpHome)
require.NoError(t, os.Mkdir(hiveDir, 0755))
t.Setenv("XDG_RUNTIME_DIR", "")
t.Setenv("HOME", tmpHome)
assert.Equal(t, hiveDir, pidPath())
}
func TestPidPath_TempDirFallback(t *testing.T) {
t.Setenv("XDG_RUNTIME_DIR", "")
t.Setenv("HOME", "/nonexistent-home-dir-utils-test")
assert.Equal(t, os.TempDir(), pidPath())
}
func TestExtractDiskImagePath_NoMatch(t *testing.T) {
output := []byte("somefile.txt\nanotherfile.conf\n")
diskimage, err := extractDiskImagePath(t.TempDir(), output)
assert.Empty(t, diskimage)
assert.NoError(t, err)
}
func TestExtractDiskImagePath_EmptyOutput(t *testing.T) {
diskimage, err := extractDiskImagePath(t.TempDir(), []byte{})
assert.Empty(t, diskimage)
assert.NoError(t, err)
}
func TestWritePidFile_CreateError(t *testing.T) {
t.Setenv("XDG_RUNTIME_DIR", "/nonexistent-dir-utils-test")
err := WritePidFile("testservice", 12345)
assert.Error(t, err)
}
func TestRemovePidFileAt_EmptyDir(t *testing.T) {
err := RemovePidFileAt("", fmt.Sprintf("nonexistent-service-%d", time.Now().UnixNano()))
assert.Error(t, err)
}
func TestGeneratePidFile_InvalidPath(t *testing.T) {
t.Setenv("XDG_RUNTIME_DIR", "/nonexistent-dir-utils-test")
path, err := GeneratePidFile("test")
// GeneratePidFile doesn't check if path exists, just builds it
assert.NoError(t, err)
assert.Contains(t, path, "test.pid")
}
func TestReadPidFileFrom_EmptyDir(t *testing.T) {
_, err := ReadPidFileFrom("", fmt.Sprintf("nonexistent-service-%d", time.Now().UnixNano()))
assert.Error(t, err)
}