Skip to content
quarry Artifact [21ecf749db]
Login

Artifact [21ecf749db]

Artifact 21ecf749dbc408de1bc906a7130a1fe80d42ded1053311989e73da7465ab6ed7:


package web

import (
	"net/url"
	"path/filepath"
	"regexp"
	"strings"
	"testing"

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

// newTokenListForge is the account page over a real token store, with dan
// holding check-in on alpha.
func newTokenListForge(t *testing.T) (prefsForge, *oltp.DB) {
	t.Helper()
	dir := t.TempDir()
	ix, err := index.Open(filepath.Join(dir, "index.db"))
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { ix.Close() })
	db, err := oltp.Open(filepath.Join(dir, "oltp.db"))
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { db.Close() })
	if err := db.AddAccount("dan", "pw-dan-12345", false); err != nil {
		t.Fatal(err)
	}
	prefs := newMemPrefs()
	r := ops.NewRegistry()
	if err := api.RegisterAll(r, api.Deps{Version: "t",
		Repos:      api.NewRepos(map[string]*api.Repo{"alpha": {Index: ix, Code: "c1"}}),
		Prefs:      prefs,
		SyncTokens: db,
	}); err != nil {
		t.Fatal(err)
	}
	h := HandlerWith(r, nil, nil, []Option{WithSessions(fakeSessions{user: "dan"}), WithPrefs(prefs)})
	return prefsForge{h: h, prefs: prefs}, db
}

var tokenRowRe = regexp.MustCompile(`data-hook="push-row" data-token="([0-9A-Z]+)" data-state="([a-z]+)"`)

// tokenRows is each listed token's id and state, in page order.
func tokenRowsOf(body string) [][2]string {
	var out [][2]string
	for _, m := range tokenRowRe.FindAllStringSubmatch(body, -1) {
		out = append(out, [2]string{m[1], m[2]})
	}
	return out
}

// The Push credential section is a list: minting adds a row and leaves
// the others; each row revokes on its own; a revoked row stays, marked.
func TestThePushCredentialSectionIsAList(t *testing.T) {
	f, db := newTokenListForge(t)

	if rec := f.post(t, "/me/push-token", url.Values{}); rec.Code != 200 {
		t.Fatalf("FAIL: first mint answered %d", rec.Code)
	}
	rec := f.post(t, "/me/push-token", url.Values{})
	if rec.Code != 200 {
		t.Fatalf("FAIL: second mint answered %d", rec.Code)
	}
	if !strings.Contains(rec.Body.String(), `data-hook="push-token"`) {
		t.Errorf("FAIL: the second mint does not show its value")
	}
	_, body := f.get(t, "/me")
	rows := tokenRowsOf(body)
	if len(rows) != 2 || rows[0][1] != "live" || rows[1][1] != "live" {
		t.Fatalf("FAIL: after two mints the list is %v, want two live rows", rows)
	}
	t.Logf("ok: two mints, two live rows")
	for _, hook := range []string{`data-hook="push-list"`, `data-hook="push-held"`, `data-hook="push-row-revoke"`} {
		if !strings.Contains(body, hook) {
			t.Errorf("FAIL: the list lacks %s", hook)
		}
	}
	for _, field := range []string{"push-row-name", "push-row-scope", "push-row-created", "push-row-expires", "push-row-used"} {
		if strings.Count(body, `data-hook="`+field+`"`) != 2 {
			t.Errorf("FAIL: not every row carries %s", field)
		}
	}

	// Revoke the first: it stays listed as revoked, the other stays live
	// and still opens the sync door.
	first, second := rows[0][0], rows[1][0]
	if rec := f.post(t, "/me/push-token/revoke", url.Values{"id": {first}}); rec.Code != 303 {
		t.Fatalf("FAIL: revoking one answered %d", rec.Code)
	}
	_, body = f.get(t, "/me")
	states := map[string]string{}
	for _, r := range tokenRowsOf(body) {
		states[r[0]] = r[1]
	}
	if states[first] != "revoked" || states[second] != "live" {
		t.Errorf("FAIL: after revoking one, states are %v", states)
	} else {
		t.Logf("ok: revoking one row leaves the other live")
	}
	if strings.Count(body, `data-hook="push-row-revoke"`) != 1 {
		t.Errorf("FAIL: a revoked row still offers revoke")
	}

	// Legacy rows are listed per repository, with a revoke of their own.
	if err := db.SetSyncUser("alpha", "dan", "issued-by-hand"); err != nil {
		t.Fatal(err)
	}
	_, body = f.get(t, "/me")
	if !strings.Contains(body, `data-hook="push-legacy" data-repo="alpha"`) {
		t.Errorf("FAIL: the hand-issued row is not listed")
	}
	if rec := f.post(t, "/me/push-token/revoke", url.Values{"repo": {"alpha"}}); rec.Code != 303 {
		t.Errorf("FAIL: revoking the legacy row answered %d", rec.Code)
	}
	if repos, _ := db.LegacySyncUsers("dan"); len(repos) != 0 {
		t.Errorf("FAIL: the legacy row survived its revoke")
	}
}