package httpapi import ( "errors" "net/http" "net/http/httptest" "strings" "testing" "data-terminal-cloud/internal/deviceauth" "data-terminal-cloud/internal/identity" "data-terminal-cloud/internal/media" "github.com/google/uuid" ) func TestMediaWebHandlersRejectInvalidRequestsBeforeService(t *testing.T) { principal := identity.Principal{Issuer: "issuer", Subject: "subject"} resourceID := uuid.NewString() tests := []struct { name string handler http.Handler request *http.Request status int code string }{ {"grant unauthenticated", GrantRecordingAccessHandler(nil), httptest.NewRequest(http.MethodPost, "/api/v1/conversations/"+resourceID+"/recording-grants", nil), http.StatusUnauthorized, "AUTHENTICATION_REQUIRED"}, {"grant invalid resource", GrantRecordingAccessHandler(nil), authenticatedRequest(http.MethodPost, "/api/v1/conversations/bad/recording-grants", `{}`, principal), http.StatusBadRequest, "VALIDATION_FAILED"}, {"grant malformed body", GrantRecordingAccessHandler(nil), authenticatedRequest(http.MethodPost, "/api/v1/conversations/"+resourceID+"/recording-grants", `{`, principal), http.StatusBadRequest, "VALIDATION_FAILED"}, {"grant missing fields", GrantRecordingAccessHandler(nil), authenticatedRequest(http.MethodPost, "/api/v1/conversations/"+resourceID+"/recording-grants", `{}`, principal), http.StatusBadRequest, "VALIDATION_FAILED"}, {"list unauthenticated", ListRecordingAccessGrantsHandler(nil), httptest.NewRequest(http.MethodGet, "/api/v1/conversations/"+resourceID+"/recording-grants", nil), http.StatusUnauthorized, "AUTHENTICATION_REQUIRED"}, {"revoke invalid resource", RevokeRecordingAccessHandler(nil), authenticatedRequest(http.MethodDelete, "/api/v1/recording-grants/bad", "", principal), http.StatusBadRequest, "VALIDATION_FAILED"}, {"conversation unauthenticated", GetConversationMediaHandler(nil), httptest.NewRequest(http.MethodGet, "/api/v1/conversations/"+resourceID+"/media", nil), http.StatusUnauthorized, "AUTHENTICATION_REQUIRED"}, {"access malformed body", CreateMediaAccessHandler(nil), authenticatedRequest(http.MethodPost, "/api/v1/media/"+resourceID+":access", `{`, principal), http.StatusBadRequest, "VALIDATION_FAILED"}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { if !strings.Contains(test.name, "invalid resource") && !strings.Contains(test.name, "unauthenticated") { test.request.SetPathValue("id", resourceID) } else if strings.Contains(test.name, "invalid resource") { test.request.SetPathValue("id", "bad") } recorder := httptest.NewRecorder() test.handler.ServeHTTP(recorder, test.request) if recorder.Code != test.status || !strings.Contains(recorder.Body.String(), test.code) { t.Fatalf("status=%d body=%s", recorder.Code, recorder.Body.String()) } }) } } func TestMediaDeviceHandlersRejectInvalidRequestsBeforeService(t *testing.T) { resourceID := uuid.NewString() device := deviceauth.Identity{ID: uuid.NewString(), PublicID: uuid.NewString(), TenantID: uuid.NewString(), Status: "registered"} authenticated := func(method, target, body string) *http.Request { request := httptest.NewRequest(method, target, strings.NewReader(body)) request.SetPathValue("id", resourceID) return request.WithContext(deviceauth.WithIdentity(request.Context(), device)) } tests := []struct { name string handler http.Handler request *http.Request status int code string }{ {"prepare unauthenticated", PrepareMediaUploadBatchHandler(nil), httptest.NewRequest(http.MethodPost, "/device/v2/conversations/"+resourceID+"/media-uploads", nil), http.StatusUnauthorized, "DEVICE_AUTHENTICATION_FAILED"}, {"prepare missing idempotency", PrepareMediaUploadBatchHandler(nil), authenticated(http.MethodPost, "/device/v2/conversations/"+resourceID+"/media-uploads", `{}`), http.StatusBadRequest, "VALIDATION_FAILED"}, {"prepare malformed body", PrepareMediaUploadBatchHandler(nil), authenticated(http.MethodPost, "/device/v2/conversations/"+resourceID+"/media-uploads", `{`), http.StatusBadRequest, "VALIDATION_FAILED"}, {"complete unauthenticated", CompleteMediaUploadBatchHandler(nil), httptest.NewRequest(http.MethodPost, "/device/v2/media-upload-batches/"+resourceID+":complete", nil), http.StatusUnauthorized, "DEVICE_AUTHENTICATION_FAILED"}, {"get unauthenticated", GetDeviceMediaUploadBatchHandler(nil), httptest.NewRequest(http.MethodGet, "/device/v2/media-upload-batches/"+resourceID, nil), http.StatusUnauthorized, "DEVICE_AUTHENTICATION_FAILED"}, {"delete unauthenticated", AcknowledgeLocalMediaDeleteHandler(nil), httptest.NewRequest(http.MethodPost, "/device/v2/media/"+resourceID+":local-delete", nil), http.StatusUnauthorized, "DEVICE_AUTHENTICATION_FAILED"}, {"delete malformed body", AcknowledgeLocalMediaDeleteHandler(nil), authenticated(http.MethodPost, "/device/v2/media/"+resourceID+":local-delete", `{`), http.StatusBadRequest, "VALIDATION_FAILED"}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { if strings.Contains(test.name, "malformed") { test.request.Header.Set("Idempotency-Key", "http-media-valid-key") } recorder := httptest.NewRecorder() test.handler.ServeHTTP(recorder, test.request) if recorder.Code != test.status || !strings.Contains(recorder.Body.String(), test.code) { t.Fatalf("status=%d body=%s", recorder.Code, recorder.Body.String()) } }) } } func TestWriteMediaErrorStableMappings(t *testing.T) { tests := []struct { err error status int code string }{ {media.ErrRecordingAccessDenied, http.StatusForbidden, "RECORDING_ACCESS_DENIED"}, {media.ErrNotFound, http.StatusNotFound, "RESOURCE_NOT_FOUND"}, {media.ErrAccessDenied, http.StatusForbidden, "UPLOAD_NOT_ALLOWED"}, {media.ErrInvalidDeclaration, http.StatusBadRequest, "VALIDATION_FAILED"}, {media.ErrExpired, http.StatusGone, "UPLOAD_BATCH_EXPIRED"}, {media.ErrConflict, http.StatusConflict, "RESOURCE_STATE_CONFLICT"}, {media.ErrLocalDeleteTooEarly, http.StatusConflict, "LOCAL_DELETE_NOT_DUE"}, {errors.New("database unavailable"), http.StatusInternalServerError, "INTERNAL_ERROR"}, } for _, test := range tests { recorder := httptest.NewRecorder() request := httptest.NewRequest(http.MethodGet, "/api/v1/media", nil) writeMediaError(recorder, request, test.err) if recorder.Code != test.status || !strings.Contains(recorder.Body.String(), test.code) { t.Fatalf("error=%v status=%d body=%s", test.err, recorder.Code, recorder.Body.String()) } } }