Files
silo/internal/api/revision_handlers_test.go
Forbes 257e3d99ac test(api): add revision, schema, audit, and auth handler tests (#78)
Revision tests (8):
- List, get, create, update status/labels, compare, rollback
- Not-found paths for missing items and revisions

Schema tests (4):
- List schemas, get by name, form descriptor, not-found

Audit tests (4):
- Completeness summary (empty + with items), item detail, not-found

Auth tests (4):
- Get current user (authenticated + unauthenticated)
- Auth config response
- Token lifecycle: create, list, revoke
2026-02-13 15:22:28 -06:00

223 lines
6.8 KiB
Go

package api
import (
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/go-chi/chi/v5"
)
func newRevisionRouter(s *Server) http.Handler {
r := chi.NewRouter()
r.Route("/api/items/{partNumber}", func(r chi.Router) {
r.Get("/revisions", s.HandleListRevisions)
r.Get("/revisions/compare", s.HandleCompareRevisions)
r.Get("/revisions/{revision}", s.HandleGetRevision)
r.Post("/revisions", s.HandleCreateRevision)
r.Patch("/revisions/{revision}", s.HandleUpdateRevision)
r.Post("/revisions/{revision}/rollback", s.HandleRollbackRevision)
})
return r
}
func TestHandleListRevisions(t *testing.T) {
s := newTestServer(t)
router := newRevisionRouter(s)
createItemDirect(t, s, "REV-API-001", "revision list", nil)
req := httptest.NewRequest("GET", "/api/items/REV-API-001/revisions", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Fatalf("status: got %d, want %d; body: %s", w.Code, http.StatusOK, w.Body.String())
}
var revisions []RevisionResponse
if err := json.Unmarshal(w.Body.Bytes(), &revisions); err != nil {
t.Fatalf("decoding response: %v", err)
}
if len(revisions) != 1 {
t.Errorf("expected 1 revision (initial), got %d", len(revisions))
}
}
func TestHandleListRevisionsNotFound(t *testing.T) {
s := newTestServer(t)
router := newRevisionRouter(s)
req := httptest.NewRequest("GET", "/api/items/NOEXIST/revisions", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
if w.Code != http.StatusNotFound {
t.Errorf("status: got %d, want %d", w.Code, http.StatusNotFound)
}
}
func TestHandleGetRevision(t *testing.T) {
s := newTestServer(t)
router := newRevisionRouter(s)
createItemDirect(t, s, "REVGET-001", "get revision", nil)
req := httptest.NewRequest("GET", "/api/items/REVGET-001/revisions/1", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Fatalf("status: got %d, want %d; body: %s", w.Code, http.StatusOK, w.Body.String())
}
var rev RevisionResponse
if err := json.Unmarshal(w.Body.Bytes(), &rev); err != nil {
t.Fatalf("decoding response: %v", err)
}
if rev.RevisionNumber != 1 {
t.Errorf("revision_number: got %d, want 1", rev.RevisionNumber)
}
}
func TestHandleGetRevisionNotFound(t *testing.T) {
s := newTestServer(t)
router := newRevisionRouter(s)
createItemDirect(t, s, "REVNF-001", "rev not found", nil)
req := httptest.NewRequest("GET", "/api/items/REVNF-001/revisions/99", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
if w.Code != http.StatusNotFound {
t.Errorf("status: got %d, want %d", w.Code, http.StatusNotFound)
}
}
func TestHandleCreateRevision(t *testing.T) {
s := newTestServer(t)
router := newRevisionRouter(s)
createItemDirect(t, s, "REVCR-001", "create revision", nil)
body := `{"properties":{"material":"steel"},"comment":"added material"}`
req := authRequest(httptest.NewRequest("POST", "/api/items/REVCR-001/revisions", strings.NewReader(body)))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
if w.Code != http.StatusCreated {
t.Fatalf("status: got %d, want %d; body: %s", w.Code, http.StatusCreated, w.Body.String())
}
var rev RevisionResponse
if err := json.Unmarshal(w.Body.Bytes(), &rev); err != nil {
t.Fatalf("decoding response: %v", err)
}
if rev.RevisionNumber != 2 {
t.Errorf("revision_number: got %d, want 2", rev.RevisionNumber)
}
}
func TestHandleUpdateRevision(t *testing.T) {
s := newTestServer(t)
router := newRevisionRouter(s)
createItemDirect(t, s, "REVUP-001", "update revision", nil)
body := `{"status":"released","labels":["production"]}`
req := authRequest(httptest.NewRequest("PATCH", "/api/items/REVUP-001/revisions/1", strings.NewReader(body)))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Fatalf("status: got %d, want %d; body: %s", w.Code, http.StatusOK, w.Body.String())
}
var rev RevisionResponse
if err := json.Unmarshal(w.Body.Bytes(), &rev); err != nil {
t.Fatalf("decoding response: %v", err)
}
if rev.Status != "released" {
t.Errorf("status: got %q, want %q", rev.Status, "released")
}
if len(rev.Labels) != 1 || rev.Labels[0] != "production" {
t.Errorf("labels: got %v, want [production]", rev.Labels)
}
}
func TestHandleCompareRevisions(t *testing.T) {
s := newTestServer(t)
router := newRevisionRouter(s)
// Create item with properties, then create second revision with changed properties
cost := 10.0
createItemDirect(t, s, "REVCMP-001", "compare revisions", &cost)
body := `{"properties":{"standard_cost":20,"material":"aluminum"},"comment":"updated cost"}`
req := authRequest(httptest.NewRequest("POST", "/api/items/REVCMP-001/revisions", strings.NewReader(body)))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
if w.Code != http.StatusCreated {
t.Fatalf("create rev 2: status %d; body: %s", w.Code, w.Body.String())
}
// Compare rev 1 vs rev 2
req = httptest.NewRequest("GET", "/api/items/REVCMP-001/revisions/compare?from=1&to=2", nil)
w = httptest.NewRecorder()
router.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Fatalf("status: got %d, want %d; body: %s", w.Code, http.StatusOK, w.Body.String())
}
var diff RevisionDiffResponse
if err := json.Unmarshal(w.Body.Bytes(), &diff); err != nil {
t.Fatalf("decoding response: %v", err)
}
if diff.FromRevision != 1 || diff.ToRevision != 2 {
t.Errorf("revisions: got from=%d to=%d, want from=1 to=2", diff.FromRevision, diff.ToRevision)
}
}
func TestHandleRollbackRevision(t *testing.T) {
s := newTestServer(t)
router := newRevisionRouter(s)
createItemDirect(t, s, "REVRB-001", "rollback test", nil)
// Create rev 2
body := `{"properties":{"version":"v2"},"comment":"version 2"}`
req := authRequest(httptest.NewRequest("POST", "/api/items/REVRB-001/revisions", strings.NewReader(body)))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
if w.Code != http.StatusCreated {
t.Fatalf("create rev 2: status %d; body: %s", w.Code, w.Body.String())
}
// Rollback to rev 1 — should create rev 3
body = `{"comment":"rolling back"}`
req = authRequest(httptest.NewRequest("POST", "/api/items/REVRB-001/revisions/1/rollback", strings.NewReader(body)))
req.Header.Set("Content-Type", "application/json")
w = httptest.NewRecorder()
router.ServeHTTP(w, req)
if w.Code != http.StatusCreated {
t.Fatalf("status: got %d, want %d; body: %s", w.Code, http.StatusCreated, w.Body.String())
}
var rev RevisionResponse
if err := json.Unmarshal(w.Body.Bytes(), &rev); err != nil {
t.Fatalf("decoding response: %v", err)
}
if rev.RevisionNumber != 3 {
t.Errorf("revision_number: got %d, want 3", rev.RevisionNumber)
}
}