All checks were successful
Build and Publish Docker Image / deploy (push) Successful in 1m8s
- Update navigation labels from "My Account" to "Dashboard" and "Login" to "Sign in", updating tests accordingly. - Redesign settings forms into structured sections with improved spacing and layout. - Add CSS styles for tabs, small buttons, and responsive settings sections to enhance the user experience.
445 lines
17 KiB
Go
445 lines
17 KiB
Go
package handlers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
"warpbox.dev/backend/libs/services"
|
|
)
|
|
|
|
func TestLoggedInUploadStoresOwnerAndAnonymousUploadDoesNot(t *testing.T) {
|
|
app, cleanup := newTestApp(t)
|
|
defer cleanup()
|
|
|
|
user, err := app.authService.CreateBootstrapUser("daniel", "daniel@example.test", "password123")
|
|
if err != nil {
|
|
t.Fatalf("CreateBootstrapUser returned error: %v", err)
|
|
}
|
|
_, token, err := app.authService.Login("daniel@example.test", "password123")
|
|
if err != nil {
|
|
t.Fatalf("Login returned error: %v", err)
|
|
}
|
|
|
|
request := multipartUploadRequest(t, "/api/v1/upload", "file", "owned.txt", "owned")
|
|
request.Header.Set("Accept", "application/json")
|
|
request.AddCookie(&http.Cookie{Name: userSessionCookieName, Value: token})
|
|
response := httptest.NewRecorder()
|
|
app.Upload(response, request)
|
|
if response.Code != http.StatusCreated {
|
|
t.Fatalf("owned upload status = %d, body = %s", response.Code, response.Body.String())
|
|
}
|
|
var ownedPayload services.UploadResult
|
|
if err := json.Unmarshal(response.Body.Bytes(), &ownedPayload); err != nil {
|
|
t.Fatalf("json.Unmarshal owned returned error: %v", err)
|
|
}
|
|
ownedBox, err := app.uploadService.GetBox(ownedPayload.BoxID)
|
|
if err != nil {
|
|
t.Fatalf("GetBox owned returned error: %v", err)
|
|
}
|
|
if ownedBox.OwnerID != user.ID {
|
|
t.Fatalf("owned OwnerID = %q, want %q", ownedBox.OwnerID, user.ID)
|
|
}
|
|
|
|
owned := uploadThroughApp(t, app)
|
|
anonymous, err := app.uploadService.GetBox(owned.BoxID)
|
|
if err != nil {
|
|
t.Fatalf("GetBox anonymous returned error: %v", err)
|
|
}
|
|
if anonymous.OwnerID != "" {
|
|
t.Fatalf("anonymous OwnerID = %q, want empty", anonymous.OwnerID)
|
|
}
|
|
|
|
boxes, err := app.uploadService.ListBoxes(0)
|
|
if err != nil {
|
|
t.Fatalf("ListBoxes returned error: %v", err)
|
|
}
|
|
foundOwned := false
|
|
for _, box := range boxes {
|
|
if box.OwnerID == user.ID {
|
|
foundOwned = true
|
|
}
|
|
}
|
|
if !foundOwned {
|
|
t.Fatalf("logged-in upload did not store owner id %q", user.ID)
|
|
}
|
|
}
|
|
|
|
func TestInviteHandlerCreatesUserAndMarksInviteUsed(t *testing.T) {
|
|
app, cleanup := newTestApp(t)
|
|
defer cleanup()
|
|
|
|
admin, err := app.authService.CreateBootstrapUser("admin", "admin@example.test", "password123")
|
|
if err != nil {
|
|
t.Fatalf("CreateBootstrapUser returned error: %v", err)
|
|
}
|
|
invite, err := app.authService.CreateInvite("friend@example.test", services.UserRoleUser, admin.ID, 0)
|
|
if err != nil {
|
|
t.Fatalf("CreateInvite returned error: %v", err)
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodPost, "/invite/"+invite.Token, strings.NewReader("username=friend&password=password123"))
|
|
request.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
request.SetPathValue("token", invite.Token)
|
|
response := httptest.NewRecorder()
|
|
app.InvitePost(response, request)
|
|
if response.Code != http.StatusSeeOther {
|
|
t.Fatalf("InvitePost status = %d, body = %s", response.Code, response.Body.String())
|
|
}
|
|
if _, err := app.authService.AcceptInvite(invite.Token, "friend", "password123"); err == nil {
|
|
t.Fatalf("invite token remained reusable")
|
|
}
|
|
}
|
|
|
|
func TestNonOwnerCannotManageOwnedBox(t *testing.T) {
|
|
app, cleanup := newTestApp(t)
|
|
defer cleanup()
|
|
|
|
owner, err := app.authService.CreateBootstrapUser("owner", "owner@example.test", "password123")
|
|
if err != nil {
|
|
t.Fatalf("CreateBootstrapUser returned error: %v", err)
|
|
}
|
|
invite, err := app.authService.CreateInvite("other@example.test", services.UserRoleUser, owner.ID, 0)
|
|
if err != nil {
|
|
t.Fatalf("CreateInvite returned error: %v", err)
|
|
}
|
|
other, err := app.authService.AcceptInvite(invite.Token, "other", "password123")
|
|
if err != nil {
|
|
t.Fatalf("AcceptInvite returned error: %v", err)
|
|
}
|
|
|
|
result := createOwnedBoxThroughApp(t, app, owner.ID)
|
|
if err := app.uploadService.RenameOwnedBox(result.BoxID, other.ID, "stolen"); err == nil {
|
|
t.Fatalf("RenameOwnedBox allowed non-owner")
|
|
}
|
|
}
|
|
|
|
func TestAdminUploadBypassesMaxUploadSize(t *testing.T) {
|
|
app, cleanup := newTestApp(t)
|
|
defer cleanup()
|
|
|
|
_, err := app.authService.CreateBootstrapUser("admin", "admin@example.test", "password123")
|
|
if err != nil {
|
|
t.Fatalf("CreateBootstrapUser returned error: %v", err)
|
|
}
|
|
_, token, err := app.authService.Login("admin@example.test", "password123")
|
|
if err != nil {
|
|
t.Fatalf("Login returned error: %v", err)
|
|
}
|
|
|
|
request := multipartUploadRequest(t, "/api/v1/upload", "file", "large.txt", strings.Repeat("x", int(app.uploadService.MaxUploadSize())+1))
|
|
request.Header.Set("Accept", "application/json")
|
|
request.AddCookie(&http.Cookie{Name: userSessionCookieName, Value: token})
|
|
response := httptest.NewRecorder()
|
|
app.Upload(response, request)
|
|
if response.Code != http.StatusCreated {
|
|
t.Fatalf("admin upload status = %d, body = %s", response.Code, response.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestAnonymousUploadDisabled(t *testing.T) {
|
|
app, cleanup := newTestApp(t)
|
|
defer cleanup()
|
|
policy := testPolicy(t, app)
|
|
policy.AnonymousUploadsEnabled = false
|
|
if err := app.settingsService.UpdateUploadPolicy(policy); err != nil {
|
|
t.Fatalf("UpdateUploadPolicy returned error: %v", err)
|
|
}
|
|
|
|
request := multipartUploadRequest(t, "/api/v1/upload", "file", "note.txt", "hello")
|
|
request.Header.Set("Accept", "application/json")
|
|
response := httptest.NewRecorder()
|
|
app.Upload(response, request)
|
|
if response.Code != http.StatusForbidden {
|
|
t.Fatalf("status = %d, want 403, body = %s", response.Code, response.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestAnonymousUploadLimits(t *testing.T) {
|
|
app, cleanup := newTestApp(t)
|
|
defer cleanup()
|
|
policy := testPolicy(t, app)
|
|
policy.AnonymousMaxUploadMB = 1
|
|
policy.AnonymousDailyUploadMB = 0.001
|
|
if err := app.settingsService.UpdateUploadPolicy(policy); err != nil {
|
|
t.Fatalf("UpdateUploadPolicy returned error: %v", err)
|
|
}
|
|
|
|
large := multipartUploadRequest(t, "/api/v1/upload", "file", "large.txt", strings.Repeat("x", 2*1024*1024))
|
|
large.Header.Set("Accept", "application/json")
|
|
large.RemoteAddr = "192.0.2.10:1234"
|
|
largeResponse := httptest.NewRecorder()
|
|
app.Upload(largeResponse, large)
|
|
if largeResponse.Code != http.StatusRequestEntityTooLarge {
|
|
t.Fatalf("large status = %d, body = %s", largeResponse.Code, largeResponse.Body.String())
|
|
}
|
|
|
|
daily := multipartUploadRequest(t, "/api/v1/upload", "file", "note.txt", strings.Repeat("x", 2048))
|
|
daily.Header.Set("Accept", "application/json")
|
|
daily.RemoteAddr = "192.0.2.10:1234"
|
|
dailyResponse := httptest.NewRecorder()
|
|
app.Upload(dailyResponse, daily)
|
|
if dailyResponse.Code != http.StatusTooManyRequests {
|
|
t.Fatalf("daily status = %d, body = %s", dailyResponse.Code, dailyResponse.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestSignedInUploadQuotaAndOverride(t *testing.T) {
|
|
app, cleanup := newTestApp(t)
|
|
defer cleanup()
|
|
user, err := app.authService.CreateBootstrapUser("admin", "admin@example.test", "password123")
|
|
if err != nil {
|
|
t.Fatalf("CreateBootstrapUser returned error: %v", err)
|
|
}
|
|
invite, err := app.authService.CreateInvite("user@example.test", services.UserRoleUser, user.ID, 0)
|
|
if err != nil {
|
|
t.Fatalf("CreateInvite returned error: %v", err)
|
|
}
|
|
normal, err := app.authService.AcceptInvite(invite.Token, "user", "password123")
|
|
if err != nil {
|
|
t.Fatalf("AcceptInvite returned error: %v", err)
|
|
}
|
|
_, token, err := app.authService.Login(normal.Email, "password123")
|
|
if err != nil {
|
|
t.Fatalf("Login returned error: %v", err)
|
|
}
|
|
policy := testPolicy(t, app)
|
|
policy.DefaultUserStorageMB = 0.001
|
|
policy.UserDailyUploadMB = 8
|
|
if err := app.settingsService.UpdateUploadPolicy(policy); err != nil {
|
|
t.Fatalf("UpdateUploadPolicy returned error: %v", err)
|
|
}
|
|
|
|
request := multipartUploadRequest(t, "/api/v1/upload", "file", "quota.txt", strings.Repeat("x", 2048))
|
|
request.Header.Set("Accept", "application/json")
|
|
request.AddCookie(&http.Cookie{Name: userSessionCookieName, Value: token})
|
|
response := httptest.NewRecorder()
|
|
app.Upload(response, request)
|
|
if response.Code != http.StatusRequestEntityTooLarge {
|
|
t.Fatalf("quota status = %d, body = %s", response.Code, response.Body.String())
|
|
}
|
|
|
|
override := 10.0
|
|
if err := app.authService.SetUserStorageQuota(normal.ID, &override); err != nil {
|
|
t.Fatalf("SetUserStorageQuota returned error: %v", err)
|
|
}
|
|
request = multipartUploadRequest(t, "/api/v1/upload", "file", "quota.txt", strings.Repeat("x", 2048))
|
|
request.Header.Set("Accept", "application/json")
|
|
request.AddCookie(&http.Cookie{Name: userSessionCookieName, Value: token})
|
|
response = httptest.NewRecorder()
|
|
app.Upload(response, request)
|
|
if response.Code != http.StatusCreated {
|
|
t.Fatalf("override status = %d, body = %s", response.Code, response.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestSignedInDailyCap(t *testing.T) {
|
|
app, cleanup := newTestApp(t)
|
|
defer cleanup()
|
|
admin, err := app.authService.CreateBootstrapUser("admin", "admin@example.test", "password123")
|
|
if err != nil {
|
|
t.Fatalf("CreateBootstrapUser returned error: %v", err)
|
|
}
|
|
invite, err := app.authService.CreateInvite("user@example.test", services.UserRoleUser, admin.ID, 0)
|
|
if err != nil {
|
|
t.Fatalf("CreateInvite returned error: %v", err)
|
|
}
|
|
user, err := app.authService.AcceptInvite(invite.Token, "user", "password123")
|
|
if err != nil {
|
|
t.Fatalf("AcceptInvite returned error: %v", err)
|
|
}
|
|
_, token, err := app.authService.Login(user.Email, "password123")
|
|
if err != nil {
|
|
t.Fatalf("Login returned error: %v", err)
|
|
}
|
|
policy := testPolicy(t, app)
|
|
policy.UserDailyUploadMB = 0.001
|
|
if err := app.settingsService.UpdateUploadPolicy(policy); err != nil {
|
|
t.Fatalf("UpdateUploadPolicy returned error: %v", err)
|
|
}
|
|
request := multipartUploadRequest(t, "/api/v1/upload", "file", "daily.txt", strings.Repeat("x", 2048))
|
|
request.Header.Set("Accept", "application/json")
|
|
request.AddCookie(&http.Cookie{Name: userSessionCookieName, Value: token})
|
|
response := httptest.NewRecorder()
|
|
app.Upload(response, request)
|
|
if response.Code != http.StatusTooManyRequests {
|
|
t.Fatalf("daily status = %d, body = %s", response.Code, response.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestAdminSettingsPostChangesUploadEnforcement(t *testing.T) {
|
|
app, cleanup := newTestApp(t)
|
|
defer cleanup()
|
|
_, err := app.authService.CreateBootstrapUser("admin", "admin@example.test", "password123")
|
|
if err != nil {
|
|
t.Fatalf("CreateBootstrapUser returned error: %v", err)
|
|
}
|
|
_, token, err := app.authService.Login("admin@example.test", "password123")
|
|
if err != nil {
|
|
t.Fatalf("Login returned error: %v", err)
|
|
}
|
|
|
|
settingsForm := strings.NewReader("anonymous_max_upload_mb=512&anonymous_daily_upload_mb=2048&user_daily_upload_mb=8192&default_user_storage_mb=51200&usage_retention_days=30")
|
|
settingsRequest := httptest.NewRequest(http.MethodPost, "/admin/settings", settingsForm)
|
|
settingsRequest.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
settingsRequest.AddCookie(&http.Cookie{Name: userSessionCookieName, Value: token})
|
|
settingsResponse := httptest.NewRecorder()
|
|
app.AdminSettingsPost(settingsResponse, settingsRequest)
|
|
if settingsResponse.Code != http.StatusSeeOther {
|
|
t.Fatalf("AdminSettingsPost status = %d, body = %s", settingsResponse.Code, settingsResponse.Body.String())
|
|
}
|
|
|
|
uploadRequest := multipartUploadRequest(t, "/api/v1/upload", "file", "note.txt", "hello")
|
|
uploadRequest.Header.Set("Accept", "application/json")
|
|
uploadResponse := httptest.NewRecorder()
|
|
app.Upload(uploadResponse, uploadRequest)
|
|
if uploadResponse.Code != http.StatusForbidden {
|
|
t.Fatalf("upload status = %d, want 403, body = %s", uploadResponse.Code, uploadResponse.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestAdminUserQuotaPostChangesEnforcement(t *testing.T) {
|
|
app, cleanup := newTestApp(t)
|
|
defer cleanup()
|
|
admin, err := app.authService.CreateBootstrapUser("admin", "admin@example.test", "password123")
|
|
if err != nil {
|
|
t.Fatalf("CreateBootstrapUser returned error: %v", err)
|
|
}
|
|
invite, err := app.authService.CreateInvite("user@example.test", services.UserRoleUser, admin.ID, 0)
|
|
if err != nil {
|
|
t.Fatalf("CreateInvite returned error: %v", err)
|
|
}
|
|
user, err := app.authService.AcceptInvite(invite.Token, "user", "password123")
|
|
if err != nil {
|
|
t.Fatalf("AcceptInvite returned error: %v", err)
|
|
}
|
|
_, adminToken, err := app.authService.Login(admin.Email, "password123")
|
|
if err != nil {
|
|
t.Fatalf("admin Login returned error: %v", err)
|
|
}
|
|
|
|
quotaRequest := httptest.NewRequest(http.MethodPost, "/admin/users/"+user.ID+"/quota", strings.NewReader("storage_quota_mb=0.001"))
|
|
quotaRequest.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
quotaRequest.AddCookie(&http.Cookie{Name: userSessionCookieName, Value: adminToken})
|
|
quotaRequest.SetPathValue("userID", user.ID)
|
|
quotaResponse := httptest.NewRecorder()
|
|
app.AdminUpdateUserQuota(quotaResponse, quotaRequest)
|
|
if quotaResponse.Code != http.StatusSeeOther {
|
|
t.Fatalf("AdminUpdateUserQuota status = %d, body = %s", quotaResponse.Code, quotaResponse.Body.String())
|
|
}
|
|
|
|
_, userToken, err := app.authService.Login(user.Email, "password123")
|
|
if err != nil {
|
|
t.Fatalf("user Login returned error: %v", err)
|
|
}
|
|
uploadRequest := multipartUploadRequest(t, "/api/v1/upload", "file", "quota.txt", strings.Repeat("x", 2048))
|
|
uploadRequest.Header.Set("Accept", "application/json")
|
|
uploadRequest.AddCookie(&http.Cookie{Name: userSessionCookieName, Value: userToken})
|
|
uploadResponse := httptest.NewRecorder()
|
|
app.Upload(uploadResponse, uploadRequest)
|
|
if uploadResponse.Code != http.StatusRequestEntityTooLarge {
|
|
t.Fatalf("upload status = %d, want 413, body = %s", uploadResponse.Code, uploadResponse.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestHomeReflectsUploadPolicySettings(t *testing.T) {
|
|
app, cleanup := newTestApp(t)
|
|
defer cleanup()
|
|
policy := testPolicy(t, app)
|
|
policy.AnonymousMaxUploadMB = 123
|
|
policy.AnonymousDailyUploadMB = 456
|
|
if err := app.settingsService.UpdateUploadPolicy(policy); err != nil {
|
|
t.Fatalf("UpdateUploadPolicy returned error: %v", err)
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodGet, "/", nil)
|
|
response := httptest.NewRecorder()
|
|
app.Home(response, request)
|
|
if response.Code != http.StatusOK {
|
|
t.Fatalf("Home status = %d", response.Code)
|
|
}
|
|
body := response.Body.String()
|
|
if !strings.Contains(body, "Max file size: 123 MB") || !strings.Contains(body, "456 MB") {
|
|
t.Fatalf("home did not reflect policy settings: %s", body)
|
|
}
|
|
}
|
|
|
|
func TestAPIDocsHeaderReflectsLoggedInUser(t *testing.T) {
|
|
app, cleanup := newTestApp(t)
|
|
defer cleanup()
|
|
_, err := app.authService.CreateBootstrapUser("admin", "admin@example.test", "password123")
|
|
if err != nil {
|
|
t.Fatalf("CreateBootstrapUser returned error: %v", err)
|
|
}
|
|
_, token, err := app.authService.Login("admin@example.test", "password123")
|
|
if err != nil {
|
|
t.Fatalf("Login returned error: %v", err)
|
|
}
|
|
|
|
request := httptest.NewRequest(http.MethodGet, "/api", nil)
|
|
request.AddCookie(&http.Cookie{Name: userSessionCookieName, Value: token})
|
|
response := httptest.NewRecorder()
|
|
app.APIDocs(response, request)
|
|
if response.Code != http.StatusOK {
|
|
t.Fatalf("APIDocs status = %d", response.Code)
|
|
}
|
|
body := response.Body.String()
|
|
header := body[:strings.Index(body, "<main")]
|
|
if !strings.Contains(header, "Dashboard") || strings.Contains(header, "Sign in") || strings.Contains(header, "Health") {
|
|
t.Fatalf("api header did not reflect logged-in state: %s", body)
|
|
}
|
|
}
|
|
|
|
func TestAPIDocsHeaderReflectsLoggedOutUser(t *testing.T) {
|
|
app, cleanup := newTestApp(t)
|
|
defer cleanup()
|
|
|
|
request := httptest.NewRequest(http.MethodGet, "/api", nil)
|
|
response := httptest.NewRecorder()
|
|
app.APIDocs(response, request)
|
|
if response.Code != http.StatusOK {
|
|
t.Fatalf("APIDocs status = %d", response.Code)
|
|
}
|
|
body := response.Body.String()
|
|
header := body[:strings.Index(body, "<main")]
|
|
if !strings.Contains(header, "Sign in") || !strings.Contains(header, ">API<") || strings.Contains(header, "Health") || strings.Contains(header, "Dashboard") {
|
|
t.Fatalf("api header did not reflect logged-out state: %s", body)
|
|
}
|
|
}
|
|
|
|
func createOwnedBoxThroughApp(t *testing.T, app *App, userID string) services.UploadResult {
|
|
t.Helper()
|
|
user, err := app.authService.UserByID(userID)
|
|
if err != nil {
|
|
t.Fatalf("UserByID returned error: %v", err)
|
|
}
|
|
_, token, err := app.authService.Login(user.Email, "password123")
|
|
if err != nil {
|
|
t.Fatalf("Login returned error: %v", err)
|
|
}
|
|
request := multipartUploadRequest(t, "/api/v1/upload", "file", "owned.txt", "owned")
|
|
request.Header.Set("Accept", "application/json")
|
|
request.AddCookie(&http.Cookie{Name: userSessionCookieName, Value: token})
|
|
response := httptest.NewRecorder()
|
|
app.Upload(response, request)
|
|
if response.Code != http.StatusCreated {
|
|
t.Fatalf("upload status = %d, body = %s", response.Code, response.Body.String())
|
|
}
|
|
var payload services.UploadResult
|
|
if err := json.Unmarshal(response.Body.Bytes(), &payload); err != nil {
|
|
t.Fatalf("json.Unmarshal returned error: %v", err)
|
|
}
|
|
return payload
|
|
}
|
|
|
|
func testPolicy(t *testing.T, app *App) services.UploadPolicySettings {
|
|
t.Helper()
|
|
policy, err := app.settingsService.UploadPolicy()
|
|
if err != nil {
|
|
t.Fatalf("UploadPolicy returned error: %v", err)
|
|
}
|
|
return policy
|
|
}
|