diff --git a/web/fup/fuphttp/fngen/fngen.go b/web/fup/fuphttp/fngen/fngen.go index 9828bd69cf..647bdce4e3 100644 --- a/web/fup/fuphttp/fngen/fngen.go +++ b/web/fup/fuphttp/fngen/fngen.go @@ -63,7 +63,8 @@ func BoringGenerator(filenamePrefix string, filenameExt string, attempt int) str return string(out) } -func uniqueName(ctx context.Context, existsFn func(ctx context.Context, name string) (bool, error), filenamePrefix, extension string, generator FilenameGenerator) (string, error) { +// UniqueName repeatedly attempts to generate a name until existsFn returns false. +func UniqueName(ctx context.Context, existsFn func(ctx context.Context, name string) (bool, error), filenamePrefix, extension string, generator FilenameGenerator) (string, error) { // There's a race condition here, because we might generate a currently-not-taken filename and collide with someone uploading with the same name. // However, given the usecase of this software, I've decided not to care. I'm unlikely to be uploading enough things concurrently that this is a real issue. attempt := 0 diff --git a/web/fup/fuphttp/fngen/fngen_test.go b/web/fup/fuphttp/fngen/fngen_test.go index b2e9abf1c1..0678533a0c 100644 --- a/web/fup/fuphttp/fngen/fngen_test.go +++ b/web/fup/fuphttp/fngen/fngen_test.go @@ -5,6 +5,7 @@ package fngen_test import ( + "context" "fmt" "strings" "testing" @@ -47,3 +48,49 @@ func TestBoringGenerator(t *testing.T) { t.Errorf("BoringGenerator(%q, %q, 0) = %q; want something ending in %v", "prefix", ".ext", got, ".ext") } } + +func TestUniqueName(t *testing.T) { + gen := func(filenamePrefix string, filenameExt string, attempt int) string { + return fmt.Sprintf("%s.%d%s", filenamePrefix, attempt, filenameExt) + } + exists := func(ctx context.Context, name string) (bool, error) { + switch name { + case "firstTry.0.txt": + return false, nil + case "secondTry.0.txt": + return true, nil + case "secondTry.1.txt": + return false, nil + default: + return false, fmt.Errorf("no existence recorded for %q for testing", name) + } + } + + ctx := context.Background() + tcs := []struct { + prefix string + want string + wantErr bool + }{{ + prefix: "firstTry", + want: "firstTry.0.txt", + }, { + prefix: "secondTry", + want: "secondTry.1.txt", + }, { + prefix: "error", + wantErr: true, + }} + for _, tc := range tcs { + got, err := fngen.UniqueName(ctx, exists, tc.prefix, ".txt", gen) + switch { + case err != nil && !tc.wantErr: + t.Errorf("UniqueName(%q): %v", tc.prefix, err) + case err == nil && tc.wantErr: + t.Errorf("UniqueName(%q) didn't return error; expected one", tc.prefix) + } + if got != tc.want { + t.Errorf("UniqueName(%q) = %q; want %q", tc.prefix, got, tc.want) + } + } +}