Verify releases use original Git objects
This commit is contained in:
@@ -18,6 +18,41 @@ import (
|
|||||||
releasekit "go.maximumdirect.net/releasekit"
|
releasekit "go.maximumdirect.net/releasekit"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestBuildReplacementDerivedSourceIsNotEligibleForDelivery(t *testing.T) {
|
||||||
|
repository := newTaggedDeliveryConsumer(t)
|
||||||
|
original := buildGit(t, repository, "rev-parse", "HEAD")
|
||||||
|
mainPath := filepath.Join(repository, "cmd/app/main.go")
|
||||||
|
main, err := os.ReadFile(mainPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
writeBuildFixture(t, repository, "cmd/app/main.go", string(main)+"\n// replacement source\n")
|
||||||
|
buildGit(t, repository, "add", "cmd/app/main.go")
|
||||||
|
buildGit(t, repository, "commit", "-m", "Replacement source")
|
||||||
|
replacement := buildGit(t, repository, "rev-parse", "HEAD")
|
||||||
|
buildGit(t, repository, "reset", "--hard", original)
|
||||||
|
buildGit(t, repository, "replace", original, replacement)
|
||||||
|
buildGit(t, repository, "reset", "--hard", original)
|
||||||
|
result, err := releasekit.Build(context.Background(), releasekit.BuildRequest{
|
||||||
|
RepositoryDir: repository, Version: "v1.2.3", OutputDir: t.TempDir(),
|
||||||
|
Spec: releasekit.BuildSpec{MainPackage: "./cmd/app", BinaryName: "fixture", Targets: []releasekit.Target{{OS: runtime.GOOS, Arch: runtime.GOARCH}}},
|
||||||
|
})
|
||||||
|
if err != nil || result.SourceVerified || len(result.Artifacts) != 1 || result.CandidateCommit != original {
|
||||||
|
t.Fatalf("replacement build = %#v, error = %v", result, err)
|
||||||
|
}
|
||||||
|
buildGit(t, repository, "replace", "-d", original)
|
||||||
|
buildGit(t, repository, "reset", "--hard", original)
|
||||||
|
server, remote := newFacadeDeliveryServer(t, repository)
|
||||||
|
defer server.Close()
|
||||||
|
delivery, err := releasekit.Deliver(context.Background(), releasekit.DeliveryRequest{
|
||||||
|
RepositoryDir: repository, Product: "Fixture", Version: "v1.2.3",
|
||||||
|
Destination: facadeDeliveryDestination(server.URL), Prebuilt: &result,
|
||||||
|
})
|
||||||
|
if err == nil || delivery.State != releasekit.DeliveryNotAttempted || remote.snapshot().writes != 0 {
|
||||||
|
t.Fatalf("delivery of replacement build = %#v, error = %v", delivery, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestBuildPreservesInvalidVersionAndDeclarationFailureEvidence(t *testing.T) {
|
func TestBuildPreservesInvalidVersionAndDeclarationFailureEvidence(t *testing.T) {
|
||||||
spec := releasekit.BuildSpec{MainPackage: "./cmd/app", BinaryName: "widget", Targets: []releasekit.Target{{OS: "linux", Arch: "amd64"}, {OS: "linux", Arch: "amd64"}}}
|
spec := releasekit.BuildSpec{MainPackage: "./cmd/app", BinaryName: "widget", Targets: []releasekit.Target{{OS: "linux", Arch: "amd64"}, {OS: "linux", Arch: "amd64"}}}
|
||||||
invalid, err := releasekit.Build(context.Background(), releasekit.BuildRequest{
|
invalid, err := releasekit.Build(context.Background(), releasekit.BuildRequest{
|
||||||
|
|||||||
@@ -402,6 +402,29 @@ func TestDeliverPageOnlySkipsBuildAndHostedAssets(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDeliverUsesOriginalNoteWithBlobReplacement(t *testing.T) {
|
||||||
|
repository := newTaggedDeliveryConsumer(t)
|
||||||
|
original := buildGit(t, repository, "rev-parse", "HEAD:docs/releases/v1.2.3.md")
|
||||||
|
replacementPath := filepath.Join(t.TempDir(), "note.md")
|
||||||
|
if err := os.WriteFile(replacementPath, []byte("# Fixture v1.2.3\n\nReplacement note.\n"), 0o600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
replacement := buildGit(t, repository, "hash-object", "-w", replacementPath)
|
||||||
|
buildGit(t, repository, "replace", original, replacement)
|
||||||
|
server, remote := newFacadeDeliveryServer(t, repository)
|
||||||
|
defer server.Close()
|
||||||
|
result, err := releasekit.Deliver(context.Background(), releasekit.DeliveryRequest{
|
||||||
|
RepositoryDir: repository, Product: "Fixture", Version: "v1.2.3",
|
||||||
|
Destination: facadeDeliveryDestination(server.URL),
|
||||||
|
})
|
||||||
|
if err != nil || result.State != releasekit.DeliveryComplete {
|
||||||
|
t.Fatalf("delivery = %#v, error = %v", result, err)
|
||||||
|
}
|
||||||
|
if got := remote.snapshot().createdBody; got != "# Fixture v1.2.3\n\nDelivery fixture.\n" {
|
||||||
|
t.Fatalf("delivered note = %q", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestDeliverUsesCommittedModuleIdentityForSeparatePages(t *testing.T) {
|
func TestDeliverUsesCommittedModuleIdentityForSeparatePages(t *testing.T) {
|
||||||
repository := newTwoModuleDeliveryConsumer(t)
|
repository := newTwoModuleDeliveryConsumer(t)
|
||||||
server, remote := newFacadeDeliveryServer(t, repository)
|
server, remote := newFacadeDeliveryServer(t, repository)
|
||||||
@@ -817,6 +840,7 @@ type facadeDeliveryData struct {
|
|||||||
tag string
|
tag string
|
||||||
version string
|
version string
|
||||||
noteBody string
|
noteBody string
|
||||||
|
createdBody string
|
||||||
pages map[string]bool
|
pages map[string]bool
|
||||||
requests int
|
requests int
|
||||||
writes int
|
writes int
|
||||||
@@ -876,6 +900,7 @@ func (state *facadeDeliveryServer) serveHTTP(w http.ResponseWriter, r *http.Requ
|
|||||||
if payload.Tag != data.tag || payload.Target != data.commit || payload.Name != "Fixture "+data.version {
|
if payload.Tag != data.tag || payload.Target != data.commit || payload.Name != "Fixture "+data.version {
|
||||||
data.t.Errorf("release payload = %#v", payload)
|
data.t.Errorf("release payload = %#v", payload)
|
||||||
}
|
}
|
||||||
|
data.createdBody = payload.Body
|
||||||
data.pages[data.tag] = true
|
data.pages[data.tag] = true
|
||||||
state.writeRelease(w)
|
state.writeRelease(w)
|
||||||
case "/api/v1/repos/owner/example/releases/10/assets":
|
case "/api/v1/repos/owner/example/releases/10/assets":
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ the agent policy; an intermediate compilation dependency is recorded accurately.
|
|||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| 1 | A01; publication assertion portion of A19 | Complete — regression failed before the fix and passed afterward; focused publication and reconciliation tests passed. |
|
| 1 | A01; publication assertion portion of A19 | Complete — regression failed before the fix and passed afterward; focused publication and reconciliation tests passed. |
|
||||||
| 2 | A02, A12: common Git mechanics | Complete — focused Git, candidate, identity, and public build/publication/delivery tests passed; A02 regression evidence follows in Stage 3. |
|
| 2 | A02, A12: common Git mechanics | Complete — focused Git, candidate, identity, and public build/publication/delivery tests passed; A02 regression evidence follows in Stage 3. |
|
||||||
| 3 | A02: source identity regression evidence | Pending |
|
| 3 | A02: source identity regression evidence | Complete — commit, tree, and blob replacement regressions and public publication, build, and delivery cases passed. |
|
||||||
| 4 | A03: buffered command teardown | Pending |
|
| 4 | A03: buffered command teardown | Pending |
|
||||||
| 5 | A03, A18: piped lifetime and queued cancellation | Pending |
|
| 5 | A03, A18: piped lifetime and queued cancellation | Pending |
|
||||||
| 6 | A04 | Pending |
|
| 6 | A04 | Pending |
|
||||||
|
|||||||
@@ -142,3 +142,52 @@ func TestInspectTaggedSourceReportsDirtyCheckoutAndReadCommitFile(t *testing.T)
|
|||||||
t.Fatalf("dirty status = %q", source.Status)
|
t.Fatalf("dirty status = %q", source.Status)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCommittedReadsIgnoreReplacementObjects(t *testing.T) {
|
||||||
|
for _, kind := range []string{"commit", "tree", "blob"} {
|
||||||
|
t.Run(kind, func(t *testing.T) {
|
||||||
|
fixture := newRemoteFixture(t)
|
||||||
|
original := map[string]string{
|
||||||
|
"commit": fixture.commit,
|
||||||
|
"tree": gitText(t, fixture.work, "rev-parse", "HEAD^{tree}"),
|
||||||
|
"blob": gitText(t, fixture.work, "rev-parse", "HEAD:source.txt"),
|
||||||
|
}
|
||||||
|
writeRepoFile(t, fixture.work, "source.txt", "replacement")
|
||||||
|
gitRun(t, fixture.work, "add", "source.txt")
|
||||||
|
gitRun(t, fixture.work, "commit", "-m", "Replacement")
|
||||||
|
replacement := map[string]string{
|
||||||
|
"commit": gitText(t, fixture.work, "rev-parse", "HEAD"),
|
||||||
|
"tree": gitText(t, fixture.work, "rev-parse", "HEAD^{tree}"),
|
||||||
|
"blob": gitText(t, fixture.work, "rev-parse", "HEAD:source.txt"),
|
||||||
|
}
|
||||||
|
gitRun(t, fixture.work, "reset", "--hard", fixture.commit)
|
||||||
|
gitRun(t, fixture.work, "replace", original[kind], replacement[kind])
|
||||||
|
|
||||||
|
content, err := ReadCommitFile(t.Context(), fixture.work, fixture.commit, "source.txt")
|
||||||
|
if err != nil || string(content) != "candidate" {
|
||||||
|
t.Fatalf("committed content = %q, error = %v", content, err)
|
||||||
|
}
|
||||||
|
entries, err := ReadTree(t.Context(), fixture.work, fixture.commit)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var object string
|
||||||
|
for _, entry := range entries {
|
||||||
|
if entry.Path == "source.txt" {
|
||||||
|
object = entry.ObjectID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if object != original["blob"] {
|
||||||
|
t.Fatalf("tree blob = %q, want %q", object, original["blob"])
|
||||||
|
}
|
||||||
|
reader := NewBlobReader(t.Context(), fixture.work)
|
||||||
|
content, err = reader.Read(t.Context(), object)
|
||||||
|
if err != nil || string(content) != "candidate" {
|
||||||
|
t.Fatalf("batch content = %q, error = %v", content, err)
|
||||||
|
}
|
||||||
|
if err := reader.Close(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ func TestCandidateOperationsDiscoverGoOnlyWhenLaterWorkNeedsIt(t *testing.T) {
|
|||||||
func TestPublishPushesOnlyTheCapturedAnnotatedTag(t *testing.T) {
|
func TestPublishPushesOnlyTheCapturedAnnotatedTag(t *testing.T) {
|
||||||
repo := newPublishFixture(t)
|
repo := newPublishFixture(t)
|
||||||
gitRun(t, repo.work, "config", "push.recurseSubmodules", "only")
|
gitRun(t, repo.work, "config", "push.recurseSubmodules", "only")
|
||||||
|
gitRun(t, repo.work, "replace", repo.unrelatedCommit, repo.commit)
|
||||||
suiteRuns := filepath.Join(t.TempDir(), "suite-runs")
|
suiteRuns := filepath.Join(t.TempDir(), "suite-runs")
|
||||||
t.Setenv("RELEASEKIT_FIXTURE_SUITE_RUNS", suiteRuns)
|
t.Setenv("RELEASEKIT_FIXTURE_SUITE_RUNS", suiteRuns)
|
||||||
refsBefore := gitText(t, repo.bare, "show-ref", "--heads", "--tags")
|
refsBefore := gitText(t, repo.bare, "show-ref", "--heads", "--tags")
|
||||||
@@ -162,6 +163,36 @@ func TestPublishPushesOnlyTheCapturedAnnotatedTag(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPublishRejectsReplacementDerivedCheckout(t *testing.T) {
|
||||||
|
repo := newPublishFixture(t)
|
||||||
|
writeFile(t, repo.work, "broken.go", "package fixture\nfunc broken(\n")
|
||||||
|
gitRun(t, repo.work, "add", "broken.go")
|
||||||
|
gitRun(t, repo.work, "commit", "-m", "Original source does not compile")
|
||||||
|
original := gitText(t, repo.work, "rev-parse", "HEAD")
|
||||||
|
gitRun(t, repo.work, "-c", "remote.origin.mirror=false", "push", "origin", "HEAD:refs/heads/main")
|
||||||
|
writeFile(t, repo.work, "broken.go", "package fixture\nfunc valid() {}\n")
|
||||||
|
writeFile(t, repo.work, "docs/releases/v1.2.3.md", "# Sample App v1.2.3\n\nReplacement note.\n")
|
||||||
|
gitRun(t, repo.work, "add", "broken.go", "docs/releases/v1.2.3.md")
|
||||||
|
gitRun(t, repo.work, "commit", "-m", "Replacement source")
|
||||||
|
replacement := gitText(t, repo.work, "rev-parse", "HEAD")
|
||||||
|
gitRun(t, repo.work, "reset", "--hard", original)
|
||||||
|
gitRun(t, repo.work, "replace", original, replacement)
|
||||||
|
gitRun(t, repo.work, "reset", "--hard", original)
|
||||||
|
contents, err := os.ReadFile(filepath.Join(repo.work, "broken.go"))
|
||||||
|
if err != nil || !strings.Contains(string(contents), "func valid") {
|
||||||
|
t.Fatalf("replacement checkout = %q, error = %v", contents, err)
|
||||||
|
}
|
||||||
|
result, err := releasekit.Publish(context.Background(), releasekit.ReleaseRequest{
|
||||||
|
RepositoryDir: repo.work, Product: "Sample App", Version: "v1.2.3",
|
||||||
|
})
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "clean") || result.State != releasekit.PublicationNotAttempted {
|
||||||
|
t.Fatalf("publication = %#v, error = %v; want dirty source rejection", result, err)
|
||||||
|
}
|
||||||
|
if err := exec.Command("git", "-C", repo.bare, "show-ref", "--verify", "--quiet", "refs/tags/v1.2.3").Run(); err == nil {
|
||||||
|
t.Fatal("replacement-derived source was published")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestPublishUsesSeparateFullTagsForModulesAtOneCommit(t *testing.T) {
|
func TestPublishUsesSeparateFullTagsForModulesAtOneCommit(t *testing.T) {
|
||||||
repo := newPublishFixture(t)
|
repo := newPublishFixture(t)
|
||||||
firstDir, secondDir := "integrations/first", "integrations/second"
|
firstDir, secondDir := "integrations/first", "integrations/second"
|
||||||
|
|||||||
Reference in New Issue
Block a user