package main import ( "bytes" "context" "encoding/json" "fmt" "testing" "time" ) func newTestGames(t *testing.T) Games { db := newTestDB(t) games, err := NewGames(context.Background(), db) if err != nil { t.Fatal(err) } return games } func TestGames(t *testing.T) { ctx := context.Background() t.Run("empty", func(t *testing.T) { games := newTestGames(t) if v, err := games.GamesForUser(ctx, ""); err != nil { t.Error("err getting games for empty user:", err) } else if len(v) > 0 { t.Error(v) } if v, err := games.GameByName(ctx, ""); err != nil { t.Error("err getting game by empty name for empty user:", err) } else if len(v) > 0 { t.Error(v) } if v, err := games.GameState(ctx, ""); err != nil { t.Error("err getting game state for empty:", err) } else if len(v.Players) > 0 || !v.Completed.IsZero() { t.Error(v) } }) t.Run("mvp", func(t *testing.T) { games := newTestGames(t) id, err := games.CreateGame(ctx, "g1") if err != nil { t.Fatal("err creating game:", err) } else if id2, err := games.CreateGame(ctx, "g1"); err != nil { t.Fatal("err creating game redundantly:", err) } else if id != id2 { t.Fatal("redundant create game didnt return same id:", id2) } if err := games.CreateEventPlayerJoin(ctx, id, "p0"); err != nil { t.Fatal("err creating event player join:", err) } else if err := games.CreateEventPlayerLeave(ctx, id, "p0"); err != nil { t.Fatal("err creating event player leave:", err) } for i := 0; i < 4; i++ { p := fmt.Sprintf("p%d", i+1) if err := games.CreateEventPlayerJoin(ctx, id, p); err != nil { t.Fatal(p, "err creating event player join", err) } if name, err := games.UserName(ctx, p); err != nil { t.Fatal(p, "err getting user name", err) } else if name == "" { t.Fatal("name wrong", name) } if err := games.UpdateUserName(ctx, p, "player! "+p); err != nil { t.Fatal(p, "failed to rename:", err) } else if name, err := games.UserName(ctx, p); err != nil { t.Fatal(p, "err getting user name", err) } else if name != "player! "+p { t.Fatal("updated name wrong", name) } } if events, err := games.GameEvents(ctx, id, time.Time{}); err != nil { t.Fatal("failed to get player join, leave events:", err) } else if len(events) != 6 { t.Error("wrong number of events:", len(events)) } now := time.Now() if err := games.CreateEventAssignmentRotation(ctx, id, "", "", "", 1); err != nil { t.Fatal("err creating rotation:", err) } if events, err := games.GameEvents(ctx, id, time.Time{}); err != nil { t.Fatal("failed to get player join, leave events:", err) } else if len(events) != 7 { t.Error("wrong number of events:", len(events)) } else if events, err = games.GameEvents(ctx, id, now); err != nil { t.Fatal("failed to get assignment rotation event:", err) } else if len(events) != 1 { t.Error("wrong number of events:", len(events)) } else if _, ok := events[0].(EventAssignmentRotation); !ok { t.Errorf("not an assignment rotation event: %T", events[0]) } if v, err := games.GamesForUser(ctx, "p1"); err != nil { t.Error("err getting games for user:", err) } else if len(v) < 1 { t.Error("no games found for user:", v) } else if v[0] != id { t.Error("wrong game found for user:", v) } if v, err := games.GameByName(ctx, "g1"); err != nil { t.Error("err getting game by name for user:", err) } else if v != id { t.Error("wrong game by name for user:", v) } if v, err := games.GameState(ctx, id); err != nil { t.Error("err getting game state:", err) } else if len(v.Players) != 4 || !v.Completed.IsZero() { t.Error("wrong game state:", v) } else { for i := 0; i < 4; i++ { p := fmt.Sprintf("p%d", i+1) if v.Players[p].Points() != 0 { t.Error("nonzero points after zero kills:", v.Players[p].Points()) } if v.Players[p].KillWords.Codename.KillWord.Word == "" { t.Error(p, "no killwords.Codename") } else if v.Players[p].KillWords.Assigned.IsZero() { t.Error(p, "no killwords.assigned") } else if v.Players[p].KillWords.Assignee == "" { t.Error(p, "no killwords.assignee") } else if len(v.Players[p].KillWords.Assignment.Public) == 0 { t.Error(p, "no killwords.assigment.public") } else if len(v.Players[p].KillWords.Assignment.Private) == 0 { t.Error(p, "no killwords.assigment.private") } } } if err := games.CreateEventGameComplete(ctx, id); err != nil { t.Fatal("err creating game complete:", err) } else if state, err := games.GameState(ctx, id); err != nil { t.Fatal("err fetching state after completing:", err) } else if state.Completed.IsZero() { t.Fatal("state.Completed is zero") } if err := games.CreateEventGameReset(ctx, id); err != nil { t.Fatal(err) } else if state, err := games.GameState(ctx, id); err != nil { t.Fatal(err) } else if state.ID == id { t.Fatal("getting state for reset game didnt return state for new game") } else if state.Started { t.Fatal("reset game is started", state.Started) } else if !state.Completed.IsZero() { t.Fatal("reset game is complete", state.Completed) } else if len(state.Players) != 4 { t.Fatal("reset game doesnt have all players", len(state.Players)) } else if p := state.Players["p1"]; !p.Empty() { t.Fatal("reset game missing p1", p) } else if p := state.Players["p2"]; !p.Empty() { t.Fatal("reset game missing p2", p) } else if p := state.Players["p3"]; !p.Empty() { t.Fatal("reset game missing p3", p) } else if p := state.Players["p4"]; !p.Empty() { t.Fatal("reset game missing p4", p) } }) } func TestParseEvent(t *testing.T) { now := time.Now() cases := map[string]Event{ "player join": EventPlayerJoin{ Type: PlayerJoin, ID: "x", }, "player leave": EventPlayerLeave{ Type: PlayerLeave, ID: "x", }, "game complete": EventGameComplete{ Type: GameComplete, }, "assignment rotation": EventAssignmentRotation{ Type: AssignmentRotation, Killer: "x", Victim: "y", KillWord: KillWord{ Word: "word", Points: 1, }, AllKillWords: map[string]KillWords{ "x": KillWords{ Codename: Codename{KillWord: KillWord{ Word: "a", Points: 200, }}, Assignee: "z", Assigned: now, Assignment: Assignment{ Public: []KillWord{{ Word: "word2", Points: 2, }}, Private: []KillWord{{ Word: "word3", Points: 3, }}, }, }, }, }, } for name, d := range cases { c := d t.Run(name, func(t *testing.T) { c := EventWithTime(c, now) b, _ := json.Marshal(c) got, err := parseEvent(b, now) if err != nil { t.Fatal(err) } gotb, _ := json.Marshal(got) if string(b) != string(gotb) { t.Errorf("expected (%T) %+v, but got (%T) %+v", c, c, got, got) } }) } } func TestAllKillWordsFill(t *testing.T) { kw := func(p int, w string) KillWord { return KillWord{Word: w, Points: p} } kws := func(points int, w string) []KillWord { if w == "" { return nil } return []KillWord{kw(points, w)} } ass := func(pub, pri string) Assignment { return Assignment{ Public: kws(50, pub), Private: kws(100, pri), } } cases := map[string]struct { given KillWords expect KillWords }{ "full": { given: KillWords{ Codename: Codename{KillWord: kw(200, "global")}, Assignment: ass("pub", "pri"), }, expect: KillWords{ Codename: Codename{KillWord: kw(200, "global")}, Assignment: ass("pub", "pri"), }, }, "no ass": { given: KillWords{ Codename: Codename{KillWord: kw(200, "global")}, Assignment: Assignment{}, }, expect: KillWords{ Codename: Codename{KillWord: kw(200, "global")}, Assignment: ass("filled-public", "filled-private"), }, }, "no pub": { given: KillWords{ Codename: Codename{KillWord: kw(200, "global")}, Assignment: ass("", "pri"), }, expect: KillWords{ Codename: Codename{KillWord: kw(200, "global")}, Assignment: ass("filled-public", "pri"), }, }, "no pri": { given: KillWords{ Codename: Codename{KillWord: kw(200, "global")}, Assignment: ass("pub", ""), }, expect: KillWords{ Codename: Codename{KillWord: kw(200, "global")}, Assignment: ass("pub", "filled-private"), }, }, "empty": { given: KillWords{}, expect: KillWords{ Codename: Codename{KillWord: kw(200, "filled-global")}, Assignment: ass("filled-public", "filled-private"), }, }, "no global": { given: KillWords{ Assignment: ass("pub", "pri"), }, expect: KillWords{ Codename: Codename{KillWord: kw(200, "filled-global")}, Assignment: ass("pub", "pri"), }, }, } equal := func(a, b KillWords) bool { ba, _ := json.Marshal(a) bb, _ := json.Marshal(b) return bytes.Equal(ba, bb) } for name, d := range cases { c := d t.Run(name, func(t *testing.T) { akw := make(AllKillWords) akw[name] = c.given akw = akw.fillKillWords( []string{"filled-global"}, 1, []string{"filled-public"}, 1, []string{"filled-private"}, ) got := akw[name] if !equal(c.expect, got) { t.Errorf("expected \n\t%+v but got \n\t%+v", c.expect, got) } }) } } func TestAllKillWordsUnused(t *testing.T) { t.Run("empty", func(t *testing.T) { akw := make(AllKillWords) if got := akw.unusedPublic([]string{"x"}); got != "x" { t.Error("empty playerbase didnt think only option was unused") } if got := akw.unusedPrivate([]string{"x"}); got != "x" { t.Error("empty playerbase didnt think only option was unused") } if got := akw.unusedCodename([]string{"x"}); got != "x" { t.Error("empty playerbase didnt think only option was unused") } }) t.Run("dont return used", func(t *testing.T) { t.Run("private", func(t *testing.T) { akw := make(AllKillWords) akw["k"] = KillWords{ Codename: Codename{KillWord: KillWord{Word: "x"}}, Assignment: Assignment{ Private: []KillWord{{}, {Word: "y"}}, Public: []KillWord{{}, {Word: "x"}}, }, } got := akw.unusedPrivate([]string{"x", "y"}) if got != "x" { t.Error("didnt return only unused option") } }) t.Run("global", func(t *testing.T) { akw := make(AllKillWords) akw["k"] = KillWords{ Codename: Codename{KillWord: KillWord{Word: "y"}}, Assignment: Assignment{ Private: []KillWord{{}, {Word: "x"}}, Public: []KillWord{{}, {Word: "x"}}, }, } got := akw.unusedCodename([]string{"x", "y"}) if got != "x" { t.Error("didnt return only unused option") } }) t.Run("public", func(t *testing.T) { akw := make(AllKillWords) akw["k"] = KillWords{ Codename: Codename{KillWord: KillWord{Word: "x"}}, Assignment: Assignment{ Private: []KillWord{{}, {Word: "x"}}, Public: []KillWord{{}, {Word: "y"}}, }, } got := akw.unusedPublic([]string{"x", "y"}) if got != "x" { t.Error("didnt return only unused option") } }) }) } func TestGenerateUserName(t *testing.T) { games := newTestGames(t) name, err := games.UserName(context.Background(), "id") if err != nil { t.Fatal(err) } if name == "" { t.Fatal(name) } name2, err := games.UserName(context.Background(), "id") if err != nil { t.Fatal(err) } if name2 != name { t.Fatal(name2) } if err := games.CreateEventPlayerJoin(context.Background(), "gid", "id"); err != nil { t.Fatal("err creating event player join:", err) } if id, err := games.UserByName(context.Background(), "gid", name); err != nil { t.Fatal("err getting user by name:", err) } else if id != "id" { t.Fatal("getting user by name yielded wrong id:", id) } t.Log(name) }