112 lines
3.4 KiB
Go
112 lines
3.4 KiB
Go
// Package apitest regroupe les tests unitaires et de sécurité de l'API Omnex
|
|
// (tests en boîte noire, isolés du code de production dans un dossier dédié).
|
|
package apitest
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
"github.com/omnex/control-plane/api/internal/auth"
|
|
"github.com/omnex/control-plane/api/internal/config"
|
|
"github.com/omnex/control-plane/api/internal/demos"
|
|
"github.com/omnex/control-plane/api/internal/leads"
|
|
"github.com/omnex/control-plane/api/internal/router"
|
|
"github.com/omnex/control-plane/api/internal/session"
|
|
)
|
|
|
|
const testSecret = "01234567890123456789012345678901"
|
|
|
|
// seedUsers : UserStore mémoire pour les tests.
|
|
type seedUsers map[string]auth.User
|
|
|
|
func (m seedUsers) ByUsername(username string) (auth.User, bool) { u, ok := m[username]; return u, ok }
|
|
|
|
func (m seedUsers) Create(username, passwordHash string, role auth.Role) (auth.User, error) {
|
|
u := auth.User{ID: "u-" + username, Username: username, PasswordHash: passwordHash, Role: role}
|
|
m[username] = u
|
|
return u, nil
|
|
}
|
|
|
|
// testEnv : dépendances assemblées pour un test HTTP.
|
|
type testEnv struct {
|
|
engine *gin.Engine
|
|
issuer *auth.Issuer
|
|
sessions session.Manager
|
|
}
|
|
|
|
func newTestEnv(t *testing.T) testEnv {
|
|
return newTestEnvOpts(t)
|
|
}
|
|
|
|
// newTestEnvOpts permet de configurer un code d'inscription.
|
|
func newTestEnvOpts(t *testing.T) testEnv {
|
|
t.Helper()
|
|
gin.SetMode(gin.TestMode)
|
|
iss := auth.NewIssuer([]byte(testSecret), time.Minute)
|
|
sessions := session.NewMemManager(time.Hour)
|
|
|
|
hash, _ := auth.HashPassword("correct-horse")
|
|
users := seedUsers{"sales": {ID: "u1", Username: "sales", PasswordHash: hash, Role: auth.RoleClient}}
|
|
|
|
demoSvc := demos.NewService(demos.NewMemStore(), demos.NewMemPool(), demos.NoopProvisioner{}, demos.Config{})
|
|
|
|
deps := router.Deps{
|
|
Cfg: config.Config{Env: "test", AllowedOrigins: []string{"http://localhost:5173"}},
|
|
Issuer: iss,
|
|
Sessions: sessions,
|
|
AuthH: auth.NewHandler(users, sessions, iss, false),
|
|
LeadsH: leads.NewHandler(leads.NewMemStore()),
|
|
DemosH: demos.NewHandler(demoSvc),
|
|
}
|
|
return testEnv{engine: router.New(deps), issuer: iss, sessions: sessions}
|
|
}
|
|
|
|
// token : JWT commercial (rôle sales) adossé à une vraie session Redis(mem).
|
|
func (e testEnv) token(t *testing.T) string { return e.tokenAs(t, auth.RoleClient) }
|
|
|
|
// adminToken : JWT administrateur.
|
|
func (e testEnv) adminToken(t *testing.T) string { return e.tokenAs(t, auth.RoleAdmin) }
|
|
|
|
func (e testEnv) tokenAs(t *testing.T, role auth.Role) string {
|
|
t.Helper()
|
|
sid, err := e.sessions.Create(context.Background(), session.Session{UserID: "u1", Role: string(role)})
|
|
if err != nil {
|
|
t.Fatalf("create session: %v", err)
|
|
}
|
|
tok, err := e.issuer.Issue("u1", role, sid)
|
|
if err != nil {
|
|
t.Fatalf("issue token: %v", err)
|
|
}
|
|
return tok
|
|
}
|
|
|
|
func (e testEnv) do(method, path, token string, body any) *httptest.ResponseRecorder {
|
|
var buf bytes.Buffer
|
|
if body != nil {
|
|
_ = json.NewEncoder(&buf).Encode(body)
|
|
}
|
|
req := httptest.NewRequest(method, path, &buf)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
if token != "" {
|
|
req.Header.Set("Authorization", "Bearer "+token)
|
|
}
|
|
w := httptest.NewRecorder()
|
|
e.engine.ServeHTTP(w, req)
|
|
return w
|
|
}
|
|
|
|
func decode[T any](t *testing.T, w *httptest.ResponseRecorder) T {
|
|
t.Helper()
|
|
var v T
|
|
if err := json.Unmarshal(w.Body.Bytes(), &v); err != nil {
|
|
t.Fatalf("decode: %v — body=%s", err, w.Body)
|
|
}
|
|
return v
|
|
}
|