feat(admin): implement provider-specific storage configuration pages
Some checks failed
Build and Publish Docker Image / deploy (push) Has been cancelled
Some checks failed
Build and Publish Docker Image / deploy (push) Has been cancelled
Refactor the admin storage backend creation and editing flows to use provider-specific pages (e.g., `/admin/storage/new/sftp`) instead of a single generic form. This ensures only relevant fields are rendered for each storage provider (such as SFTP, S3, or WebDAV). Additionally: - Prevent mutation of the storage provider type during backend edits. - Add comprehensive unit tests for provider-specific rendering, edit validation, and CSRF/admin route protection.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
@@ -613,6 +614,190 @@ func TestAPIDocsHeaderReflectsLoggedOutUser(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminStorageProviderPagesOnlyRenderRelevantFields(t *testing.T) {
|
||||
app, cleanup := newTestApp(t)
|
||||
defer cleanup()
|
||||
adminToken := createAdminSession(t, app)
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "/admin/storage/new/sftp", nil)
|
||||
request.AddCookie(&http.Cookie{Name: userSessionCookieName, Value: adminToken})
|
||||
response := httptest.NewRecorder()
|
||||
app.AdminNewStorageProvider(response, request)
|
||||
if response.Code != http.StatusOK {
|
||||
t.Fatalf("AdminNewStorageProvider status = %d, body = %s", response.Code, response.Body.String())
|
||||
}
|
||||
body := response.Body.String()
|
||||
if !strings.Contains(body, "Private key") || !strings.Contains(body, "SSH host key") {
|
||||
t.Fatalf("sftp page did not render sftp fields: %s", body)
|
||||
}
|
||||
for _, unwanted := range []string{"Bucket display name", "WebDAV URL", "Share</span>", "Access key"} {
|
||||
if strings.Contains(body, unwanted) {
|
||||
t.Fatalf("sftp page rendered irrelevant field %q: %s", unwanted, body)
|
||||
}
|
||||
}
|
||||
|
||||
cfg, err := app.uploadService.Storage().CreateBackend(services.StorageBackendConfig{
|
||||
Provider: services.StorageProviderSFTP,
|
||||
Name: "NAS",
|
||||
Host: "files.example.test",
|
||||
Username: "warpbox",
|
||||
Password: "secret",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("CreateBackend returned error: %v", err)
|
||||
}
|
||||
editRequest := httptest.NewRequest(http.MethodGet, "/admin/storage/"+cfg.ID+"/edit", nil)
|
||||
editRequest.AddCookie(&http.Cookie{Name: userSessionCookieName, Value: adminToken})
|
||||
editRequest.SetPathValue("backendID", cfg.ID)
|
||||
editResponse := httptest.NewRecorder()
|
||||
app.AdminEditStorageForm(editResponse, editRequest)
|
||||
if editResponse.Code != http.StatusOK {
|
||||
t.Fatalf("AdminEditStorageForm status = %d, body = %s", editResponse.Code, editResponse.Body.String())
|
||||
}
|
||||
editBody := editResponse.Body.String()
|
||||
if !strings.Contains(editBody, "Immutable provider") || strings.Contains(editBody, "Bucket display name") || strings.Contains(editBody, "WebDAV URL") {
|
||||
t.Fatalf("edit page did not stay provider-specific: %s", editBody)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminStorageEditRejectsProviderMutation(t *testing.T) {
|
||||
app, cleanup := newTestApp(t)
|
||||
defer cleanup()
|
||||
adminToken := createAdminSession(t, app)
|
||||
cfg, err := app.uploadService.Storage().CreateBackend(services.StorageBackendConfig{
|
||||
Provider: services.StorageProviderSFTP,
|
||||
Name: "NAS",
|
||||
Host: "files.example.test",
|
||||
Username: "warpbox",
|
||||
Password: "secret",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("CreateBackend returned error: %v", err)
|
||||
}
|
||||
|
||||
form := strings.NewReader("provider=s3&name=Changed&endpoint=https://s3.example.test&bucket=bucket&access_key=access&secret_key=secret&use_ssl=on&csrf_token=test-csrf")
|
||||
request := httptest.NewRequest(http.MethodPost, "/admin/storage/"+cfg.ID+"/edit", form)
|
||||
request.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
request.AddCookie(&http.Cookie{Name: userSessionCookieName, Value: adminToken})
|
||||
request.AddCookie(&http.Cookie{Name: csrfCookieName, Value: "test-csrf"})
|
||||
request.SetPathValue("backendID", cfg.ID)
|
||||
response := httptest.NewRecorder()
|
||||
app.AdminEditStorage(response, request)
|
||||
if response.Code != http.StatusSeeOther {
|
||||
t.Fatalf("AdminEditStorage status = %d, body = %s", response.Code, response.Body.String())
|
||||
}
|
||||
stored, err := app.uploadService.Storage().BackendConfig(cfg.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("BackendConfig returned error: %v", err)
|
||||
}
|
||||
if stored.Provider != services.StorageProviderSFTP || stored.Type != services.StorageBackendSFTP || stored.Name != "NAS" {
|
||||
t.Fatalf("storage backend mutated despite rejected provider change: %+v", stored)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminStorageJobRoutesRequireAdminAndCSRF(t *testing.T) {
|
||||
app, cleanup := newTestApp(t)
|
||||
defer cleanup()
|
||||
|
||||
unauthorized := httptest.NewRecorder()
|
||||
app.AdminRunStorageCleanup(unauthorized, httptest.NewRequest(http.MethodPost, "/admin/storage/jobs/cleanup", nil))
|
||||
if unauthorized.Code != http.StatusSeeOther {
|
||||
t.Fatalf("unauthorized cleanup status = %d", unauthorized.Code)
|
||||
}
|
||||
|
||||
adminToken := createAdminSession(t, app)
|
||||
missingCSRFRequest := httptest.NewRequest(http.MethodPost, "/admin/storage/jobs/cleanup", nil)
|
||||
missingCSRFRequest.AddCookie(&http.Cookie{Name: userSessionCookieName, Value: adminToken})
|
||||
missingCSRFResponse := httptest.NewRecorder()
|
||||
app.AdminRunStorageCleanup(missingCSRFResponse, missingCSRFRequest)
|
||||
if missingCSRFResponse.Code != http.StatusForbidden {
|
||||
t.Fatalf("missing csrf cleanup status = %d", missingCSRFResponse.Code)
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodPost, "/admin/storage/jobs/cleanup", strings.NewReader("csrf_token=test-csrf"))
|
||||
request.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
request.AddCookie(&http.Cookie{Name: userSessionCookieName, Value: adminToken})
|
||||
request.AddCookie(&http.Cookie{Name: csrfCookieName, Value: "test-csrf"})
|
||||
response := httptest.NewRecorder()
|
||||
app.AdminRunStorageCleanup(response, request)
|
||||
if response.Code != http.StatusSeeOther {
|
||||
t.Fatalf("authorized cleanup status = %d, body = %s", response.Code, response.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminStorageSpeedTestStartsBackgroundJob(t *testing.T) {
|
||||
app, cleanup := newTestApp(t)
|
||||
defer cleanup()
|
||||
adminToken := createAdminSession(t, app)
|
||||
if _, err := app.uploadService.Storage().TestBackend(services.StorageBackendLocal); err != nil {
|
||||
t.Fatalf("TestBackend returned error: %v", err)
|
||||
}
|
||||
|
||||
request := httptest.NewRequest(http.MethodPost, "/admin/storage/local/speed-test", strings.NewReader("mode=custom&custom_file_count=2&custom_file_size_mb=0.001&csrf_token=test-csrf"))
|
||||
request.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
request.AddCookie(&http.Cookie{Name: userSessionCookieName, Value: adminToken})
|
||||
request.AddCookie(&http.Cookie{Name: csrfCookieName, Value: "test-csrf"})
|
||||
request.SetPathValue("backendID", services.StorageBackendLocal)
|
||||
response := httptest.NewRecorder()
|
||||
app.AdminStartStorageSpeedTest(response, request)
|
||||
if response.Code != http.StatusSeeOther {
|
||||
t.Fatalf("AdminStartStorageSpeedTest status = %d, body = %s", response.Code, response.Body.String())
|
||||
}
|
||||
tests, err := app.uploadService.Storage().ListSpeedTests(services.StorageBackendLocal, 10)
|
||||
if err != nil {
|
||||
t.Fatalf("ListSpeedTests returned error: %v", err)
|
||||
}
|
||||
if len(tests) != 1 {
|
||||
t.Fatalf("speed tests len = %d, want 1", len(tests))
|
||||
}
|
||||
if tests[0].Mode != services.StorageSpeedModeCustom || tests[0].CustomFileCount != 2 || tests[0].CustomFileSizeMB != 0.001 {
|
||||
t.Fatalf("custom speed test options were not stored: %+v", tests[0])
|
||||
}
|
||||
location := response.Header().Get("Location")
|
||||
if !strings.Contains(location, "/admin/storage/local/tests") {
|
||||
t.Fatalf("speed test redirect location = %q", location)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminStorageTestingPageRendersHistory(t *testing.T) {
|
||||
app, cleanup := newTestApp(t)
|
||||
defer cleanup()
|
||||
adminToken := createAdminSession(t, app)
|
||||
if _, err := app.uploadService.Storage().TestBackend(services.StorageBackendLocal); err != nil {
|
||||
t.Fatalf("TestBackend returned error: %v", err)
|
||||
}
|
||||
test, err := app.uploadService.Storage().StartSpeedTest(services.StorageBackendLocal, services.StorageSpeedModeSmall)
|
||||
if err != nil {
|
||||
t.Fatalf("StartSpeedTest returned error: %v", err)
|
||||
}
|
||||
app.uploadService.Storage().RunSpeedTest(context.Background(), test.ID)
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "/admin/storage/local/tests", nil)
|
||||
request.AddCookie(&http.Cookie{Name: userSessionCookieName, Value: adminToken})
|
||||
request.SetPathValue("backendID", services.StorageBackendLocal)
|
||||
response := httptest.NewRecorder()
|
||||
app.AdminStorageTests(response, request)
|
||||
if response.Code != http.StatusOK {
|
||||
t.Fatalf("AdminStorageTests status = %d, body = %s", response.Code, response.Body.String())
|
||||
}
|
||||
body := response.Body.String()
|
||||
if !strings.Contains(body, "New Test") || !strings.Contains(body, "Many small files") || strings.Contains(body, "storage-test-menu") {
|
||||
t.Fatalf("testing page missing expected page-based controls: %s", body)
|
||||
}
|
||||
|
||||
jsonRequest := httptest.NewRequest(http.MethodGet, "/admin/storage/local/tests.json", nil)
|
||||
jsonRequest.AddCookie(&http.Cookie{Name: userSessionCookieName, Value: adminToken})
|
||||
jsonRequest.SetPathValue("backendID", services.StorageBackendLocal)
|
||||
jsonResponse := httptest.NewRecorder()
|
||||
app.AdminStorageTestsJSON(jsonResponse, jsonRequest)
|
||||
if jsonResponse.Code != http.StatusOK {
|
||||
t.Fatalf("AdminStorageTestsJSON status = %d, body = %s", jsonResponse.Code, jsonResponse.Body.String())
|
||||
}
|
||||
if !strings.Contains(jsonResponse.Body.String(), `"progress":100`) || !strings.Contains(jsonResponse.Body.String(), `"stage":"complete"`) {
|
||||
t.Fatalf("tests json missing progress fields: %s", jsonResponse.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func createOwnedBoxThroughApp(t *testing.T, app *App, userID string) services.UploadResult {
|
||||
t.Helper()
|
||||
user, err := app.authService.UserByID(userID)
|
||||
@@ -638,6 +823,19 @@ func createOwnedBoxThroughApp(t *testing.T, app *App, userID string) services.Up
|
||||
return payload
|
||||
}
|
||||
|
||||
func createAdminSession(t *testing.T, app *App) string {
|
||||
t.Helper()
|
||||
_, err := app.authService.CreateBootstrapUser("admin", "admin@example.test", "password123")
|
||||
if err != nil && !strings.Contains(err.Error(), "registration is closed") {
|
||||
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)
|
||||
}
|
||||
return token
|
||||
}
|
||||
|
||||
func testPolicy(t *testing.T, app *App) services.UploadPolicySettings {
|
||||
t.Helper()
|
||||
policy, err := app.settingsService.UploadPolicy()
|
||||
|
||||
Reference in New Issue
Block a user