138 lines
4.7 KiB
Go
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)
|
|
}
|
|
}
|