feat: add upload policies, daily limits, and storage quotas
All checks were successful
Build and Publish Docker Image / deploy (push) Successful in 1m8s
All checks were successful
Build and Publish Docker Image / deploy (push) Successful in 1m8s
- Add environment variables to configure anonymous uploads, daily upload caps, and default user storage limits. - Update config loader to parse and validate the new settings. - Implement backend logic to track daily usage and active storage per user. - Update README and `.env.example` to document the new settings and admin panels.
This commit is contained in:
173
backend/libs/services/settings_test.go
Normal file
173
backend/libs/services/settings_test.go
Normal file
@@ -0,0 +1,173 @@
|
||||
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 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 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
|
||||
}
|
||||
Reference in New Issue
Block a user