package oltp
import "testing"
// RFC 0018's one screen asks "who reaches these repositories", so a
// non-member holding a string on one of them belongs on it. An answer
// that listed only members would be answering a different question.
func TestTeamPeopleIncludesNonMembersWhoReachIt(t *testing.T) {
db := teamDB(t)
if err := db.AddTeam("core", "", "", "dan"); err != nil {
t.Fatal(err)
}
if err := db.SetTeamRepo("core", "quarry", "dan"); err != nil {
t.Fatal(err)
}
if err := db.SetTeamRepo("core", "zit", "dan"); err != nil {
t.Fatal(err)
}
// lou is a member with the floor alone.
if err := db.SetTeamMember("core", "lou", "", false, true); err != nil {
t.Fatal(err)
}
// ann is in no team but holds a string on one of the repositories.
if err := db.SetCaps("ann", "zit", "a"); err != nil {
t.Fatal(err)
}
// And a grant on a repository the team does NOT hold must not pull
// somebody onto this screen.
if err := db.SetCaps("ann", "elsewhere", "a"); err != nil {
t.Fatal(err)
}
people, err := db.TeamPeople("core")
if err != nil {
t.Fatal(err)
}
got := map[string]TeamPerson{}
for _, p := range people {
got[p.Account] = p
}
if len(people) != 3 {
t.Fatalf("FAIL: %d people, want dan, lou and ann: %+v", len(people), people)
}
if !got["dan"].Owner {
t.Error("FAIL: the creator does not read as owner")
}
if !got["lou"].Member || got["lou"].Caps != "" {
t.Errorf("FAIL: lou = %+v, want a member holding the floor alone", got["lou"])
}
ann := got["ann"]
if ann.Member {
t.Error("FAIL: ann reads as a member of a team she is not in")
}
if len(ann.Direct) != 1 || ann.Direct[0].Repo != "zit" {
t.Errorf("FAIL: ann's direct grants = %+v, want zit only — elsewhere is not this team's", ann.Direct)
}
if !ann.Reaches() {
t.Error("FAIL: ann does not read as reaching anything")
}
}
// Owners first, then members, then everybody else, each by name: the
// order somebody scanning for "who runs this" needs.
func TestTeamPeopleAreOrderedByStanding(t *testing.T) {
db := teamDB(t)
if err := db.AddTeam("core", "", "", "lou"); err != nil {
t.Fatal(err)
}
if err := db.SetTeamRepo("core", "quarry", "lou"); err != nil {
t.Fatal(err)
}
if err := db.SetTeamMember("core", "ann", "", false, true); err != nil {
t.Fatal(err)
}
if err := db.SetCaps("dan", "quarry", "a"); err != nil {
t.Fatal(err)
}
people, err := db.TeamPeople("core")
if err != nil {
t.Fatal(err)
}
var order []string
for _, p := range people {
order = append(order, p.Account)
}
// lou owns it, ann is a member, dan only holds a direct grant.
want := []string{"lou", "ann", "dan"}
for i := range want {
if i >= len(order) || order[i] != want[i] {
t.Fatalf("FAIL: order = %v, want %v", order, want)
}
}
}
// A team holding no repositories has members and nobody reaching
// anything, which the screen says rather than listing people against
// nothing.
func TestATeamWithNoReposStillListsItsMembers(t *testing.T) {
db := teamDB(t)
if err := db.AddTeam("core", "", "", "dan"); err != nil {
t.Fatal(err)
}
people, err := db.TeamPeople("core")
if err != nil {
t.Fatal(err)
}
if len(people) != 1 || people[0].Account != "dan" {
t.Fatalf("FAIL: people = %+v", people)
}
if len(people[0].Direct) != 0 {
t.Errorf("FAIL: direct grants on a team holding nothing: %+v", people[0].Direct)
}
}
// Somebody who is both a member and holds a string of their own shows
// both routes, because the page has to be able to say which is which.
func TestBothRoutesAppearOnOnePerson(t *testing.T) {
db := teamDB(t)
if err := db.AddTeam("core", "", "", "dan"); err != nil {
t.Fatal(err)
}
if err := db.SetTeamRepo("core", "quarry", "dan"); err != nil {
t.Fatal(err)
}
if err := db.SetTeamMember("core", "lou", "i", false, true); err != nil {
t.Fatal(err)
}
if err := db.SetCaps("lou", "quarry", "a"); err != nil {
t.Fatal(err)
}
people, err := db.TeamPeople("core")
if err != nil {
t.Fatal(err)
}
for _, p := range people {
if p.Account != "lou" {
continue
}
if !p.Member || p.Caps != "i" {
t.Errorf("FAIL: the team route is missing: %+v", p)
}
if len(p.Direct) != 1 || p.Direct[0].Caps != "a" {
t.Errorf("FAIL: the direct route is missing: %+v", p.Direct)
}
return
}
t.Fatal("FAIL: lou is not on the screen at all")
}
// The paged read must be the unpaged read, cut into pages — same
// people, same order, same detail. Anything else and the screen shows
// one population while the count describes another, which is the shape
// of bug this whole area has already produced twice.
func TestTeamPeoplePageAgreesWithTheWholeList(t *testing.T) {
db := teamDB(t)
for _, who := range []string{"owen", "mia", "mel", "moe", "dot", "eve", "fay"} {
if err := db.AddAccount(who, "pw-"+who+"-long-enough", false); err != nil {
t.Fatal(err)
}
}
if err := db.AddTeam("core", "", "", "dan"); err != nil {
t.Fatal(err)
}
for _, repo := range []string{"quarry", "zit"} {
if err := db.SetTeamRepo("core", repo, "dan"); err != nil {
t.Fatal(err)
}
}
// A spread of every route: owners, plain members, a member who also
// holds a direct string, and non-members who only hold one.
if err := db.SetTeamMember("core", "owen", "", true, true); err != nil {
t.Fatal(err)
}
for _, who := range []string{"mia", "mel", "moe"} {
if err := db.SetTeamMember("core", who, "uv3bBC78", false, true); err != nil {
t.Fatal(err)
}
}
if err := db.SetCaps("mel", "quarry", "a"); err != nil {
t.Fatal(err)
}
for _, who := range []string{"dot", "eve", "fay"} {
if err := db.SetCaps(who, "zit", "uv3bBC78"); err != nil {
t.Fatal(err)
}
}
whole, err := db.TeamPeople("core")
if err != nil {
t.Fatal(err)
}
n, err := db.TeamPeopleCount("core")
if err != nil {
t.Fatal(err)
}
if n != len(whole) {
t.Fatalf("FAIL: count says %d, the list holds %d", n, len(whole))
}
// Walk it two at a time and rebuild the list from the pages.
var paged []TeamPerson
for off := 0; off < n; off += 2 {
page, err := db.TeamPeoplePage("core", 2, off)
if err != nil {
t.Fatal(err)
}
if len(page) == 0 {
t.Fatalf("FAIL: page at offset %d is empty before the end (%d)", off, n)
}
paged = append(paged, page...)
}
if len(paged) != len(whole) {
t.Fatalf("FAIL: the pages hold %d people, the whole list %d", len(paged), len(whole))
}
for i := range whole {
w, p := whole[i], paged[i]
if w.Account != p.Account || w.Member != p.Member ||
w.Owner != p.Owner || w.Caps != p.Caps {
t.Errorf("FAIL: row %d: whole=%+v paged=%+v", i, w, p)
continue
}
if len(w.Direct) != len(p.Direct) {
t.Errorf("FAIL: %s has %d direct grants whole, %d paged",
w.Account, len(w.Direct), len(p.Direct))
continue
}
for j := range w.Direct {
if w.Direct[j] != p.Direct[j] {
t.Errorf("FAIL: %s direct[%d]: whole=%+v paged=%+v",
w.Account, j, w.Direct[j], p.Direct[j])
}
}
}
// Past the end is empty, not an error, and a zero limit asks for
// nothing rather than for everything.
if page, err := db.TeamPeoplePage("core", 10, n); err != nil || len(page) != 0 {
t.Errorf("FAIL: past the end: %d rows, err %v", len(page), err)
}
if page, err := db.TeamPeoplePage("core", 0, 0); err != nil || len(page) != 0 {
t.Errorf("FAIL: a zero limit returned %d rows, err %v", len(page), err)
}
}