Files
artifactapi/e2e-docker/cargo_local_test.go
T
unkin-agent ce8026d46e
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful
ci/woodpecker/pr/build Pipeline was successful
Guard cargo publishes against name and version collisions
- serialise publishes per crate with an advisory lock in one transaction
- add a unique index on normalised name and version without build metadata
- validate versions as strict SemVer 2.0 and reject an empty .crate
- renumber the migration to 0005
2026-10-17 12:43:44 +11:00

67 lines
2.6 KiB
Go

//go:build dockere2e
package e2edocker
import (
"bytes"
"encoding/binary"
"net/http"
"strings"
"testing"
)
// TestLocalCargoRegistry publishes through the cargo Web API, then reads the
// sparse index, downloads the crate and yanks it.
func TestLocalCargoRegistry(t *testing.T) {
createRepo(t, `{"name":"local-cargo","package_type":"cargo","repo_type":"local"}`)
defer deleteRepo(t, "local-cargo")
base := api("/api/v1/local/local-cargo")
meta := `{"name":"e2e-crate","vers":"0.1.0","deps":[],"features":{}}`
crate := []byte("e2e crate bytes")
publish := func(meta string) *http.Response {
var body []byte
body = binary.LittleEndian.AppendUint32(body, uint32(len(meta)))
body = append(body, meta...)
body = binary.LittleEndian.AppendUint32(body, uint32(len(crate)))
body = append(body, crate...)
resp, _ := doRequest(t, http.MethodPut, base+"/api/v1/crates/new", body, "application/octet-stream")
return resp
}
if resp := publish(meta); resp.StatusCode != http.StatusOK {
t.Fatalf("publish: status %d", resp.StatusCode)
}
if resp := publish(meta); resp.StatusCode != http.StatusConflict {
t.Fatalf("republish: status %d, want 409", resp.StatusCode)
}
if resp := publish(`{"name":"e2e-crate","vers":"0.1.0+other","deps":[],"features":{}}`); resp.StatusCode != http.StatusConflict {
t.Fatalf("build-metadata republish: status %d, want 409", resp.StatusCode)
}
if resp := publish(`{"name":"E2E_crate","vers":"0.2.0","deps":[],"features":{}}`); resp.StatusCode != http.StatusBadRequest {
t.Fatalf("name collision publish: status %d, want 400", resp.StatusCode)
}
resp, b := doRequest(t, http.MethodGet, base+"/config.json", nil, "")
if resp.StatusCode != http.StatusOK || !strings.Contains(string(b), `"api":"`) {
t.Fatalf("config.json: status %d: %s", resp.StatusCode, b)
}
resp, b = doRequest(t, http.MethodGet, base+"/e2/e-/e2e-crate", nil, "")
if resp.StatusCode != http.StatusOK || !strings.Contains(string(b), `"cksum":"`) || !strings.Contains(string(b), `"yanked":false`) {
t.Fatalf("index: status %d: %s", resp.StatusCode, b)
}
resp, b = doRequest(t, http.MethodGet, base+"/crates/e2e-crate/e2e-crate-0.1.0.crate", nil, "")
if resp.StatusCode != http.StatusOK || !bytes.Equal(b, crate) {
t.Fatalf("download: status %d", resp.StatusCode)
}
if resp, b := doRequest(t, http.MethodDelete, base+"/api/v1/crates/e2e-crate/0.1.0/yank", nil, ""); resp.StatusCode != http.StatusOK {
t.Fatalf("yank: status %d: %s", resp.StatusCode, b)
}
if _, b := doRequest(t, http.MethodGet, base+"/e2/e-/e2e-crate", nil, ""); !strings.Contains(string(b), `"yanked":true`) {
t.Fatalf("index after yank: %s", b)
}
}