205 lines
8.4 KiB
Go
205 lines
8.4 KiB
Go
|
|
package delivery
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"errors"
|
||
|
|
"io"
|
||
|
|
"strings"
|
||
|
|
"testing"
|
||
|
|
|
||
|
|
"go.maximumdirect.net/releasekit/internal/forge"
|
||
|
|
)
|
||
|
|
|
||
|
|
func TestCoordinateReleaseAcceptsExistingPageAndRetainsItsTitle(t *testing.T) {
|
||
|
|
backend := &pageBackend{
|
||
|
|
commit: "commit",
|
||
|
|
findResults: []pageFindResult{{release: forge.Release{
|
||
|
|
Tag: "v1.2.3", Title: "Maintained title", Body: "# Release\n\nNotes", Handle: "release",
|
||
|
|
}}},
|
||
|
|
}
|
||
|
|
|
||
|
|
release, evidence, err := coordinateRelease(context.Background(), backend, "v1.2.3", "commit", "# Release\n\nNotes")
|
||
|
|
if err != nil || release.Title != "Maintained title" || evidence.Attempted {
|
||
|
|
t.Fatalf("coordinateRelease() = %#v, evidence %#v, error %v", release, evidence, err)
|
||
|
|
}
|
||
|
|
if backend.createCalls != 0 || backend.findCalls != 1 {
|
||
|
|
t.Fatalf("calls = create %d, find %d", backend.createCalls, backend.findCalls)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestCoordinateReleaseRejectsWrongHostedTagAndExistingPage(t *testing.T) {
|
||
|
|
t.Run("wrong hosted tag", func(t *testing.T) {
|
||
|
|
backend := &pageBackend{commit: "other"}
|
||
|
|
_, evidence, err := coordinateRelease(context.Background(), backend, "v1.2.3", "commit", "# Release")
|
||
|
|
if !isForgeFailure(err, forge.FailureConflict) || evidence.Attempted || backend.findCalls != 0 || backend.createCalls != 0 {
|
||
|
|
t.Fatalf("coordinateRelease() evidence %#v, error %v, calls find=%d create=%d", evidence, err, backend.findCalls, backend.createCalls)
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
t.Run("missing hosted tag", func(t *testing.T) {
|
||
|
|
backend := &pageBackend{tagErr: &forge.Failure{Kind: forge.FailureNotFound, Err: errors.New("tag missing")}}
|
||
|
|
_, _, err := coordinateRelease(context.Background(), backend, "v1.2.3", "commit", "# Release")
|
||
|
|
if !isForgeFailure(err, forge.FailureNotFound) || backend.findCalls != 0 || backend.createCalls != 0 {
|
||
|
|
t.Fatalf("coordinateRelease() error = %v, calls find=%d create=%d", err, backend.findCalls, backend.createCalls)
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
for _, release := range []forge.Release{
|
||
|
|
{Tag: "v9.9.9", Body: "# Release", Title: "Release"},
|
||
|
|
{Tag: "v1.2.3", Body: "different", Title: "Release"},
|
||
|
|
{Tag: "v1.2.3", Body: "# Release", Title: "Release", Draft: true},
|
||
|
|
{Tag: "v1.2.3", Body: "# Release", Title: "Release", Prerelease: true},
|
||
|
|
} {
|
||
|
|
backend := &pageBackend{commit: "commit", findResults: []pageFindResult{{release: release}}}
|
||
|
|
_, _, err := coordinateRelease(context.Background(), backend, "v1.2.3", "commit", "# Release")
|
||
|
|
if !isForgeFailure(err, forge.FailureConflict) || backend.createCalls != 0 {
|
||
|
|
t.Fatalf("coordinateRelease(%#v) error = %v, create calls = %d", release, err, backend.createCalls)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestCoordinateReleaseCreatesWithFullNoteAndVerifiesRequestedTitle(t *testing.T) {
|
||
|
|
backend := &pageBackend{
|
||
|
|
commit: "commit",
|
||
|
|
findResults: []pageFindResult{{err: &forge.Failure{Kind: forge.FailureNotFound}}},
|
||
|
|
createResult: pageCreateResult{
|
||
|
|
release: forge.Release{Tag: "v1.2.3", Title: "Release", Body: "# Release\n\nFull note", Handle: "release"},
|
||
|
|
evidence: forge.WriteEvidence{Attempted: true, Disposition: forge.WriteAccepted},
|
||
|
|
},
|
||
|
|
}
|
||
|
|
|
||
|
|
release, evidence, err := coordinateRelease(context.Background(), backend, "v1.2.3", "commit", "# Release\n\nFull note")
|
||
|
|
if err != nil || release.Handle != "release" || evidence.Disposition != forge.WriteAccepted || !evidence.Attempted {
|
||
|
|
t.Fatalf("coordinateRelease() = %#v, evidence %#v, error %v", release, evidence, err)
|
||
|
|
}
|
||
|
|
if got := backend.createRequest; got.Tag != "v1.2.3" || got.Commit != "commit" || got.Title != "Release" || got.Body != "# Release\n\nFull note" || got.Draft || got.Prerelease {
|
||
|
|
t.Fatalf("CreateRelease request = %#v", got)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestCoordinateReleaseReconcilesCreateOutcomes(t *testing.T) {
|
||
|
|
createErr := &forge.Failure{Kind: forge.FailureTransport, Err: errors.New("connection lost")}
|
||
|
|
lookupErr := errors.New("lookup unavailable")
|
||
|
|
matching := forge.Release{Tag: "v1.2.3", Title: "Original", Body: "# Release\n\nNotes", Handle: "release"}
|
||
|
|
wrong := forge.Release{Tag: "v1.2.3", Body: "different", Handle: "other"}
|
||
|
|
notFound := &forge.Failure{Kind: forge.FailureNotFound}
|
||
|
|
for _, test := range []struct {
|
||
|
|
name string
|
||
|
|
create pageCreateResult
|
||
|
|
followup pageFindResult
|
||
|
|
wantKind forge.FailureKind
|
||
|
|
wantRecovered bool
|
||
|
|
wantCause error
|
||
|
|
}{
|
||
|
|
{
|
||
|
|
name: "matching page after conflict",
|
||
|
|
create: pageCreateResult{err: createErr, evidence: forge.WriteEvidence{Attempted: true, Disposition: forge.WriteConflict}},
|
||
|
|
followup: pageFindResult{release: matching}, wantRecovered: true,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "different page after uncertain write",
|
||
|
|
create: pageCreateResult{err: createErr, evidence: forge.WriteEvidence{Attempted: true, Disposition: forge.WriteUncertain}},
|
||
|
|
followup: pageFindResult{release: wrong}, wantKind: forge.FailureConflict,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "missing after conflict",
|
||
|
|
create: pageCreateResult{err: createErr, evidence: forge.WriteEvidence{Attempted: true, Disposition: forge.WriteConflict}},
|
||
|
|
followup: pageFindResult{err: notFound}, wantKind: forge.FailureConflict,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "missing after uncertain write",
|
||
|
|
create: pageCreateResult{err: createErr, evidence: forge.WriteEvidence{Attempted: true, Disposition: forge.WriteUncertain}},
|
||
|
|
followup: pageFindResult{err: notFound}, wantKind: forge.FailureUnknown, wantCause: createErr,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "unavailable follow-up",
|
||
|
|
create: pageCreateResult{err: createErr, evidence: forge.WriteEvidence{Attempted: true, Disposition: forge.WriteUncertain}},
|
||
|
|
followup: pageFindResult{err: lookupErr}, wantKind: forge.FailureUnknown, wantCause: lookupErr,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "inconsistent accepted response reconciled",
|
||
|
|
create: pageCreateResult{release: wrong, evidence: forge.WriteEvidence{Attempted: true, Disposition: forge.WriteAccepted}},
|
||
|
|
followup: pageFindResult{release: matching}, wantRecovered: true,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "inconsistent accepted response missing",
|
||
|
|
create: pageCreateResult{release: wrong, evidence: forge.WriteEvidence{Attempted: true, Disposition: forge.WriteAccepted}},
|
||
|
|
followup: pageFindResult{err: notFound}, wantKind: forge.FailureUnknown,
|
||
|
|
},
|
||
|
|
} {
|
||
|
|
t.Run(test.name, func(t *testing.T) {
|
||
|
|
backend := &pageBackend{
|
||
|
|
commit: "commit",
|
||
|
|
findResults: []pageFindResult{{err: notFound}, test.followup},
|
||
|
|
createResult: test.create,
|
||
|
|
}
|
||
|
|
release, evidence, err := coordinateRelease(context.Background(), backend, "v1.2.3", "commit", "# Release\n\nNotes")
|
||
|
|
if test.wantRecovered {
|
||
|
|
if err != nil || release.Handle != "release" || !evidence.Attempted || backend.findCalls != 2 {
|
||
|
|
t.Fatalf("recovered result = %#v, evidence %#v, error %v, find calls %d", release, evidence, err, backend.findCalls)
|
||
|
|
}
|
||
|
|
return
|
||
|
|
}
|
||
|
|
if !isForgeFailure(err, test.wantKind) || !evidence.Attempted {
|
||
|
|
t.Fatalf("result evidence %#v, error %v; want kind %s", evidence, err, test.wantKind)
|
||
|
|
}
|
||
|
|
if test.wantCause != nil && !errors.Is(err, test.wantCause) {
|
||
|
|
t.Fatalf("error %v does not retain cause %v", err, test.wantCause)
|
||
|
|
}
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
type pageFindResult struct {
|
||
|
|
release forge.Release
|
||
|
|
err error
|
||
|
|
}
|
||
|
|
|
||
|
|
type pageCreateResult struct {
|
||
|
|
release forge.Release
|
||
|
|
evidence forge.WriteEvidence
|
||
|
|
err error
|
||
|
|
}
|
||
|
|
|
||
|
|
type pageBackend struct {
|
||
|
|
commit string
|
||
|
|
tagErr error
|
||
|
|
findResults []pageFindResult
|
||
|
|
findCalls int
|
||
|
|
createResult pageCreateResult
|
||
|
|
createCalls int
|
||
|
|
createRequest forge.CreateReleaseRequest
|
||
|
|
}
|
||
|
|
|
||
|
|
func (backend *pageBackend) Preflight(context.Context) error { return nil }
|
||
|
|
|
||
|
|
func (backend *pageBackend) TagCommit(context.Context, string) (string, error) {
|
||
|
|
return backend.commit, backend.tagErr
|
||
|
|
}
|
||
|
|
|
||
|
|
func (backend *pageBackend) FindRelease(context.Context, string) (forge.Release, error) {
|
||
|
|
index := backend.findCalls
|
||
|
|
backend.findCalls++
|
||
|
|
if index >= len(backend.findResults) {
|
||
|
|
return forge.Release{}, &forge.Failure{Kind: forge.FailureNotFound}
|
||
|
|
}
|
||
|
|
result := backend.findResults[index]
|
||
|
|
return result.release, result.err
|
||
|
|
}
|
||
|
|
|
||
|
|
func (backend *pageBackend) CreateRelease(_ context.Context, request forge.CreateReleaseRequest) (forge.Release, forge.WriteEvidence, error) {
|
||
|
|
backend.createCalls++
|
||
|
|
backend.createRequest = request
|
||
|
|
return backend.createResult.release, backend.createResult.evidence, backend.createResult.err
|
||
|
|
}
|
||
|
|
|
||
|
|
func (*pageBackend) ListAssets(context.Context, any) ([]forge.Asset, error) { return nil, nil }
|
||
|
|
|
||
|
|
func (*pageBackend) OpenAsset(context.Context, any) (io.ReadCloser, error) {
|
||
|
|
return io.NopCloser(strings.NewReader("")), nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (*pageBackend) UploadAsset(context.Context, any, string, io.Reader) (forge.WriteEvidence, error) {
|
||
|
|
return forge.WriteEvidence{}, nil
|
||
|
|
}
|