first commit
This commit is contained in:
195
backend/internal/handler/http_test.go
Normal file
195
backend/internal/handler/http_test.go
Normal file
@@ -0,0 +1,195 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"filefast/backend/internal/config"
|
||||
"filefast/backend/internal/model"
|
||||
"filefast/backend/internal/service"
|
||||
"filefast/backend/internal/store"
|
||||
"filefast/backend/internal/ws"
|
||||
)
|
||||
|
||||
type registeredDevice struct {
|
||||
ID string `json:"id"`
|
||||
AuthToken string `json:"auth_token"`
|
||||
}
|
||||
|
||||
type transferRecord struct {
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
func TestProtectedRoutesRequireDeviceCredentials(t *testing.T) {
|
||||
router, _ := newTestRouter()
|
||||
|
||||
device := registerDevice(t, router, map[string]any{
|
||||
"device_id": "alpha",
|
||||
"name": "Alpha",
|
||||
"type": "desktop",
|
||||
})
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/devices/candidates?deviceId="+device.ID, nil)
|
||||
resp := httptest.NewRecorder()
|
||||
router.ServeHTTP(resp, req)
|
||||
|
||||
if resp.Code != http.StatusUnauthorized {
|
||||
t.Fatalf("expected 401 for missing device credentials, got %d", resp.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProtectedRoutesRejectMismatchedDeviceIdentity(t *testing.T) {
|
||||
router, _ := newTestRouter()
|
||||
|
||||
alpha := registerDevice(t, router, map[string]any{
|
||||
"device_id": "alpha",
|
||||
"name": "Alpha",
|
||||
"type": "desktop",
|
||||
})
|
||||
bravo := registerDevice(t, router, map[string]any{
|
||||
"device_id": "bravo",
|
||||
"name": "Bravo",
|
||||
"type": "desktop",
|
||||
})
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/devices/candidates?deviceId="+bravo.ID, nil)
|
||||
req.Header.Set("X-Device-ID", alpha.ID)
|
||||
req.Header.Set("X-Device-Token", alpha.AuthToken)
|
||||
resp := httptest.NewRecorder()
|
||||
router.ServeHTTP(resp, req)
|
||||
|
||||
if resp.Code != http.StatusForbidden {
|
||||
t.Fatalf("expected 403 for mismatched device identity, got %d", resp.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTransferStatusUpdateRequiresParticipantOwnership(t *testing.T) {
|
||||
router, _ := newTestRouter()
|
||||
|
||||
sender := registerDevice(t, router, map[string]any{
|
||||
"device_id": "sender",
|
||||
"name": "Sender",
|
||||
"type": "desktop",
|
||||
})
|
||||
receiver := registerDevice(t, router, map[string]any{
|
||||
"device_id": "receiver",
|
||||
"name": "Receiver",
|
||||
"type": "desktop",
|
||||
})
|
||||
attacker := registerDevice(t, router, map[string]any{
|
||||
"device_id": "attacker",
|
||||
"name": "Attacker",
|
||||
"type": "desktop",
|
||||
})
|
||||
|
||||
transfer := createTransfer(t, router, sender, map[string]any{
|
||||
"kind": "text",
|
||||
"name": "text-message",
|
||||
"content": "hello",
|
||||
"sender_device_id": sender.ID,
|
||||
"receiver_device_id": receiver.ID,
|
||||
})
|
||||
|
||||
body, err := json.Marshal(map[string]any{
|
||||
"final_status": "completed",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("marshal update status body: %v", err)
|
||||
}
|
||||
|
||||
req := httptest.NewRequest(http.MethodPatch, "/api/transfers/"+transfer.ID+"/status", bytes.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("X-Device-ID", attacker.ID)
|
||||
req.Header.Set("X-Device-Token", attacker.AuthToken)
|
||||
resp := httptest.NewRecorder()
|
||||
router.ServeHTTP(resp, req)
|
||||
|
||||
if resp.Code != http.StatusForbidden {
|
||||
t.Fatalf("expected 403 for non-participant transfer update, got %d", resp.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func newTestRouter() (http.Handler, *store.MemoryStore) {
|
||||
memStore := store.NewMemoryStore(model.RuntimeConfig{})
|
||||
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
|
||||
deviceService := service.NewDeviceService(memStore, nil, nil)
|
||||
deps := Dependencies{
|
||||
Config: config.Config{},
|
||||
Logger: logger,
|
||||
Store: memStore,
|
||||
DeviceService: deviceService,
|
||||
RoomService: service.NewRoomService(memStore, 0),
|
||||
TransferService: service.NewTransferService(memStore),
|
||||
AdminService: service.NewAdminService(memStore, config.AdminConfig{}, nil, nil),
|
||||
Hub: ws.NewHub(logger, deviceService, nil),
|
||||
StorageReady: true,
|
||||
RedisReady: true,
|
||||
}
|
||||
|
||||
return NewHTTPHandler(deps).Router(), memStore
|
||||
}
|
||||
|
||||
func registerDevice(t *testing.T, router http.Handler, payload map[string]any) registeredDevice {
|
||||
t.Helper()
|
||||
body, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
t.Fatalf("marshal register body: %v", err)
|
||||
}
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/devices/register", bytes.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
resp := httptest.NewRecorder()
|
||||
router.ServeHTTP(resp, req)
|
||||
|
||||
if resp.Code != http.StatusOK {
|
||||
t.Fatalf("expected register 200, got %d: %s", resp.Code, resp.Body.String())
|
||||
}
|
||||
|
||||
var payloadWrapper struct {
|
||||
Data registeredDevice `json:"data"`
|
||||
}
|
||||
if err := json.Unmarshal(resp.Body.Bytes(), &payloadWrapper); err != nil {
|
||||
t.Fatalf("decode register response: %v", err)
|
||||
}
|
||||
if payloadWrapper.Data.ID == "" || payloadWrapper.Data.AuthToken == "" {
|
||||
t.Fatalf("expected device registration to return id and auth token, got %+v", payloadWrapper.Data)
|
||||
}
|
||||
|
||||
return payloadWrapper.Data
|
||||
}
|
||||
|
||||
func createTransfer(t *testing.T, router http.Handler, device registeredDevice, payload map[string]any) transferRecord {
|
||||
t.Helper()
|
||||
body, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
t.Fatalf("marshal create transfer body: %v", err)
|
||||
}
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/transfers", bytes.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("X-Device-ID", device.ID)
|
||||
req.Header.Set("X-Device-Token", device.AuthToken)
|
||||
resp := httptest.NewRecorder()
|
||||
router.ServeHTTP(resp, req)
|
||||
|
||||
if resp.Code != http.StatusOK {
|
||||
t.Fatalf("expected create transfer 200, got %d: %s", resp.Code, resp.Body.String())
|
||||
}
|
||||
|
||||
var payloadWrapper struct {
|
||||
Data transferRecord `json:"data"`
|
||||
}
|
||||
if err := json.Unmarshal(resp.Body.Bytes(), &payloadWrapper); err != nil {
|
||||
t.Fatalf("decode transfer response: %v", err)
|
||||
}
|
||||
if payloadWrapper.Data.ID == "" {
|
||||
t.Fatalf("expected transfer id in response, got %+v", payloadWrapper.Data)
|
||||
}
|
||||
|
||||
return payloadWrapper.Data
|
||||
}
|
||||
Reference in New Issue
Block a user