Skip to content
go-libfossil Artifact [947c58c590]
Login

Artifact [947c58c590]

Artifact 947c58c59043cc8e7e94ec51caea0b5eb83f1578f006daa340384af63df70a69:


package manifest

import (
	"context"
	"database/sql"
	"errors"
	"fmt"
	"strings"
	"testing"

	"github.com/danmestas/go-libfossil/db"
	"github.com/danmestas/go-libfossil/internal/cone"
	"github.com/danmestas/go-libfossil/internal/content"
	libfossil "github.com/danmestas/go-libfossil/internal/fsltype"
)

type requiredExpansionCounter struct {
	db.Querier
	loads map[string]int
}

func (q *requiredExpansionCounter) QueryRow(query string, args ...any) *sql.Row {
	if strings.HasPrefix(query, "SELECT content, size FROM blob") {
		q.loads[fmt.Sprint(args[0])]++
	}
	return q.Querier.QueryRow(query, args...)
}

// Visibility and relationship validation must share expanded structural bytes:
// a dense parent cannot pay for the entire S tree twice in a single preflight.
func TestRequireParentContentExpandsEachArtifactOnce(t *testing.T) {
	r := sha3Repo(t)
	for key, value := range map[string]string{
		SettingEnableSubmanifests: "on", SettingSubmanifestK: "1",
	} {
		if err := r.SetConfig(key, value); err != nil {
			t.Fatal(err)
		}
	}
	var files []File
	for _, card := range sparseBoundaryCandidates(t) {
		files = append(files, File{Name: card.Name, Content: []byte(card.Name)})
	}
	rid, parent := commit(t, r, CheckinOpts{Files: files})
	if len(parent.S) < 2 {
		t.Fatal("fixture requires a dense structural tree")
	}
	q := &requiredExpansionCounter{Querier: r.DB(), loads: make(map[string]int)}
	if err := RequireParentContent(context.Background(), q, []libfossil.FslID{rid}); err != nil {
		t.Fatal(err)
	}
	if len(q.loads) < 3 {
		t.Fatalf("did not observe root and sibling expansion reads: %+v", q.loads)
	}
	for artifact, count := range q.loads {
		if count != 1 {
			t.Fatalf("artifact %s expanded %d times in one preflight", artifact, count)
		}
	}
}

func TestRequireParentContentElidedSibling(t *testing.T) {
	files := sparseBoundaryCandidates(t)
	old := append(files[:1:1], files[2:]...)
	tree := mustBuildTree(t, old, 1)
	r, rid, _, _ := sparseBoundaryParent(t, files, old, tree)
	err := RequireParentContent(context.Background(), r.DB(), []libfossil.FslID{rid})
	var required *RequiredContentError
	if !errors.As(err, &required) {
		t.Fatalf("required-content refusal: %v", err)
	}
	if len(required.Roots) != 1 || len(required.Artifacts) != 1 {
		t.Fatalf("incomplete recovery instruction: %+v", required)
	}
	if required.Artifacts[0] != tree.S[1].UUID || required.Elided < 2 {
		t.Fatalf("incorrect recovery instruction: %+v", required)
	}
	if !strings.Contains(err.Error(), "elided") || !strings.Contains(err.Error(), "no configured peer") {
		t.Fatalf("incorrect absence diagnostic: %v", err)
	}
	if strings.Contains(err.Error(), "missing") || strings.Contains(err.Error(), "corrupt") {
		t.Fatalf("elision described as damage: %v", err)
	}
	var before int
	if err := r.DB().QueryRow("SELECT count(*) FROM blob").Scan(&before); err != nil {
		t.Fatal(err)
	}
	_, _, err = Checkin(r, CheckinOpts{
		Files: []File{{Name: "new", Content: []byte("new bytes")}}, Parent: rid, User: "test",
	})
	if !errors.As(err, &required) {
		t.Fatalf("local-only writer did not refuse structurally incomplete parent: %v", err)
	}
	var after int
	if err := r.DB().QueryRow("SELECT count(*) FROM blob").Scan(&after); err != nil {
		t.Fatal(err)
	}
	if after != before {
		t.Fatalf("refusal leaked new blobs: before=%d after=%d", before, after)
	}
}

func TestMergeParentFilesCarriesElidedPayload(t *testing.T) {
	r := sha3Repo(t)
	parentRID, parent := commit(t, r, CheckinOpts{Files: []File{
		{Name: "inside/a", Content: []byte("inside")},
		{Name: "outside/a", Content: []byte("outside")},
	}})
	c, err := cone.New([]string{"inside"}, nil)
	if err != nil {
		t.Fatal(err)
	}
	if err := content.SetStoreCone(r.DB(), c); err != nil {
		t.Fatal(err)
	}
	outsideUUID := parent.F[1].UUID
	elideWriterNode(t, r, outsideUUID)
	files, err := MergeParentFiles(context.Background(), r, parentRID,
		[]File{{Name: "inside/a", Content: []byte("edited")}})
	if err != nil {
		t.Fatal(err)
	}
	if len(files) != 2 || files[1].UUID != outsideUUID || files[1].Content != nil {
		t.Fatalf("unchanged outside entry was not carried verbatim: %+v", files)
	}
	_, _, err = Checkin(r, CheckinOpts{Files: files, Parent: parentRID, User: "test"})
	if err != nil {
		t.Fatal(err)
	}
	if _, held := content.AvailableByUUID(r.DB(), outsideUUID); held {
		t.Fatal("metadata-only carry fetched outside payload")
	}
}

// Retaining bytes does not authorize consuming a shunned structural node.
func TestRequireParentContentHeldShunnedChild(t *testing.T) {
	files := sparseBoundaryCandidates(t)
	old := append(files[:1:1], files[2:]...)
	tree := mustBuildTree(t, old, 1)
	r, rid, _, _ := sparseBoundaryParent(t, files, old, tree)
	held := tree.S[0].UUID
	if _, ok := content.AvailableByUUID(r.DB(), held); !ok {
		t.Fatal("fixture first subtree is not held")
	}
	if _, err := r.DB().Exec("INSERT INTO shun(uuid) VALUES(?)", held); err != nil {
		t.Fatal(err)
	}
	err := RequireParentContent(context.Background(), r.DB(), []libfossil.FslID{rid})
	if err == nil || !strings.Contains(err.Error(), "shunned") {
		t.Fatalf("held shunned structural content was not refused: %v", err)
	}
}

func TestRequireParentContentCancellation(t *testing.T) {
	r := sha3Repo(t)
	ctx, cancel := context.WithCancel(context.Background())
	cancel()
	if err := RequireParentContent(ctx, r.DB(), nil); !errors.Is(err, context.Canceled) {
		t.Fatalf("cancelled preflight: %v", err)
	}
}