package api
import (
"context"
"craftdesign.group/quarry/internal/oltp"
"strings"
"testing"
"craftdesign.group/quarry/internal/ops"
)
// A grant tells its SUBJECT, transactionally, never the person who
// made the change. RFC 0005's account.caps only exists to be raised at
// exactly this moment (register.go's account.grant/.revoke).
func TestAccessChangedMailsTheSubjectNotTheActor(t *testing.T) {
accts := newFakeAccounts()
accts.accounts["dan"] = Account{Name: "dan", Admin: true}
accts.accounts["eve"] = Account{Name: "eve", Email: "[email protected]"}
mail := &fakeMail{}
r := ops.NewRegistry()
if err := RegisterAll(r, Deps{Version: "v", Accounts: accts, Mail: mail,
Repos: NewRepos(map[string]*Repo{"quarry": {}})}); err != nil {
t.Fatal(err)
}
ctx := ops.WithUser(context.Background(), "dan")
if _, err := r.Invoke(ctx, "account.grant", accountGrantIn{Name: "eve", Repo: "quarry", Role: "developer"}); err != nil {
t.Fatal(err)
}
if !mail.to("[email protected]") {
t.Fatalf("the subject was not mailed: %+v", mail.sent)
}
if len(mail.sent) != 1 {
t.Fatalf("expected exactly one message, got %+v", mail.sent)
}
// A revoke tells the same way, and a re-grant of the SAME role
// (nothing actually changed) tells nobody.
if _, err := r.Invoke(ctx, "account.grant", accountGrantIn{Name: "eve", Repo: "quarry", Role: "developer"}); err != nil {
t.Fatal(err)
}
if len(mail.sent) != 1 {
t.Fatalf("a no-op re-grant sent mail: %+v", mail.sent)
}
if _, err := r.Invoke(ctx, "account.revoke", accountRevokeIn{Name: "eve", Repo: "quarry"}); err != nil {
t.Fatal(err)
}
if len(mail.sent) != 2 || mail.sent[1].To != "[email protected]" {
t.Fatalf("revoke did not mail the subject: %+v", mail.sent)
}
}
// Granting yourself something (repo.add's owner grant) must not mail
// you about your own action.
func TestAccessChangedSilentWhenActorIsSubject(t *testing.T) {
accts := newFakeAccounts()
accts.accounts["dan"] = Account{Name: "dan", Email: "[email protected]"}
mail := &fakeMail{}
d := Deps{Accounts: accts, Mail: mail}
d.AccessChanged(context.Background(), "dan", "dan", "quarry", "", "admin")
if len(mail.sent) != 0 {
t.Fatalf("an account was mailed about its own action: %+v", mail.sent)
}
}
// With no mail plane, AccessChanged still runs its other effects (it
// must not panic or error) and simply sends nothing.
func TestAccessChangedWithNoMailPlaneSendsNothing(t *testing.T) {
accts := newFakeAccounts()
accts.accounts["eve"] = Account{Name: "eve", Email: "[email protected]"}
d := Deps{Accounts: accts} // no Mail
d.AccessChanged(context.Background(), "dan", "eve", "quarry", "", "developer")
// Nothing to assert beyond "did not panic" — there is no mail log
// to check, which is exactly the point: a forge with no mail
// provider configured for this Deps sends nothing, silently.
}
// A team tells its members when they are added, removed, or their role
// changes: one email each, as the team (its name in the header, an Open
// button to its page), never for one's own action, and a change to only
// the listing is not announced.
func TestTeamMailsAreTheTeamsToTheMember(t *testing.T) {
accts := newFakeAccounts()
accts.accounts["eve"] = Account{Name: "eve", Email: "[email protected]"}
mail := &fakeMail{}
d := Deps{Origin: "https://forge.example", Accounts: accts, Mail: mail}
ctx := context.Background()
d.TeamMembership(ctx, "dan", "eve", "crew")
d.TeamRoleChanged(ctx, "dan", "eve", "crew", oltp.TeamMember{Owner: false, Caps: "r", Listed: true}, oltp.TeamMember{Owner: true, Caps: "r", Listed: true})
d.TeamRoleChanged(ctx, "dan", "eve", "crew", oltp.TeamMember{Caps: "r", Listed: true}, oltp.TeamMember{Caps: "rw", Listed: true})
d.TeamRoleChanged(ctx, "dan", "eve", "crew", oltp.TeamMember{Caps: "r", Listed: true}, oltp.TeamMember{Caps: "r", Listed: false}) // listing only
d.TeamRemoval(ctx, "dan", "eve", "crew")
d.TeamMembership(ctx, "eve", "eve", "crew") // her own action
if len(mail.sent) != 4 {
t.Fatalf("%d mails, want added, two role changes and removed", len(mail.sent))
}
for _, m := range mail.sent {
if m.To != "[email protected]" {
t.Errorf("sent to %s", m.To)
}
head := m.HTML[strings.Index(m.HTML, "<body"):strings.Index(m.HTML, `<div class="q-text" style="font-size:15px;`)]
if !strings.Contains(head, ">crew<") || strings.Contains(head, ">QUARRY<") || !strings.Contains(m.HTML, `href="https://forge.example/t/crew"`) ||
!strings.Contains(m.HTML[strings.Index(m.HTML, `class="q-rule q-muted"`):], ">QUARRY<") {
t.Errorf("not the team's card: %s", m.Subject)
}
}
}