Synchronize delivery test fixtures and record the concurrency audit
This commit is contained in:
@@ -47,7 +47,8 @@ func TestInspectAssetsAcceptsMatchingContentAndPreservesUnrelatedFiles(t *testin
|
||||
case "/files/notes":
|
||||
_, _ = w.Write([]byte("unrelated"))
|
||||
default:
|
||||
t.Fatalf("unexpected path %s", r.URL.Path)
|
||||
t.Errorf("unexpected path %s", r.URL.Path)
|
||||
return
|
||||
}
|
||||
}))
|
||||
defer server.Close()
|
||||
@@ -107,7 +108,8 @@ func TestInspectAssetsAcceptsFullManifestWhenBinariesAreMissing(t *testing.T) {
|
||||
return
|
||||
}
|
||||
if r.URL.Path != "/files/manifest" {
|
||||
t.Fatalf("unexpected path %s", r.URL.Path)
|
||||
t.Errorf("unexpected path %s", r.URL.Path)
|
||||
return
|
||||
}
|
||||
_, _ = w.Write(manifest.Content)
|
||||
}))
|
||||
@@ -237,7 +239,8 @@ func TestInspectAssetsFollowsSameOriginRedirectWithAuthorization(t *testing.T) {
|
||||
_, _ = w.Write(content)
|
||||
return
|
||||
}
|
||||
t.Fatalf("unexpected path %s", r.URL.Path)
|
||||
t.Errorf("unexpected path %s", r.URL.Path)
|
||||
return
|
||||
}))
|
||||
defer server.Close()
|
||||
inspection, err := testClient(server).InspectAssets(context.Background(), testDestination(server.URL), 10, []ExpectedAsset{expected})
|
||||
@@ -253,9 +256,9 @@ func TestInspectAssetsVerifiesExternalContentWithoutGiteaToken(t *testing.T) {
|
||||
for _, mode := range []string{"direct", "redirect", "conflicting"} {
|
||||
t.Run(mode, func(t *testing.T) {
|
||||
expected := assetFor("app.bin", []byte("matching bytes"))
|
||||
externalGets := 0
|
||||
var externalGets atomic.Int64
|
||||
external := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
externalGets++
|
||||
externalGets.Add(1)
|
||||
if r.Header.Get("Authorization") != "" {
|
||||
t.Error("Gitea token reached external host")
|
||||
}
|
||||
@@ -287,8 +290,8 @@ func TestInspectAssetsVerifiesExternalContentWithoutGiteaToken(t *testing.T) {
|
||||
if mode == "conflicting" {
|
||||
want = AssetConflicting
|
||||
}
|
||||
if inspection.Assets[0].State != want || externalGets != 1 {
|
||||
t.Fatalf("inspection=%#v downloads=%d", inspection, externalGets)
|
||||
if inspection.Assets[0].State != want || externalGets.Load() != 1 {
|
||||
t.Fatalf("inspection=%#v downloads=%d", inspection, externalGets.Load())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -40,19 +41,23 @@ func TestEnsureReleaseEscapesSlashContainingTag(t *testing.T) {
|
||||
http.NotFound(w, r)
|
||||
case "/api/v1/repos/owner/project/releases":
|
||||
assertRequest(t, r, http.MethodPost, r.URL.Path)
|
||||
state.posts++
|
||||
state.posts.Add(1)
|
||||
var payload createReleaseRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
|
||||
t.Fatal(err)
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
if payload.TagName != tag || payload.Name != "Example v1.2.3" || payload.Body != testNote {
|
||||
t.Fatalf("create payload = %#v", payload)
|
||||
t.Errorf("create payload = %#v", payload)
|
||||
return
|
||||
}
|
||||
if err := json.NewEncoder(w).Encode(releaseResponse{ID: 7, HTMLURL: "https://example.test/release", TagName: tag, TargetCommitish: testCommit, Name: payload.Name, Body: payload.Body}); err != nil {
|
||||
t.Fatal(err)
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
default:
|
||||
t.Fatalf("unexpected escaped request %s %s", r.Method, r.URL.EscapedPath())
|
||||
t.Errorf("unexpected escaped request %s %s", r.Method, r.URL.EscapedPath())
|
||||
return
|
||||
}
|
||||
}))
|
||||
defer server.Close()
|
||||
@@ -60,8 +65,8 @@ func TestEnsureReleaseEscapesSlashContainingTag(t *testing.T) {
|
||||
request := testRequest(server.URL)
|
||||
request.Tag = tag
|
||||
result, err := testClient(server).EnsureRelease(context.Background(), request)
|
||||
if err != nil || !result.Created || state.posts != 1 {
|
||||
t.Fatalf("nested tag release = %#v, posts %d, error %v", result, state.posts, err)
|
||||
if err != nil || !result.Created || state.posts.Load() != 1 {
|
||||
t.Fatalf("nested tag release = %#v, posts %d, error %v", result, state.posts.Load(), err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +87,7 @@ func TestEnsureReleaseConflictDoesNotEditExistingPage(t *testing.T) {
|
||||
if failureKind(t, err) != FailureConflict {
|
||||
t.Fatalf("error = %v", err)
|
||||
}
|
||||
if got := state.posts; got != 0 {
|
||||
if got := state.posts.Load(); got != 0 {
|
||||
t.Fatalf("release creates = %d, want 0", got)
|
||||
}
|
||||
}
|
||||
@@ -103,68 +108,68 @@ func TestEnsureReleaseUnauthorizedIsStructuredAndHidesToken(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestEnsureReleaseMalformedTagResponseStopsBeforeCreate(t *testing.T) {
|
||||
posts := 0
|
||||
var posts atomic.Int64
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodPost {
|
||||
posts++
|
||||
posts.Add(1)
|
||||
}
|
||||
assertRequest(t, r, http.MethodGet, "/api/v1/repos/owner/project/tags/v1.2.3")
|
||||
_, _ = w.Write([]byte("{"))
|
||||
}))
|
||||
defer server.Close()
|
||||
_, err := testClient(server).EnsureRelease(context.Background(), testRequest(server.URL))
|
||||
if failureKind(t, err) != FailureMalformed || posts != 0 {
|
||||
t.Fatalf("error = %v, posts = %d", err, posts)
|
||||
if failureKind(t, err) != FailureMalformed || posts.Load() != 0 {
|
||||
t.Fatalf("error = %v, posts = %d", err, posts.Load())
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnsureReleaseWrongHostedTagStopsBeforeCreate(t *testing.T) {
|
||||
posts := 0
|
||||
var posts atomic.Int64
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assertRequest(t, r, http.MethodGet, "/api/v1/repos/owner/project/tags/v1.2.3")
|
||||
if r.Method == http.MethodPost {
|
||||
posts++
|
||||
posts.Add(1)
|
||||
}
|
||||
_, _ = fmt.Fprintf(w, `{"commit":{"sha":"%040d"}}`, 7)
|
||||
}))
|
||||
defer server.Close()
|
||||
_, err := testClient(server).EnsureRelease(context.Background(), testRequest(server.URL))
|
||||
if failureKind(t, err) != FailureConflict || posts != 0 {
|
||||
t.Fatalf("error = %v, posts = %d", err, posts)
|
||||
if failureKind(t, err) != FailureConflict || posts.Load() != 0 {
|
||||
t.Fatalf("error = %v, posts = %d", err, posts.Load())
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnsureReleaseMissingHostedTagStopsBeforeCreate(t *testing.T) {
|
||||
posts := 0
|
||||
var posts atomic.Int64
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assertRequest(t, r, http.MethodGet, "/api/v1/repos/owner/project/tags/v1.2.3")
|
||||
if r.Method == http.MethodPost {
|
||||
posts++
|
||||
posts.Add(1)
|
||||
}
|
||||
http.NotFound(w, r)
|
||||
}))
|
||||
defer server.Close()
|
||||
_, err := testClient(server).EnsureRelease(context.Background(), testRequest(server.URL))
|
||||
if failureKind(t, err) != FailureNotFound || posts != 0 {
|
||||
t.Fatalf("error = %v, posts = %d", err, posts)
|
||||
if failureKind(t, err) != FailureNotFound || posts.Load() != 0 {
|
||||
t.Fatalf("error = %v, posts = %d", err, posts.Load())
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnsureReleaseHonorsContextCancellation(t *testing.T) {
|
||||
called := false
|
||||
server := httptest.NewServer(http.HandlerFunc(func(http.ResponseWriter, *http.Request) { called = true }))
|
||||
var called atomic.Bool
|
||||
server := httptest.NewServer(http.HandlerFunc(func(http.ResponseWriter, *http.Request) { called.Store(true) }))
|
||||
defer server.Close()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
cancel()
|
||||
_, err := testClient(server).EnsureRelease(ctx, testRequest(server.URL))
|
||||
if !errors.Is(err, context.Canceled) || called {
|
||||
t.Fatalf("error = %v, server called = %t", err, called)
|
||||
if !errors.Is(err, context.Canceled) || called.Load() {
|
||||
t.Fatalf("error = %v, server called = %t", err, called.Load())
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnsureReleaseResolvesLostCreateAcknowledgmentWithOneLookup(t *testing.T) {
|
||||
created := false
|
||||
lookups := 0
|
||||
var created atomic.Bool
|
||||
var lookups atomic.Int64
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assertAuth(t, r)
|
||||
switch r.URL.Path {
|
||||
@@ -173,8 +178,8 @@ func TestEnsureReleaseResolvesLostCreateAcknowledgmentWithOneLookup(t *testing.T
|
||||
writeTag(t, w, testCommit)
|
||||
case "/api/v1/repos/owner/project/releases/tags/v1.2.3":
|
||||
assertRequest(t, r, http.MethodGet, r.URL.Path)
|
||||
lookups++
|
||||
if !created {
|
||||
lookups.Add(1)
|
||||
if !created.Load() {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
@@ -183,23 +188,28 @@ func TestEnsureReleaseResolvesLostCreateAcknowledgmentWithOneLookup(t *testing.T
|
||||
assertRequest(t, r, http.MethodPost, r.URL.Path)
|
||||
var payload createReleaseRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
|
||||
t.Fatal(err)
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
if payload.TagName != "v1.2.3" || payload.TargetCommitish != testCommit || payload.Name != "Example v1.2.3" || payload.Body != testNote || payload.Draft || payload.Prerelease {
|
||||
t.Fatalf("create payload = %#v", payload)
|
||||
t.Errorf("create payload = %#v", payload)
|
||||
return
|
||||
}
|
||||
created = true
|
||||
created.Store(true)
|
||||
hijacker, ok := w.(http.Hijacker)
|
||||
if !ok {
|
||||
t.Fatal("HTTP writer does not support hijacking")
|
||||
t.Error("HTTP writer does not support hijacking")
|
||||
return
|
||||
}
|
||||
connection, _, err := hijacker.Hijack()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
_ = connection.Close()
|
||||
default:
|
||||
t.Fatalf("unexpected request %s %s", r.Method, r.URL.Path)
|
||||
t.Errorf("unexpected request %s %s", r.Method, r.URL.Path)
|
||||
return
|
||||
}
|
||||
}))
|
||||
defer server.Close()
|
||||
@@ -207,13 +217,13 @@ func TestEnsureReleaseResolvesLostCreateAcknowledgmentWithOneLookup(t *testing.T
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if result.Created || lookups != 2 {
|
||||
t.Fatalf("result = %#v, release lookups = %d", result, lookups)
|
||||
if result.Created || lookups.Load() != 2 {
|
||||
t.Fatalf("result = %#v, release lookups = %d", result, lookups.Load())
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnsureReleaseResolvesCreateConflictWithOneLookup(t *testing.T) {
|
||||
lookups := 0
|
||||
var lookups atomic.Int64
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assertAuth(t, r)
|
||||
switch r.URL.Path {
|
||||
@@ -222,8 +232,8 @@ func TestEnsureReleaseResolvesCreateConflictWithOneLookup(t *testing.T) {
|
||||
writeTag(t, w, testCommit)
|
||||
case "/api/v1/repos/owner/project/releases/tags/v1.2.3":
|
||||
assertRequest(t, r, http.MethodGet, r.URL.Path)
|
||||
lookups++
|
||||
if lookups == 1 {
|
||||
lookups.Add(1)
|
||||
if lookups.Load() == 1 {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
@@ -232,7 +242,8 @@ func TestEnsureReleaseResolvesCreateConflictWithOneLookup(t *testing.T) {
|
||||
assertRequest(t, r, http.MethodPost, r.URL.Path)
|
||||
w.WriteHeader(http.StatusConflict)
|
||||
default:
|
||||
t.Fatalf("unexpected request %s %s", r.Method, r.URL.Path)
|
||||
t.Errorf("unexpected request %s %s", r.Method, r.URL.Path)
|
||||
return
|
||||
}
|
||||
}))
|
||||
defer server.Close()
|
||||
@@ -240,13 +251,13 @@ func TestEnsureReleaseResolvesCreateConflictWithOneLookup(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if result.Created || result.Release.Title != "Created by another request" || lookups != 2 {
|
||||
t.Fatalf("result = %#v, release lookups = %d", result, lookups)
|
||||
if result.Created || result.Release.Title != "Created by another request" || lookups.Load() != 2 {
|
||||
t.Fatalf("result = %#v, release lookups = %d", result, lookups.Load())
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnsureReleaseReportsUnknownWhenAmbiguousCreateCannotBeInspected(t *testing.T) {
|
||||
lookups := 0
|
||||
var lookups atomic.Int64
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assertAuth(t, r)
|
||||
switch r.URL.Path {
|
||||
@@ -255,8 +266,8 @@ func TestEnsureReleaseReportsUnknownWhenAmbiguousCreateCannotBeInspected(t *test
|
||||
writeTag(t, w, testCommit)
|
||||
case "/api/v1/repos/owner/project/releases/tags/v1.2.3":
|
||||
assertRequest(t, r, http.MethodGet, r.URL.Path)
|
||||
lookups++
|
||||
if lookups == 1 {
|
||||
lookups.Add(1)
|
||||
if lookups.Load() == 1 {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
@@ -265,13 +276,14 @@ func TestEnsureReleaseReportsUnknownWhenAmbiguousCreateCannotBeInspected(t *test
|
||||
assertRequest(t, r, http.MethodPost, r.URL.Path)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
default:
|
||||
t.Fatalf("unexpected request %s %s", r.Method, r.URL.Path)
|
||||
t.Errorf("unexpected request %s %s", r.Method, r.URL.Path)
|
||||
return
|
||||
}
|
||||
}))
|
||||
defer server.Close()
|
||||
_, err := testClient(server).EnsureRelease(context.Background(), testRequest(server.URL))
|
||||
if failureKind(t, err) != FailureUnknown || lookups != 2 {
|
||||
t.Fatalf("error = %v, release lookups = %d", err, lookups)
|
||||
if failureKind(t, err) != FailureUnknown || lookups.Load() != 2 {
|
||||
t.Fatalf("error = %v, release lookups = %d", err, lookups.Load())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,7 +294,7 @@ type releaseFixture struct {
|
||||
pre bool
|
||||
}
|
||||
|
||||
type fixtureState struct{ posts int }
|
||||
type fixtureState struct{ posts atomic.Int64 }
|
||||
|
||||
func newReleaseServer(t *testing.T, existing releaseFixture, createMissing bool) (*httptest.Server, *fixtureState) {
|
||||
t.Helper()
|
||||
@@ -302,13 +314,15 @@ func newReleaseServer(t *testing.T, existing releaseFixture, createMissing bool)
|
||||
writeRelease(t, w, existing)
|
||||
case "/api/v1/repos/owner/project/releases":
|
||||
assertRequest(t, r, http.MethodPost, r.URL.Path)
|
||||
state.posts++
|
||||
state.posts.Add(1)
|
||||
var payload createReleaseRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
|
||||
t.Fatal(err)
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
if payload.TagName != "v1.2.3" || payload.TargetCommitish != testCommit || payload.Name != "Example v1.2.3" || payload.Body != testNote || payload.Draft || payload.Prerelease {
|
||||
t.Fatalf("create payload = %#v", payload)
|
||||
t.Errorf("create payload = %#v", payload)
|
||||
return
|
||||
}
|
||||
if !createMissing {
|
||||
w.WriteHeader(http.StatusConflict)
|
||||
@@ -316,10 +330,12 @@ func newReleaseServer(t *testing.T, existing releaseFixture, createMissing bool)
|
||||
}
|
||||
writeRelease(t, w, releaseFixture{title: payload.Name})
|
||||
default:
|
||||
t.Fatalf("unexpected request %s %s", r.Method, r.URL.Path)
|
||||
t.Errorf("unexpected request %s %s", r.Method, r.URL.Path)
|
||||
return
|
||||
}
|
||||
})
|
||||
server := httptest.NewServer(handler)
|
||||
t.Cleanup(server.Close)
|
||||
return server, state
|
||||
}
|
||||
|
||||
@@ -346,7 +362,8 @@ func (fixture releaseFixture) bodyOrDefault() string {
|
||||
func assertRequest(t *testing.T, request *http.Request, method, path string) {
|
||||
t.Helper()
|
||||
if request.Method != method || request.URL.Path != path {
|
||||
t.Fatalf("request = %s %s, want %s %s", request.Method, request.URL.Path, method, path)
|
||||
t.Errorf("request = %s %s, want %s %s", request.Method, request.URL.Path, method, path)
|
||||
return
|
||||
}
|
||||
assertAuth(t, request)
|
||||
}
|
||||
@@ -354,7 +371,8 @@ func assertRequest(t *testing.T, request *http.Request, method, path string) {
|
||||
func assertAuth(t *testing.T, request *http.Request) {
|
||||
t.Helper()
|
||||
if got := request.Header.Get("Authorization"); got != "token "+testToken {
|
||||
t.Fatalf("Authorization = %q", got)
|
||||
t.Errorf("Authorization = %q", got)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -382,14 +400,14 @@ func contains(value, substring string) bool {
|
||||
func TestEnsureReleaseReconcilesInconsistentSuccessfulCreate(t *testing.T) {
|
||||
for _, mode := range []string{"matching", "conflicting", "unavailable"} {
|
||||
t.Run(mode, func(t *testing.T) {
|
||||
posts, lookups := 0, 0
|
||||
var posts, lookups atomic.Int64
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch {
|
||||
case strings.Contains(r.URL.Path, "/tags/") && !strings.Contains(r.URL.Path, "/releases/"):
|
||||
writeTag(t, w, testCommit)
|
||||
case strings.Contains(r.URL.Path, "/releases/tags/"):
|
||||
lookups++
|
||||
if posts == 0 {
|
||||
lookups.Add(1)
|
||||
if posts.Load() == 0 {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
@@ -403,7 +421,7 @@ func TestEnsureReleaseReconcilesInconsistentSuccessfulCreate(t *testing.T) {
|
||||
}
|
||||
writeRelease(t, w, releaseFixture{title: "Example v1.2.3", body: body})
|
||||
case r.Method == http.MethodPost:
|
||||
posts++
|
||||
posts.Add(1)
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
json.NewEncoder(w).Encode(releaseResponse{ID: 10, TagName: "v1.2.3", Name: "inconsistent title", Body: testNote})
|
||||
default:
|
||||
@@ -412,8 +430,8 @@ func TestEnsureReleaseReconcilesInconsistentSuccessfulCreate(t *testing.T) {
|
||||
}))
|
||||
defer server.Close()
|
||||
result, err := testClient(server).EnsureRelease(context.Background(), testRequest(server.URL))
|
||||
if posts != 1 || lookups != 2 {
|
||||
t.Fatalf("posts=%d lookups=%d", posts, lookups)
|
||||
if posts.Load() != 1 || lookups.Load() != 2 {
|
||||
t.Fatalf("posts=%d lookups=%d", posts.Load(), lookups.Load())
|
||||
}
|
||||
switch mode {
|
||||
case "matching":
|
||||
|
||||
Reference in New Issue
Block a user