package server import ( "net/http" "net/http/httptest" "net/url" "os" "path/filepath" "strings" "testing" "time" "warpbox/lib/boxstore" "warpbox/lib/metastore" "warpbox/lib/models" ) func TestAccountBoxesAdminListsBoxes(t *testing.T) { app, user := setupAccountTestApp(t) router := setupAccountTestRouter(t, app) session := createAccountTestSession(t, app, user) createIndexedBox(t, app, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "", "", 10, false) response := getAccountBoxes(router, session, "/account/boxes") if response.Code != http.StatusOK { t.Fatalf("expected boxes page, got %d body=%s", response.Code, response.Body.String()) } if !strings.Contains(response.Body.String(), "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") { t.Fatal("expected indexed box in admin list") } } func TestAccountBoxesRegularUserSeesOnlyOwnBoxes(t *testing.T) { app, _ := setupAccountTestApp(t) user, err := app.store.CreateUserWithPassword("box-user", "box-user@example.test", "secret", nil) if err != nil { t.Fatalf("CreateUserWithPassword returned error: %v", err) } router := setupAccountTestRouter(t, app) session := createAccountTestSession(t, app, user) createIndexedBox(t, app, "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", user.ID, user.Username, 10, false) createIndexedBox(t, app, "cccccccccccccccccccccccccccccccc", "other", "other", 20, false) response := getAccountBoxes(router, session, "/account/boxes") if response.Code != http.StatusOK { t.Fatalf("expected boxes page, got %d", response.Code) } body := response.Body.String() if !strings.Contains(body, "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb") { t.Fatal("expected own box") } if strings.Contains(body, "cccccccccccccccccccccccccccccccc") { t.Fatal("did not expect other user's box") } } func TestAccountBoxesFiltersSortAndPagination(t *testing.T) { app, user := setupAccountTestApp(t) router := setupAccountTestRouter(t, app) session := createAccountTestSession(t, app, user) createIndexedBox(t, app, "11111111111111111111111111111111", "", "", 10, false) createIndexedBox(t, app, "22222222222222222222222222222222", "", "", 1000, true) createIndexedBox(t, app, "33333333333333333333333333333333", "", "", 500, false) response := getAccountBoxes(router, session, "/account/boxes?flag=password&sort=largest&page_size=25") if response.Code != http.StatusOK { t.Fatalf("expected boxes page, got %d", response.Code) } body := response.Body.String() if !strings.Contains(body, "22222222222222222222222222222222") { t.Fatal("expected password filtered box") } if strings.Contains(body, "11111111111111111111111111111111") { t.Fatal("did not expect unfiltered box") } page, err := app.store.ListBoxRecords(metastore.BoxFilters{Sort: "largest"}, metastore.BoxPageRequest{Page: 1, PageSize: 25}) if err != nil { t.Fatalf("ListBoxRecords returned error: %v", err) } if len(page.Rows) != 3 || page.Rows[0].ID != "22222222222222222222222222222222" { t.Fatalf("expected largest sort first, got %#v", page.Rows) } } func TestAccountBoxesBulkExpireAndDelete(t *testing.T) { app, user := setupAccountTestApp(t) router := setupAccountTestRouter(t, app) session := createAccountTestSession(t, app, user) id := "dddddddddddddddddddddddddddddddd" createIndexedBox(t, app, id, "", "", 10, false) values := url.Values{"box_ids": []string{id}} response := postAccountBoxesForm(router, session, "/account/boxes/bulk/expire", values) if response.Code != http.StatusSeeOther { t.Fatalf("expected expire redirect, got %d", response.Code) } record, ok, err := app.store.GetBoxRecord(id) if err != nil || !ok { t.Fatalf("GetBoxRecord returned ok=%v err=%v", ok, err) } if record.ExpiresAt.After(time.Now().UTC()) { t.Fatal("expected box to be expired") } response = postAccountBoxesForm(router, session, "/account/boxes/bulk/delete", values) if response.Code != http.StatusSeeOther { t.Fatalf("expected delete redirect, got %d", response.Code) } if _, ok, err := app.store.GetBoxRecord(id); err != nil || ok { t.Fatalf("expected deleted record, ok=%v err=%v", ok, err) } if _, err := os.Stat(boxstore.BoxPath(id)); !os.IsNotExist(err) { t.Fatalf("expected box directory deleted, stat err=%v", err) } } func TestAccountBoxesBulkDeletePermissionDenied(t *testing.T) { app, _ := setupAccountTestApp(t) user, err := app.store.CreateUserWithPassword("box-limited", "box-limited@example.test", "secret", nil) if err != nil { t.Fatalf("CreateUserWithPassword returned error: %v", err) } router := setupAccountTestRouter(t, app) session := createAccountTestSession(t, app, user) id := "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" createIndexedBox(t, app, id, "other", "other", 10, false) response := postAccountBoxesForm(router, session, "/account/boxes/bulk/delete", url.Values{"box_ids": []string{id}}) if response.Code != http.StatusForbidden { t.Fatalf("expected permission denied, got %d", response.Code) } } func TestAccountBoxesBumpExpiryPolicyRejection(t *testing.T) { app, user := setupAccountTestApp(t) app.config.BoxOwnerRefreshEnabled = false router := setupAccountTestRouter(t, app) session := createAccountTestSession(t, app, user) id := "ffffffffffffffffffffffffffffffff" createIndexedBox(t, app, id, "", "", 10, false) response := postAccountBoxesForm(router, session, "/account/boxes/bulk/bump-expiry", url.Values{"box_ids": []string{id}, "bump_seconds": []string{"60"}}) if response.Code != http.StatusForbidden { t.Fatalf("expected policy rejection, got %d", response.Code) } } func TestAccountBoxesDeleteLargest(t *testing.T) { app, user := setupAccountTestApp(t) router := setupAccountTestRouter(t, app) session := createAccountTestSession(t, app, user) small := "12345123451234512345123451234512" large := "99999999999999999999999999999999" createIndexedBox(t, app, small, "", "", 10, false) createIndexedBox(t, app, large, "", "", 1000, false) response := postAccountBoxesForm(router, session, "/account/boxes/delete-largest", nil) if response.Code != http.StatusSeeOther { t.Fatalf("expected delete-largest redirect, got %d", response.Code) } if _, ok, err := app.store.GetBoxRecord(large); err != nil || ok { t.Fatalf("expected largest deleted, ok=%v err=%v", ok, err) } } func createIndexedBox(t *testing.T, app *App, id string, ownerID string, ownerUsername string, size int64, password bool) { t.Helper() if err := os.MkdirAll(boxstore.BoxPath(id), 0755); err != nil { t.Fatalf("MkdirAll returned error: %v", err) } filename := "file-" + id[:4] + ".txt" if err := os.WriteFile(filepath.Join(boxstore.BoxPath(id), filename), []byte(strings.Repeat("x", int(size))), 0644); err != nil { t.Fatalf("WriteFile returned error: %v", err) } manifest := models.BoxManifest{ OwnerID: ownerID, OwnerUsername: ownerUsername, Files: []models.BoxFile{{ ID: "abcdabcdabcdabcd", Name: filename, Size: size, Status: models.FileStatusReady, }}, CreatedAt: time.Now().UTC().Add(-time.Duration(size) * time.Second), ExpiresAt: time.Now().UTC().Add(time.Hour), RetentionSecs: 3600, } if password { manifest.PasswordHash = "hash" manifest.AuthToken = "token" } if err := boxstore.WriteManifest(id, manifest); err != nil { t.Fatalf("WriteManifest returned error: %v", err) } if err := app.store.UpsertBoxRecord(boxRecordFromManifest(id, manifest)); err != nil { t.Fatalf("UpsertBoxRecord returned error: %v", err) } } func getAccountBoxes(router http.Handler, session metastore.Session, path string) *httptest.ResponseRecorder { request := httptest.NewRequest(http.MethodGet, path, nil) request.AddCookie(&http.Cookie{Name: accountSessionCookie, Value: session.Token}) response := httptest.NewRecorder() router.ServeHTTP(response, request) return response } func postAccountBoxesForm(router http.Handler, session metastore.Session, path string, values url.Values) *httptest.ResponseRecorder { if values == nil { values = url.Values{} } values.Set("csrf_token", session.CSRFToken) request := httptest.NewRequest(http.MethodPost, path, strings.NewReader(values.Encode())) request.Header.Set("Content-Type", "application/x-www-form-urlencoded") request.AddCookie(&http.Cookie{Name: accountSessionCookie, Value: session.Token}) response := httptest.NewRecorder() router.ServeHTTP(response, request) return response }