mirror of
https://github.com/lukaszraczylo/claude-mnemonic.git
synced 2026-06-11 00:09:28 +00:00
Increase test coverage to 45.6%
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user