Skip to content
quarry Artifact [ff7696d195]
Login

Artifact [ff7696d195]

Artifact ff7696d195c61a27f8aba28fac07b5d9c3a1e2e5c85d24bf84c9ed81ea837b65:


package api

import (
	"net/http"
	"net/http/httptest"
	"path/filepath"
	"strings"
	"testing"
	"time"

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

// #228: a read with ?indexed=HASH answers once the repository's index
// holds that artifact, and 425 with Retry-After when it does not in
// time; the parameter never reaches the op's own decoding.
func TestAReadWaitsForItsOwnWrite(t *testing.T) {
	ix, err := index.Open(filepath.Join(t.TempDir(), "index.db"))
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { ix.Close() })
	tip := "c0ffee" + strings.Repeat("0", 34)
	if err := ix.FoldNow([]index.Parsed{{Hash: tip, Art: &artifact.Artifact{Type: artifact.Manifest,
		Date: "2026-10-29T12:00:00", User: "dan", Comment: "pushed"}}}); err != nil {
		t.Fatal(err)
	}
	r := ops.NewRegistry()
	if err := RegisterAll(r, Deps{Version: "v", Repos: NewRepos(map[string]*Repo{"test": {Index: ix}})}); err != nil {
		t.Fatal(err)
	}
	h, err := Router(r, "v")
	if err != nil {
		t.Fatal(err)
	}
	defer func(b time.Duration) { indexedBound = b }(indexedBound)
	indexedBound = 300 * time.Millisecond
	get := func(q string) *httptest.ResponseRecorder {
		w := httptest.NewRecorder()
		h.ServeHTTP(w, httptest.NewRequest("GET", "/api/v1/r/test/timeline"+q, nil))
		return w
	}
	if w := get("?indexed=c0ffee"); w.Code != http.StatusOK {
		t.Errorf("FAIL: a read after an indexed write answered %d: %s", w.Code, w.Body)
	}
	w := get("?indexed=deadbeef")
	if w.Code != http.StatusTooEarly || w.Header().Get("Retry-After") == "" {
		t.Errorf("FAIL: a read ahead of the index answered %d (Retry-After %q), want 425 with Retry-After", w.Code, w.Header().Get("Retry-After"))
	}
	if w := get("?indexed=not-a-hash"); w.Code != http.StatusBadRequest {
		t.Errorf("FAIL: a malformed hash answered %d, want 400", w.Code)
	}
}