diff options
Diffstat (limited to 'pkg/artifactcache/handler_test.go')
-rw-r--r-- | pkg/artifactcache/handler_test.go | 471 |
1 files changed, 471 insertions, 0 deletions
diff --git a/pkg/artifactcache/handler_test.go b/pkg/artifactcache/handler_test.go new file mode 100644 index 0000000..35ec753 --- /dev/null +++ b/pkg/artifactcache/handler_test.go @@ -0,0 +1,471 @@ +package artifactcache + +import ( + "bytes" + "crypto/rand" + "encoding/json" + "fmt" + "io" + "net/http" + "path/filepath" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.etcd.io/bbolt" +) + +func TestHandler(t *testing.T) { + dir := filepath.Join(t.TempDir(), "artifactcache") + handler, err := StartHandler(dir, "", 0, nil) + require.NoError(t, err) + + base := fmt.Sprintf("%s%s", handler.ExternalURL(), urlBase) + + defer func() { + t.Run("inpect db", func(t *testing.T) { + db, err := handler.openDB() + require.NoError(t, err) + defer db.Close() + require.NoError(t, db.Bolt().View(func(tx *bbolt.Tx) error { + return tx.Bucket([]byte("Cache")).ForEach(func(k, v []byte) error { + t.Logf("%s: %s", k, v) + return nil + }) + })) + }) + t.Run("close", func(t *testing.T) { + require.NoError(t, handler.Close()) + assert.Nil(t, handler.server) + assert.Nil(t, handler.listener) + _, err := http.Post(fmt.Sprintf("%s/caches/%d", base, 1), "", nil) + assert.Error(t, err) + }) + }() + + t.Run("get not exist", func(t *testing.T) { + key := strings.ToLower(t.Name()) + version := "c19da02a2bd7e77277f1ac29ab45c09b7d46a4ee758284e26bb3045ad11d9d20" + resp, err := http.Get(fmt.Sprintf("%s/cache?keys=%s&version=%s", base, key, version)) + require.NoError(t, err) + require.Equal(t, 204, resp.StatusCode) + }) + + t.Run("reserve and upload", func(t *testing.T) { + key := strings.ToLower(t.Name()) + version := "c19da02a2bd7e77277f1ac29ab45c09b7d46a4ee758284e26bb3045ad11d9d20" + content := make([]byte, 100) + _, err := rand.Read(content) + require.NoError(t, err) + uploadCacheNormally(t, base, key, version, content) + }) + + t.Run("clean", func(t *testing.T) { + resp, err := http.Post(fmt.Sprintf("%s/clean", base), "", nil) + require.NoError(t, err) + assert.Equal(t, 200, resp.StatusCode) + }) + + t.Run("reserve with bad request", func(t *testing.T) { + body := []byte(`invalid json`) + require.NoError(t, err) + resp, err := http.Post(fmt.Sprintf("%s/caches", base), "application/json", bytes.NewReader(body)) + require.NoError(t, err) + assert.Equal(t, 400, resp.StatusCode) + }) + + t.Run("duplicate reserve", func(t *testing.T) { + key := strings.ToLower(t.Name()) + version := "c19da02a2bd7e77277f1ac29ab45c09b7d46a4ee758284e26bb3045ad11d9d20" + { + body, err := json.Marshal(&Request{ + Key: key, + Version: version, + Size: 100, + }) + require.NoError(t, err) + resp, err := http.Post(fmt.Sprintf("%s/caches", base), "application/json", bytes.NewReader(body)) + require.NoError(t, err) + assert.Equal(t, 200, resp.StatusCode) + + got := struct { + CacheID uint64 `json:"cacheId"` + }{} + require.NoError(t, json.NewDecoder(resp.Body).Decode(&got)) + } + { + body, err := json.Marshal(&Request{ + Key: key, + Version: version, + Size: 100, + }) + require.NoError(t, err) + resp, err := http.Post(fmt.Sprintf("%s/caches", base), "application/json", bytes.NewReader(body)) + require.NoError(t, err) + assert.Equal(t, 400, resp.StatusCode) + } + }) + + t.Run("upload with bad id", func(t *testing.T) { + req, err := http.NewRequest(http.MethodPatch, + fmt.Sprintf("%s/caches/invalid_id", base), bytes.NewReader(nil)) + require.NoError(t, err) + req.Header.Set("Content-Type", "application/octet-stream") + req.Header.Set("Content-Range", "bytes 0-99/*") + resp, err := http.DefaultClient.Do(req) + require.NoError(t, err) + assert.Equal(t, 400, resp.StatusCode) + }) + + t.Run("upload without reserve", func(t *testing.T) { + req, err := http.NewRequest(http.MethodPatch, + fmt.Sprintf("%s/caches/%d", base, 1000), bytes.NewReader(nil)) + require.NoError(t, err) + req.Header.Set("Content-Type", "application/octet-stream") + req.Header.Set("Content-Range", "bytes 0-99/*") + resp, err := http.DefaultClient.Do(req) + require.NoError(t, err) + assert.Equal(t, 400, resp.StatusCode) + }) + + t.Run("upload with complete", func(t *testing.T) { + key := strings.ToLower(t.Name()) + version := "c19da02a2bd7e77277f1ac29ab45c09b7d46a4ee758284e26bb3045ad11d9d20" + var id uint64 + content := make([]byte, 100) + _, err := rand.Read(content) + require.NoError(t, err) + { + body, err := json.Marshal(&Request{ + Key: key, + Version: version, + Size: 100, + }) + require.NoError(t, err) + resp, err := http.Post(fmt.Sprintf("%s/caches", base), "application/json", bytes.NewReader(body)) + require.NoError(t, err) + assert.Equal(t, 200, resp.StatusCode) + + got := struct { + CacheID uint64 `json:"cacheId"` + }{} + require.NoError(t, json.NewDecoder(resp.Body).Decode(&got)) + id = got.CacheID + } + { + req, err := http.NewRequest(http.MethodPatch, + fmt.Sprintf("%s/caches/%d", base, id), bytes.NewReader(content)) + require.NoError(t, err) + req.Header.Set("Content-Type", "application/octet-stream") + req.Header.Set("Content-Range", "bytes 0-99/*") + resp, err := http.DefaultClient.Do(req) + require.NoError(t, err) + assert.Equal(t, 200, resp.StatusCode) + } + { + resp, err := http.Post(fmt.Sprintf("%s/caches/%d", base, id), "", nil) + require.NoError(t, err) + assert.Equal(t, 200, resp.StatusCode) + } + { + req, err := http.NewRequest(http.MethodPatch, + fmt.Sprintf("%s/caches/%d", base, id), bytes.NewReader(content)) + require.NoError(t, err) + req.Header.Set("Content-Type", "application/octet-stream") + req.Header.Set("Content-Range", "bytes 0-99/*") + resp, err := http.DefaultClient.Do(req) + require.NoError(t, err) + assert.Equal(t, 400, resp.StatusCode) + } + }) + + t.Run("upload with invalid range", func(t *testing.T) { + key := strings.ToLower(t.Name()) + version := "c19da02a2bd7e77277f1ac29ab45c09b7d46a4ee758284e26bb3045ad11d9d20" + var id uint64 + content := make([]byte, 100) + _, err := rand.Read(content) + require.NoError(t, err) + { + body, err := json.Marshal(&Request{ + Key: key, + Version: version, + Size: 100, + }) + require.NoError(t, err) + resp, err := http.Post(fmt.Sprintf("%s/caches", base), "application/json", bytes.NewReader(body)) + require.NoError(t, err) + assert.Equal(t, 200, resp.StatusCode) + + got := struct { + CacheID uint64 `json:"cacheId"` + }{} + require.NoError(t, json.NewDecoder(resp.Body).Decode(&got)) + id = got.CacheID + } + { + req, err := http.NewRequest(http.MethodPatch, + fmt.Sprintf("%s/caches/%d", base, id), bytes.NewReader(content)) + require.NoError(t, err) + req.Header.Set("Content-Type", "application/octet-stream") + req.Header.Set("Content-Range", "bytes xx-99/*") + resp, err := http.DefaultClient.Do(req) + require.NoError(t, err) + assert.Equal(t, 400, resp.StatusCode) + } + }) + + t.Run("commit with bad id", func(t *testing.T) { + { + resp, err := http.Post(fmt.Sprintf("%s/caches/invalid_id", base), "", nil) + require.NoError(t, err) + assert.Equal(t, 400, resp.StatusCode) + } + }) + + t.Run("commit with not exist id", func(t *testing.T) { + { + resp, err := http.Post(fmt.Sprintf("%s/caches/%d", base, 100), "", nil) + require.NoError(t, err) + assert.Equal(t, 400, resp.StatusCode) + } + }) + + t.Run("duplicate commit", func(t *testing.T) { + key := strings.ToLower(t.Name()) + version := "c19da02a2bd7e77277f1ac29ab45c09b7d46a4ee758284e26bb3045ad11d9d20" + var id uint64 + content := make([]byte, 100) + _, err := rand.Read(content) + require.NoError(t, err) + { + body, err := json.Marshal(&Request{ + Key: key, + Version: version, + Size: 100, + }) + require.NoError(t, err) + resp, err := http.Post(fmt.Sprintf("%s/caches", base), "application/json", bytes.NewReader(body)) + require.NoError(t, err) + assert.Equal(t, 200, resp.StatusCode) + + got := struct { + CacheID uint64 `json:"cacheId"` + }{} + require.NoError(t, json.NewDecoder(resp.Body).Decode(&got)) + id = got.CacheID + } + { + req, err := http.NewRequest(http.MethodPatch, + fmt.Sprintf("%s/caches/%d", base, id), bytes.NewReader(content)) + require.NoError(t, err) + req.Header.Set("Content-Type", "application/octet-stream") + req.Header.Set("Content-Range", "bytes 0-99/*") + resp, err := http.DefaultClient.Do(req) + require.NoError(t, err) + assert.Equal(t, 200, resp.StatusCode) + } + { + resp, err := http.Post(fmt.Sprintf("%s/caches/%d", base, id), "", nil) + require.NoError(t, err) + assert.Equal(t, 200, resp.StatusCode) + } + { + resp, err := http.Post(fmt.Sprintf("%s/caches/%d", base, id), "", nil) + require.NoError(t, err) + assert.Equal(t, 400, resp.StatusCode) + } + }) + + t.Run("commit early", func(t *testing.T) { + key := strings.ToLower(t.Name()) + version := "c19da02a2bd7e77277f1ac29ab45c09b7d46a4ee758284e26bb3045ad11d9d20" + var id uint64 + content := make([]byte, 100) + _, err := rand.Read(content) + require.NoError(t, err) + { + body, err := json.Marshal(&Request{ + Key: key, + Version: version, + Size: 100, + }) + require.NoError(t, err) + resp, err := http.Post(fmt.Sprintf("%s/caches", base), "application/json", bytes.NewReader(body)) + require.NoError(t, err) + assert.Equal(t, 200, resp.StatusCode) + + got := struct { + CacheID uint64 `json:"cacheId"` + }{} + require.NoError(t, json.NewDecoder(resp.Body).Decode(&got)) + id = got.CacheID + } + { + req, err := http.NewRequest(http.MethodPatch, + fmt.Sprintf("%s/caches/%d", base, id), bytes.NewReader(content[:50])) + require.NoError(t, err) + req.Header.Set("Content-Type", "application/octet-stream") + req.Header.Set("Content-Range", "bytes 0-59/*") + resp, err := http.DefaultClient.Do(req) + require.NoError(t, err) + assert.Equal(t, 200, resp.StatusCode) + } + { + resp, err := http.Post(fmt.Sprintf("%s/caches/%d", base, id), "", nil) + require.NoError(t, err) + assert.Equal(t, 500, resp.StatusCode) + } + }) + + t.Run("get with bad id", func(t *testing.T) { + resp, err := http.Get(fmt.Sprintf("%s/artifacts/invalid_id", base)) + require.NoError(t, err) + require.Equal(t, 400, resp.StatusCode) + }) + + t.Run("get with not exist id", func(t *testing.T) { + resp, err := http.Get(fmt.Sprintf("%s/artifacts/%d", base, 100)) + require.NoError(t, err) + require.Equal(t, 404, resp.StatusCode) + }) + + t.Run("get with not exist id", func(t *testing.T) { + resp, err := http.Get(fmt.Sprintf("%s/artifacts/%d", base, 100)) + require.NoError(t, err) + require.Equal(t, 404, resp.StatusCode) + }) + + t.Run("get with multiple keys", func(t *testing.T) { + version := "c19da02a2bd7e77277f1ac29ab45c09b7d46a4ee758284e26bb3045ad11d9d20" + key := strings.ToLower(t.Name()) + keys := [3]string{ + key + "_a", + key + "_a_b", + key + "_a_b_c", + } + contents := [3][]byte{ + make([]byte, 100), + make([]byte, 200), + make([]byte, 300), + } + for i := range contents { + _, err := rand.Read(contents[i]) + require.NoError(t, err) + uploadCacheNormally(t, base, keys[i], version, contents[i]) + } + + reqKeys := strings.Join([]string{ + key + "_a_b_x", + key + "_a_b", + key + "_a", + }, ",") + var archiveLocation string + { + resp, err := http.Get(fmt.Sprintf("%s/cache?keys=%s&version=%s", base, reqKeys, version)) + require.NoError(t, err) + require.Equal(t, 200, resp.StatusCode) + got := struct { + Result string `json:"result"` + ArchiveLocation string `json:"archiveLocation"` + CacheKey string `json:"cacheKey"` + }{} + require.NoError(t, json.NewDecoder(resp.Body).Decode(&got)) + assert.Equal(t, "hit", got.Result) + assert.Equal(t, keys[1], got.CacheKey) + archiveLocation = got.ArchiveLocation + } + { + resp, err := http.Get(archiveLocation) //nolint:gosec + require.NoError(t, err) + require.Equal(t, 200, resp.StatusCode) + got, err := io.ReadAll(resp.Body) + require.NoError(t, err) + assert.Equal(t, contents[1], got) + } + }) + + t.Run("case insensitive", func(t *testing.T) { + version := "c19da02a2bd7e77277f1ac29ab45c09b7d46a4ee758284e26bb3045ad11d9d20" + key := strings.ToLower(t.Name()) + content := make([]byte, 100) + _, err := rand.Read(content) + require.NoError(t, err) + uploadCacheNormally(t, base, key+"_ABC", version, content) + + { + reqKey := key + "_aBc" + resp, err := http.Get(fmt.Sprintf("%s/cache?keys=%s&version=%s", base, reqKey, version)) + require.NoError(t, err) + require.Equal(t, 200, resp.StatusCode) + got := struct { + Result string `json:"result"` + ArchiveLocation string `json:"archiveLocation"` + CacheKey string `json:"cacheKey"` + }{} + require.NoError(t, json.NewDecoder(resp.Body).Decode(&got)) + assert.Equal(t, "hit", got.Result) + assert.Equal(t, key+"_abc", got.CacheKey) + } + }) +} + +func uploadCacheNormally(t *testing.T, base, key, version string, content []byte) { + var id uint64 + { + body, err := json.Marshal(&Request{ + Key: key, + Version: version, + Size: int64(len(content)), + }) + require.NoError(t, err) + resp, err := http.Post(fmt.Sprintf("%s/caches", base), "application/json", bytes.NewReader(body)) + require.NoError(t, err) + assert.Equal(t, 200, resp.StatusCode) + + got := struct { + CacheID uint64 `json:"cacheId"` + }{} + require.NoError(t, json.NewDecoder(resp.Body).Decode(&got)) + id = got.CacheID + } + { + req, err := http.NewRequest(http.MethodPatch, + fmt.Sprintf("%s/caches/%d", base, id), bytes.NewReader(content)) + require.NoError(t, err) + req.Header.Set("Content-Type", "application/octet-stream") + req.Header.Set("Content-Range", "bytes 0-99/*") + resp, err := http.DefaultClient.Do(req) + require.NoError(t, err) + assert.Equal(t, 200, resp.StatusCode) + } + { + resp, err := http.Post(fmt.Sprintf("%s/caches/%d", base, id), "", nil) + require.NoError(t, err) + assert.Equal(t, 200, resp.StatusCode) + } + var archiveLocation string + { + resp, err := http.Get(fmt.Sprintf("%s/cache?keys=%s&version=%s", base, key, version)) + require.NoError(t, err) + require.Equal(t, 200, resp.StatusCode) + got := struct { + Result string `json:"result"` + ArchiveLocation string `json:"archiveLocation"` + CacheKey string `json:"cacheKey"` + }{} + require.NoError(t, json.NewDecoder(resp.Body).Decode(&got)) + assert.Equal(t, "hit", got.Result) + assert.Equal(t, strings.ToLower(key), got.CacheKey) + archiveLocation = got.ArchiveLocation + } + { + resp, err := http.Get(archiveLocation) //nolint:gosec + require.NoError(t, err) + require.Equal(t, 200, resp.StatusCode) + got, err := io.ReadAll(resp.Body) + require.NoError(t, err) + assert.Equal(t, content, got) + } +} |