Files
uncensored-send/internal/config/config_test.go
T
2026-09-13 08:57:01 +02:00

208 lines
5.7 KiB
Go

package config
import (
"strings"
"testing"
"time"
)
func TestParseSize(t *testing.T) {
cases := map[string]int64{
"": Unlimited,
"0": Unlimited,
"unlimited": Unlimited,
"512": 512,
"512B": 512,
"2GiB": 2 << 30,
"2 GiB": 2 << 30,
"2G": 2 << 30,
"500MB": 500_000_000,
"1.5MiB": 1572864,
"2gib": 2 << 30,
}
for in, want := range cases {
got, err := ParseSize(in)
if err != nil {
t.Errorf("ParseSize(%q): %v", in, err)
continue
}
if got != want {
t.Errorf("ParseSize(%q) = %d, want %d", in, got, want)
}
}
for _, in := range []string{"-1", "lots", "2XiB", "GiB2"} {
if _, err := ParseSize(in); err == nil {
t.Errorf("ParseSize(%q) accepted an invalid size", in)
}
}
}
func TestParseDuration(t *testing.T) {
cases := map[string]time.Duration{
"": Unlimited,
"never": Unlimited,
"0": Unlimited,
"90m": 90 * time.Minute,
"72h": 72 * time.Hour,
"3d": 72 * time.Hour,
"1w": 168 * time.Hour,
"1.5d": 36 * time.Hour,
}
for in, want := range cases {
got, err := ParseDuration(in)
if err != nil {
t.Errorf("ParseDuration(%q): %v", in, err)
continue
}
if got != want {
t.Errorf("ParseDuration(%q) = %s, want %s", in, got, want)
}
}
for _, in := range []string{"-3d", "soon", "3days"} {
if _, err := ParseDuration(in); err == nil {
t.Errorf("ParseDuration(%q) accepted an invalid duration", in)
}
}
}
// The formatters are human-facing and round to two decimals, so they only
// round-trip exactly for values that land on a unit boundary. Nothing in the
// program re-parses their output; this pins the cases that are shown most.
func TestFormatRoundTrips(t *testing.T) {
for _, n := range []int64{0, 512, 2 << 30, 100 << 20, 1536} {
if got, err := ParseSize(FormatSize(n)); err != nil || got != n {
t.Errorf("FormatSize(%d) = %q, which parses back to %d (%v)", n, FormatSize(n), got, err)
}
}
for _, d := range []time.Duration{0, time.Hour, 72 * time.Hour, 90 * time.Minute} {
if got, err := ParseDuration(FormatDuration(d)); err != nil || got != d {
t.Errorf("FormatDuration(%s) = %q, which parses back to %s (%v)", d, FormatDuration(d), got, err)
}
}
}
// The convention is: one hyphen for a letter, two for a word. It is enforced
// here because the stdlib flag package treats both forms as the same.
func TestHyphenConvention(t *testing.T) {
newSet := func() (*Set, *string) {
var listen string
s := NewSet("test", "UNCENSORED_SEND_TEST_")
s.String(&listen, "listen", "l", "default", "ADDR", "listen address")
return s, &listen
}
for _, args := range [][]string{{"--listen", "x"}, {"-l", "x"}, {"--listen=x"}, {"-l=x"}} {
s, listen := newSet()
if err := s.Parse(args); err != nil {
t.Errorf("%v: %v", args, err)
continue
}
if *listen != "x" {
t.Errorf("%v: listen = %q", args, *listen)
}
}
for _, args := range [][]string{{"-listen", "x"}, {"--l", "x"}, {"-listen=x"}} {
s, _ := newSet()
err := s.Parse(args)
if err == nil {
t.Errorf("%v was accepted; it breaks the hyphen convention", args)
continue
}
if !strings.Contains(err.Error(), "hyphen") {
t.Errorf("%v: error does not explain the convention: %v", args, err)
}
}
}
func TestEnvFallback(t *testing.T) {
t.Setenv("UNCENSORED_SEND_TEST_MAX_SIZE", "4GiB")
var size int64
s := NewSet("test", "UNCENSORED_SEND_TEST_")
s.Size(&size, "max-size", "s", "2GiB", "cap")
if err := s.Parse(nil); err != nil {
t.Fatal(err)
}
if size != 4<<30 {
t.Errorf("size = %d, want 4GiB from the environment", size)
}
// An explicit flag still wins over the environment.
s = NewSet("test", "UNCENSORED_SEND_TEST_")
s.Size(&size, "max-size", "s", "2GiB", "cap")
if err := s.Parse([]string{"--max-size", "1GiB"}); err != nil {
t.Fatal(err)
}
if size != 1<<30 {
t.Errorf("size = %d, want the flag to win", size)
}
}
func TestNormalisePath(t *testing.T) {
cases := map[string]string{
"": "/",
"/": "/",
"send": "/send/",
"/send": "/send/",
"/send/": "/send/",
"a/b": "/a/b/",
}
for in, want := range cases {
if got := NormalisePath(in); got != want {
t.Errorf("NormalisePath(%q) = %q, want %q", in, got, want)
}
}
}
func TestDefaultExpiryMustFitWithinMax(t *testing.T) {
c := &Config{BasePath: "/", DataDir: "d", SweepInterval: time.Minute, MaxConcurrent: 1,
MaxExpiry: time.Hour, DefaultExpiry: 24 * time.Hour}
if err := c.Normalise(); err == nil {
t.Error("a default longer than the maximum was accepted")
}
c.DefaultExpiry = Unlimited
if err := c.Normalise(); err == nil {
t.Error("an unlimited default under a finite maximum was accepted")
}
}
func TestPortOverridesListen(t *testing.T) {
cases := []struct {
listen string
port int
want string
}{
{"127.0.0.1:8080", 0, "127.0.0.1:8080"},
{"127.0.0.1:8080", 9000, "127.0.0.1:9000"},
{"0.0.0.0:8080", 3000, "0.0.0.0:3000"},
{"localhost", 3000, "localhost:3000"}, // a bare host is fine with --port
{"[::1]:8080", 3000, "[::1]:3000"},
}
for _, c := range cases {
cfg := &Config{
Listen: c.listen, Port: c.port, BasePath: "/", DataDir: "d",
SweepInterval: time.Minute, MaxConcurrent: 1,
}
if err := cfg.Normalise(); err != nil {
t.Errorf("listen %q port %d: %v", c.listen, c.port, err)
continue
}
if cfg.Listen != c.want {
t.Errorf("listen %q port %d => %q, want %q", c.listen, c.port, cfg.Listen, c.want)
}
}
for _, c := range []struct {
listen string
port int
}{{"127.0.0.1:8080", 70000}, {"127.0.0.1:8080", -1}, {"localhost", 0}} {
cfg := &Config{
Listen: c.listen, Port: c.port, BasePath: "/", DataDir: "d",
SweepInterval: time.Minute, MaxConcurrent: 1,
}
if err := cfg.Normalise(); err == nil {
t.Errorf("listen %q port %d was accepted", c.listen, c.port)
}
}
}