Skip to content

Commit ed63699

Browse files
author
Alpine
committed
chore: add lint workflow and extensive unit tests for jobs, metrics and builtins
1 parent 003d6e9 commit ed63699

4 files changed

Lines changed: 250 additions & 0 deletions

File tree

.github/workflows/lint.yml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
name: Lint
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
permissions:
10+
contents: read
11+
pull-requests: read
12+
13+
jobs:
14+
golangci:
15+
name: lint
16+
runs-on: ubuntu-latest
17+
steps:
18+
- uses: actions/checkout@v4
19+
- uses: actions/setup-go@v5
20+
with:
21+
go-version: '1.23'
22+
- name: golangci-lint
23+
uses: golangci/golangci-lint-action@v6
24+
with:
25+
version: v1.60
26+
args: --timeout=5m

internal/jobs/jobs_test.go

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
package jobs
2+
3+
import (
4+
"testing"
5+
"time"
6+
)
7+
8+
func TestJobStoreCreateAndGet(t *testing.T) {
9+
store := NewStore()
10+
job, err := store.Create()
11+
if err != nil {
12+
t.Fatal(err)
13+
}
14+
if job.Status != StatusPending {
15+
t.Fatalf("expected pending, got %s", job.Status)
16+
}
17+
18+
fetched, ok := store.Get(job.ID)
19+
if !ok || fetched.ID != job.ID {
20+
t.Fatal("failed to get job")
21+
}
22+
}
23+
24+
func TestJobStoreUpdateProgress(t *testing.T) {
25+
store := NewStore()
26+
job, _ := store.Create()
27+
28+
store.UpdateProgress(job.ID, 50)
29+
fetched, _ := store.Get(job.ID)
30+
if fetched.Progress != 50 || fetched.Status != StatusSlicing {
31+
t.Fatalf("expected progress 50 and status slicing, got %d %s", fetched.Progress, fetched.Status)
32+
}
33+
34+
// Should cap at 99
35+
store.UpdateProgress(job.ID, 150)
36+
fetched, _ = store.Get(job.ID)
37+
if fetched.Progress != 99 {
38+
t.Fatalf("expected progress capped at 99, got %d", fetched.Progress)
39+
}
40+
}
41+
42+
func TestJobStoreUpdateStatus(t *testing.T) {
43+
store := NewStore()
44+
job, _ := store.Create()
45+
46+
store.UpdateStatus(job.ID, StatusCompleted, nil)
47+
fetched, _ := store.Get(job.ID)
48+
if fetched.Status != StatusCompleted || fetched.Progress != 100 {
49+
t.Fatalf("expected completed and 100%%, got %s %d", fetched.Status, fetched.Progress)
50+
}
51+
}
52+
53+
func TestJobStoreCancel(t *testing.T) {
54+
store := NewStore()
55+
job, _ := store.Create()
56+
57+
cancelled := false
58+
store.SetCancelFunc(job.ID, func() {
59+
cancelled = true
60+
})
61+
62+
if err := store.Cancel(job.ID); err != nil {
63+
t.Fatal(err)
64+
}
65+
if !cancelled {
66+
t.Fatal("cancel func not called")
67+
}
68+
69+
fetched, _ := store.Get(job.ID)
70+
if fetched.Status != StatusFailed {
71+
t.Fatalf("expected failed status, got %s", fetched.Status)
72+
}
73+
}
74+
75+
func TestPersistentStore(t *testing.T) {
76+
dir := t.TempDir()
77+
store := NewPersistentStore(dir)
78+
79+
job, _ := store.Create()
80+
store.UpdateStatus(job.ID, StatusSlicing, nil)
81+
82+
// Recreate store to simulate restart
83+
store2 := NewPersistentStore(dir)
84+
fetched, ok := store2.Get(job.ID)
85+
if !ok {
86+
t.Fatal("expected job to be recovered")
87+
}
88+
if fetched.Status != StatusFailed {
89+
t.Fatalf("expected slicing job to become failed after restart, got %s", fetched.Status)
90+
}
91+
92+
// Test completed job recovery
93+
job2, _ := store.Create()
94+
store.UpdateStatus(job2.ID, StatusCompleted, nil)
95+
96+
store3 := NewPersistentStore(dir)
97+
fetched2, _ := store3.Get(job2.ID)
98+
if fetched2.Status != StatusCompleted {
99+
t.Fatalf("expected completed job to remain completed, got %s", fetched2.Status)
100+
}
101+
}
102+
103+
func TestStoreCleanup(t *testing.T) {
104+
store := NewStore()
105+
job, _ := store.Create()
106+
store.UpdateStatus(job.ID, StatusCompleted, nil)
107+
108+
// Fast forward time for test
109+
store.mu.Lock()
110+
store.jobs[job.ID].UpdatedAt = time.Now().Add(-2 * time.Hour)
111+
store.mu.Unlock()
112+
113+
store.Cleanup(1 * time.Hour)
114+
if _, ok := store.Get(job.ID); ok {
115+
t.Fatal("expected old completed job to be cleaned up")
116+
}
117+
}

