Skip to content
quarry Artifact [83d16d2b7a]
Login

Artifact [83d16d2b7a]

Artifact 83d16d2b7a634e5721f83f7f3792626a845005585f3cecdb4f64ee2ef7f1e0e5:


package web

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

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

// A person can get a push credential from a page.
//
// Until this section existed the only door was POST /api/v1/me/sync-token,
// which is not a door a person opens from a browser. That was a gap when
// legacy sync_user rows still worked; it became the whole story when they
// were dropped, because the minted token is now the ONLY credential the
// forge accepts for a push. An account without one cannot push, and no
// page said so or offered to fix it.
//
// The assertions are on data hooks and on the token VALUE, never on the
// prose around them.
func TestAPageMintsAPushCredentialAndShowsItOnce(t *testing.T) {
	f, tok := newPushForge(t)

	// Before: the page says there is none, and offers to make one.
	_, body := f.get(t, "/me")
	if !strings.Contains(body, `data-hook="account-push"`) {
		t.Fatalf("FAIL: the account page has no push-credential section")
	}
	if !strings.Contains(body, `data-hook="push-none"`) {
		t.Errorf("FAIL: an account with no credential is not told so")
	}
	if strings.Contains(body, `data-hook="push-token"`) {
		t.Errorf("FAIL: a credential was shown before one was made")
	}

	// Minting renders the value in THAT response.
	rec := f.post(t, "/me/push-token", url.Values{})
	made := rec.Body.String()
	if rec.Code != 200 {
		t.Fatalf("FAIL: minting answered %d", rec.Code)
	}
	if !strings.Contains(made, `data-hook="push-token"`) || !strings.Contains(made, tok.minted) {
		t.Fatalf("FAIL: the minted credential is not in the response that made it")
	}
	if !strings.Contains(made, `data-hook="push-token-once"`) {
		t.Errorf("FAIL: nothing says the value is shown once")
	}

	// And never again. This is the property the whole section turns on:
	// the token is readable at the moment it is made and at no other,
	// so a page that could show it twice would be storing it somewhere.
	_, after := f.get(t, "/me")
	if strings.Contains(after, tok.minted) {
		t.Errorf("FAIL: the credential is readable again on a later page load")
	}
	if !strings.Contains(after, `data-hook="push-held"`) {
		t.Errorf("FAIL: the page does not say a credential exists")
	}

	// Revoking puts it back to having none, so the person is told again
	// that pushing will be refused.
	rev := f.post(t, "/me/push-token/revoke", url.Values{})
	if rev.Code != 303 {
		t.Errorf("FAIL: revoke answered %d, want a redirect", rev.Code)
	}
	if tok.held {
		t.Errorf("FAIL: revoke did not reach the store")
	}
	_, gone := f.get(t, "/me")
	if !strings.Contains(gone, `data-hook="push-none"`) {
		t.Errorf("FAIL: after revoking, the page still claims a credential")
	}
}

// A forge with no token store draws no section at all, rather than one
// whose button cannot work — the rule the password control already
// follows on this page.
func TestNoTokenStoreDrawsNoPushSection(t *testing.T) {
	f := newPrefsForge(t, nil)
	_, body := f.get(t, "/me")
	if strings.Contains(body, `data-hook="account-push"`) {
		t.Errorf("FAIL: a forge that cannot mint credentials offers to mint one")
	}
}

// fakeSyncTokens is the SyncTokens surface, holding one account's token.
type fakeSyncTokens struct {
	minted  string
	held    bool
	created string
}

func (s *fakeSyncTokens) MintSyncToken(string) (string, error) {
	s.held, s.created = true, "2026-09-21T10:00:00Z"
	return s.minted, nil
}
func (s *fakeSyncTokens) RevokeSyncToken(string) error { s.held = false; return nil }
func (s *fakeSyncTokens) SyncTokenIssued(string) (string, bool) {
	return s.created, s.held
}

func newPushForge(t *testing.T) (prefsForge, *fakeSyncTokens) {
	t.Helper()
	ix, err := index.Open(filepath.Join(t.TempDir(), "index.db"))
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { ix.Close() })
	prefs := newMemPrefs()
	tok := &fakeSyncTokens{minted: "zt_a1b2c3d4e5f60718293a4b5c6d7e8f90"}
	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: tok,
	}); err != nil {
		t.Fatal(err)
	}
	h := HandlerWith(r, nil, nil, []Option{
		WithSessions(fakeSessions{user: "dan"}), WithPrefs(prefs)})
	return prefsForge{h: h, prefs: prefs}, tok
}