Introduce support for configuring unlimited upload limits by allowing -1 as a valid value for anonymous and user upload MB limits. Changes include: - Added `envMegabytesLimitFloat` and helper functions to parse and validate limits where -1 is allowed. - Updated validation logic to accept -1 for `AnonymousMaxUploadMB`, `AnonymousDailyUploadMB`, and `UserDailyUploadMB`. - Added a test case to verify unlimited upload policy behavior.
236 lines
7.3 KiB
Go
236 lines
7.3 KiB
Go
package services
|
|
|
|
import (
|
|
"log/slog"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"warpbox.dev/backend/libs/config"
|
|
)
|
|
|
|
func TestSettingsLoadDefaultsAndOverrides(t *testing.T) {
|
|
settings := newTestSettingsService(t)
|
|
policy, err := settings.UploadPolicy()
|
|
if err != nil {
|
|
t.Fatalf("UploadPolicy returned error: %v", err)
|
|
}
|
|
if !policy.AnonymousUploadsEnabled || policy.AnonymousMaxUploadMB != 512 {
|
|
t.Fatalf("default policy = %+v", policy)
|
|
}
|
|
|
|
policy.AnonymousUploadsEnabled = false
|
|
policy.UserDailyUploadMB = 123
|
|
if err := settings.UpdateUploadPolicy(policy); err != nil {
|
|
t.Fatalf("UpdateUploadPolicy returned error: %v", err)
|
|
}
|
|
next, err := settings.UploadPolicy()
|
|
if err != nil {
|
|
t.Fatalf("UploadPolicy returned error: %v", err)
|
|
}
|
|
if next.AnonymousUploadsEnabled || next.UserDailyUploadMB != 123 {
|
|
t.Fatalf("override policy = %+v", next)
|
|
}
|
|
}
|
|
|
|
func TestSettingsUseNewEnvDefaultsUntilSaved(t *testing.T) {
|
|
root := t.TempDir()
|
|
upload, err := NewUploadService(1024*1024, filepath.Join(root, "data"), "http://example.test", slog.Default())
|
|
if err != nil {
|
|
t.Fatalf("NewUploadService returned error: %v", err)
|
|
}
|
|
defer upload.Close()
|
|
|
|
first, err := NewSettingsService(upload.DB(), config.SettingsDefaults{
|
|
AnonymousUploadsEnabled: true,
|
|
AnonymousMaxUploadMB: 111,
|
|
AnonymousDailyUploadMB: 222,
|
|
UserDailyUploadMB: 333,
|
|
DefaultUserStorageMB: 444,
|
|
UsageRetentionDays: 30,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("NewSettingsService first returned error: %v", err)
|
|
}
|
|
firstPolicy, err := first.UploadPolicy()
|
|
if err != nil {
|
|
t.Fatalf("UploadPolicy first returned error: %v", err)
|
|
}
|
|
if firstPolicy.AnonymousMaxUploadMB != 111 {
|
|
t.Fatalf("first AnonymousMaxUploadMB = %v, want 111", firstPolicy.AnonymousMaxUploadMB)
|
|
}
|
|
|
|
second, err := NewSettingsService(upload.DB(), config.SettingsDefaults{
|
|
AnonymousUploadsEnabled: true,
|
|
AnonymousMaxUploadMB: 555,
|
|
AnonymousDailyUploadMB: 666,
|
|
UserDailyUploadMB: 777,
|
|
DefaultUserStorageMB: 888,
|
|
UsageRetentionDays: 30,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("NewSettingsService second returned error: %v", err)
|
|
}
|
|
secondPolicy, err := second.UploadPolicy()
|
|
if err != nil {
|
|
t.Fatalf("UploadPolicy second returned error: %v", err)
|
|
}
|
|
if secondPolicy.AnonymousMaxUploadMB != 555 {
|
|
t.Fatalf("second AnonymousMaxUploadMB = %v, want 555", secondPolicy.AnonymousMaxUploadMB)
|
|
}
|
|
|
|
if err := second.UpdateUploadPolicy(secondPolicy); err != nil {
|
|
t.Fatalf("UpdateUploadPolicy returned error: %v", err)
|
|
}
|
|
third, err := NewSettingsService(upload.DB(), config.SettingsDefaults{
|
|
AnonymousUploadsEnabled: true,
|
|
AnonymousMaxUploadMB: 999,
|
|
AnonymousDailyUploadMB: 999,
|
|
UserDailyUploadMB: 999,
|
|
DefaultUserStorageMB: 999,
|
|
UsageRetentionDays: 30,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("NewSettingsService third returned error: %v", err)
|
|
}
|
|
thirdPolicy, err := third.UploadPolicy()
|
|
if err != nil {
|
|
t.Fatalf("UploadPolicy third returned error: %v", err)
|
|
}
|
|
if thirdPolicy.AnonymousMaxUploadMB != 555 {
|
|
t.Fatalf("third AnonymousMaxUploadMB = %v, want persisted 555", thirdPolicy.AnonymousMaxUploadMB)
|
|
}
|
|
}
|
|
|
|
func TestSettingsRejectInvalidMegabytes(t *testing.T) {
|
|
if _, err := ParseMegabytesValue("0"); err == nil {
|
|
t.Fatalf("ParseMegabytesValue accepted zero")
|
|
}
|
|
settings := newTestSettingsService(t)
|
|
policy, err := settings.UploadPolicy()
|
|
if err != nil {
|
|
t.Fatalf("UploadPolicy returned error: %v", err)
|
|
}
|
|
policy.DefaultUserStorageMB = -1
|
|
if err := settings.UpdateUploadPolicy(policy); err == nil {
|
|
t.Fatalf("UpdateUploadPolicy accepted negative storage")
|
|
}
|
|
}
|
|
|
|
func TestUploadPolicyAllowsNegativeOneForUnlimitedUploadLimits(t *testing.T) {
|
|
settings := newTestSettingsService(t)
|
|
policy, err := settings.UploadPolicy()
|
|
if err != nil {
|
|
t.Fatalf("UploadPolicy returned error: %v", err)
|
|
}
|
|
policy.AnonymousMaxUploadMB = -1
|
|
policy.AnonymousDailyUploadMB = -1
|
|
policy.UserDailyUploadMB = -1
|
|
if err := settings.UpdateUploadPolicy(policy); err != nil {
|
|
t.Fatalf("UpdateUploadPolicy rejected -1 unlimited upload limits: %v", err)
|
|
}
|
|
next, err := settings.UploadPolicy()
|
|
if err != nil {
|
|
t.Fatalf("UploadPolicy returned error: %v", err)
|
|
}
|
|
if next.AnonymousMaxUploadMB != -1 || next.AnonymousDailyUploadMB != -1 || next.UserDailyUploadMB != -1 {
|
|
t.Fatalf("unlimited upload limits were not persisted: %+v", next)
|
|
}
|
|
if got := FormatMegabytesLabel(-1); got != "unlimited" {
|
|
t.Fatalf("FormatMegabytesLabel(-1) = %q, want unlimited", got)
|
|
}
|
|
}
|
|
|
|
func TestDailyUsageAndCleanup(t *testing.T) {
|
|
settings := newTestSettingsService(t)
|
|
now := time.Date(2026, 5, 30, 12, 0, 0, 0, time.UTC)
|
|
if err := settings.AddUsage("ip", "127.0.0.1", 1024, now); err != nil {
|
|
t.Fatalf("AddUsage returned error: %v", err)
|
|
}
|
|
if err := settings.AddUsage("ip", "127.0.0.1", 2048, now); err != nil {
|
|
t.Fatalf("AddUsage returned error: %v", err)
|
|
}
|
|
usage, err := settings.UsageForIP("127.0.0.1", now)
|
|
if err != nil {
|
|
t.Fatalf("UsageForIP returned error: %v", err)
|
|
}
|
|
if usage.UploadedBytes != 3072 {
|
|
t.Fatalf("UploadedBytes = %d, want 3072", usage.UploadedBytes)
|
|
}
|
|
|
|
if err := settings.CleanupUsage(now.AddDate(0, 0, 31), 30); err != nil {
|
|
t.Fatalf("CleanupUsage returned error: %v", err)
|
|
}
|
|
usage, err = settings.UsageForIP("127.0.0.1", now)
|
|
if err != nil {
|
|
t.Fatalf("UsageForIP returned error: %v", err)
|
|
}
|
|
if usage.UploadedBytes != 0 {
|
|
t.Fatalf("UploadedBytes after cleanup = %d, want 0", usage.UploadedBytes)
|
|
}
|
|
}
|
|
|
|
func TestEffectiveUserPolicyUsesOverridesAndInheritance(t *testing.T) {
|
|
settings := newTestSettingsService(t)
|
|
policy, err := settings.UploadPolicy()
|
|
if err != nil {
|
|
t.Fatalf("UploadPolicy returned error: %v", err)
|
|
}
|
|
policy.UserDailyUploadMB = 100
|
|
policy.DefaultUserStorageMB = 200
|
|
policy.UserMaxDays = 30
|
|
policy.UserDailyBoxes = 40
|
|
policy.UserActiveBoxes = 50
|
|
policy.UserStorageBackend = "local"
|
|
|
|
overrideDaily := 300.0
|
|
overrideQuota := 0.0
|
|
overrideDays := 12
|
|
overrideBackend := "bucket-1"
|
|
user := User{
|
|
ID: "user-1",
|
|
Policy: UserPolicy{
|
|
DailyUploadMB: &overrideDaily,
|
|
StorageQuotaMB: &overrideQuota,
|
|
MaxDays: &overrideDays,
|
|
StorageBackendID: &overrideBackend,
|
|
},
|
|
}
|
|
effective := settings.EffectivePolicyForUser(policy, user)
|
|
if effective.DailyUploadMB != overrideDaily || effective.MaxDays != overrideDays || effective.StorageBackendID != overrideBackend {
|
|
t.Fatalf("effective policy did not use overrides: %+v", effective)
|
|
}
|
|
if effective.StorageQuotaSet {
|
|
t.Fatalf("zero storage quota override should mean unlimited: %+v", effective)
|
|
}
|
|
if effective.DailyBoxes != policy.UserDailyBoxes || effective.ActiveBoxes != policy.UserActiveBoxes {
|
|
t.Fatalf("effective policy did not inherit box caps: %+v", effective)
|
|
}
|
|
}
|
|
|
|
func newTestSettingsService(t *testing.T) *SettingsService {
|
|
t.Helper()
|
|
root := t.TempDir()
|
|
upload, err := NewUploadService(1024*1024, filepath.Join(root, "data"), "http://example.test", slog.Default())
|
|
if err != nil {
|
|
t.Fatalf("NewUploadService returned error: %v", err)
|
|
}
|
|
t.Cleanup(func() {
|
|
if err := upload.Close(); err != nil {
|
|
t.Fatalf("Close returned error: %v", err)
|
|
}
|
|
})
|
|
settings, err := NewSettingsService(upload.DB(), config.SettingsDefaults{
|
|
AnonymousUploadsEnabled: true,
|
|
AnonymousMaxUploadMB: 512,
|
|
AnonymousDailyUploadMB: 2048,
|
|
UserDailyUploadMB: 8192,
|
|
DefaultUserStorageMB: 51200,
|
|
UsageRetentionDays: 30,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("NewSettingsService returned error: %v", err)
|
|
}
|
|
return settings
|
|
}
|