Check expected publication push URL
This commit is contained in:
9
check.go
9
check.go
@@ -6,12 +6,17 @@ import (
|
||||
|
||||
internalbuild "gitea.maximumdirect.net/eric/ReleaseKit/internal/build"
|
||||
"gitea.maximumdirect.net/eric/ReleaseKit/internal/candidate"
|
||||
"gitea.maximumdirect.net/eric/ReleaseKit/internal/publish"
|
||||
"gitea.maximumdirect.net/eric/ReleaseKit/internal/validation"
|
||||
)
|
||||
|
||||
// ErrInvalidVersion reports a version that is not stable vMAJOR.MINOR.PATCH.
|
||||
var ErrInvalidVersion = candidate.ErrInvalidVersion
|
||||
|
||||
// ErrUnexpectedPushURL reports that the effective publication push URL does
|
||||
// not exactly match ReleaseRequest.ExpectedPushURL.
|
||||
var ErrUnexpectedPushURL = publish.ErrUnexpectedPushURL
|
||||
|
||||
// Candidate identifies one committed local source revision selected for
|
||||
// validation.
|
||||
type Candidate struct {
|
||||
@@ -28,6 +33,10 @@ type ReleaseRequest struct {
|
||||
Product string
|
||||
Version string
|
||||
Remote string
|
||||
// ExpectedPushURL optionally requires Publish's resolved push URL to match
|
||||
// this string exactly before validation or tag creation. Check ignores this
|
||||
// publication-only expectation. A mismatch returns ErrUnexpectedPushURL.
|
||||
ExpectedPushURL string
|
||||
// Validation selects optional checks run against the captured candidate.
|
||||
Validation ValidationSpec
|
||||
// AdditionalChecks optionally runs product checks after shared validation.
|
||||
|
||||
@@ -112,15 +112,16 @@ type CheckCommand struct {
|
||||
Env map[string]string `json:"env,omitempty"`
|
||||
}
|
||||
|
||||
// Settings contains the product, remote, shared validation selections, and
|
||||
// check commands read from a releasekit.json profile.
|
||||
// Settings contains the product, remote, publication expectation, shared
|
||||
// validation selections, and check commands read from a releasekit.json profile.
|
||||
type Settings struct {
|
||||
Product string `json:"product"`
|
||||
Remote string `json:"remote,omitempty"`
|
||||
Checks []CheckCommand `json:"checks,omitempty"`
|
||||
Validation *ValidationSettings `json:"validation,omitempty"`
|
||||
Build *BuildSettings `json:"build,omitempty"`
|
||||
Delivery *DeliverySettings `json:"delivery,omitempty"`
|
||||
Product string `json:"product"`
|
||||
Remote string `json:"remote,omitempty"`
|
||||
ExpectedPushURL string `json:"expected_push_url,omitempty"`
|
||||
Checks []CheckCommand `json:"checks,omitempty"`
|
||||
Validation *ValidationSettings `json:"validation,omitempty"`
|
||||
Build *BuildSettings `json:"build,omitempty"`
|
||||
Delivery *DeliverySettings `json:"delivery,omitempty"`
|
||||
}
|
||||
|
||||
// ValidationSettings selects shared candidate checks in a release profile.
|
||||
|
||||
@@ -144,7 +144,7 @@ func TestLoadSettingsDefaultsRemoteAndConfigPath(t *testing.T) {
|
||||
func TestLoadSettingsReadsGoValidation(t *testing.T) {
|
||||
repoDir := t.TempDir()
|
||||
profilePath := filepath.Join(repoDir, "releasekit.json")
|
||||
if err := os.WriteFile(profilePath, []byte(`{"product":"Example","validation":{"go":{"race":true,"full_vet":true,"compile_targets":["linux/amd64","linux/arm64","darwin/arm64"]}}}`), 0o600); err != nil {
|
||||
if err := os.WriteFile(profilePath, []byte(`{"product":"Example","expected_push_url":"ssh://git.example/owner/repo.git","validation":{"go":{"race":true,"full_vet":true,"compile_targets":["linux/amd64","linux/arm64","darwin/arm64"]}}}`), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -158,6 +158,9 @@ func TestLoadSettingsReadsGoValidation(t *testing.T) {
|
||||
if got, want := strings.Join(settings.Validation.Go.CompileTargets, ","), "linux/amd64,linux/arm64,darwin/arm64"; got != want {
|
||||
t.Fatalf("compile targets = %q, want %q", got, want)
|
||||
}
|
||||
if settings.ExpectedPushURL != "ssh://git.example/owner/repo.git" {
|
||||
t.Fatalf("expected push URL = %q", settings.ExpectedPushURL)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExampleProfileLoads(t *testing.T) {
|
||||
|
||||
@@ -42,6 +42,7 @@ func run(ctx context.Context, args []string, stdout, stderr io.Writer) int {
|
||||
}
|
||||
request := candidateRequest(invocation, settings, stderr)
|
||||
if invocation.Command == cli.PublishAction {
|
||||
request.ExpectedPushURL = settings.ExpectedPushURL
|
||||
fmt.Fprintf(stderr, "validating and publishing %s\n", invocation.Version)
|
||||
result, err := releasekit.Publish(ctx, request)
|
||||
if err != nil {
|
||||
|
||||
@@ -114,7 +114,8 @@ func TestCheckCommandBindsConfiguredChecksToCapturedCandidate(t *testing.T) {
|
||||
|
||||
func TestCandidateRequestMapsGoValidation(t *testing.T) {
|
||||
request := candidateRequest(cli.Invocation{Repository: "repository", Version: "v1.2.3"}, cli.Settings{
|
||||
Product: "Example Product",
|
||||
Product: "Example Product",
|
||||
ExpectedPushURL: "publication-only value",
|
||||
Validation: &cli.ValidationSettings{
|
||||
Go: cli.GoValidationSettings{Race: true, FullVet: true, CompileTargets: []string{"linux/amd64", "darwin/arm64"}},
|
||||
},
|
||||
@@ -122,6 +123,9 @@ func TestCandidateRequestMapsGoValidation(t *testing.T) {
|
||||
if !request.Validation.Go.Race || !request.Validation.Go.FullVet {
|
||||
t.Fatalf("candidate request validation = %#v, want Go race and full vet enabled", request.Validation)
|
||||
}
|
||||
if request.ExpectedPushURL != "" {
|
||||
t.Fatalf("candidate request carried publication-only expected push URL %q", request.ExpectedPushURL)
|
||||
}
|
||||
wantTargets := []releasekit.Target{{OS: "linux", Arch: "amd64"}, {OS: "darwin", Arch: "arm64"}}
|
||||
if len(request.Validation.Go.CompileTargets) != len(wantTargets) {
|
||||
t.Fatalf("candidate request compile targets = %#v, want %#v", request.Validation.Go.CompileTargets, wantTargets)
|
||||
@@ -217,9 +221,11 @@ printf '%s %s\n' "$version" "$candidate" >> "$RELEASEKIT_COMPATIBILITY_LOG"
|
||||
cliGitRun(t, repository, "add", "docs/releases/v1.2.3.md", "scripts/check-release-note.sh")
|
||||
cliGitRun(t, repository, "commit", "-m", "add compatibility note and check")
|
||||
checkLog := filepath.Join(t.TempDir(), "compatibility-check.log")
|
||||
remote := filepath.Join(t.TempDir(), "origin.git")
|
||||
settings := cli.Settings{
|
||||
Product: "Example Product",
|
||||
Remote: "origin",
|
||||
Product: "Example Product",
|
||||
Remote: "origin",
|
||||
ExpectedPushURL: remote,
|
||||
Checks: []cli.CheckCommand{{
|
||||
Args: []string{"sh", "scripts/check-release-note.sh"},
|
||||
Env: map[string]string{"RELEASEKIT_COMPATIBILITY_LOG": checkLog},
|
||||
@@ -237,7 +243,6 @@ printf '%s %s\n' "$version" "$candidate" >> "$RELEASEKIT_COMPATIBILITY_LOG"
|
||||
}
|
||||
cliGitRun(t, repository, "add", "releasekit.json")
|
||||
cliGitRun(t, repository, "commit", "-m", "configure source release")
|
||||
remote := filepath.Join(t.TempDir(), "origin.git")
|
||||
cliGitRun(t, repository, "init", "--bare", remote)
|
||||
cliGitRun(t, repository, "remote", "add", "origin", remote)
|
||||
cliGitRun(t, repository, "push", "--set-upstream", "origin", "main")
|
||||
|
||||
@@ -71,6 +71,12 @@ after tests and full vet and before configured checks. Each failure is reported
|
||||
as `package-compile` with its selected target; this does not create build
|
||||
artifacts or execute target binaries.
|
||||
|
||||
The optional profile field `expected_push_url` applies only to `publish`. A
|
||||
nonempty value must exactly match the effective push URL resolved by Git;
|
||||
mismatches fail during preflight before Go validation or tag creation. Git URL
|
||||
rewrites are resolved first, and the push URL is used when fetch and push URLs
|
||||
differ. `check` ignores this field.
|
||||
|
||||
The command returns status 0 for confirmed publication, 1 for a known
|
||||
publication failure or conflict, 2 for usage, profile, or invalid-version
|
||||
errors, and 3 when the push outcome remains unknown. Unknown status must be
|
||||
|
||||
@@ -10,6 +10,7 @@ The current profile fields are:
|
||||
| --- | --- | --- |
|
||||
| `product` | Yes | Product name used by release operations. |
|
||||
| `remote` | No | Selected Git remote; defaults to `origin`. |
|
||||
| `expected_push_url` | No | Exact effective push URL required by `publish`; ignored by local `check`. |
|
||||
| `checks` | No | Ordered additional child commands, each with an argument vector and optional environment map. |
|
||||
| `validation` | No | Optional shared checks selected by ReleaseKit. |
|
||||
| `build` | No | Optional main package, binary name, and selected `GOOS/GOARCH` target strings. |
|
||||
@@ -58,6 +59,13 @@ environment variable containing the Gitea token; it defaults to
|
||||
`RELEASEKIT_GITEA_TOKEN`. The CLI reads this variable only for `deliver` and
|
||||
passes its value explicitly to the library operation.
|
||||
|
||||
`expected_push_url`, when nonempty, is compared exactly with Git's resolved
|
||||
effective push URL during `publish`, before candidate validation. It applies
|
||||
to the push destination when fetch and push URLs differ, and Git URL rewrites
|
||||
such as `insteadOf` are resolved before comparison. A mismatch fails preflight;
|
||||
omitting the field keeps the selected remote's existing behavior. `check` is
|
||||
local-only and ignores this publication expectation.
|
||||
|
||||
The `check` and `publish` commands run one
|
||||
`go test -mod=readonly -count=1 ./...` suite with Go module and toolchain network
|
||||
access disabled, adding `-race` when `validation.go.race` is true. When
|
||||
|
||||
@@ -5,9 +5,10 @@ annotated source tag:
|
||||
|
||||
```go
|
||||
result, err := releasekit.Publish(ctx, releasekit.ReleaseRequest{
|
||||
RepositoryDir: "/work/project",
|
||||
Product: "Example",
|
||||
Version: "v1.2.3",
|
||||
RepositoryDir: "/work/project",
|
||||
Product: "Example",
|
||||
Version: "v1.2.3",
|
||||
ExpectedPushURL: "ssh://git.example/owner/example.git",
|
||||
Validation: releasekit.ValidationSpec{
|
||||
Go: releasekit.GoValidationSpec{Race: true, FullVet: true},
|
||||
},
|
||||
@@ -23,6 +24,12 @@ fmt.Println(result.Version, result.CandidateCommit, result.TagObject)
|
||||
|
||||
Publication captures a clean candidate on `main`, validates its committed
|
||||
release note, and checks the selected remote before running candidate checks.
|
||||
Set `ExpectedPushURL` to require the effective push destination to match an
|
||||
exact string. Git URL rewrites are resolved first, and the push URL is compared
|
||||
when fetch and push URLs differ. A mismatch fails preflight before Go validation,
|
||||
callbacks, or tag creation; detect it with
|
||||
`errors.Is(err, releasekit.ErrUnexpectedPushURL)`. `Check` ignores this
|
||||
publication-only field.
|
||||
It runs one `go test -mod=readonly -count=1 ./...` suite with the available
|
||||
compatible local Go toolchain. Set `Validation.Go.Race` to run that suite with
|
||||
`-race`. Set `Validation.Go.FullVet` to run `go vet -mod=readonly ./...` after
|
||||
|
||||
@@ -80,7 +80,7 @@ not a separate release matrix:
|
||||
| Runtime version lookup and injected application version | `buildinfo`, `internal/runtimeinfo`, `internal/versioncontract`, `internal/build` | `TestVersionInConsumerExecutable`, `TestVersion` |
|
||||
| Candidate capture, shared validation, public diagnostics, additional checks, and rechecks | `releasekit.Check`, `releasekit.Publish`, `internal/candidate`, `internal/validation`, `cmd/releasekit/internal/cli` | `TestRunUsesOneSuiteWithSelectedRaceSetting`, `TestRunReportsRacePrerequisiteFailureWithoutFallback`, `TestRunExecutesRaceEnabledSuiteOnHost`, `TestRunCompilesTargetsAfterTestAndVetInSelectionOrder`, `TestRunAttributesTargetCompileFailureAndStops`, `TestRunCompilesSourceOnlyAndExecutablePackagesWithoutArtifacts`, `TestCandidateRequestMapsGoValidation`, `TestLoadSettingsReadsGoValidation`, `TestCheckUsesSelectedRaceSuiteBeforeAdditionalChecks`, `TestCheckCapturesCandidateAndCallsAdditionalChecksOnce`, `TestCheckReturnsPublicValidationError`, `TestPublishPushesOnlyTheCapturedAnnotatedTag`, `TestPublishFailedStandardSuiteSkipsAdditionalChecksAndTagCreation`, `TestPublishAdditionalCheckFailurePreservesCauseAndPreventsTagCreation`, `TestCheckCommandBindsConfiguredChecksToCapturedCandidate`, `TestPublishCommandPublishesConfiguredCandidate`, `TestCandidateCommandsPresentValidationDiagnosticsOnce`, `TestDeliverPageOnlyCreatesAndReusesRelease` |
|
||||
| Shared Go settings for validation and builds | `internal/gotool`, `internal/process`, `internal/candidate`, `internal/build` | `TestDefaultValidationDoesNotInheritGOFLAGSRunFilter`, `TestNativeAndStaticCommandsUseOwnedBaseline`, `TestPersistentSettingsPreserveCachesAndNativeCTools`, `TestBuildIgnoresAmbientAndPersistentArchitectureTuning` |
|
||||
| Authoritative endpoint and exact annotated tag publication | `releasekit.Publish`, `internal/publish`, `internal/git` | `TestPublishPushesOnlyTheCapturedAnnotatedTag`, `TestResolveUsesPushURLAndQueriesWithoutMutation` |
|
||||
| Authoritative endpoint and exact annotated tag publication | `releasekit.Publish`, `internal/publish`, `internal/git` | `TestPublishPushesOnlyTheCapturedAnnotatedTag`, `TestPublishRejectsUnexpectedPushURLBeforeValidation`, `TestPublishComparesExpectedURLWithResolvedInsteadOfDestination`, `TestPublishExpectedPushURLUsesPushURLWhenFetchAndPushDiffer`, `TestCheckIgnoresExpectedPushURL`, `TestResolveUsesPushURLAndQueriesWithoutMutation` |
|
||||
| Publication partial and uncertain recovery | `internal/publish`, `internal/git` | `TestPublishRejectedPushKeepsLocalTagAndReportsNotPublished`, `TestPublishLostAcknowledgmentConfirmsExactRemoteObject`, `TestPublishLostAcknowledgmentWithChangedEndpointRemainsUnknown`, `TestPublishUnavailableReconciliationRemainsUnknownAndPreservesPushError` |
|
||||
| Cross-builds, version smoke checks, empty target sets, and partial outputs | `releasekit.Build`, `internal/build`, `buildinfo` | `TestBuildCreatesSelectedTargetsAndInjectsVersion`, `TestBuildWithNoTargetsDoesNotInvokeCompiler`, `TestBuildReturnsPartialArtifactsAfterTargetFailure` |
|
||||
| Page-only and managed or prebuilt binary delivery | `releasekit.Deliver`, `internal/delivery`, `internal/gitea`, CLI | `TestDeliverPageOnlySkipsBuildAndHostedAssets`, `TestDeliverManagedBuildUploadsArtifactsAndCleansTemporaryOutput`, `TestDeliverBinaryConsumerWithBuildinfoAndSelectedTargets`, `TestDeliverPrebuiltArtifactsRemainCallerOwned`, `TestDeliverManagedBuildFailureCleansTemporaryOutputBeforeHostedWrites`, `TestDeliverManagedBuildCancellationCleansTemporaryOutput`, `TestDeliverBuildsTaggedArtifactsAndReusesAssets`, `TestDeliverRejectsTamperedAssetsAndStaleReceiptBeforeRemoteWrites` |
|
||||
|
||||
@@ -295,6 +295,11 @@ Reuse the existing Go environment tests for controls already protected there.
|
||||
|
||||
## Stage 4: Compare the optional expected push URL
|
||||
|
||||
**Completion:** Complete. `ReleaseRequest.ExpectedPushURL` and profile
|
||||
`expected_push_url` require an exact match with Git's resolved effective push
|
||||
URL before validation. Mismatches return `ErrUnexpectedPushURL` with expected
|
||||
and resolved values; `Check` ignores the publication-only expectation.
|
||||
|
||||
**Outcome:** Publication rejects a different effective destination before validation.
|
||||
|
||||
1. Add `ExpectedPushURL` and `expected_push_url`, with an empty value selecting
|
||||
|
||||
@@ -36,11 +36,16 @@ const (
|
||||
|
||||
const recoveryTimeout = 30 * time.Second
|
||||
|
||||
// ErrUnexpectedPushURL reports that the resolved push URL differs from the
|
||||
// caller's expected publication destination.
|
||||
var ErrUnexpectedPushURL = errors.New("unexpected publication push URL")
|
||||
|
||||
type Request struct {
|
||||
RepositoryDir string
|
||||
Product string
|
||||
Version string
|
||||
Remote string
|
||||
ExpectedPushURL string
|
||||
Validation validation.Settings
|
||||
AdditionalChecks func(context.Context, candidate.Candidate) error
|
||||
AfterValidate func(context.Context, candidate.Candidate) error
|
||||
@@ -56,8 +61,9 @@ type Result struct {
|
||||
State State
|
||||
}
|
||||
|
||||
// Publish validates one captured candidate and pushes only its annotated tag
|
||||
// object. It runs one standard Go suite, with the selected settings, then
|
||||
// Publish captures a candidate, resolves and checks the optional expected push
|
||||
// URL, then validates the candidate and pushes only its annotated tag object.
|
||||
// It runs one standard Go suite, with the selected settings, then
|
||||
// AdditionalChecks when supplied.
|
||||
// A failed push receives one bounded authoritative tag query. Exact
|
||||
// object equality confirms publication; absence and conflict are classified,
|
||||
@@ -87,6 +93,9 @@ func Publish(ctx context.Context, request Request) (Result, error) {
|
||||
return result, fmt.Errorf("resolve publication endpoint: %w", err)
|
||||
}
|
||||
result.Endpoint = endpoint.PushURL
|
||||
if request.ExpectedPushURL != "" && request.ExpectedPushURL != endpoint.PushURL {
|
||||
return result, fmt.Errorf("%w: expected %q, resolved %q", ErrUnexpectedPushURL, request.ExpectedPushURL, endpoint.PushURL)
|
||||
}
|
||||
if err := endpoint.Recheck(ctx, captured.RepositoryDir); err != nil {
|
||||
return result, fmt.Errorf("pre-validation endpoint guard: %w", err)
|
||||
}
|
||||
|
||||
28
publish.go
28
publish.go
@@ -48,21 +48,24 @@ type PublicationResult struct {
|
||||
State PublicationState // publication outcome for this invocation
|
||||
}
|
||||
|
||||
// Publish always runs one offline Go test suite for the captured candidate.
|
||||
// Validation.Go.Race enables the race detector for that suite, and
|
||||
// After publication preflight succeeds, Publish runs one offline Go test suite
|
||||
// for the captured candidate.
|
||||
// Validation.Go.Race enables the race detector for that suite,
|
||||
// Validation.Go.FullVet runs go vet afterward instead of test-time vet, and
|
||||
// Validation.Go.CompileTargets compiles all packages for each selected target
|
||||
// with cgo disabled after tests and vet. Compilation produces no artifacts and
|
||||
// does not execute target binaries. It then calls AdditionalChecks when
|
||||
// supplied. It optionally builds selected
|
||||
// targets in temporary storage outside the worktree and pushes the exact
|
||||
// annotated tag object to the selected remote. It returns a result with errors: preflight
|
||||
// failures are not_attempted, failed authoritative guards are conflict, and
|
||||
// errors after local tag creation but before push are not_published. A failed
|
||||
// push gets one bounded authoritative tag query: an exact object match confirms
|
||||
// publication, absence is not_published, a different object is conflict, and
|
||||
// an unreadable result is unknown. A nil error means publication is confirmed
|
||||
// by the successful push or exact remote object match.
|
||||
// does not execute target binaries. ExpectedPushURL, when nonempty, is compared
|
||||
// exactly with the resolved effective push URL before validation; a mismatch
|
||||
// returns ErrUnexpectedPushURL. It then calls AdditionalChecks when supplied.
|
||||
// It optionally builds selected targets in temporary storage outside the
|
||||
// worktree and pushes the exact annotated tag object to the selected remote.
|
||||
// It returns a result with errors: preflight failures are not_attempted, failed
|
||||
// authoritative guards are conflict, and errors after local tag creation but
|
||||
// before push are not_published. A failed push gets one bounded authoritative
|
||||
// tag query: an exact object match confirms publication, absence is
|
||||
// not_published, a different object is conflict, and an unreadable result is
|
||||
// unknown. A nil error means publication is confirmed by the successful push
|
||||
// or exact remote object match.
|
||||
func Publish(ctx context.Context, request ReleaseRequest) (PublicationResult, error) {
|
||||
candidateInput := candidateRequest(request)
|
||||
result, err := publish.Publish(ctx, publish.Request{
|
||||
@@ -70,6 +73,7 @@ func Publish(ctx context.Context, request ReleaseRequest) (PublicationResult, er
|
||||
Product: candidateInput.Product,
|
||||
Version: candidateInput.Version,
|
||||
Remote: request.Remote,
|
||||
ExpectedPushURL: request.ExpectedPushURL,
|
||||
Validation: candidateInput.Validation,
|
||||
AdditionalChecks: candidateInput.AdditionalChecks,
|
||||
AfterValidate: candidateInput.AfterValidate,
|
||||
|
||||
@@ -94,6 +94,103 @@ func TestPublishPushesOnlyTheCapturedAnnotatedTag(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPublishRejectsUnexpectedPushURLBeforeValidation(t *testing.T) {
|
||||
repo := newPublishFixture(t)
|
||||
suiteRuns := filepath.Join(t.TempDir(), "suite-runs")
|
||||
t.Setenv("RELEASEKIT_FIXTURE_SUITE_RUNS", suiteRuns)
|
||||
expected := repo.bare + string(os.PathSeparator)
|
||||
var callbacks int
|
||||
result, err := releasekit.Publish(context.Background(), releasekit.ReleaseRequest{
|
||||
RepositoryDir: repo.work,
|
||||
Product: "Sample App",
|
||||
Version: "v1.2.3",
|
||||
ExpectedPushURL: expected,
|
||||
AdditionalChecks: func(context.Context, releasekit.Candidate) error {
|
||||
callbacks++
|
||||
return nil
|
||||
},
|
||||
})
|
||||
if !errors.Is(err, releasekit.ErrUnexpectedPushURL) {
|
||||
t.Fatalf("Publish() error = %v, want ErrUnexpectedPushURL", err)
|
||||
}
|
||||
if !strings.Contains(err.Error(), fmt.Sprintf("expected %q", expected)) || !strings.Contains(err.Error(), fmt.Sprintf("resolved %q", repo.bare)) {
|
||||
t.Fatalf("Publish() error = %v, want expected and resolved URLs", err)
|
||||
}
|
||||
if result.State != releasekit.PublicationNotAttempted || result.Phase != releasekit.PublicationPreflight || result.Endpoint != repo.bare || result.TagObject != "" {
|
||||
t.Fatalf("publication result = %#v, want preflight failure at resolved endpoint", result)
|
||||
}
|
||||
if callbacks != 0 {
|
||||
t.Fatalf("additional checks ran %d times, want none", callbacks)
|
||||
}
|
||||
if _, statErr := os.Stat(suiteRuns); !errors.Is(statErr, os.ErrNotExist) {
|
||||
t.Fatalf("standard-suite marker stat error = %v, want no suite run", statErr)
|
||||
}
|
||||
if got := gitText(t, repo.work, "tag", "--list", "v1.2.3"); got != "" {
|
||||
t.Fatalf("local tag exists after URL mismatch: %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPublishComparesExpectedURLWithResolvedInsteadOfDestination(t *testing.T) {
|
||||
repo := newPublishFixture(t)
|
||||
alias := "releasekit-expected-push:"
|
||||
gitRun(t, repo.work, "config", "url."+repo.bare+".insteadOf", alias)
|
||||
gitRun(t, repo.work, "remote", "set-url", "origin", alias)
|
||||
|
||||
result, err := releasekit.Publish(context.Background(), releasekit.ReleaseRequest{
|
||||
RepositoryDir: repo.work,
|
||||
Product: "Sample App",
|
||||
Version: "v1.2.3",
|
||||
ExpectedPushURL: repo.bare,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Publish() with the resolved insteadOf destination failed: %v", err)
|
||||
}
|
||||
if result.Endpoint != repo.bare || result.State != releasekit.PublicationPublished {
|
||||
t.Fatalf("publication result = %#v, want resolved push URL and successful publication", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPublishExpectedPushURLUsesPushURLWhenFetchAndPushDiffer(t *testing.T) {
|
||||
repo := newPublishFixture(t)
|
||||
fetchURL := filepath.Join(t.TempDir(), "fetch-only.git")
|
||||
gitRun(t, repo.work, "remote", "set-url", "origin", fetchURL)
|
||||
gitRun(t, repo.work, "config", "remote.origin.pushurl", repo.bare)
|
||||
|
||||
result, err := releasekit.Publish(context.Background(), releasekit.ReleaseRequest{
|
||||
RepositoryDir: repo.work,
|
||||
Product: "Sample App",
|
||||
Version: "v1.2.3",
|
||||
ExpectedPushURL: fetchURL,
|
||||
})
|
||||
if !errors.Is(err, releasekit.ErrUnexpectedPushURL) {
|
||||
t.Fatalf("Publish() error = %v, want ErrUnexpectedPushURL", err)
|
||||
}
|
||||
if result.Endpoint != repo.bare || !strings.Contains(err.Error(), fmt.Sprintf("resolved %q", repo.bare)) {
|
||||
t.Fatalf("result = %#v, error = %v; want the effective push URL %q", result, err, repo.bare)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckIgnoresExpectedPushURL(t *testing.T) {
|
||||
repo := newPublishFixture(t)
|
||||
var callbacks int
|
||||
_, err := releasekit.Check(context.Background(), releasekit.ReleaseRequest{
|
||||
RepositoryDir: repo.work,
|
||||
Product: "Sample App",
|
||||
Version: "v1.2.3",
|
||||
ExpectedPushURL: "unrelated destination",
|
||||
AdditionalChecks: func(context.Context, releasekit.Candidate) error {
|
||||
callbacks++
|
||||
return nil
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Check() considered publication URL expectation: %v", err)
|
||||
}
|
||||
if callbacks != 1 {
|
||||
t.Fatalf("additional checks ran %d times, want one", callbacks)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckUsesSelectedRaceSuiteBeforeAdditionalChecks(t *testing.T) {
|
||||
requireRacePrerequisites(t)
|
||||
repo := newPublishFixture(t)
|
||||
|
||||
Reference in New Issue
Block a user