Skip to content

Commit 9f237ee

Browse files
Thomas StrombergThomas Stromberg
authored andcommitted
Increase test coverage to 80%
1 parent 4e6165f commit 9f237ee

17 files changed

+2884
-30
lines changed

.github/workflows/test.yml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
name: Test
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
pull_request:
7+
branches: [ main ]
8+
9+
jobs:
10+
test:
11+
name: Run Tests
12+
runs-on: ubuntu-latest
13+
14+
steps:
15+
- name: Checkout code
16+
uses: actions/checkout@v4
17+
18+
- name: Set up Go
19+
uses: actions/setup-go@v5
20+
with:
21+
go-version: '1.23'
22+
cache: true
23+
24+
- name: Download dependencies
25+
run: go mod download
26+
27+
- name: Run tests
28+
run: make test
29+
30+
- name: Upload coverage to artifacts
31+
if: always()
32+
uses: actions/upload-artifact@v4
33+
with:
34+
name: coverage-report
35+
path: coverage.out
36+
retention-days: 30

Makefile

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
.PHONY: test
2+
test:
3+
@echo "Running tests with race detection and coverage..."
4+
@go test -race -coverprofile=coverage.out -covermode=atomic ./...
5+
@echo ""
6+
@echo "Coverage by package:"
7+
@go tool cover -func=coverage.out | grep -E '^(github.com|total:)' | grep -v 'total:' || true
8+
@echo ""
9+
@echo "Checking coverage threshold (80%)..."
10+
@./scripts/check-coverage.sh coverage.out 80
111

212
# BEGIN: lint-install .
313
# http://github.com/codeGROOVE-dev/lint-install

pkg/prx/cache.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -211,11 +211,11 @@ func (c *CacheClient) saveCache(ctx context.Context, key string, v any) error {
211211
}
212212

213213
func (c *CacheClient) cleanOldCaches() {
214-
c.logger.DebugContext(context.Background(), "cleaning old cache files")
214+
c.logger.Debug("cleaning old cache files")
215215

216216
entries, err := os.ReadDir(c.cacheDir)
217217
if err != nil {
218-
c.logger.ErrorContext(context.Background(), "failed to read cache directory", "error", err)
218+
c.logger.Error("failed to read cache directory", "error", err)
219219
return
220220
}
221221

@@ -235,14 +235,14 @@ func (c *CacheClient) cleanOldCaches() {
235235
if info.ModTime().Before(cutoff) {
236236
path := filepath.Join(c.cacheDir, entry.Name())
237237
if err := os.Remove(path); err != nil {
238-
c.logger.WarnContext(context.Background(), "failed to remove old cache file", "path", path, "error", err)
238+
c.logger.Warn("failed to remove old cache file", "path", path, "error", err)
239239
} else {
240240
removed++
241241
}
242242
}
243243
}
244244

245245
if removed > 0 {
246-
c.logger.InfoContext(context.Background(), "cleaned old cache files", "removed", removed)
246+
c.logger.Info("cleaned old cache files", "removed", removed)
247247
}
248248
}

pkg/prx/cache_extended_test.go

