Skip to content
quarry Artifact [a6a479ffb4]
Login

Artifact [a6a479ffb4]

Artifact a6a479ffb4ecb7cbb5349a9bbb58c2458bd1f02b079bf66e02b37befe864f3dc:


package api

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

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

func localDeps(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", Repos: testRepos(wikiIndex(t)), Local: db}); err != nil {
		t.Fatal(err)
	}
	return r, db
}

func TestLocalTicketIntakeListGetSet(t *testing.T) {
	r, _ := localDeps(t)
	ctx := ops.WithUser(context.Background(), "visitor")
	got, err := r.Invoke(ctx, "ticket.intake", ticketIntakeIn{
		Repo: "test", Title: "crash on empty input", Type: "Code_Defect",
		Comment: "steps: run with no args"})
	if err != nil {
		t.Fatal(err)
	}
	id := got.(ticketIntakeOut).ID
	if len(id) != 40 {
		t.Fatalf("id = %q", id)
	}
	// The listing carries the row marked local, above the archive.
	lo, err := r.Invoke(ctx, "ticket.list", ticketListIn{Repo: "test"})
	if err != nil {
		t.Fatal(err)
	}
	tl := lo.(ticketListOut)
	if tl.Count != 1 || !tl.Tickets[0].Local || tl.Tickets[0].Title != "crash on empty input" {
		t.Fatalf("list = %+v", tl)
	}
	if lo, _ = r.Invoke(ctx, "ticket.list", ticketListIn{Repo: "test", Origin: "fossil"}); lo.(ticketListOut).Count != 0 {
		t.Fatal("origin=fossil still listed the local row")
	}
	if lo, _ = r.Invoke(ctx, "ticket.list", ticketListIn{Repo: "test", Origin: "local"}); lo.(ticketListOut).Count != 1 {
		t.Fatal("origin=local lost the local row")
	}
	// Cursor pages stay purely fossil.
	if lo, _ = r.Invoke(ctx, "ticket.list", ticketListIn{Repo: "test", Offset: 10}); lo.(ticketListOut).Count != 0 {
		t.Fatal("a deep page carried the intake queue")
	}
	// ticket.get serves the same shape either tier.
	go1, err := r.Invoke(ctx, "ticket.get", ticketGetIn{Repo: "test", ID: id[:12]})
	if err != nil {
		t.Fatal(err)
	}
	tg := go1.(ticketGetOut)
	if !tg.Local || tg.Ticket.Description != "steps: run with no args" || tg.DescriptionHTML == "" {
		t.Fatalf("get = %+v", tg)
	}
	if fieldValue(tg.Ticket.Fields, "status") != "Open" || fieldValue(tg.Ticket.Fields, "comment") != "" {
		t.Fatalf("fields = %+v", tg.Ticket.Fields)
	}
	// ticket.set falls through to the local tier with fossil semantics.
	if _, err := r.Invoke(ops.WithUser(ctx, "dan"), "ticket.set", ticketSetIn{
		Repo: "test", ID: id[:12], Status: "Verified", Comment: "reproduced"}); err != nil {
		t.Fatal(err)
	}
	go1, _ = r.Invoke(ctx, "ticket.get", ticketGetIn{Repo: "test", ID: id[:12]})
	tg = go1.(ticketGetOut)
	if fieldValue(tg.Ticket.Fields, "status") != "Verified" || len(tg.Comments) != 1 {
		t.Fatalf("after set = %+v", tg)
	}
	if !strings.Contains(string(tg.Comments[0].HTML), "reproduced") {
		t.Fatalf("comment html = %q", tg.Comments[0].HTML)
	}
}

func TestLocalTicketDiscardOp(t *testing.T) {
	r, _ := localDeps(t)
	ctx := ops.WithUser(context.Background(), "dan")
	got, err := r.Invoke(ctx, "ticket.intake", ticketIntakeIn{Repo: "test", Title: "buy things"})
	if err != nil {
		t.Fatal(err)
	}
	id := got.(ticketIntakeOut).ID
	if _, err := r.Invoke(ctx, "ticket.discard", ticketDiscardIn{Repo: "test", ID: id[:12]}); err != nil {
		t.Fatal(err)
	}
	if _, err := r.Invoke(ctx, "ticket.get", ticketGetIn{Repo: "test", ID: id[:12]}); !errors.Is(err, ops.ErrNotFound) {
		t.Fatalf("discarded ticket: %v", err)
	}
	if _, err := r.Invoke(ctx, "ticket.discard", ticketDiscardIn{Repo: "test", ID: "deadbeef"}); !errors.Is(err, ops.ErrNotFound) {
		t.Fatalf("discard of nothing: %v", err)
	}
}

func TestLocalTicketPromoteRefusals(t *testing.T) {
	r, db := localDeps(t)
	ctx := ops.WithUser(context.Background(), "dan")
	if _, err := r.Invoke(ctx, "ticket.promote", ticketPromoteIn{Repo: "test", ID: "deadbeef"}); !errors.Is(err, ops.ErrNotFound) {
		t.Fatalf("promote of nothing: %v", err)
	}
	got, err := r.Invoke(ctx, "ticket.intake", ticketIntakeIn{Repo: "test", Title: "worthy"})
	if err != nil {
		t.Fatal(err)
	}
	id := got.(ticketIntakeOut).ID
	// The test repo has no zit client; promotion says so instead of
	// panicking. (The happy path writes real artifacts and lives in the
	// harness.)
	if _, err := r.Invoke(ctx, "ticket.promote", ticketPromoteIn{Repo: "test", ID: id[:12]}); !errors.Is(err, ops.ErrUnavailable) {
		t.Fatalf("promote without zit: %v", err)
	}
	// A promoted id answers ticket.get with the redirect and refuses a
	// second promotion.
	if err := db.LocalTicketPromoted("test", id, "f0551171", ""); err != nil {
		t.Fatal(err)
	}
	go1, err := r.Invoke(ctx, "ticket.get", ticketGetIn{Repo: "test", ID: id[:12]})
	if err != nil {
		t.Fatal(err)
	}
	if tg := go1.(ticketGetOut); tg.PromotedTo != "f0551171" || tg.Local {
		t.Fatalf("redirect = %+v", tg)
	}
	if _, err := r.Invoke(ctx, "ticket.promote", ticketPromoteIn{Repo: "test", ID: id[:12]}); !errors.Is(err, ops.ErrInvalid) {
		t.Fatalf("second promote: %v", err)
	}
}

func TestLocalTicketIntakeNeedsAStore(t *testing.T) {
	r := ops.NewRegistry()
	if err := RegisterAll(r, Deps{Version: "v", Repos: testRepos(wikiIndex(t))}); err != nil {
		t.Fatal(err)
	}
	_, err := r.Invoke(context.Background(), "ticket.intake", ticketIntakeIn{Repo: "test", Title: "x"})
	if !errors.Is(err, ops.ErrUnavailable) {
		t.Fatalf("intake without oltp: %v", err)
	}
}