279 lines
8.4 KiB
Go
279 lines
8.4 KiB
Go
package services
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"io"
|
|
"log/slog"
|
|
"mime/multipart"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestDeleteTokenVerification(t *testing.T) {
|
|
service := newTestUploadService(t)
|
|
result := createTestBox(t, service, "file.txt", "hello")
|
|
box := getTestBox(t, service, result.BoxID)
|
|
token := tokenFromManageURL(t, result.ManageURL)
|
|
|
|
if box.DeleteTokenHash == "" {
|
|
t.Fatalf("DeleteTokenHash was not stored")
|
|
}
|
|
if strings.Contains(box.DeleteTokenHash, token) {
|
|
t.Fatalf("DeleteTokenHash contains the raw token")
|
|
}
|
|
if !service.VerifyDeleteToken(box, token) {
|
|
t.Fatalf("VerifyDeleteToken rejected the correct token")
|
|
}
|
|
if service.VerifyDeleteToken(box, "wrong-token") {
|
|
t.Fatalf("VerifyDeleteToken accepted the wrong token")
|
|
}
|
|
}
|
|
|
|
func TestDeleteBoxWithTokenRemovesMetadataAndFiles(t *testing.T) {
|
|
service := newTestUploadService(t)
|
|
result := createTestBox(t, service, "file.txt", "hello")
|
|
box := getTestBox(t, service, result.BoxID)
|
|
token := tokenFromManageURL(t, result.ManageURL)
|
|
|
|
if _, err := os.Stat(filepath.Join(service.filesDir, box.ID)); err != nil {
|
|
t.Fatalf("box files were not created: %v", err)
|
|
}
|
|
if err := service.DeleteBoxWithToken(box.ID, "wrong-token"); err == nil {
|
|
t.Fatalf("DeleteBoxWithToken accepted the wrong token")
|
|
}
|
|
if _, err := service.GetBox(box.ID); err != nil {
|
|
t.Fatalf("box was deleted after wrong token: %v", err)
|
|
}
|
|
|
|
if err := service.DeleteBoxWithToken(box.ID, token); err != nil {
|
|
t.Fatalf("DeleteBoxWithToken returned error: %v", err)
|
|
}
|
|
if _, err := service.GetBox(box.ID); !os.IsNotExist(err) {
|
|
t.Fatalf("GetBox after delete error = %v, want os.ErrNotExist", err)
|
|
}
|
|
if _, err := os.Stat(filepath.Join(service.filesDir, box.ID)); !os.IsNotExist(err) {
|
|
t.Fatalf("box directory still exists after delete: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestUserActiveStorageUsedIgnoresExpiredBoxes(t *testing.T) {
|
|
service := newTestUploadService(t)
|
|
active, err := service.CreateBox(testFileHeaders(t, "file", "active.txt", "active"), UploadOptions{MaxDays: 1, OwnerID: "user-1"})
|
|
if err != nil {
|
|
t.Fatalf("CreateBox active returned error: %v", err)
|
|
}
|
|
expired, err := service.CreateBox(testFileHeaders(t, "file", "expired.txt", "expired"), UploadOptions{MaxDays: 1, OwnerID: "user-1"})
|
|
if err != nil {
|
|
t.Fatalf("CreateBox expired returned error: %v", err)
|
|
}
|
|
expiredBox, err := service.GetBox(expired.BoxID)
|
|
if err != nil {
|
|
t.Fatalf("GetBox returned error: %v", err)
|
|
}
|
|
expiredBox.ExpiresAt = time.Now().UTC().Add(-time.Hour)
|
|
if err := service.SaveBox(expiredBox); err != nil {
|
|
t.Fatalf("SaveBox returned error: %v", err)
|
|
}
|
|
|
|
activeBox, err := service.GetBox(active.BoxID)
|
|
if err != nil {
|
|
t.Fatalf("GetBox active returned error: %v", err)
|
|
}
|
|
want := activeBox.Files[0].Size
|
|
got, err := service.UserActiveStorageUsed("user-1")
|
|
if err != nil {
|
|
t.Fatalf("UserActiveStorageUsed returned error: %v", err)
|
|
}
|
|
if got != want {
|
|
t.Fatalf("UserActiveStorageUsed = %d, want %d", got, want)
|
|
}
|
|
}
|
|
|
|
func TestLocalStorageBackendAndLegacyFallback(t *testing.T) {
|
|
service := newTestUploadService(t)
|
|
result := createTestBox(t, service, "file.txt", "hello")
|
|
box := getTestBox(t, service, result.BoxID)
|
|
if service.BoxStorageBackendID(box) != StorageBackendLocal {
|
|
t.Fatalf("BoxStorageBackendID = %q", service.BoxStorageBackendID(box))
|
|
}
|
|
if box.Files[0].ObjectKey == "" {
|
|
t.Fatalf("new file did not store object key")
|
|
}
|
|
object, err := service.OpenFileObject(testContext(), box, box.Files[0])
|
|
if err != nil {
|
|
t.Fatalf("OpenFileObject returned error: %v", err)
|
|
}
|
|
data, err := io.ReadAll(object.Body)
|
|
object.Body.Close()
|
|
if err != nil {
|
|
t.Fatalf("ReadAll returned error: %v", err)
|
|
}
|
|
if string(data) != "hello" {
|
|
t.Fatalf("object body = %q", string(data))
|
|
}
|
|
|
|
box.StorageBackendID = ""
|
|
box.Files[0].ObjectKey = ""
|
|
object, err = service.OpenFileObject(testContext(), box, box.Files[0])
|
|
if err != nil {
|
|
t.Fatalf("legacy OpenFileObject returned error: %v", err)
|
|
}
|
|
object.Body.Close()
|
|
}
|
|
|
|
func TestContaboStorageConfigAllowsDisplayNamesWithSpaces(t *testing.T) {
|
|
service := newTestUploadService(t)
|
|
cfg, err := service.Storage().CreateS3Backend(StorageBackendConfig{
|
|
Provider: StorageProviderContabo,
|
|
Name: "Contabo main",
|
|
Endpoint: "https://eu2.contabostorage.com",
|
|
Region: "EU",
|
|
Bucket: "My Main Bucket",
|
|
AccessKey: "access",
|
|
SecretKey: "secret",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("CreateS3Backend returned error: %v", err)
|
|
}
|
|
if cfg.Provider != StorageProviderContabo || !cfg.UseSSL || !cfg.PathStyle {
|
|
t.Fatalf("contabo config was not normalized: %+v", cfg)
|
|
}
|
|
if cfg.Bucket != "My Main Bucket" {
|
|
t.Fatalf("bucket = %q", cfg.Bucket)
|
|
}
|
|
}
|
|
|
|
func TestSFTPStorageConfigValidation(t *testing.T) {
|
|
service := newTestUploadService(t)
|
|
cfg, err := service.Storage().CreateS3Backend(StorageBackendConfig{
|
|
Provider: StorageProviderSFTP,
|
|
Name: "NAS storage",
|
|
Host: "files.example.test",
|
|
Username: "warpbox",
|
|
Password: "secret",
|
|
RemotePath: "/srv/warpbox//",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("CreateS3Backend returned error: %v", err)
|
|
}
|
|
if cfg.Type != StorageBackendSFTP || cfg.Provider != StorageProviderSFTP {
|
|
t.Fatalf("sftp config type/provider = %+v", cfg)
|
|
}
|
|
if cfg.Port != 22 {
|
|
t.Fatalf("port = %d, want 22", cfg.Port)
|
|
}
|
|
if cfg.RemotePath != "/srv/warpbox" {
|
|
t.Fatalf("remote path = %q", cfg.RemotePath)
|
|
}
|
|
}
|
|
|
|
func TestSMBAndWebDAVStorageConfigValidation(t *testing.T) {
|
|
service := newTestUploadService(t)
|
|
smb, err := service.Storage().CreateS3Backend(StorageBackendConfig{
|
|
Provider: StorageProviderSMB,
|
|
Name: "Office NAS",
|
|
Host: "nas.example.test",
|
|
Username: "warpbox",
|
|
Password: "secret",
|
|
Share: "uploads",
|
|
RemotePath: "/warpbox//",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("CreateS3Backend smb returned error: %v", err)
|
|
}
|
|
if smb.Type != StorageBackendSMB || smb.Provider != StorageProviderSMB || smb.Port != 445 {
|
|
t.Fatalf("smb config was not normalized: %+v", smb)
|
|
}
|
|
if smb.RemotePath != "/warpbox" {
|
|
t.Fatalf("smb remote path = %q", smb.RemotePath)
|
|
}
|
|
|
|
webdav, err := service.Storage().CreateS3Backend(StorageBackendConfig{
|
|
Provider: StorageProviderWebDAV,
|
|
Name: "Nextcloud",
|
|
Endpoint: "https://files.example.test/webdav",
|
|
Username: "warpbox",
|
|
Password: "secret",
|
|
RemotePath: "/warpbox",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("CreateS3Backend webdav returned error: %v", err)
|
|
}
|
|
if webdav.Type != StorageBackendWebDAV || webdav.Provider != StorageProviderWebDAV {
|
|
t.Fatalf("webdav config was not normalized: %+v", webdav)
|
|
}
|
|
}
|
|
|
|
func testContext() context.Context {
|
|
return context.Background()
|
|
}
|
|
|
|
func newTestUploadService(t *testing.T) *UploadService {
|
|
t.Helper()
|
|
service, err := NewUploadService(1024*1024, t.TempDir(), "http://example.test", slog.New(slog.NewTextHandler(io.Discard, nil)))
|
|
if err != nil {
|
|
t.Fatalf("NewUploadService returned error: %v", err)
|
|
}
|
|
t.Cleanup(func() {
|
|
if err := service.Close(); err != nil {
|
|
t.Fatalf("Close returned error: %v", err)
|
|
}
|
|
})
|
|
return service
|
|
}
|
|
|
|
func createTestBox(t *testing.T, service *UploadService, filename, body string) UploadResult {
|
|
t.Helper()
|
|
result, err := service.CreateBox(testFileHeaders(t, "file", filename, body), UploadOptions{MaxDays: 1})
|
|
if err != nil {
|
|
t.Fatalf("CreateBox returned error: %v", err)
|
|
}
|
|
return result
|
|
}
|
|
|
|
func getTestBox(t *testing.T, service *UploadService, boxID string) Box {
|
|
t.Helper()
|
|
box, err := service.GetBox(boxID)
|
|
if err != nil {
|
|
t.Fatalf("GetBox returned error: %v", err)
|
|
}
|
|
return box
|
|
}
|
|
|
|
func testFileHeaders(t *testing.T, field, filename, body string) []*multipart.FileHeader {
|
|
t.Helper()
|
|
var payload bytes.Buffer
|
|
writer := multipart.NewWriter(&payload)
|
|
part, err := writer.CreateFormFile(field, filename)
|
|
if err != nil {
|
|
t.Fatalf("CreateFormFile returned error: %v", err)
|
|
}
|
|
if _, err := part.Write([]byte(body)); err != nil {
|
|
t.Fatalf("part.Write returned error: %v", err)
|
|
}
|
|
if err := writer.Close(); err != nil {
|
|
t.Fatalf("writer.Close returned error: %v", err)
|
|
}
|
|
|
|
request := httptest.NewRequest("POST", "/upload", &payload)
|
|
request.Header.Set("Content-Type", writer.FormDataContentType())
|
|
if err := request.ParseMultipartForm(1024 * 1024); err != nil {
|
|
t.Fatalf("ParseMultipartForm returned error: %v", err)
|
|
}
|
|
return request.MultipartForm.File[field]
|
|
}
|
|
|
|
func tokenFromManageURL(t *testing.T, manageURL string) string {
|
|
t.Helper()
|
|
parts := strings.Split(strings.TrimRight(manageURL, "/"), "/")
|
|
if len(parts) == 0 {
|
|
t.Fatalf("empty manage URL")
|
|
}
|
|
return parts[len(parts)-1]
|
|
}
|