Skip to content
go-libfossil Artifact [0bec1617d6]
Login

Artifact [0bec1617d6]

Artifact 0bec1617d6958b82a2ea20ef6b64eb12eb9e587d8175c7b575cd4d7e864dac3e:


package sync

import (
	"context"
	"errors"
	"path/filepath"
	"strings"
	"testing"
	"time"

	"github.com/danmestas/go-libfossil/internal/cone"
	"github.com/danmestas/go-libfossil/internal/content"
	"github.com/danmestas/go-libfossil/internal/deck"
	"github.com/danmestas/go-libfossil/internal/xfer"
)

func TestFetchConeExplicitPeer(t *testing.T) {
	source := setupSyncTestRepo(t)
	inside := storeAndLink(t, source, []byte("inside"))
	outside := storeAndLink(t, source, []byte("outside"))
	data, err := (&deck.Deck{C: "policy fetch", D: time.Now().UTC(), U: deck.User("u"), F: []deck.FileCard{{Name: "src/a", UUID: inside}, {Name: "zzz/a", UUID: outside}}}).Marshal()
	if err != nil {
		t.Fatal(err)
	}
	storeAndLink(t, source, data)
	c, err := cone.New([]string{"src"}, nil)
	if err != nil {
		t.Fatal(err)
	}
	calls := 0
	peer := &MockTransport{Handler: func(req *xfer.Message) *xfer.Message {
		calls++
		resp, err := HandleSync(context.Background(), source, req)
		if err != nil {
			t.Fatal(err)
		}
		return resp
	}}
	r, _, err := Clone(context.Background(), filepath.Join(t.TempDir(), "fetch.fossil"), peer, CloneOpts{StoreCone: c, PullPhantoms: true})
	if err != nil {
		t.Fatal(err)
	}
	defer r.Close()
	if _, ok := content.AvailableByUUID(r.DB(), outside); ok {
		t.Fatal("outside content unexpectedly held")
	}
	before := calls
	_, err = FetchCone(context.Background(), r, nil, nil, SyncOpts{})
	if err == nil || !strings.Contains(err.Error(), "1 elided") || !strings.Contains(err.Error(), "no configured peer") || strings.Contains(err.Error(), "missing") || strings.Contains(err.Error(), "corrupt") {
		t.Fatalf("no-peer diagnostic: %v", err)
	}
	if calls != before {
		t.Fatal("no-peer fetch used a network")
	}
	if got, _ := content.StoreCone(r.DB()); got == nil || !got.Equal(c) {
		t.Fatal("no-peer refusal changed policy")
	}
	ctx, cancel := context.WithCancel(context.Background())
	cancel()
	if _, err := FetchCone(ctx, r, peer, nil, SyncOpts{}); !errors.Is(err, context.Canceled) {
		t.Fatalf("cancelled demand: %v", err)
	}
	if calls != before {
		t.Fatal("cancelled demand used peer")
	}
	t.Run("failed-fetch-preserves-policy-intent", func(t *testing.T) {
		var beforeRID int64
		if err := r.DB().QueryRow("SELECT rid FROM blob WHERE uuid=?", outside).Scan(&beforeRID); err != nil {
			t.Fatal(err)
		}
		failure := errors.New("configured peer unavailable")
		failed := &countingTransport{failures: 1, fail: failure}
		if _, err := FetchCone(context.Background(), r, failed, nil, SyncOpts{}); !errors.Is(err, failure) {
			t.Fatalf("failed policy fetch: %v", err)
		}
		if failed.attempts != 1 {
			t.Fatalf("permanent failure attempts=%d, want 1", failed.attempts)
		}
		if got, err := content.StoreCone(r.DB()); err != nil || got != nil {
			t.Fatalf("failed fetch discarded widened intent: cone=%v, err=%v", got, err)
		}
		var afterRID, size, phantomCount int64
		if err := r.DB().QueryRow("SELECT rid,size FROM blob WHERE uuid=?", outside).Scan(&afterRID, &size); err != nil {
			t.Fatal(err)
		}
		if err := r.DB().QueryRow("SELECT count(*) FROM phantom WHERE rid=?", afterRID).Scan(&phantomCount); err != nil {
			t.Fatal(err)
		}
		if afterRID != beforeRID || size != -1 || phantomCount != 1 {
			t.Fatalf("failed fetch changed recoverable identity: rid=%d/%d size=%d phantoms=%d", beforeRID, afterRID, size, phantomCount)
		}
		a, err := content.LoadAbsencesContext(context.Background(), r.DB())
		if err != nil || a.Count(content.Transit) != 1 || a.Count(content.Elided) != 0 {
			t.Fatalf("failed widened fetch did not retain a transit phantom: absences=%v, err=%v", a, err)
		}
	})
	res, err := FetchCone(context.Background(), r, peer, nil, SyncOpts{})
	if err != nil {
		t.Fatal(err)
	}
	if res == nil || res.FilesRecvd == 0 {
		t.Fatalf("no fetch progress: %+v", res)
	}
	if _, ok := content.AvailableByUUID(r.DB(), outside); !ok {
		t.Fatal("widened file not fetched")
	}
	if got, err := content.StoreCone(r.DB()); err != nil || got != nil {
		t.Fatalf("dense policy: %v %v", got, err)
	}
}