Lines changed: 316 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,316 @@
1+
package prx
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"os"
8+
"path/filepath"
9+
"testing"
10+
"time"
11+
)
12+
13+
func TestNewCacheClient(t *testing.T) {
14+
tmpDir := t.TempDir()
15+
16+
tests := []struct {
17+
name string
18+
dir string
19+
wantErr bool
20+
}{
21+
{
22+
name: "valid absolute path",
23+
dir: filepath.Join(tmpDir, "cache"),
24+
wantErr: false,
25+
},
26+
{
27+
name: "relative path should error",
28+
dir: "relative/path",
29+
wantErr: true,
30+
},
31+
}
32+
33+
for _, tt := range tests {
34+
t.Run(tt.name, func(t *testing.T) {
35+
client, err := NewCacheClient("test-token", tt.dir)
36+
if tt.wantErr {
37+
if err == nil {
38+
t.Errorf("Expected error but got none")
39+
}
40+
} else {
41+
if err != nil {
42+
t.Errorf("Unexpected error: %v", err)
43+
}
44+
if client == nil {
45+
t.Errorf("Expected client but got nil")
46+
}
47+
if client.cacheDir != tt.dir {
48+
t.Errorf("Expected cache dir %s, got %s", tt.dir, client.cacheDir)
49+
}
50+
}
51+
})
52+
}
53+
}
54+
55+
func TestCacheClient_CacheKey(t *testing.T) {
56+
tmpDir := t.TempDir()
57+
client, err := NewCacheClient("test-token", tmpDir)
58+
if err != nil {
59+
t.Fatalf("Failed to create cache client: %v", err)
60+
}
61+
62+
tests := []struct {
63+
name string
64+
parts []string
65+
}{
66+
{
67+
name: "simple key",
68+
parts: []string{"pr", "owner", "repo", "1"},
69+
},
70+
{
71+
name: "different key",
72+
parts: []string{"pr", "owner", "repo", "2"},
73+
},
74+
}
75+
76+
for _, tt := range tests {
77+
t.Run(tt.name, func(t *testing.T) {
78+
key1 := client.cacheKey(tt.parts...)
79+
key2 := client.cacheKey(tt.parts...)
80+
81+
// Same inputs should produce same key
82+
if key1 != key2 {
83+
t.Errorf("Same inputs produced different keys")
84+
}
85+
86+
// Key should be a hex string (sha256)
87+
if len(key1) != 64 {
88+
t.Errorf("Expected 64 character hex string, got %d characters", len(key1))
89+
}
90+
})
91+
}
92+
93+
// Different inputs should produce different keys
94+
key1 := client.cacheKey("pr", "owner", "repo", "1")
95+
key2 := client.cacheKey("pr", "owner", "repo", "2")
96+
if key1 == key2 {
97+
t.Errorf("Different inputs produced same key")
98+
}
99+
}
100+
101+
func TestCacheClient_SaveAndLoad(t *testing.T) {
102+
tmpDir := t.TempDir()
103+
client, err := NewCacheClient("test-token", tmpDir)
104+
if err != nil {
105+
t.Fatalf("Failed to create cache client: %v", err)
106+
}
107+
108+
ctx := context.Background()
109+
key := "test-key"
110+
111+
entry := cacheEntry{
112+
UpdatedAt: time.Now().Add(-1 * time.Hour),
113+
CachedAt: time.Now(),
114+
Data: json.RawMessage(`{"test": "data", "number": 123}`),
115+
}
116+
117+
// Save cache
118+
err = client.saveCache(ctx, key, entry)
119+
if err != nil {
120+
t.Fatalf("Failed to save cache: %v", err)
121+
}
122+
123+
// Verify file was created
124+
cachePath := filepath.Join(tmpDir, key+".json")
125+
if _, err := os.Stat(cachePath); os.IsNotExist(err) {
126+
t.Fatalf("Cache file was not created")
127+
}
128+
129+
// Load cache
130+
var loaded cacheEntry
131+
if !client.loadCache(ctx, key, &loaded) {
132+
t.Fatalf("Failed to load cache")
133+
}
134+
135+
// Verify data
136+
if !loaded.UpdatedAt.Equal(entry.UpdatedAt) {
137+
t.Errorf("UpdatedAt mismatch: expected %v, got %v", entry.UpdatedAt, loaded.UpdatedAt)
138+
}
139+
// Compare JSON content, not formatting
140+
var entryJSON, loadedJSON map[string]interface{}
141+
if err := json.Unmarshal(entry.Data, &entryJSON); err != nil {
142+
t.Fatalf("Failed to unmarshal entry data: %v", err)
143+
}
144+
if err := json.Unmarshal(loaded.Data, &loadedJSON); err != nil {
145+
t.Fatalf("Failed to unmarshal loaded data: %v", err)
146+
}
147+
if fmt.Sprintf("%v", entryJSON) != fmt.Sprintf("%v", loadedJSON) {
148+
t.Errorf("Data mismatch: expected %v, got %v", entryJSON, loadedJSON)
149+
}
150+
}
151+
152+
func TestCacheClient_LoadNonExistent(t *testing.T) {
153+
tmpDir := t.TempDir()
154+
client, err := NewCacheClient("test-token", tmpDir)
155+
if err != nil {
156+
t.Fatalf("Failed to create cache client: %v", err)
157+
}
158+
159+
ctx := context.Background()
160+
161+
var entry cacheEntry
162+
if client.loadCache(ctx, "nonexistent", &entry) {
163+
t.Errorf("Expected load to fail for nonexistent key")
164+
}
165+
}
166+
167+
func TestCacheClient_LoadInvalidJSON(t *testing.T) {
168+
tmpDir := t.TempDir()
169+
client, err := NewCacheClient("test-token", tmpDir)
170+
if err != nil {
171+
t.Fatalf("Failed to create cache client: %v", err)
172+
}
173+
174+
// Write invalid JSON to cache file
175+
key := "invalid"
176+
cachePath := filepath.Join(tmpDir, key+".json")
177+
err = os.WriteFile(cachePath, []byte(`{invalid json}`), 0o600)
178+
if err != nil {
179+
t.Fatalf("Failed to write invalid cache file: %v", err)
180+
}
181+
182+
ctx := context.Background()
183+
var entry cacheEntry
184+
if client.loadCache(ctx, key, &entry) {
185+
t.Errorf("Expected load to fail for invalid JSON")
186+
}
187+
}
188+
189+
func TestCacheClient_CleanOldCaches(t *testing.T) {
190+
tmpDir := t.TempDir()
191+
client, err := NewCacheClient("test-token", tmpDir)
192+
if err != nil {
193+
t.Fatalf("Failed to create cache client: %v", err)
194+
}
195+
196+
// Create some old cache files
197+
oldFile := filepath.Join(tmpDir, "old.json")
198+
err = os.WriteFile(oldFile, []byte(`{"test": "old"}`), 0o600)
199+
if err != nil {
200+
t.Fatalf("Failed to create old file: %v", err)
201+
}
202+
203+
// Set the modification time to be very old
204+
oldTime := time.Now().Add(-30 * 24 * time.Hour)
205+
err = os.Chtimes(oldFile, oldTime, oldTime)
206+
if err != nil {
207+
t.Fatalf("Failed to set old file time: %v", err)
208+
}
209+
210+
// Create a recent cache file
211+
recentFile := filepath.Join(tmpDir, "recent.json")
212+
err = os.WriteFile(recentFile, []byte(`{"test": "recent"}`), 0o600)
213+
if err != nil {
214+
t.Fatalf("Failed to create recent file: %v", err)
215+
}
216+
217+
// Clean old caches
218+
client.cleanOldCaches()
219+
220+
// Old file should be removed
221+
if _, err := os.Stat(oldFile); !os.IsNotExist(err) {
222+
t.Errorf("Old file was not removed")
223+
}
224+
225+
// Recent file should still exist
226+
if _, err := os.Stat(recentFile); os.IsNotExist(err) {
227+
t.Errorf("Recent file was removed")
228+
}
229+
}
230+
231+
func TestCacheClient_CleanOldCaches_SkipsDirectories(t *testing.T) {
232+
tmpDir := t.TempDir()
233+
client, err := NewCacheClient("test-token", tmpDir)
234+
if err != nil {
235+
t.Fatalf("Failed to create cache client: %v", err)
236+
}
237+
238+
// Create a subdirectory
239+
subDir := filepath.Join(tmpDir, "subdir")
240+
err = os.Mkdir(subDir, 0o700)
241+
if err != nil {
242+
t.Fatalf("Failed to create subdirectory: %v", err)
243+
}
244+
245+
// Clean should not error on directories
246+
client.cleanOldCaches()
247+
248+
// Directory should still exist
249+
if _, err := os.Stat(subDir); os.IsNotExist(err) {
250+
t.Errorf("Subdirectory was removed")
251+
}
252+
}
253+
254+
func TestCacheClient_CleanOldCaches_SkipsNonJSON(t *testing.T) {
255+
tmpDir := t.TempDir()
256+
client, err := NewCacheClient("test-token", tmpDir)
257+
if err != nil {
258+
t.Fatalf("Failed to create cache client: %v", err)
259+
}
260+
261+
// Create a non-JSON file
262+
nonJSONFile := filepath.Join(tmpDir, "test.txt")
263+
err = os.WriteFile(nonJSONFile, []byte("test"), 0o600)
264+
if err != nil {
265+
t.Fatalf("Failed to create non-JSON file: %v", err)
266+
}
267+
268+
// Set the modification time to be very old
269+
oldTime := time.Now().Add(-30 * 24 * time.Hour)
270+
err = os.Chtimes(nonJSONFile, oldTime, oldTime)
271+
if err != nil {
272+
t.Fatalf("Failed to set old file time: %v", err)
273+
}
274+
275+
// Clean should skip non-JSON files
276+
client.cleanOldCaches()
277+
278+
// Non-JSON file should still exist
279+
if _, err := os.Stat(nonJSONFile); os.IsNotExist(err) {
280+
t.Errorf("Non-JSON file was removed")
281+
}
282+
}
283+
284+
func TestCacheClient_SaveCache_AtomicWrite(t *testing.T) {
285+
tmpDir := t.TempDir()
286+
client, err := NewCacheClient("test-token", tmpDir)
287+
if err != nil {
288+
t.Fatalf("Failed to create cache client: %v", err)
289+
}
290+
291+
ctx := context.Background()
292+
key := "atomic-test"
293+
294+
entry := cacheEntry{
295+
CachedAt: time.Now(),
296+
Data: json.RawMessage(`{"test": "data"}`),
297+
}
298+
299+
// Save cache
300+
err = client.saveCache(ctx, key, entry)
301+
if err != nil {
302+
t.Fatalf("Failed to save cache: %v", err)
303+
}
304+
305+
// Verify no .tmp file exists (atomic write should have cleaned up)
306+
tmpPath := filepath.Join(tmpDir, key+".json.tmp")
307+
if _, err := os.Stat(tmpPath); !os.IsNotExist(err) {
308+
t.Errorf("Temporary file was not cleaned up")
309+
}
310+
311+
// Verify final file exists
312+
cachePath := filepath.Join(tmpDir, key+".json")
313+
if _, err := os.Stat(cachePath); os.IsNotExist(err) {
314+
t.Errorf("Cache file was not created")
315+
}
316+
}

0 commit comments

Comments
 (0)