Files
AetherForge/server/internal/api/blueprint_handler_test.go

160 lines
4.9 KiB
Go

package api
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
"github.com/go-chi/chi/v5"
)
func newTestBlueprintHandler(t *testing.T) (*BlueprintHandler, string) {
t.Helper()
dataDir := t.TempDir()
return NewBlueprintHandler(dataDir), dataDir
}
func TestSanitizeFilename(t *testing.T) {
if sanitizeFilename(" my preset ") != "my preset" {
t.Fatalf("trim failed: %q", sanitizeFilename(" my preset "))
}
if sanitizeFilename("../../../etc/passwd") == "" || strings.Contains(sanitizeFilename("../../../etc/passwd"), "..") {
t.Fatalf("traversal not sanitized: %q", sanitizeFilename("../../../etc/passwd"))
}
if sanitizeFilename("bad/name") != "badname" {
t.Fatalf("slashes removed: %q", sanitizeFilename("bad/name"))
}
}
func TestBlueprintListEmpty(t *testing.T) {
h, _ := newTestBlueprintHandler(t)
req := httptest.NewRequest(http.MethodGet, "/api/v1/blueprints", nil)
rec := httptest.NewRecorder()
h.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status %d body %s", rec.Code, rec.Body.String())
}
var list []BlueprintInfo
if err := json.Unmarshal(rec.Body.Bytes(), &list); err != nil {
t.Fatal(err)
}
if len(list) != 0 {
t.Fatalf("expected empty list, got %d", len(list))
}
}
func TestBlueprintSaveGetDelete(t *testing.T) {
h, dataDir := newTestBlueprintHandler(t)
saveBody, _ := json.Marshal(map[string]interface{}{
"name": "fleet-default",
"data": map[string]interface{}{"pool_host": "pool.example.com", "threads": 4},
})
req := httptest.NewRequest(http.MethodPost, "/api/v1/blueprints", bytes.NewReader(saveBody))
rec := httptest.NewRecorder()
h.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("save status %d body %s", rec.Code, rec.Body.String())
}
filePath := filepath.Join(dataDir, "blueprints", "fleet-default.json")
if _, err := os.Stat(filePath); err != nil {
t.Fatalf("blueprint file missing: %v", err)
}
req = httptest.NewRequest(http.MethodGet, "/api/v1/blueprints", nil)
rec = httptest.NewRecorder()
h.ServeHTTP(rec, req)
var listed []BlueprintInfo
if err := json.Unmarshal(rec.Body.Bytes(), &listed); err != nil {
t.Fatal(err)
}
if len(listed) != 1 || listed[0].Name != "fleet-default" {
t.Fatalf("list after save: %+v", listed)
}
r := chi.NewRouter()
r.Get("/blueprints/{name}", h.GetBlueprint)
req = httptest.NewRequest(http.MethodGet, "/blueprints/fleet-default", nil)
rec = httptest.NewRecorder()
r.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("get status %d body %s", rec.Code, rec.Body.String())
}
if !strings.Contains(rec.Body.String(), "pool.example.com") {
t.Fatalf("unexpected blueprint body: %s", rec.Body.String())
}
req = httptest.NewRequest(http.MethodDelete, "/api/v1/blueprints?name=fleet-default", nil)
rec = httptest.NewRecorder()
h.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("delete status %d body %s", rec.Code, rec.Body.String())
}
if _, err := os.Stat(filePath); !os.IsNotExist(err) {
t.Fatal("blueprint file should be removed")
}
}
func TestBlueprintSaveValidationErrors(t *testing.T) {
h, _ := newTestBlueprintHandler(t)
req := httptest.NewRequest(http.MethodPost, "/api/v1/blueprints", bytes.NewReader([]byte(`{"name":"","data":{}}`)))
rec := httptest.NewRecorder()
h.ServeHTTP(rec, req)
if rec.Code != http.StatusBadRequest {
t.Fatalf("empty name should be 400, got %d", rec.Code)
}
req = httptest.NewRequest(http.MethodPost, "/api/v1/blueprints", bytes.NewReader([]byte(`not-json`)))
rec = httptest.NewRecorder()
h.ServeHTTP(rec, req)
if rec.Code != http.StatusBadRequest {
t.Fatalf("invalid json should be 400, got %d", rec.Code)
}
req = httptest.NewRequest(http.MethodPost, "/api/v1/blueprints", bytes.NewReader([]byte(`{"name":"ok","data":}`)))
rec = httptest.NewRecorder()
h.ServeHTTP(rec, req)
if rec.Code != http.StatusBadRequest {
t.Fatalf("malformed data json should be 400, got %d", rec.Code)
}
}
func TestBlueprintGetNotFound(t *testing.T) {
h, _ := newTestBlueprintHandler(t)
r := chi.NewRouter()
r.Get("/blueprints/{name}", h.GetBlueprint)
req := httptest.NewRequest(http.MethodGet, "/blueprints/missing", nil)
rec := httptest.NewRecorder()
r.ServeHTTP(rec, req)
if rec.Code != http.StatusNotFound {
t.Fatalf("expected 404, got %d", rec.Code)
}
}
func TestBlueprintDeleteMissingName(t *testing.T) {
h, _ := newTestBlueprintHandler(t)
req := httptest.NewRequest(http.MethodDelete, "/api/v1/blueprints", nil)
rec := httptest.NewRecorder()
h.ServeHTTP(rec, req)
if rec.Code != http.StatusBadRequest {
t.Fatalf("expected 400, got %d", rec.Code)
}
}
func TestBlueprintMethodNotAllowed(t *testing.T) {
h, _ := newTestBlueprintHandler(t)
req := httptest.NewRequest(http.MethodPatch, "/api/v1/blueprints", nil)
rec := httptest.NewRecorder()
h.ServeHTTP(rec, req)
if rec.Code != http.StatusMethodNotAllowed {
t.Fatalf("expected 405, got %d", rec.Code)
}
}