github.com/filecoin-project/bacalhau@v0.3.23-0.20230228154132-45c989550ace/pkg/storage/url/urldownload/storage_test.go (about) 1 //go:build unit || !integration 2 3 package urldownload 4 5 import ( 6 "context" 7 "fmt" 8 "net/http" 9 "net/http/httptest" 10 "os" 11 "path/filepath" 12 "testing" 13 14 "github.com/filecoin-project/bacalhau/pkg/logger" 15 "github.com/filecoin-project/bacalhau/pkg/model" 16 "github.com/filecoin-project/bacalhau/pkg/system" 17 "github.com/spf13/cobra" 18 "github.com/stretchr/testify/suite" 19 ) 20 21 // Define the suite, and absorb the built-in basic suite 22 // functionality from testify - including a T() method which 23 // returns the current testing context 24 type StorageSuite struct { 25 suite.Suite 26 RootCmd *cobra.Command 27 } 28 29 func TestStorageSuite(t *testing.T) { 30 suite.Run(t, new(StorageSuite)) 31 } 32 33 // Before each test 34 func (s *StorageSuite) SetupTest() { 35 logger.ConfigureTestLogging(s.T()) 36 s.Require().NoError(system.InitConfigForTesting(s.T())) 37 } 38 39 func (s *StorageSuite) TestNewStorageProvider() { 40 cm := system.NewCleanupManager() 41 42 sp, err := NewStorage(cm) 43 s.Require().NoError(err, "failed to create storage provider") 44 45 // is dir writable? 46 f, err := os.Create(filepath.Join(sp.localDir, "data.txt")) 47 s.Require().NoError(err, "failed to create file") 48 49 _, err = f.WriteString("test\n") 50 s.Require().NoError(err, "failed to write to file") 51 52 s.NoError(f.Close()) 53 s.Require().NotNil(sp.client, "HTTPClient is nil") 54 } 55 56 func (s *StorageSuite) TestHasStorageLocally() { 57 sp := newStorage(s.T().TempDir()) 58 59 spec := model.StorageSpec{ 60 StorageSource: model.StorageSourceURLDownload, 61 URL: "foo", 62 Path: "foo", 63 } 64 // files are not cached thus shall never return true 65 locally, err := sp.HasStorageLocally(context.Background(), spec) 66 s.Require().NoError(err, "failed to check if storage is locally available") 67 68 s.False(locally, "storage should not be locally available") 69 } 70 71 func (s *StorageSuite) TestPrepareStorageURL() { 72 type dummyRequest struct { 73 path string 74 code int 75 content string 76 } 77 tests := []struct { 78 name string 79 requests []dummyRequest 80 expectedContent string 81 expectedFilename string 82 }{ 83 { 84 name: "follows-redirect", 85 requests: []dummyRequest{ 86 { 87 path: "/initial", 88 code: 302, 89 content: "/second.png", 90 }, 91 { 92 path: "/second.png", 93 code: 302, 94 content: "/third.txt", 95 }, 96 { 97 path: "/third.txt", 98 code: 200, 99 content: "this is from the final redirect", 100 }, 101 }, 102 expectedContent: "this is from the final redirect", 103 expectedFilename: "third.txt", 104 }, 105 { 106 name: "retries", 107 requests: []dummyRequest{ 108 { 109 path: "/initial", 110 code: 500, 111 content: "", 112 }, 113 { 114 path: "/initial", 115 code: 500, 116 content: "", 117 }, 118 { 119 path: "/initial", 120 code: 200, 121 content: "got there eventually", 122 }, 123 }, 124 expectedContent: "got there eventually", 125 expectedFilename: "initial", 126 }, 127 { 128 name: "retry-anything", 129 requests: []dummyRequest{ 130 { 131 path: "/initial", 132 code: 401, 133 content: "not allowed", 134 }, 135 { 136 path: "/initial", 137 code: 401, 138 content: "not allowed", 139 }, 140 { 141 path: "/initial", 142 code: 200, 143 content: "changed my mind", 144 }, 145 }, 146 expectedContent: "changed my mind", 147 expectedFilename: "initial", 148 }, 149 { 150 name: "generates-name", 151 requests: []dummyRequest{ 152 { 153 path: "/", 154 code: 200, 155 content: "name should be a UUID", 156 }, 157 }, 158 expectedContent: "name should be a UUID", 159 expectedFilename: "", 160 }, 161 { 162 name: "no-content", 163 requests: []dummyRequest{ 164 { 165 path: "/nothing.txt", 166 code: 204, 167 content: "", 168 }, 169 }, 170 expectedContent: "", 171 expectedFilename: "nothing.txt", 172 }, 173 { 174 name: "picsum.photos", 175 requests: []dummyRequest{ 176 { 177 path: "/200/300", 178 code: 302, 179 content: "/id/568/200/300.jpg", 180 }, 181 { 182 path: "/id/568/200/300.jpg", 183 code: 200, 184 content: "i'm not putting an image here", 185 }, 186 }, 187 expectedContent: "i'm not putting an image here", 188 expectedFilename: "300.jpg", 189 }, 190 } 191 192 for _, test := range tests { 193 s.Run(test.name, func() { 194 responseCount := 0 195 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 196 response := test.requests[responseCount] 197 responseCount++ 198 199 if r.URL.Path != response.path { 200 http.Error(w, fmt.Sprintf("invalid path: %s should be %s", r.URL.Path, response.path), 999) 201 return 202 } 203 204 if response.code == http.StatusFound { 205 http.Redirect(w, r, response.content, http.StatusFound) 206 return 207 } 208 209 w.WriteHeader(response.code) 210 _, err := w.Write([]byte(response.content)) 211 s.NoError(err) 212 })) 213 s.T().Cleanup(ts.Close) 214 215 subject := newStorage(s.T().TempDir()) 216 217 vol, err := subject.PrepareStorage(context.Background(), model.StorageSpec{ 218 URL: fmt.Sprintf("%s%s", ts.URL, test.requests[0].path), 219 Path: "/inputs", 220 }) 221 s.Require().NoError(err) 222 223 actualFilename := filepath.Base(vol.Source) 224 if test.expectedFilename == "" { 225 s.Regexp(`[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$`, actualFilename, 226 "Filename should be a UUID if it can't come from the HTTP response ") 227 s.Regexp(`[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$`, filepath.Base(vol.Target)) 228 s.Equal(fmt.Sprintf("%s%s", string(os.PathSeparator), "inputs"), filepath.Dir(vol.Target)) 229 } else { 230 s.Equal(test.expectedFilename, actualFilename) 231 s.Equal(filepath.Join("/inputs", test.expectedFilename), vol.Target) 232 } 233 234 s.FileExists(vol.Source) 235 actualContent, err := os.ReadFile(vol.Source) 236 s.Require().NoError(err) 237 238 s.Equal(test.expectedContent, string(actualContent)) 239 }) 240 } 241 }