Skip to content
go-libfossil Artifact [30e089bbce]
Login

Artifact [30e089bbce]

Artifact 30e089bbce75b045fea47e1e37541b16ba81da9996bd7da18b2571289a40d96b:


package libfossil

import (
	"context"
	"os"
	"path/filepath"
	"strings"
	"testing"

	"github.com/danmestas/go-libfossil/internal/blob"
	"github.com/danmestas/go-libfossil/internal/checkout"
	"github.com/danmestas/go-libfossil/internal/content"
)

func requiredUpdateFixture(t *testing.T) (requiredPublicFixture, *Checkout, int64, string) {
	t.Helper()
	f := newRequiredPublicFixture(t, "sha3")
	co := requiredPublicCheckout(t, f)
	parent, err := f.source.ResolveVersion("tip")
	if err != nil {
		t.Fatal(err)
	}
	_, uuid, err := f.source.Commit(CommitOpts{ParentID: parent, User: "test",
		Files: []FileToCommit{{Name: "f0000", Content: []byte("updated payload")}}})
	if err != nil {
		t.Fatal(err)
	}
	if _, err := f.target.Sync(context.Background(), f.peer, SyncOpts{Pull: true}); err != nil {
		t.Fatal(err)
	}
	target, found := blob.Exists(f.target.inner.DB(), uuid)
	if !found {
		t.Fatal("update target not synchronized")
	}
	var payload string
	if err := f.source.inner.DB().QueryRow(`SELECT uuid FROM blob WHERE rid IN
		(SELECT fid FROM mlink WHERE mid IN (SELECT rid FROM blob WHERE uuid=?)
		AND fnid IN (SELECT fnid FROM filename WHERE name='f0000'))`, uuid).
		Scan(&payload); err != nil {
		t.Fatal(err)
	}
	if _, err := f.target.inner.DB().Exec(
		"UPDATE blob SET size=-1,content=NULL WHERE uuid=?", payload); err != nil {
		t.Fatal(err)
	}
	return f, co, int64(target), payload
}

// An update fetches exactly the covered target payload, never arbitrary outside
// F bytes. An implicit target is resolved before recovery and reused locally.
func TestUpdateContextRequiredPayload(t *testing.T) {
	for _, implicit := range []bool{false, true} {
		f, co, target, payload := requiredUpdateFixture(t)
		requested := false
		peer := TransportFunc(func(ctx context.Context, data []byte) ([]byte, error) {
			requested = requested || requiredRequestsPayload(t, data, payload)
			for _, outside := range f.outside {
				if requiredRequestsPayload(t, data, outside) {
					t.Fatal("update fetched an outside F payload")
				}
			}
			return f.source.HandleSync(ctx, data)
		})
		opts := UpdateOpts{TargetRID: target}
		if implicit {
			opts.TargetRID = 0
		}
		_, err := co.UpdateContext(context.Background(), opts, RequiredContentOpts{Peer: peer})
		if err != nil {
			t.Fatal(err)
		}
		got, err := os.ReadFile(filepath.Join(co.Dir(), "f0000"))
		if err != nil || string(got) != "updated payload" || !requested {
			t.Fatalf("update result: %q, requested=%v, %v", got, requested, err)
		}
		version, _, err := co.Version()
		if err != nil || version != target {
			t.Fatalf("update version: %d, want %d, %v", version, target, err)
		}
		for _, uuid := range f.outside {
			if _, held := content.AvailableByUUID(f.target.inner.DB(), uuid); held {
				t.Fatal("outside payload became available")
			}
		}
	}
}

// A network callback can change pending selection. Recovery must reject the
// captured snapshot rather than apply an update to the changed checkout.
func TestUpdateContextRefusesChangedQueue(t *testing.T) {
	f, co, target, _ := requiredUpdateFixture(t)
	changed := false
	peer := TransportFunc(func(ctx context.Context, data []byte) ([]byte, error) {
		if !changed {
			changed = true
			if err := co.inner.Enqueue(checkout.EnqueueOpts{Paths: []string{"f0000"}}); err != nil {
				t.Fatal(err)
			}
		}
		return f.source.HandleSync(ctx, data)
	})
	_, err := co.UpdateContext(context.Background(), UpdateOpts{TargetRID: target},
		RequiredContentOpts{Peer: peer})
	if err == nil || !strings.Contains(err.Error(), "snapshot") {
		t.Fatalf("changed snapshot was not rejected: %v", err)
	}
	version, _, err := co.Version()
	if err != nil || version != f.parent {
		t.Fatalf("refused update moved version: %d, %v", version, err)
	}
	got, err := os.ReadFile(filepath.Join(co.Dir(), "f0000"))
	if err != nil || string(got) != "f0000" {
		t.Fatalf("refused update changed worktree: %q, %v", got, err)
	}
}

// No-peer refusal precedes checkout scanning and leaves pending metadata and
// working bytes untouched. A saved remote is not used by this explicit API.
func TestUpdateContextNoPeerPreservesCheckout(t *testing.T) {
	_, co, target, _ := requiredUpdateFixture(t)
	before, err := co.inner.RecoverySnapshot()
	if err != nil {
		t.Fatal(err)
	}
	name := filepath.Join(co.Dir(), "f0000")
	if err := os.WriteFile(name, []byte("unscanned edit"), 0o644); err != nil {
		t.Fatal(err)
	}
	_, err = co.UpdateContext(context.Background(), UpdateOpts{TargetRID: target},
		RequiredContentOpts{})
	if err == nil || !strings.Contains(err.Error(), "no configured peer") {
		t.Fatalf("no-peer update refusal: %v", err)
	}
	after, err := co.inner.RecoverySnapshot()
	if err != nil || before != after {
		t.Fatalf("refusal changed checkout metadata: %v", err)
	}
	got, err := os.ReadFile(name)
	if err != nil || string(got) != "unscanned edit" {
		t.Fatalf("refusal changed worktree: %q, %v", got, err)
	}
}