Skip to content
quarry Artifact [9d7dae4a51]
Login

Artifact [9d7dae4a51]

Artifact 9d7dae4a51a66366f64187c93c82517b91700fb5538d24820018848da1a1a93e:


package xfer

import (
	"log"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
)

// A repository the forge serves but has not registered yet is starting,
// not missing. Telling a sync client 404 during a deploy makes it
// conclude the repository is gone; 503 with Retry-After makes it wait.
func TestSyncSaysStartingRatherThanMissing(t *testing.T) {
	f := &Fronter{repos: map[string]*backend{}}
	f.SetKnown(func(name string) bool { return name == "quarry" })

	for _, tc := range []struct {
		name   string
		repo   string
		want   int
		retry  bool
		reason string
	}{
		{"served but not yet registered", "quarry", http.StatusServiceUnavailable, true,
			"a store the forge serves is starting, not gone"},
		{"genuinely absent", "nosuchrepo", http.StatusNotFound, false,
			"a name the forge does not serve is still not found"},
	} {
		w := httptest.NewRecorder()
		req := httptest.NewRequest("POST", "/r/"+tc.repo+"/xfer", strings.NewReader(""))
		req.SetPathValue("repo", tc.repo)
		f.ServeHTTP(w, req)
		if w.Code != tc.want {
			t.Errorf("FAIL: %s: status %d, want %d — %s", tc.name, w.Code, tc.want, tc.reason)
			continue
		}
		if got := w.Header().Get("Retry-After"); (got != "") != tc.retry {
			t.Errorf("FAIL: %s: Retry-After %q, want present=%v", tc.name, got, tc.retry)
			continue
		}
		t.Logf("ok: %s answers %d", tc.name, w.Code)
	}
}

// Without the seam wired, behaviour is exactly as before: a missing
// backend is a missing repository.
func TestSyncWithoutTheKnownSeamStillSaysNotFound(t *testing.T) {
	f := &Fronter{repos: map[string]*backend{}}
	w := httptest.NewRecorder()
	req := httptest.NewRequest("POST", "/r/quarry/xfer", strings.NewReader(""))
	req.SetPathValue("repo", "quarry")
	f.ServeHTTP(w, req)
	if w.Code != http.StatusNotFound {
		t.Fatalf("FAIL: status %d, want 404 when nothing can answer whether the name is served", w.Code)
	}
	t.Log("ok: an unwired fronter is unchanged")
}

// The log line is a diagnostic surface and was wrong in the same way
// the status was: it said "unknown repository" while the server
// answered "starting". Asserting the reason string keeps the two from
// drifting apart again.
func TestStartingAndMissingAreDistinctReasons(t *testing.T) {
	f := &Fronter{repos: map[string]*backend{}}
	f.SetKnown(func(name string) bool { return name == "quarry" })

	var lines []string
	restore := log.Writer()
	log.SetOutput(writerFunc(func(b []byte) (int, error) {
		lines = append(lines, string(b))
		return len(b), nil
	}))
	defer log.SetOutput(restore)

	for _, repo := range []string{"quarry", "nosuchrepo"} {
		w := httptest.NewRecorder()
		req := httptest.NewRequest("POST", "/r/"+repo+"/xfer", strings.NewReader(""))
		req.SetPathValue("repo", repo)
		f.ServeHTTP(w, req)
	}
	all := strings.Join(lines, "")
	if !strings.Contains(all, "still starting") {
		t.Errorf("FAIL: the starting case is not named in the log: %s", all)
	} else {
		t.Log("ok: the starting case says it is starting")
	}
	if !strings.Contains(all, "unknown repository") {
		t.Errorf("FAIL: the genuinely missing case lost its reason: %s", all)
	} else {
		t.Log("ok: the missing case still says unknown repository")
	}
}

type writerFunc func([]byte) (int, error)

func (f writerFunc) Write(b []byte) (int, error) { return f(b) }