Increase test coverage to 45.6%

This commit is contained in:
2025-12-17 12:39:47 +00:00
parent 4add030bed
commit c259bb1d18
13 changed files with 5484 additions and 0 deletions
+492
View File
@@ -710,3 +710,495 @@ func TestGetWorkerVersion_WithServer(t *testing.T) {
})
}
}
// TestGetWorkerPort_EdgeCases tests GetWorkerPort with various edge cases.
func TestGetWorkerPort_EdgeCases(t *testing.T) {
tests := []struct {
name string
envValue string
expectedPort int
shouldSetEnv bool
}{
{
name: "zero port uses default",
envValue: "0",
expectedPort: DefaultWorkerPort,
shouldSetEnv: true,
},
{
name: "negative port uses default",
envValue: "-1",
expectedPort: DefaultWorkerPort,
shouldSetEnv: true,
},
{
name: "empty string uses default",
envValue: "",
expectedPort: DefaultWorkerPort,
shouldSetEnv: true,
},
{
name: "whitespace uses default",
envValue: " ",
expectedPort: DefaultWorkerPort,
shouldSetEnv: true,
},
{
name: "large valid port",
envValue: "65535",
expectedPort: 65535,
shouldSetEnv: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.shouldSetEnv {
t.Setenv("CLAUDE_MNEMONIC_WORKER_PORT", tt.envValue)
}
port := GetWorkerPort()
assert.Equal(t, tt.expectedPort, port)
})
}
}
// TestVersionVariable tests the Version variable.
func TestVersionVariable(t *testing.T) {
// Version is set at build time, but defaults to "dev"
assert.NotEmpty(t, Version)
}
// TestProjectIDWithName_RootPath tests ProjectIDWithName with root path.
func TestProjectIDWithName_RootPath(t *testing.T) {
result := ProjectIDWithName("/")
// Should handle root path gracefully
assert.NotEmpty(t, result)
assert.Contains(t, result, "_") // Should still have underscore separator
}
// TestProjectIDWithName_SameDirname tests that same dirname with different paths get different IDs.
func TestProjectIDWithName_SameDirname(t *testing.T) {
id1 := ProjectIDWithName("/home/user1/project")
id2 := ProjectIDWithName("/home/user2/project")
// Both have same dirname "project" but different full paths
assert.Contains(t, id1, "project_")
assert.Contains(t, id2, "project_")
// But different hashes due to different full paths
assert.NotEqual(t, id1, id2)
}
// TestBaseInput_PartialFields tests BaseInput with partial fields.
func TestBaseInput_PartialFields(t *testing.T) {
tests := []struct {
name string
input string
expected BaseInput
}{
{
name: "only session_id",
input: `{"session_id":"test-123"}`,
expected: BaseInput{SessionID: "test-123"},
},
{
name: "only cwd",
input: `{"cwd":"/tmp/test"}`,
expected: BaseInput{CWD: "/tmp/test"},
},
{
name: "empty object",
input: `{}`,
expected: BaseInput{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var base BaseInput
err := json.Unmarshal([]byte(tt.input), &base)
require.NoError(t, err)
assert.Equal(t, tt.expected.SessionID, base.SessionID)
assert.Equal(t, tt.expected.CWD, base.CWD)
})
}
}
// TestHookResponse_Marshal tests HookResponse JSON marshaling.
func TestHookResponse_Marshal(t *testing.T) {
tests := []struct {
name string
response HookResponse
contains []string
}{
{
name: "continue true",
response: HookResponse{Continue: true},
contains: []string{`"continue":true`},
},
{
name: "continue false",
response: HookResponse{Continue: false},
contains: []string{`"continue":false`},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
data, err := json.Marshal(tt.response)
require.NoError(t, err)
for _, s := range tt.contains {
assert.Contains(t, string(data), s)
}
})
}
}
// TestHookResponse_Unmarshal tests HookResponse JSON unmarshaling.
func TestHookResponse_Unmarshal(t *testing.T) {
tests := []struct {
name string
input string
expected HookResponse
}{
{
name: "continue true",
input: `{"continue":true}`,
expected: HookResponse{Continue: true},
},
{
name: "continue false",
input: `{"continue":false}`,
expected: HookResponse{Continue: false},
},
{
name: "missing continue defaults to false",
input: `{}`,
expected: HookResponse{Continue: false},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var resp HookResponse
err := json.Unmarshal([]byte(tt.input), &resp)
require.NoError(t, err)
assert.Equal(t, tt.expected.Continue, resp.Continue)
})
}
}
// TestHookContext_Initialization tests HookContext struct initialization.
func TestHookContext_Initialization(t *testing.T) {
tests := []struct {
name string
ctx HookContext
}{
{
name: "full context",
ctx: HookContext{
HookName: "session-start",
Port: 37777,
Project: "my-project_abc123",
SessionID: "session-123",
CWD: "/home/user/project",
RawInput: []byte(`{"key":"value"}`),
},
},
{
name: "minimal context",
ctx: HookContext{
HookName: "stop",
},
},
{
name: "empty context",
ctx: HookContext{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Just verify the struct can be created and accessed
assert.Equal(t, tt.ctx.HookName, tt.ctx.HookName)
assert.Equal(t, tt.ctx.Port, tt.ctx.Port)
assert.Equal(t, tt.ctx.Project, tt.ctx.Project)
})
}
}
// TestPOST_MarshalError tests POST with unmarshalable body.
func TestPOST_MarshalError(t *testing.T) {
// Create a value that can't be marshaled
badValue := make(chan int)
_, err := POST(99999, "/test", badValue)
require.Error(t, err)
}
// TestPOST_Timeout tests POST with timeout.
func TestPOST_Timeout(t *testing.T) {
// Try to connect to a port that's not listening
_, err := POST(99998, "/test", map[string]string{"key": "value"})
require.Error(t, err)
}
// TestGET_Timeout tests GET with timeout.
func TestGET_Timeout(t *testing.T) {
// Try to connect to a port that's not listening
_, err := GET(99998, "/test")
require.Error(t, err)
}
// TestIsWorkerRunning_Timeout tests IsWorkerRunning with timeout.
func TestIsWorkerRunning_Timeout(t *testing.T) {
// Non-existent port should quickly return false
start := time.Now()
result := IsWorkerRunning(99997)
elapsed := time.Since(start)
assert.False(t, result)
assert.Less(t, elapsed, 5*time.Second) // Should not hang
}
// TestIsPortInUse_Timeout tests IsPortInUse with timeout.
func TestIsPortInUse_Timeout(t *testing.T) {
// Non-existent port should quickly return false
start := time.Now()
result := IsPortInUse(99996)
elapsed := time.Since(start)
assert.False(t, result)
assert.Less(t, elapsed, 2*time.Second) // Should not hang
}
// TestGetWorkerVersion_Timeout tests GetWorkerVersion with timeout.
func TestGetWorkerVersion_Timeout(t *testing.T) {
// Non-existent port should quickly return empty
start := time.Now()
result := GetWorkerVersion(99995)
elapsed := time.Since(start)
assert.Empty(t, result)
assert.Less(t, elapsed, 5*time.Second) // Should not hang
}
// TestVersionsCompatible_EdgeCases tests versionsCompatible edge cases.
func TestVersionsCompatible_EdgeCases(t *testing.T) {
tests := []struct {
name string
v1 string
v2 string
expected bool
}{
{
name: "empty versions",
v1: "",
v2: "",
expected: true, // Same base (empty)
},
{
name: "one empty one dev",
v1: "",
v2: "dev",
expected: true, // dev is compatible with anything
},
{
name: "prerelease versions same base",
v1: "v1.0.0-alpha",
v2: "v1.0.0-beta",
expected: true, // Same base 1.0.0
},
{
name: "version with rc suffix",
v1: "v2.0.0-rc1",
v2: "v2.0.0",
expected: true, // Same base 2.0.0
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := versionsCompatible(tt.v1, tt.v2)
assert.Equal(t, tt.expected, result)
})
}
}
// TestExtractBaseVersion_EdgeCases tests extractBaseVersion edge cases.
func TestExtractBaseVersion_EdgeCases(t *testing.T) {
tests := []struct {
name string
version string
expected string
}{
{
name: "version starting with hyphen",
version: "-dirty",
expected: "-dirty", // hyphen at index 0 is not > 0, so no truncation
},
{
name: "just v",
version: "v",
expected: "",
},
{
name: "multiple hyphens",
version: "v1.0.0-alpha-beta-gamma",
expected: "1.0.0",
},
{
name: "no hyphen at all",
version: "v2.0.0",
expected: "2.0.0",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := extractBaseVersion(tt.version)
assert.Equal(t, tt.expected, result)
})
}
}
// TestProjectIDWithName_RelativePath tests ProjectIDWithName with relative paths.
func TestProjectIDWithName_RelativePath(t *testing.T) {
// Relative paths should be converted to absolute
result := ProjectIDWithName(".")
assert.NotEmpty(t, result)
assert.Contains(t, result, "_")
}
// TestProjectIDWithName_DeepPath tests ProjectIDWithName with deep paths.
func TestProjectIDWithName_DeepPath(t *testing.T) {
result := ProjectIDWithName("/a/very/deep/nested/path/to/project")
assert.Contains(t, result, "project_")
assert.NotEmpty(t, result)
}
// TestPOST_EmptyBody tests POST with empty body.
func TestPOST_EmptyBody(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, http.MethodPost, r.Method)
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
}))
defer server.Close()
var port int
_, err := fmt.Sscanf(server.URL, "http://127.0.0.1:%d", &port)
require.NoError(t, err)
result, err := POST(port, "/test", map[string]string{})
require.NoError(t, err)
assert.NotNil(t, result)
}
// TestGET_WithQueryParams tests GET with query parameters.
func TestGET_WithQueryParams(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, http.MethodGet, r.Method)
assert.Equal(t, "/test?foo=bar", r.URL.String())
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
}))
defer server.Close()
var port int
_, err := fmt.Sscanf(server.URL, "http://127.0.0.1:%d", &port)
require.NoError(t, err)
result, err := GET(port, "/test?foo=bar")
require.NoError(t, err)
assert.NotNil(t, result)
}
// TestHookResponse_RoundTrip tests JSON marshal/unmarshal round-trip.
func TestHookResponse_RoundTrip(t *testing.T) {
original := HookResponse{Continue: true}
data, err := json.Marshal(original)
require.NoError(t, err)
var decoded HookResponse
err = json.Unmarshal(data, &decoded)
require.NoError(t, err)
assert.Equal(t, original.Continue, decoded.Continue)
}
// TestBaseInput_RoundTrip tests BaseInput JSON round-trip.
func TestBaseInput_RoundTrip(t *testing.T) {
original := BaseInput{
SessionID: "test-session",
CWD: "/home/user/project",
PermissionMode: "standard",
HookEventName: "session-start",
}
data, err := json.Marshal(original)
require.NoError(t, err)
var decoded BaseInput
err = json.Unmarshal(data, &decoded)
require.NoError(t, err)
assert.Equal(t, original.SessionID, decoded.SessionID)
assert.Equal(t, original.CWD, decoded.CWD)
assert.Equal(t, original.PermissionMode, decoded.PermissionMode)
assert.Equal(t, original.HookEventName, decoded.HookEventName)
}
// TestHookContext_RawInput tests HookContext with different raw input types.
func TestHookContext_RawInput(t *testing.T) {
tests := []struct {
name string
rawInput []byte
}{
{
name: "json object",
rawInput: []byte(`{"key":"value"}`),
},
{
name: "json array",
rawInput: []byte(`[1,2,3]`),
},
{
name: "empty object",
rawInput: []byte(`{}`),
},
{
name: "nil input",
rawInput: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := HookContext{
HookName: "test",
RawInput: tt.rawInput,
}
assert.Equal(t, tt.rawInput, ctx.RawInput)
})
}
}
// TestDefaultWorkerPort tests that the default port constant is valid.
func TestDefaultWorkerPort(t *testing.T) {
assert.Greater(t, DefaultWorkerPort, 1024, "Default port should be above privileged port range")
assert.Less(t, DefaultWorkerPort, 65535, "Default port should be valid TCP port")
}
// TestHealthCheckTimeout tests the health check timeout is reasonable.
func TestHealthCheckTimeout(t *testing.T) {
assert.Greater(t, HealthCheckTimeout, 100*time.Millisecond)
assert.Less(t, HealthCheckTimeout, 10*time.Second)
}
// TestStartupTimeout tests the startup timeout is reasonable.
func TestStartupTimeout(t *testing.T) {
assert.Greater(t, StartupTimeout, 5*time.Second)
assert.LessOrEqual(t, StartupTimeout, time.Minute)
}