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
223 lines
6.8 KiB
Go
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)
|
|
}
|
|
}
|