Files
omnex/control-plane/api/internal/projects/handler_test.go
T
Xor290 8d857faa45
ci-api / test (push) Successful in 23m32s
ci-web / test (push) Successful in 14m7s
chore: build
2026-09-20 18:52:09 +02:00

138 lines
4.7 KiB
Go

package projects
import (
"encoding/json"
"errors"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/gin-gonic/gin"
"github.com/omnex/control-plane/api/internal/demos"
)
func newTestRouter(t *testing.T) (*gin.Engine, *fakeProv) {
t.Helper()
gin.SetMode(gin.TestMode)
svc, prov, _ := newTestService(t)
h := NewHandler(svc)
r := gin.New()
r.POST("/projects", h.Create)
r.GET("/projects", h.List)
r.GET("/projects/:id", h.Get)
r.DELETE("/projects/:id", h.Delete)
r.POST("/projects/:id/extend", h.Extend)
r.GET("/projects/:id/details", h.Details)
return r, prov
}
func do(r *gin.Engine, method, path, body string) *httptest.ResponseRecorder {
req := httptest.NewRequest(method, path, strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
return w
}
const goodBody = `{"client_name":"Dupont","months":%s,"admin_username":"admin","admin_password":"un-mot-de-passe-solide"}`
func body(months string) string { return strings.Replace(goodBody, "%s", months, 1) }
func TestHandlerCreate_MonthsBounds(t *testing.T) {
r, _ := newTestRouter(t)
for _, months := range []string{"0", "13", "-1", `"x"`} {
if w := do(r, "POST", "/projects", body(months)); w.Code != http.StatusBadRequest {
t.Errorf("months=%s : HTTP %d, attendu 400", months, w.Code)
}
}
for _, months := range []string{"1", "12"} {
w := do(r, "POST", "/projects", body(months))
if w.Code != http.StatusAccepted {
t.Errorf("months=%s : HTTP %d, attendu 202 (%s)", months, w.Code, w.Body)
}
}
}
func TestHandlerCreate_NeverEchoesAdminPassword(t *testing.T) {
r, prov := newTestRouter(t)
w := do(r, "POST", "/projects", body("2"))
if w.Code != http.StatusAccepted {
t.Fatalf("HTTP %d: %s", w.Code, w.Body)
}
<-prov.provisioned
if strings.Contains(w.Body.String(), "un-mot-de-passe-solide") {
t.Error("le mot de passe admin figure dans la réponse")
}
if list := do(r, "GET", "/projects", ""); strings.Contains(list.Body.String(), "un-mot-de-passe-solide") {
t.Error("le mot de passe admin figure dans la liste")
}
}
func TestHandlerCreate_RejectsWeakPasswordAndBadAdminName(t *testing.T) {
r, _ := newTestRouter(t)
for name, payload := range map[string]string{
"mot de passe court": `{"client_name":"D","months":1,"admin_username":"admin","admin_password":"court"}`,
"admin non alphanum": `{"client_name":"D","months":1,"admin_username":"ad min","admin_password":"un-mot-de-passe-solide"}`,
"client manquant": `{"months":1,"admin_username":"admin","admin_password":"un-mot-de-passe-solide"}`,
"trop d'admins": `{"client_name":"D","months":1,"admin_username":"admin","admin_password":"un-mot-de-passe-solide","admin_number":21}`,
} {
if w := do(r, "POST", "/projects", payload); w.Code != http.StatusBadRequest {
t.Errorf("%s : HTTP %d, attendu 400", name, w.Code)
}
}
}
func TestHandlerExtend_BoundsAndNotFound(t *testing.T) {
r, _ := newTestRouter(t)
if w := do(r, "POST", "/projects/inconnu/extend", `{"months":2}`); w.Code != http.StatusNotFound {
t.Errorf("projet inconnu : HTTP %d, attendu 404", w.Code)
}
if w := do(r, "POST", "/projects/inconnu/extend", `{"months":13}`); w.Code != http.StatusBadRequest {
t.Errorf("13 mois : HTTP %d, attendu 400", w.Code)
}
if w := do(r, "POST", "/projects/inconnu/extend", `{}`); w.Code != http.StatusBadRequest {
t.Errorf("sans durée : HTTP %d, attendu 400", w.Code)
}
}
func TestHandlerDetails(t *testing.T) {
r, prov := newTestRouter(t)
prov.state = demos.ResourceState{API: demos.ComponentState{Phase: "Running"}}
if w := do(r, "GET", "/projects/inconnu/details", ""); w.Code != http.StatusNotFound {
t.Errorf("projet inconnu : %d, attendu 404", w.Code)
}
w := do(r, "POST", "/projects", body("1"))
if w.Code != http.StatusAccepted {
t.Fatalf("Create: %d %s", w.Code, w.Body)
}
var created Project
if err := json.Unmarshal(w.Body.Bytes(), &created); err != nil {
t.Fatal(err)
}
<-prov.provisioned
w = do(r, "GET", "/projects/"+created.ID+"/details", "")
if w.Code != http.StatusOK {
t.Fatalf("Details: %d %s", w.Code, w.Body)
}
var got struct {
Namespace string `json:"namespace"`
State demos.ResourceState `json:"state"`
}
if err := json.Unmarshal(w.Body.Bytes(), &got); err != nil {
t.Fatal(err)
}
if got.Namespace != created.Namespace || got.State.API.Phase != "Running" {
t.Errorf("réponse inattendue : %+v", got)
}
prov.stateErr = errors.New("api k8s indisponible")
if w := do(r, "GET", "/projects/"+created.ID+"/details", ""); w.Code != http.StatusInternalServerError || strings.Contains(w.Body.String(), "k8s") {
t.Errorf("erreur cluster : %d %s (500 sans détail interne attendu)", w.Code, w.Body)
}
}