Skip to content
quarry Artifact [404718805d]
Login

Artifact [404718805d]

Artifact 404718805d3cbb8f678c7caed1352a3306584da3cefa6f6fc7da52880f8d374c:


package api

import (
	"context"
	"encoding/json"
	"errors"
	"net/http"
	"net/http/httptest"
	"path/filepath"
	"strings"
	"testing"

	"craftdesign.group/quarry/internal/oltp"
	"craftdesign.group/quarry/internal/ops"
)

func idemRegistry(t *testing.T) (*ops.Registry, *oltp.DB) {
	t.Helper()
	db, err := oltp.Open(filepath.Join(t.TempDir(), "o.db"))
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { db.Close() })
	r := ops.NewRegistry()
	if err := RegisterAll(r, Deps{Version: "v", Origin: "https://forge.example",
		Repos: testRepos(wikiIndex(t)), Local: db, Idem: db}); err != nil {
		t.Fatal(err)
	}
	return r, db
}

// A filing retried with the same key returns the ticket the first one
// filed, with its link — an agent whose connection dropped does not
// file twice. The same key for a different ticket is refused; no key,
// or another caller's key, files as before.
func TestAFilingRetriedWithItsKeyFilesOnce(t *testing.T) {
	r, db := idemRegistry(t)
	dan := ops.WithUser(context.Background(), "dan")
	file := func(ctx context.Context, title, key string) (ticketIntakeOut, error) {
		out, err := r.Invoke(ctx, "ticket.intake", ticketIntakeIn{Repo: "test", Title: title, IdempotencyKey: key})
		if err != nil {
			return ticketIntakeOut{}, err
		}
		return out.(ticketIntakeOut), nil
	}
	first, err := file(dan, "broken", "k1")
	if err != nil {
		t.Fatal(err)
	}
	again, err := file(dan, "broken", "k1")
	if err != nil || again.ID != first.ID {
		t.Fatalf("FAIL: a retry filed %q, first was %q (%v)", again.ID, first.ID, err)
	}
	if first.URL != "https://forge.example/r/test/ticket/"+first.ID {
		t.Fatalf("FAIL: the answer's url is %q", first.URL)
	}
	if _, err := file(dan, "something else", "k1"); !errors.Is(err, ops.ErrInvalid) {
		t.Fatalf("FAIL: the same key for a different ticket answered %v", err)
	}
	eve := ops.WithUser(context.Background(), "eve")
	if other, err := file(eve, "broken", "k1"); err != nil || other.ID == first.ID {
		t.Fatalf("FAIL: another caller's key reached dan's ticket (%q, %v)", other.ID, err)
	}
	if _, err := file(dan, "broken", ""); err != nil {
		t.Fatal(err)
	}
	all, _ := db.LocalTickets("test")
	if len(all) != 3 {
		t.Fatalf("FAIL: %d tickets on file, want 3 (dan once for k1, eve, dan without a key)", len(all))
	}
	t.Log("ok: one ticket for a retried key, with its url; a different request refused; another caller and no key file afresh")
}

// The key rides the conventional header, through the router.
func TestTheIdempotencyKeyHeaderReachesTheOp(t *testing.T) {
	r, db := idemRegistry(t)
	h, err := Router(r, "v")
	if err != nil {
		t.Fatal(err)
	}
	var ids []string
	for i := 0; i < 2; i++ {
		req := httptest.NewRequest("POST", "/api/v1/r/test/ticket/intake", strings.NewReader(`{"title":"from an agent"}`))
		req.Header.Set("Content-Type", "application/json")
		req.Header.Set("Idempotency-Key", "agent-run-7")
		rec := httptest.NewRecorder()
		h.ServeHTTP(rec, req)
		if rec.Code != http.StatusOK {
			t.Fatalf("FAIL: %d %s", rec.Code, rec.Body)
		}
		var out ticketIntakeOut
		json.Unmarshal(rec.Body.Bytes(), &out)
		ids = append(ids, out.ID)
	}
	all, _ := db.LocalTickets("test")
	if ids[0] == "" || ids[0] != ids[1] || len(all) != 1 {
		t.Fatalf("FAIL: two posts with one header filed %v (%d on file)", ids, len(all))
	}
	t.Log("ok: two posts with one Idempotency-Key header, one ticket")
}

// ticket.config describes filing field by field, so an agent fills the
// form without guessing: what is required, the words, the defaults.
func TestTicketConfigDescribesFiling(t *testing.T) {
	r, _ := idemRegistry(t)
	out, err := r.Invoke(context.Background(), "ticket.config", ticketConfigIn{Repo: "test"})
	if err != nil {
		t.Fatal(err)
	}
	by := map[string]ticketInput{}
	for _, in := range out.(ticketConfigOut).Inputs {
		by[in.Name] = in
	}
	if !by["title"].Required || by["title"].MaxLength != 200 {
		t.Errorf("FAIL: title %+v", by["title"])
	}
	if by["type"].Kind != "choice" || len(by["type"].Words) == 0 || by["status"].Default != "Open" {
		t.Errorf("FAIL: type %+v, status %+v", by["type"], by["status"])
	}
	if by["comment"].Kind != "markdown" || by["idempotencyKey"].Name == "" || by["fossil"].Kind != "boolean" {
		t.Errorf("FAIL: comment %+v, key %+v, fossil %+v", by["comment"], by["idempotencyKey"], by["fossil"])
	}
	for name := range by {
		if strings.HasPrefix(name, "fields.+") {
			t.Errorf("FAIL: an append-syntax column is offered: %s", name)
		}
	}
	t.Logf("ok: %d inputs; title required, type a choice, status defaults to Open, comment markdown, retry key described", len(by))
}