Skip to content
quarry Artifact [ddbaa12794]
Login

Artifact [ddbaa12794]

Artifact ddbaa12794997c2e281725233395a39155d3a1970927c343162c170a674fc4ec:


package api

import (
	"context"
	"crypto/sha1"
	"crypto/sha256"
	"encoding/hex"
	"net/http"
	"net/http/httptest"
	"strings"
	"sync"
	"testing"
	"time"

	"craftdesign.group/quarry/internal/artifact"
	"craftdesign.group/quarry/internal/attach"
	"craftdesign.group/quarry/internal/index"
	"craftdesign.group/quarry/internal/oltp"
	"craftdesign.group/quarry/internal/ops"
)

type memMirrorAttach struct {
	mu   sync.Mutex
	rows map[string]oltp.MirrorAttach
}

func (m *memMirrorAttach) MirrorAttachment(repo, src string) (oltp.MirrorAttach, bool, error) {
	m.mu.Lock()
	defer m.mu.Unlock()
	r, ok := m.rows[repo+"/"+src]
	return r, ok, nil
}
func (m *memMirrorAttach) RecordMirrorAttachment(r oltp.MirrorAttach) error {
	m.mu.Lock()
	defer m.mu.Unlock()
	m.rows[r.Repo+"/"+r.Src] = r
	return nil
}
func (m *memMirrorAttach) MirrorAttachSettled(repo string, cutoff time.Time) (map[string]bool, error) {
	m.mu.Lock()
	defer m.mu.Unlock()
	out := map[string]bool{}
	for _, r := range m.rows {
		if r.Repo == repo && r.State != oltp.MirrorAttachFailed {
			out[r.Src] = true
		}
	}
	return out, nil
}

// #273: a Fossil mirror with storage fetches an attachment its store
// lacks from the upstream, keeps it only when it hashes to the A card's
// src, stores it in the bucket by SHA-256, and from then on serves it
// as an external file. Wrong bytes from the upstream are refused and
// recorded, and never reach the bucket.
func TestAMirrorFetchesMissingAttachmentsIntoItsBucket(t *testing.T) {
	ix, z, event := attachStore(t)
	good := []byte("all tests passed\n")
	sum := sha1.Sum(good)
	src := hex.EncodeToString(sum[:])
	liar := strings.Repeat("f", 40)
	for i, s := range []string{src, liar} {
		if err := ix.FoldNow([]index.Parsed{{Hash: strings.Repeat(string(rune('a'+i)), 40), Art: &artifact.Artifact{
			Type: artifact.Attachment, Date: "2026-01-0" + string(rune('2'+i)) + "T00:00:00", User: "dan",
			Attachment: &artifact.Attach{Name: "test" + string(rune('0'+i)) + ".result", Target: event, Source: s},
		}}}); err != nil {
			t.Fatal(err)
		}
	}
	upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.URL.Path {
		case "/tcl/raw/" + src:
			w.Write(good)
		case "/tcl/raw/" + liar:
			w.Write([]byte("not what the card names\n"))
		default:
			http.NotFound(w, r)
		}
	}))
	t.Cleanup(upstream.Close)
	s3srv, objects := newObjectS3(t)
	buckets := newMemBuckets()
	buckets.rows["test"] = oltp.BucketBinding{Endpoint: s3srv.URL, Bucket: "files", Region: "auto", AccessKey: "AK", Secret: "SK", LimitBytes: 1 << 20}
	store := &memMirrorAttach{rows: map[string]oltp.MirrorAttach{}}
	d := Deps{Version: "v", Repos: NewRepos(map[string]*Repo{"test": {Index: ix, Zit: z}}),
		Mirrors: memMirrors{"test": {Repo: "test", URL: upstream.URL + "/tcl", Enabled: true}},
		Buckets: buckets, Attachments: newMemAttachments(buckets), MirrorAttach: store}
	r := ops.NewRegistry()
	if err := RegisterAll(r, d); err != nil {
		t.Fatal(err)
	}
	head := func(hash string) attachmentHeadOut {
		t.Helper()
		out, err := r.Invoke(context.Background(), "attachment.head", attachmentHeadIn{Repo: "test", Hash: hash})
		if err != nil {
			t.Fatalf("FAIL: head %.8s: %v", hash, err)
		}
		return out.(attachmentHeadOut)
	}
	if h := head(src); h.Kind != attachKindMissing || h.Upstream == "" {
		t.Fatalf("FAIL: before the fetch the file reads %+v, want held upstream", h)
	}
	mirrorAttachRuns.Wait()
	shaSum := sha256.Sum256(good)
	sha := hex.EncodeToString(shaSum[:])
	if row, ok, _ := store.MirrorAttachment("test", src); !ok || row.State != oltp.MirrorAttachFetched || row.SHA256 != sha || row.Size != int64(len(good)) {
		t.Fatalf("FAIL: the fetch recorded %+v", row)
	}
	if n, ok := objects.objects[attach.Key(sha)]; !ok || n != int64(len(good)) {
		t.Fatalf("FAIL: the bucket holds %v", objects.objects)
	}
	if h := head(src); !h.External || h.SHA256 != sha || h.Kind == attachKindMissing {
		t.Errorf("FAIL: after the fetch the file reads %+v, want external", h)
	}
	link, err := r.Invoke(context.Background(), "attachment.link", attachmentLinkIn{Repo: "test", Hash: src})
	if err != nil || !link.(attachmentLinkOut).External || link.(attachmentLinkOut).Download == "" {
		t.Errorf("FAIL: the link after the fetch: %+v, %v", link, err)
	}
	// The backfill reaches the other one: the upstream's bytes are not
	// what the card names, so it is refused and recorded.
	if _, err := d.mirrorAttachBackfill(context.Background(), "test"); err != nil {
		t.Fatal(err)
	}
	if row, ok, _ := store.MirrorAttachment("test", liar); !ok || row.State != oltp.MirrorAttachFailed || !strings.Contains(row.Err, "hash") {
		t.Errorf("FAIL: wrong bytes recorded %+v", row)
	}
	if len(objects.objects) != 1 {
		t.Errorf("FAIL: refused bytes reached the bucket: %v", objects.objects)
	}
}