internal/metrics/handler_test.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package metrics
2+
3+
import (
4+
"encoding/json"
5+
"net/http"
6+
"net/http/httptest"
7+
"testing"
8+
"time"
9+
10+
"github.com/Brook-sys/orca-slicer-api/internal/jobs"
11+
"github.com/Brook-sys/orca-slicer-api/internal/slicer"
12+
)
13+
14+
func TestGetMetrics(t *testing.T) {
15+
jobStore := jobs.NewStore()
16+
jobStore.Create() // Creates a pending job
17+
18+
cache := slicer.NewResultCache(time.Hour, 10)
19+
handler := Handler{
20+
JobStore: jobStore,
21+
Cache: cache,
22+
Startup: time.Now().Add(-10 * time.Second),
23+
}
24+
25+
w := httptest.NewRecorder()
26+
r := httptest.NewRequest("GET", "/metrics", nil)
27+
handler.GetMetrics(w, r)
28+
29+
if w.Code != http.StatusOK {
30+
t.Fatalf("expected 200, got %d", w.Code)
31+
}
32+
33+
var stats map[string]any
34+
if err := json.Unmarshal(w.Body.Bytes(), &stats); err != nil {
35+
t.Fatal(err)
36+
}
37+
38+
if stats["uptime_seconds"].(float64) < 9 {
39+
t.Fatalf("expected uptime >= 9s")
40+
}
41+
if stats["cache_enabled"] != true {
42+
t.Fatalf("expected cache_enabled=true")
43+
}
44+
45+
jobStats := stats["jobs"].(map[string]any)
46+
if jobStats["total"].(float64) != 1 {
47+
t.Fatalf("expected 1 total job, got %v", jobStats["total"])
48+
}
49+
}

internal/slicer/builtins_test.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package slicer
2+
3+
import (
4+
"encoding/json"
5+
"net/http"
6+
"net/http/httptest"
7+
"os"
8+
"path/filepath"
9+
"testing"
10+
)
11+
12+
func TestListBuiltinProfiles(t *testing.T) {
13+
dir := t.TempDir()
14+
printersDir := filepath.Join(dir, "printers")
15+
if err := os.MkdirAll(printersDir, 0o755); err != nil {
16+
t.Fatal(err)
17+
}
18+
if err := os.WriteFile(filepath.Join(printersDir, "A.json"), []byte("{}"), 0o644); err != nil {
19+
t.Fatal(err)
20+
}
21+
if err := os.WriteFile(filepath.Join(printersDir, "B.json"), []byte("{}"), 0o644); err != nil {
22+
t.Fatal(err)
23+
}
24+
25+
service := &Service{OrcaProfilesPath: dir}
26+
handler := Handler{Service: service}
27+
28+
w := httptest.NewRecorder()
29+
r := httptest.NewRequest("GET", "/profiles/builtins/printers", nil)
30+
r.SetPathValue("category", "printers")
31+
32+
handler.ListBuiltinProfiles(w, r)
33+
34+
if w.Code != http.StatusOK {
35+
t.Fatalf("expected 200, got %d", w.Code)
36+
}
37+
38+
var list []ProfileListItem
39+
if err := json.Unmarshal(w.Body.Bytes(), &list); err != nil {
40+
t.Fatal(err)
41+
}
42+
43+
if len(list) != 2 || list[0].Name != "A" || list[1].Name != "B" {
44+
t.Fatalf("expected sorted list A, B, got %#v", list)
45+
}
46+
}
47+
48+
func TestListBuiltinProfilesInvalidCategory(t *testing.T) {
49+
handler := Handler{}
50+
w := httptest.NewRecorder()
51+
r := httptest.NewRequest("GET", "/profiles/builtins/invalid", nil)
52+
r.SetPathValue("category", "invalid")
53+
handler.ListBuiltinProfiles(w, r)
54+
55+
if w.Code != http.StatusBadRequest {
56+
t.Fatalf("expected 400 for invalid category, got %d", w.Code)
57+
}
58+
}

0 commit comments

Comments
 (0)