diff --git a/backend/app/store/image/image.go b/backend/app/store/image/image.go index 1026bc7d..7983f69e 100644 --- a/backend/app/store/image/image.go +++ b/backend/app/store/image/image.go @@ -1,4 +1,5 @@ -// Package image handles storing, resizing and retrival of images +// Package image handles storing, resizing and retrieval of images +// Provides Interface with Save and Load and one implementation on top of local file system. package image import ( @@ -25,9 +26,9 @@ type Interface interface { // FileSystem provides image Interface for local files. Saves and loads files from Location, restricts max size type FileSystem struct { - Location string - MaxSize int - Partitons int + Location string + MaxSize int + Partitions int crc struct { *crc64.Table @@ -98,13 +99,13 @@ func (f *FileSystem) Load(id string) (io.ReadCloser, int64, error) { // in different subdirectories and avoid too many files in a single place. // the end result is a full path like this - /tmp/images/92. Number of partitions defined by FileSystem.Partitions func (f *FileSystem) location(id string) string { - if f.Partitons == 0 { + if f.Partitions == 0 { return f.Location } f.crc.Do(func() { f.crc.Table = crc64.MakeTable(crc64.ECMA) - p := int(math.Round(math.Log10(float64(f.Partitons)))) + p := int(math.Round(math.Log10(float64(f.Partitions)))) f.crc.mask = "%0" + strconv.Itoa(p) + "d" f.crc.divider = uint64(math.Pow(10, float64(p))) }) diff --git a/backend/app/store/image/image_test.go b/backend/app/store/image/image_test.go index 29017910..3c24c239 100644 --- a/backend/app/store/image/image_test.go +++ b/backend/app/store/image/image_test.go @@ -2,6 +2,7 @@ package image import ( "io/ioutil" + "math/rand" "os" "path" "strconv" @@ -13,57 +14,39 @@ import ( ) func TestImage_Save(t *testing.T) { - loc, err := ioutil.TempDir("", "test_image_r42") - require.NoError(t, err, "failed to make temp dir") - defer os.RemoveAll(loc) + svc, teardown := prepareImageTest(t) + defer teardown() - svc := FileSystem{ - Location: loc, - Partitons: 100, - MaxSize: 50, - } id, err := svc.Save("blah_ff1.png", strings.NewReader("blah blah")) assert.NoError(t, err) assert.Equal(t, "fc77a87ad3c898b9603119711f99305145e272e103c904d85ee2deda.png", id) - dst := path.Join(loc, "56", id) + dst := path.Join(svc.Location, "56", id) data, err := ioutil.ReadFile(dst) assert.NoError(t, err) assert.Equal(t, "blah blah", string(data)) } func TestImage_SaveTooLarge(t *testing.T) { - loc, err := ioutil.TempDir("", "test_image_r42") - require.NoError(t, err, "failed to make temp dir") - defer os.RemoveAll(loc) - - svc := FileSystem{ - Location: loc, - Partitons: 100, - MaxSize: 5, - } - _, err = svc.Save("blah_ff1.png", strings.NewReader("blah blah")) + svc, teardown := prepareImageTest(t) + defer teardown() + svc.MaxSize = 5 + _, err := svc.Save("blah_ff1.png", strings.NewReader("blah blah")) assert.Error(t, err) assert.EqualError(t, err, "file blah_ff1.png is too large") } func TestImage_Load(t *testing.T) { - loc, err := ioutil.TempDir("", "test_image_r42") - require.NoError(t, err, "failed to make temp dir") - defer os.RemoveAll(loc) - // save image - svc := FileSystem{ - Location: loc, - Partitons: 100, - MaxSize: 50, - } + svc, teardown := prepareImageTest(t) + defer teardown() + id, err := svc.Save("blah_ff1.png", strings.NewReader("blah blah")) assert.NoError(t, err) r, sz, err := svc.Load(id) assert.NoError(t, err) - defer r.Close() + defer func() { assert.NoError(t, r.Close()) }() data, err := ioutil.ReadAll(r) assert.NoError(t, err) assert.Equal(t, "blah blah", string(data)) @@ -83,12 +66,52 @@ func TestImage_location(t *testing.T) { {100, "12345", "/tmp/69"}, {100, "xyzz", "/tmp/58"}, {100, "6851dcde6024e03258a66705f29e14b506048c74.png", "/tmp/02"}, + {5, "6851dcde6024e03258a66705f29e14b506048c74.png", "/tmp/2"}, + {5, "xxxyz.png", "/tmp/0"}, {0, "12345", "/tmp"}, } for n, tt := range tbl { t.Run(strconv.Itoa(n), func(t *testing.T) { - svc := FileSystem{Location: "/tmp", Partitons: tt.partitions} + svc := FileSystem{Location: "/tmp", Partitions: tt.partitions} assert.Equal(t, tt.res, svc.location(tt.id)) }) } + + // generate random names and make sure partition never runs out of allowed + letterRunes := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") + randomString := func(n int) string { + b := make([]rune, n) + for i := range b { + b[i] = letterRunes[rand.Intn(len(letterRunes))] + } + return string(b) + } + + svc := FileSystem{Location: "/tmp", Partitions: 10} + for i := 0; i < 1000; i++ { + v := randomString(rand.Intn(64)) + parts := strings.Split(svc.location(v), "/") + p, err := strconv.Atoi(parts[len(parts)-1]) + require.NoError(t, err) + assert.True(t, p >= 0 && p < 10) + } +} + +func prepareImageTest(t *testing.T) (svc FileSystem, teardown func()) { + loc, err := ioutil.TempDir("", "test_image_r42") + require.NoError(t, err, "failed to make temp dir") + + svc = FileSystem{ + Location: loc, + Partitions: 100, + MaxSize: 50, + } + + teardown = func() { + defer func() { + assert.NoError(t, os.RemoveAll(loc)) + }() + } + + return svc, teardown }