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 }