Skip to content
quarry Artifact [558740b1c2]
Login

Artifact [558740b1c2]

Artifact 558740b1c204c7a81c1353acd24a62868e98bdc670df0ce1f834f446c2b1c4e4:


package api

import (
	"context"
	"testing"

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

// fakeAccounts is the minimal AccountStore + AccountEmail + AccountAuth
// this package's tests need: an in-memory roster, grants, the
// pending/confirm code bookkeeping email.go's ops drive, and a fake
// password verifier for me.passwd.
type fakeAccounts struct {
	accounts map[string]Account
	grants   map[string]map[string]string // account -> repo -> role
	pending  map[string]string            // account -> code
	addr     map[string]string            // account -> pending address
	pw       map[string]string            // account -> current password
	ended    map[string]bool              // account -> EndSessions was called
	kept     map[string]string            // account -> last "keep" SetPasswordSelf saw
	grantErr error                        // when set, Grant fails with this instead of recording
}

func newFakeAccounts() *fakeAccounts {
	return &fakeAccounts{
		accounts: map[string]Account{},
		grants:   map[string]map[string]string{},
		pending:  map[string]string{},
		addr:     map[string]string{},
		pw:       map[string]string{},
		ended:    map[string]bool{},
		kept:     map[string]string{},
	}
}

func (f *fakeAccounts) Accounts() ([]Account, error) {
	var out []Account
	for _, a := range f.accounts {
		out = append(out, a)
	}
	return out, nil
}
func (f *fakeAccounts) AddAccount(name, password string, admin bool) error {
	f.accounts[name] = Account{Name: name, Admin: admin}
	f.pw[name] = password
	return nil
}
func (f *fakeAccounts) SetPassword(name, password string) error { f.pw[name] = password; return nil }
func (f *fakeAccounts) SetOperator(string, string) error        { return nil }
func (f *fakeAccounts) DeleteAccount(name string) error         { delete(f.accounts, name); return nil }
func (f *fakeAccounts) Grant(_, account, repo, role string) (string, error) {
	if f.grantErr != nil {
		return "", f.grantErr
	}
	if f.grants[account] == nil {
		f.grants[account] = map[string]string{}
	}
	f.grants[account][repo] = role
	return "", nil
}
func (f *fakeAccounts) Revoke(_, account, repo string) (string, error) {
	delete(f.grants[account], repo)
	return "", nil
}
func (f *fakeAccounts) GrantsFor(account string) (map[string]string, error) {
	return f.grants[account], nil
}

// The confirmed-address half (AccountEmail).
func (f *fakeAccounts) AccountByName(name string) (Account, bool) {
	a, ok := f.accounts[name]
	return a, ok
}
func (f *fakeAccounts) SetEmail(name, email string) error {
	a := f.accounts[name]
	a.Name = name
	a.Email = email
	f.accounts[name] = a
	return nil
}
func (f *fakeAccounts) SetPendingEmail(name, address string) (string, error) {
	code := "123456"
	f.pending[name] = code
	f.addr[name] = address
	a := f.accounts[name]
	a.Name = name
	a.EmailPending = address
	f.accounts[name] = a
	return code, nil
}
func (f *fakeAccounts) ConfirmEmail(account, code string) (string, bool, error) {
	want, ok := f.pending[account]
	if !ok || want != code {
		return "", false, nil
	}
	address := f.addr[account]
	a := f.accounts[account]
	a.Email, a.EmailPending = address, ""
	f.accounts[account] = a
	delete(f.pending, account)
	delete(f.addr, account)
	return address, true, nil
}
func (f *fakeAccounts) ClearEmail(name string) error {
	a := f.accounts[name]
	a.Email, a.EmailPending = "", ""
	f.accounts[name] = a
	return nil
}

// The self-service credential half (AccountAuth).
func (f *fakeAccounts) VerifyPassword(name, password string) bool {
	return f.pw[name] != "" && f.pw[name] == password
}
func (f *fakeAccounts) SetPasswordSelf(name, password, keep string) error {
	if _, ok := f.accounts[name]; !ok {
		return ops.ErrNotFound
	}
	f.pw[name] = password
	f.ended[name] = true
	f.kept[name] = keep
	return nil
}

// sentMail is one message fakeMail recorded.
type sentMail struct{ To, Subject string }

// fakeMail is a MailPlane that records what it was asked to send —
// the recipient and that a send happened, never the copy (RFC 0011
// tests already follow this rule; account mail is no exception).
type fakeMail struct{ sent []sentMail }

func (f *fakeMail) Send(ctx context.Context, to, subject, text, html string) (string, error) {
	f.sent = append(f.sent, sentMail{To: to, Subject: subject})
	return "id", nil
}

func (f *fakeMail) to(address string) bool {
	for _, m := range f.sent {
		if m.To == address {
			return true
		}
	}
	return false
}

// me.email.set sends the confirmation to the NEW address only — the
// address quarry currently mails (the old, confirmed one, if any)
// hears nothing until the new one is actually confirmed.
func TestEmailSetMailsTheNewAddressNotTheOld(t *testing.T) {
	accts := newFakeAccounts()
	accts.accounts["dan"] = Account{Name: "dan", Email: "[email protected]"}
	mail := &fakeMail{}
	r := ops.NewRegistry()
	if err := RegisterAll(r, Deps{Version: "v", Accounts: accts, Mail: mail}); err != nil {
		t.Fatal(err)
	}
	ctx := ops.WithUser(context.Background(), "dan")
	out, err := r.Invoke(ctx, "me.email.set", meEmailSetIn{Email: "[email protected]"})
	if err != nil {
		t.Fatal(err)
	}
	set := out.(meEmailSetOut)
	if !set.Sent || set.Pending != "[email protected]" {
		t.Fatalf("me.email.set = %+v", set)
	}
	if !mail.to("[email protected]") {
		t.Fatalf("the new address was not mailed: %+v", mail.sent)
	}
	if mail.to("[email protected]") {
		t.Fatalf("the old, still-confirmed address was mailed: %+v", mail.sent)
	}
	a, _ := accts.AccountByName("dan")
	if a.EmailPending != "[email protected]" || a.Email != "[email protected]" {
		t.Fatalf("account state after set = %+v", a)
	}
}

// me.email.confirm promotes the pending address and tells the address
// being LEFT, transactionally, that it is no longer the one in use.
func TestEmailConfirmPromotesAndTellsTheOldAddress(t *testing.T) {
	accts := newFakeAccounts()
	accts.accounts["dan"] = Account{Name: "dan", Email: "[email protected]"}
	mail := &fakeMail{}
	r := ops.NewRegistry()
	if err := RegisterAll(r, Deps{Version: "v", Accounts: accts, Mail: mail}); err != nil {
		t.Fatal(err)
	}
	ctx := ops.WithUser(context.Background(), "dan")
	if _, err := r.Invoke(ctx, "me.email.set", meEmailSetIn{Email: "[email protected]"}); err != nil {
		t.Fatal(err)
	}
	// The code itself never leaves the store in this harness (it went
	// to the fake mailer, not back to the caller) — read it the way
	// ConfirmEmail itself would resolve it, off the fake's own map.
	code, ok := accts.pending["dan"]
	if !ok {
		t.Fatal("no pending code recorded")
	}
	confirmOut, err := r.Invoke(ctx, "me.email.confirm", meEmailConfirmIn{Code: code})
	if err != nil {
		t.Fatal(err)
	}
	if got := confirmOut.(meEmailConfirmOut).Email; got != "[email protected]" {
		t.Fatalf("confirm reported %q", got)
	}
	a, _ := accts.AccountByName("dan")
	if a.Email != "[email protected]" || a.EmailPending != "" {
		t.Fatalf("account state after confirm = %+v", a)
	}
	if !mail.to("[email protected]") {
		t.Fatalf("the superseded address was not told: %+v", mail.sent)
	}
	// Confirming again must refuse: the code is spent.
	if _, err := r.Invoke(ctx, "me.email.confirm", meEmailConfirmIn{Code: code}); err == nil {
		t.Fatal("a spent confirmation code was accepted twice")
	}
}

// A code confirmed from a different signed-in account must refuse — it
// can never even be looked at, because ConfirmEmail is always asked
// about the CALLER's own pending code.
func TestEmailConfirmRefusesAnotherAccountsCode(t *testing.T) {
	accts := newFakeAccounts()
	accts.accounts["dan"] = Account{Name: "dan"}
	accts.accounts["eve"] = Account{Name: "eve"}
	r := ops.NewRegistry()
	if err := RegisterAll(r, Deps{Version: "v", Accounts: accts}); err != nil {
		t.Fatal(err)
	}
	danCtx := ops.WithUser(context.Background(), "dan")
	if _, err := r.Invoke(danCtx, "me.email.set", meEmailSetIn{Email: "[email protected]"}); err != nil {
		t.Fatal(err)
	}
	code := accts.pending["dan"]
	eveCtx := ops.WithUser(context.Background(), "eve")
	if _, err := r.Invoke(eveCtx, "me.email.confirm", meEmailConfirmIn{Code: code}); err == nil {
		t.Fatal("eve confirmed dan's code")
	}
	// dan's own code must still be live.
	if _, err := r.Invoke(danCtx, "me.email.confirm", meEmailConfirmIn{Code: code}); err != nil {
		t.Fatalf("dan's own code was burned by eve's attempt: %v", err)
	}
}

// me.email.clear blanks both halves.
func TestEmailClear(t *testing.T) {
	accts := newFakeAccounts()
	accts.accounts["dan"] = Account{Name: "dan", Email: "[email protected]", EmailPending: "[email protected]"}
	r := ops.NewRegistry()
	if err := RegisterAll(r, Deps{Version: "v", Accounts: accts}); err != nil {
		t.Fatal(err)
	}
	ctx := ops.WithUser(context.Background(), "dan")
	if _, err := r.Invoke(ctx, "me.email.clear", meEmailClearIn{}); err != nil {
		t.Fatal(err)
	}
	a, _ := accts.AccountByName("dan")
	if a.Email != "" || a.EmailPending != "" {
		t.Fatalf("account state after clear = %+v", a)
	}
}

// account.email.set is the admin path: stored confirmed at once, and
// the account is told at that address.
func TestAccountEmailSetIsAdminAndTellsTheAccount(t *testing.T) {
	accts := newFakeAccounts()
	accts.accounts["dan"] = Account{Name: "dan"}
	mail := &fakeMail{}
	r := ops.NewRegistry()
	if err := RegisterAll(r, Deps{Version: "v", Accounts: accts, Mail: mail}); err != nil {
		t.Fatal(err)
	}
	ctx := ops.WithUser(context.Background(), "admin")
	if _, err := r.Invoke(ctx, "account.email.set", accountEmailSetIn{Name: "dan", Email: "[email protected]"}); err != nil {
		t.Fatal(err)
	}
	a, _ := accts.AccountByName("dan")
	if a.Email != "[email protected]" {
		t.Fatalf("account state after admin set = %+v", a)
	}
	if !mail.to("[email protected]") {
		t.Fatalf("the account was not told: %+v", mail.sent)
	}
}

// Without a mail plane, every one of these still answers correctly —
// no error, the write still lands — and simply sends nothing.
func TestNoMailPlaneMeansNoMailAndNoError(t *testing.T) {
	accts := newFakeAccounts()
	accts.accounts["dan"] = Account{Name: "dan"}
	r := ops.NewRegistry()
	if err := RegisterAll(r, Deps{Version: "v", Accounts: accts}); err != nil { // no Mail
		t.Fatal(err)
	}
	ctx := ops.WithUser(context.Background(), "dan")
	out, err := r.Invoke(ctx, "me.email.set", meEmailSetIn{Email: "[email protected]"})
	if err != nil {
		t.Fatal(err)
	}
	if out.(meEmailSetOut).Sent {
		t.Fatal("Sent = true with no mail plane configured")
	}
}

// me.passwd refuses a wrong current password without touching anything.
func TestMePasswdRefusesWrongCurrentPassword(t *testing.T) {
	accts := newFakeAccounts()
	accts.accounts["dan"] = Account{Name: "dan"}
	accts.pw["dan"] = "correct-horse"
	r := ops.NewRegistry()
	if err := RegisterAll(r, Deps{Version: "v", Accounts: accts}); err != nil {
		t.Fatal(err)
	}
	ctx := ops.WithUser(context.Background(), "dan")
	if _, err := r.Invoke(ctx, "me.passwd", mePasswdIn{Current: "wrong", New: "new-password"}); err == nil {
		t.Fatal("a wrong current password was accepted")
	}
	if accts.pw["dan"] != "correct-horse" {
		t.Fatalf("the password changed despite a wrong current one: %q", accts.pw["dan"])
	}
	if accts.ended["dan"] {
		t.Fatal("sessions were ended despite a refused change")
	}
}

// me.passwd, on success, ends every other session (via SetPasswordSelf,
// keeping whatever session id it was given) and mails the account at
// its confirmed address.
func TestMePasswdEndsOtherSessionsAndMailsTheAccount(t *testing.T) {
	accts := newFakeAccounts()
	accts.accounts["dan"] = Account{Name: "dan", Email: "[email protected]"}
	accts.pw["dan"] = "correct-horse"
	mail := &fakeMail{}
	r := ops.NewRegistry()
	if err := RegisterAll(r, Deps{Version: "v", Accounts: accts, Mail: mail}); err != nil {
		t.Fatal(err)
	}
	ctx := ops.WithUser(context.Background(), "dan")
	if _, err := r.Invoke(ctx, "me.passwd", mePasswdIn{Current: "correct-horse", New: "new-password", Keep: "abc123"}); err != nil {
		t.Fatal(err)
	}
	if accts.pw["dan"] != "new-password" {
		t.Fatalf("the password did not change: %q", accts.pw["dan"])
	}
	if !accts.ended["dan"] {
		t.Fatal("other sessions were not ended")
	}
	if accts.kept["dan"] != "abc123" {
		t.Fatalf("the session to spare was not passed through: %q", accts.kept["dan"])
	}
	if !mail.to("[email protected]") {
		t.Fatalf("the account was not told at its confirmed address: %+v", mail.sent)
	}
}

// me.account answers the signed-in account's own identity.
func TestMeAccount(t *testing.T) {
	accts := newFakeAccounts()
	accts.accounts["dan"] = Account{Name: "dan", Admin: true, Created: "2026-01-01T00:00:00Z"}
	r := ops.NewRegistry()
	if err := RegisterAll(r, Deps{Version: "v", Accounts: accts}); err != nil {
		t.Fatal(err)
	}
	ctx := ops.WithUser(context.Background(), "dan")
	out, err := r.Invoke(ctx, "me.account", meAccountIn{})
	if err != nil {
		t.Fatal(err)
	}
	got := out.(meAccountOut)
	if got.Name != "dan" || !got.Admin || got.Created != "2026-01-01T00:00:00Z" || !got.PasswordAuth {
		t.Fatalf("me.account = %+v", got)
	}
}