Return write evidence from Gitea operations

This commit is contained in:
2026-11-01 18:02:05 +00:00
parent 02bd120aed
commit ddac9e3c73
4 changed files with 280 additions and 22 deletions

View File

@@ -245,3 +245,171 @@ func TestBackendPreflightPreservesCancellation(t *testing.T) {
t.Fatalf("Preflight() error = %v, want unavailable and context cancellation", err)
}
}
func TestBackendCreateReleaseSendsRequestedFieldsAndAcceptsProtocolAcknowledgment(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost || r.URL.Path != "/api/v1/repos/owner/project/releases" {
t.Errorf("request = %s %s", r.Method, r.URL.Path)
}
if got, want := r.Header.Get("Authorization"), "token fixture-secret-token"; got != want {
t.Errorf("authorization = %q, want %q", got, want)
}
var request createReleaseRequest
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
t.Errorf("decode request: %v", err)
}
if request.TagName != "v1.2.3" || request.TargetCommitish != strings.Repeat("a", 40) || request.Name != "Release title" || request.Body != "# Release title\n\nComplete notes." || !request.Draft || !request.Prerelease {
t.Errorf("release request = %#v", request)
}
_ = json.NewEncoder(w).Encode(releaseResponse{ID: 12, TagName: "server tag", Name: "server title", Body: "server body", Draft: true, Prerelease: true})
}))
defer server.Close()
backend := NewBackend(testDestination(server.URL), server.Client())
release, evidence, err := backend.CreateRelease(context.Background(), forge.CreateReleaseRequest{
Tag: "v1.2.3", Commit: strings.Repeat("a", 40), Title: "Release title",
Body: "# Release title\n\nComplete notes.", Draft: true, Prerelease: true,
})
if err != nil || evidence != (forge.WriteEvidence{Attempted: true, Disposition: forge.WriteAccepted}) {
t.Fatalf("CreateRelease() evidence = %#v, error = %v", evidence, err)
}
if release.Handle == nil || release.Tag != "server tag" || release.Title != "server title" || release.Body != "server body" || !release.Draft || !release.Prerelease {
t.Fatalf("CreateRelease() release = %#v", release)
}
}
func TestBackendCreateReleaseMarksInvalidAcknowledgmentUncertain(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_ = json.NewEncoder(w).Encode(releaseResponse{TagName: "v1.2.3"})
}))
defer server.Close()
backend := NewBackend(testDestination(server.URL), server.Client())
_, evidence, err := backend.CreateRelease(context.Background(), validCreateReleaseRequest())
var failure *forge.Failure
if !errors.As(err, &failure) || failure.Kind != forge.FailureMalformed || evidence != (forge.WriteEvidence{Attempted: true, Disposition: forge.WriteUncertain}) {
t.Fatalf("CreateRelease() evidence = %#v, error = %v", evidence, err)
}
}
func TestBackendCreateReleaseClassifiesConflictAndServerFailure(t *testing.T) {
for _, test := range []struct {
status int
disposition forge.WriteDisposition
}{
{status: http.StatusBadRequest, disposition: forge.WriteRejected},
{status: http.StatusConflict, disposition: forge.WriteConflict},
{status: http.StatusServiceUnavailable, disposition: forge.WriteUncertain},
} {
t.Run(http.StatusText(test.status), func(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(test.status) }))
defer server.Close()
backend := NewBackend(testDestination(server.URL), server.Client())
_, evidence, err := backend.CreateRelease(context.Background(), validCreateReleaseRequest())
if err == nil || evidence != (forge.WriteEvidence{Attempted: true, Disposition: test.disposition}) {
t.Fatalf("CreateRelease() evidence = %#v, error = %v", evidence, err)
}
})
}
}
func TestBackendUploadAssetBorrowsReaderAndValidatesAcknowledgment(t *testing.T) {
var uploaded string
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost || r.URL.Path != "/api/v1/repos/owner/project/releases/12/assets" || r.URL.Query().Get("name") != "app.bin" {
t.Errorf("request = %s %s?%s", r.Method, r.URL.Path, r.URL.RawQuery)
}
if got, want := r.Header.Get("Authorization"), "token fixture-secret-token"; got != want {
t.Errorf("authorization = %q, want %q", got, want)
}
file, header, err := r.FormFile("attachment")
if err != nil {
t.Errorf("attachment: %v", err)
} else {
defer file.Close()
content, readErr := io.ReadAll(file)
if readErr != nil {
t.Errorf("read attachment: %v", readErr)
}
if header.Filename != "app.bin" {
t.Errorf("multipart filename = %q", header.Filename)
}
uploaded = string(content)
}
_ = json.NewEncoder(w).Encode(assetResponse{ID: 50, Name: "app.bin"})
}))
defer server.Close()
backend := NewBackend(testDestination(server.URL), server.Client())
reader := &trackedReader{Reader: strings.NewReader("streamed bytes")}
evidence, err := backend.UploadAsset(context.Background(), releaseHandle{id: 12}, "app.bin", reader)
if err != nil || evidence != (forge.WriteEvidence{Attempted: true, Disposition: forge.WriteAccepted}) {
t.Fatalf("UploadAsset() evidence = %#v, error = %v", evidence, err)
}
if uploaded != "streamed bytes" || reader.closed {
t.Fatalf("uploaded = %q, caller reader closed = %v", uploaded, reader.closed)
}
}
func TestBackendUploadAssetMarksBadAcknowledgmentUncertain(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_ = json.NewEncoder(w).Encode(assetResponse{ID: 50, Name: "different.bin"})
}))
defer server.Close()
backend := NewBackend(testDestination(server.URL), server.Client())
evidence, err := backend.UploadAsset(context.Background(), releaseHandle{id: 12}, "app.bin", strings.NewReader("data"))
var failure *forge.Failure
if !errors.As(err, &failure) || failure.Kind != forge.FailureMalformed || evidence != (forge.WriteEvidence{Attempted: true, Disposition: forge.WriteUncertain}) {
t.Fatalf("UploadAsset() evidence = %#v, error = %v", evidence, err)
}
}
func TestBackendUploadAssetClassifiesKnownRejectionAndConflict(t *testing.T) {
for _, test := range []struct {
status int
disposition forge.WriteDisposition
}{
{status: http.StatusBadRequest, disposition: forge.WriteRejected},
{status: http.StatusConflict, disposition: forge.WriteConflict},
{status: http.StatusInternalServerError, disposition: forge.WriteUncertain},
} {
t.Run(http.StatusText(test.status), func(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(test.status) }))
defer server.Close()
backend := NewBackend(testDestination(server.URL), server.Client())
evidence, err := backend.UploadAsset(context.Background(), releaseHandle{id: 12}, "app.bin", strings.NewReader("data"))
if err == nil || evidence != (forge.WriteEvidence{Attempted: true, Disposition: test.disposition}) {
t.Fatalf("UploadAsset() evidence = %#v, error = %v", evidence, err)
}
})
}
}
func TestBackendUploadAssetPreservesCancellationAsUncertain(t *testing.T) {
server := httptest.NewServer(http.NotFoundHandler())
defer server.Close()
ctx, cancel := context.WithCancel(context.Background())
cancel()
evidence, err := NewBackend(testDestination(server.URL), server.Client()).UploadAsset(ctx, releaseHandle{id: 12}, "app.bin", strings.NewReader("data"))
if evidence != (forge.WriteEvidence{Attempted: true, Disposition: forge.WriteUncertain}) || !errors.Is(err, context.Canceled) {
t.Fatalf("UploadAsset() evidence = %#v, error = %v", evidence, err)
}
}
func validCreateReleaseRequest() forge.CreateReleaseRequest {
return forge.CreateReleaseRequest{Tag: "v1.2.3", Commit: strings.Repeat("a", 40), Title: "Release", Body: "# Release"}
}
type trackedReader struct {
io.Reader
closed bool
}
func (reader *trackedReader) Close() error {
reader.closed = true
return nil
}