- repository.Resources (insert/list/get/soft-delete scoping owner_id, UNIQUE(parent_id,name) → NAME_CONFLICT) + Devices.Upsert au register
- service.Resources : DTOs {id,name,size,mimeType,folderId,createdAt,updatedAt}, upload → UPLOAD_DIR/<device>/<id>.<ext> (nettoyage si métadonnée échoue)
- handlers : files list/get/delete/upload + folders racines ; routes consolidées dans handlers.RegisterRoutes
- dbtest package : test DB jetée par repo (open+reset+migrate, skip si PG down) ; tests repository + handlers end-to-end (register→token→CRUD, scoping cross-device, FILE_TOO_LARGE, NAME_CONFLICT)
- docs: /devices persiste le device, layout UPLOAD_DIR par device, codes NAME_CONFLICT/SERVICE_UNAVAILABLE
129 lines
3.5 KiB
Go
129 lines
3.5 KiB
Go
package handlers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
"github.com/vaultdrop/backend/dbtest"
|
|
"github.com/vaultdrop/backend/pkg/auth"
|
|
"github.com/vaultdrop/backend/repository"
|
|
"github.com/vaultdrop/backend/service"
|
|
)
|
|
|
|
const authTestURL = "postgres://vaultdrop:vaultdrop@localhost:5432/vaultdrop_handlers_auth_test?sslmode=disable"
|
|
|
|
func newTestRouterForAuth() *gin.Engine {
|
|
gin.SetMode(gin.TestMode)
|
|
r := gin.New()
|
|
r.POST("/devices", DevicesRegister)
|
|
grp := r.Group("")
|
|
grp.Use(RequireDevice)
|
|
grp.GET("/files", FilesList)
|
|
return r
|
|
}
|
|
|
|
func setTestStore(t *testing.T) *repository.Repository {
|
|
t.Helper()
|
|
conn := dbtest.OpenTestDatabase(t, authTestURL)
|
|
repo := repository.NewRepository(conn)
|
|
Store = service.NewResources(repo, t.TempDir(), 1_048_576)
|
|
t.Cleanup(func() { Store = nil })
|
|
return repo
|
|
}
|
|
|
|
func TestDevicesRegisterValid(t *testing.T) {
|
|
m, _ := auth.NewManager("test-secret")
|
|
Auth = m
|
|
defer func() { Auth = nil }()
|
|
repo := setTestStore(t)
|
|
|
|
deviceID := "0123456789abcdef0123456789abcdef"
|
|
body := `{"deviceId":"` + deviceID + `"}`
|
|
rec := httptest.NewRecorder()
|
|
req := httptest.NewRequest(http.MethodPost, "/devices", strings.NewReader(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
newTestRouterForAuth().ServeHTTP(rec, req)
|
|
|
|
if rec.Code != 200 {
|
|
t.Fatalf("status = %d body = %s", rec.Code, rec.Body.String())
|
|
}
|
|
|
|
var envelope struct {
|
|
Data struct {
|
|
DeviceID string `json:"deviceId"`
|
|
Token string `json:"token"`
|
|
} `json:"data"`
|
|
}
|
|
if err := json.Unmarshal(rec.Body.Bytes(), &envelope); err != nil {
|
|
t.Fatalf("unmarshal: %v", err)
|
|
}
|
|
if envelope.Data.DeviceID != deviceID {
|
|
t.Errorf("deviceId = %q", envelope.Data.DeviceID)
|
|
}
|
|
|
|
verified, err := m.Verify(envelope.Data.Token)
|
|
if err != nil || verified != deviceID {
|
|
t.Errorf("token invalid: %v", err)
|
|
}
|
|
|
|
// Le device est bien persisté (requis par les FK resources.owner_id).
|
|
exists, err := repo.Devices.Exists(deviceID)
|
|
if err != nil || !exists {
|
|
t.Errorf("device non persisté: exists=%v err=%v", exists, err)
|
|
}
|
|
}
|
|
|
|
func TestDevicesRegisterRejectsBadDeviceID(t *testing.T) {
|
|
Auth, _ = auth.NewManager("test-secret")
|
|
defer func() { Auth = nil }()
|
|
|
|
rec := httptest.NewRecorder()
|
|
req := httptest.NewRequest(http.MethodPost, "/devices", strings.NewReader(`{"deviceId":"UPPERCASEANDTOOLONG"}`))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
newTestRouterForAuth().ServeHTTP(rec, req)
|
|
|
|
if rec.Code != 400 {
|
|
t.Fatalf("status = %d", rec.Code)
|
|
}
|
|
}
|
|
|
|
func TestRequireDeviceRejectsMissingToken(t *testing.T) {
|
|
Auth, _ = auth.NewManager("test-secret")
|
|
defer func() { Auth = nil }()
|
|
|
|
rec := httptest.NewRecorder()
|
|
req := httptest.NewRequest(http.MethodGet, "/files", nil)
|
|
newTestRouterForAuth().ServeHTTP(rec, req)
|
|
|
|
if rec.Code != 401 {
|
|
t.Fatalf("status = %d body = %s", rec.Code, rec.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestRequireDeviceAcceptsValidToken(t *testing.T) {
|
|
Auth, _ = auth.NewManager("test-secret")
|
|
defer func() { Auth = nil }()
|
|
|
|
deviceID := "0123456789abcdef0123456789abcdef"
|
|
signed, err := Auth.Issue(deviceID)
|
|
if err != nil {
|
|
t.Fatalf("Issue: %v", err)
|
|
}
|
|
|
|
rec := httptest.NewRecorder()
|
|
req := httptest.NewRequest(http.MethodGet, "/files", nil)
|
|
req.Header.Set("Authorization", "Bearer "+signed)
|
|
newTestRouterForAuth().ServeHTTP(rec, req)
|
|
|
|
// Sans Store le handler répond SERVICE_UNAVAILABLE (503) — le point est
|
|
// que la requête a dépassé le middleware (jamais 401).
|
|
if rec.Code == 401 {
|
|
t.Fatalf("middleware a rejeté un token valide: %s", rec.Body.String())
|
|
}
|
|
}
|