221f3a7402
Add a black-box e2e suite (build tag dockere2e) that runs against the built container image via docker-compose, plus a static nginx mock upstream for hermetic caching tests. Coverage: - repository add/change/delete for remote, local and virtual repos - caching (miss -> hit + byte integrity) for all 10 remote package types - local uploads: generic, pypi (with generated simple index), rpm (with automatic repodata generation from a real package) - virtual merges: pypi simple index and helm index.yaml Driven by scripts/docker-e2e.sh (make docker-e2e): builds the image, brings the stack up, waits for health, runs the suite, and tears down. The artifactapi host port is parameterised (ARTIFACTAPI_PORT, default 8000; the e2e run uses 8001). Fixtures are force-tracked over the global gitignore.
77 lines
2.8 KiB
Go
77 lines
2.8 KiB
Go
//go:build dockere2e
|
|
|
|
package e2edocker
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"net/http"
|
|
"testing"
|
|
)
|
|
|
|
// TestCachingPerProvider proxies one immutable artifact for every remote
|
|
// package type through the mock upstream and asserts: first fetch is served
|
|
// from the remote, the second from cache, and the bytes match the origin.
|
|
func TestCachingPerProvider(t *testing.T) {
|
|
cases := []struct {
|
|
pkgType string
|
|
// path is the request path under /api/v1/remote/<name>/. The provider
|
|
// derives the upstream URL from it (docker prepends /v2/, terraform
|
|
// prepends /v1/providers/), and the fixture lives at that resolved path.
|
|
path string
|
|
fixture string
|
|
}{
|
|
{"generic", "blobs/hello.bin", "blobs/hello.bin"},
|
|
{"npm", "mypkg/-/mypkg-1.0.0.tgz", "mypkg/-/mypkg-1.0.0.tgz"},
|
|
{"helm", "charts/mychart-1.0.0.tgz", "charts/mychart-1.0.0.tgz"},
|
|
{"pypi", "packages/foo-1.0-py3-none-any.whl", "packages/foo-1.0-py3-none-any.whl"},
|
|
{"rpm", "rpmrepo/Packages/e2e-testpkg-1.0-1.noarch.rpm", "rpmrepo/Packages/e2e-testpkg-1.0-1.noarch.rpm"},
|
|
{"alpine", "alpine/x86_64/testpkg-1.0-r0.apk", "alpine/x86_64/testpkg-1.0-r0.apk"},
|
|
{"puppet", "puppet-releases/author-mod-1.0.0.tar.gz", "puppet-releases/author-mod-1.0.0.tar.gz"},
|
|
{"goproxy", "goproxy/example.com/mod/@v/v1.0.0.zip", "goproxy/example.com/mod/@v/v1.0.0.zip"},
|
|
{"terraform", "hashicorp/aws/download/pkg.zip", "v1/providers/hashicorp/aws/download/pkg.zip"},
|
|
{"docker", "library/testimg/blobs/blobdata", "v2/library/testimg/blobs/blobdata"},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.pkgType, func(t *testing.T) {
|
|
name := "cache-" + tc.pkgType
|
|
createRepo(t, fmt.Sprintf(`{
|
|
"name": %q,
|
|
"package_type": %q,
|
|
"repo_type": "remote",
|
|
"base_url": %q,
|
|
"stale_on_error": true
|
|
}`, name, tc.pkgType, mockUpstream()))
|
|
defer deleteRepo(t, name)
|
|
|
|
want := fixtureBytes(t, tc.fixture)
|
|
url := api("/api/v1/remote/" + name + "/" + tc.path)
|
|
|
|
// First fetch: from remote.
|
|
resp, body := doRequest(t, http.MethodGet, url, nil, "")
|
|
if resp.StatusCode != http.StatusOK {
|
|
t.Fatalf("first fetch: status %d: %s", resp.StatusCode, body)
|
|
}
|
|
if src := resp.Header.Get("X-Artifact-Source"); src != "remote" {
|
|
t.Fatalf("first fetch source = %q, want remote", src)
|
|
}
|
|
if !bytes.Equal(body, want) {
|
|
t.Fatalf("first fetch body mismatch: got %d bytes, want %d", len(body), len(want))
|
|
}
|
|
|
|
// Second fetch: from cache, identical bytes.
|
|
resp, body = doRequest(t, http.MethodGet, url, nil, "")
|
|
if resp.StatusCode != http.StatusOK {
|
|
t.Fatalf("second fetch: status %d: %s", resp.StatusCode, body)
|
|
}
|
|
if src := resp.Header.Get("X-Artifact-Source"); src != "cache" {
|
|
t.Fatalf("second fetch source = %q, want cache", src)
|
|
}
|
|
if !bytes.Equal(body, want) {
|
|
t.Fatalf("cached body mismatch: got %d bytes, want %d", len(body), len(want))
|
|
}
|
|
})
|
|
}
|
|
}
|