topfreegames/khan

View on GitHub
api/game_test.go

Summary

Maintainability
B
4 hrs
Test Coverage
// khan
// https://github.com/topfreegames/khan
//
// Licensed under the MIT license:
// http://www.opensource.org/licenses/mit-license
// Copyright © 2016 Top Free Games <backend@tfgco.com>

package api_test

import (
    "encoding/json"
    "fmt"
    "net/http"
    "strings"

    "github.com/Pallinder/go-randomdata"
    . "github.com/onsi/ginkgo"
    . "github.com/onsi/gomega"
    uuid "github.com/satori/go.uuid"
    "github.com/topfreegames/khan/api"
    "github.com/topfreegames/khan/models"
    "github.com/topfreegames/khan/models/fixtures"
)

func getGamePayload(publicID, name string) map[string]interface{} {
    if publicID == "" {
        publicID = randomdata.FullName(randomdata.RandomGender)
    }
    if name == "" {
        name = randomdata.FullName(randomdata.RandomGender)
    }
    return map[string]interface{}{
        "publicID":                      publicID,
        "name":                          name,
        "membershipLevels":              map[string]interface{}{"Member": 1, "Elder": 2, "CoLeader": 3},
        "metadata":                      map[string]interface{}{"x": "a"},
        "minLevelToAcceptApplication":   1,
        "minLevelToCreateInvitation":    2,
        "minLevelToRemoveMember":        3,
        "minLevelOffsetToRemoveMember":  1,
        "minLevelOffsetToPromoteMember": 1,
        "minLevelOffsetToDemoteMember":  1,
        "maxMembers":                    100,
        "maxClansPerPlayer":             1,
        "cooldownAfterDeny":             30,
        "cooldownAfterDelete":           40,
    }
}

var _ = Describe("Game API Handler", func() {
    var testDb, db models.DB
    var a *api.App

    BeforeEach(func() {
        var err error
        testDb, err = GetTestDB()
        Expect(err).NotTo(HaveOccurred())

        a = GetDefaultTestApp()
        db = a.Db(nil)
        fixtures.ConfigureAndStartGoWorkers()
    })

    Describe("Create Game Handler", func() {
        It("Should create game", func() {
            payload := getGamePayload("", "")
            status, body := PostJSON(a, "/games", payload)

            Expect(status).To(Equal(http.StatusOK))
            var result map[string]interface{}
            json.Unmarshal([]byte(body), &result)
            Expect(result["success"]).To(BeTrue())
            Expect(result["publicID"]).To(Equal(payload["publicID"].(string)))

            dbGame, err := models.GetGameByPublicID(db, payload["publicID"].(string))
            Expect(err).NotTo(HaveOccurred())
            Expect(dbGame.PublicID).To(Equal(payload["publicID"]))
            Expect(dbGame.Name).To(Equal(payload["name"]))
            membershipLevels := payload["membershipLevels"].(map[string]interface{})
            for k, v := range membershipLevels {
                Expect(v.(int)).To(Equal(int(dbGame.MembershipLevels[k].(float64))))
            }
            Expect(dbGame.Metadata).To(Equal(payload["metadata"]))
            Expect(dbGame.MinMembershipLevel).To(Equal(1))
            Expect(dbGame.MaxMembershipLevel).To(Equal(3))
            Expect(dbGame.MinLevelToAcceptApplication).To(Equal(payload["minLevelToAcceptApplication"]))
            Expect(dbGame.MinLevelToCreateInvitation).To(Equal(payload["minLevelToCreateInvitation"]))
            Expect(dbGame.MinLevelOffsetToPromoteMember).To(Equal(payload["minLevelOffsetToPromoteMember"]))
            Expect(dbGame.MinLevelOffsetToDemoteMember).To(Equal(payload["minLevelOffsetToDemoteMember"]))
            Expect(dbGame.MaxMembers).To(Equal(payload["maxMembers"]))
            Expect(dbGame.MaxClansPerPlayer).To(Equal(payload["maxClansPerPlayer"]))
            Expect(dbGame.CooldownAfterDeny).To(Equal(payload["cooldownAfterDeny"]))
            Expect(dbGame.CooldownAfterDelete).To(Equal(payload["cooldownAfterDelete"]))
            Expect(dbGame.PlayerUpdateMetadataFieldsHookTriggerWhitelist).To(BeEmpty())
            Expect(dbGame.ClanUpdateMetadataFieldsHookTriggerWhitelist).To(BeEmpty())
            Expect(dbGame.CooldownBeforeInvite).To(Equal(0))
            Expect(dbGame.CooldownBeforeApply).To(Equal(3600))
            Expect(dbGame.MaxPendingInvites).To(Equal(-1))
        })

        It("Should create game with custom optional params", func() {
            payload := getGamePayload("", "")
            payload["maxPendingInvites"] = 27
            payload["cooldownBeforeApply"] = 2874
            payload["cooldownBeforeInvite"] = 2384
            payload["playerHookFieldsWhitelist"] = "a,b"
            payload["clanHookFieldsWhitelist"] = "c,d"
            status, body := PostJSON(a, "/games", payload)

            Expect(status).To(Equal(http.StatusOK))
            var result map[string]interface{}
            json.Unmarshal([]byte(body), &result)
            Expect(result["success"]).To(BeTrue())
            Expect(result["publicID"]).To(Equal(payload["publicID"].(string)))

            dbGame, err := models.GetGameByPublicID(db, payload["publicID"].(string))
            Expect(err).NotTo(HaveOccurred())
            Expect(dbGame.CooldownBeforeInvite).To(Equal(2384))
            Expect(dbGame.CooldownBeforeApply).To(Equal(2874))
            Expect(dbGame.MaxPendingInvites).To(Equal(27))
            Expect(dbGame.PlayerUpdateMetadataFieldsHookTriggerWhitelist).To(Equal(payload["playerHookFieldsWhitelist"]))
            Expect(dbGame.ClanUpdateMetadataFieldsHookTriggerWhitelist).To(Equal(payload["clanHookFieldsWhitelist"]))
        })

        It("Should not create game if missing parameters", func() {
            payload := getGamePayload("", "")
            delete(payload, "maxMembers")
            status, body := PostJSON(a, "/games", payload)

            Expect(status).To(Equal(http.StatusBadRequest))
            var result map[string]interface{}
            json.Unmarshal([]byte(body), &result)
            Expect(result["success"]).To(BeFalse())
            Expect(result["reason"]).To(Equal("maxMembers is required"))
        })

        It("Should not create game if bad payload", func() {
            payload := getGamePayload("", "")
            payload["minLevelToCreateInvitation"] = 0
            status, body := PostJSON(a, "/games", payload)

            Expect(status).To(Equal(400))
            var result map[string]interface{}
            json.Unmarshal([]byte(body), &result)
            Expect(result["success"]).To(BeFalse())
            Expect(result["reason"]).To(Equal("minLevelToCreateInvitation should be greater or equal to minMembershipLevel"))
        })

        It("Should not create game if invalid payload", func() {
            status, body := Post(a, "/games", "invalid")

            Expect(status).To(Equal(http.StatusBadRequest))
            var result map[string]interface{}
            json.Unmarshal([]byte(body), &result)
            Expect(result["success"]).To(BeFalse())
            Expect(result["reason"].(string)).To(ContainSubstring(InvalidJSONError))
        })

        It("Should not create game and not panic if nil MembershipLevels", func() {
            payload := getGamePayload("", "")
            payload["membershipLevels"] = nil
            status, body := PostJSON(a, "/games", payload)

            Expect(status).To(Equal(http.StatusBadRequest))
            var result map[string]interface{}
            json.Unmarshal([]byte(body), &result)
            Expect(result["success"]).To(BeFalse())
            Expect(result["reason"].(string)).To(Equal("membershipLevels is required"))
        })

        It("Should not create game and not panic if empty MembershipLevels", func() {
            payload := getGamePayload("", "")
            payload["membershipLevels"] = make(map[string]interface{}, 0)
            status, body := PostJSON(a, "/games", payload)

            Expect(status).To(Equal(http.StatusBadRequest))
            var result map[string]interface{}
            json.Unmarshal([]byte(body), &result)
            Expect(result["success"]).To(BeFalse())
            Expect(result["reason"].(string)).To(Equal("membershipLevels is required"))
        })

        It("Should not create game if invalid data", func() {
            payload := getGamePayload("game-id-is-too-large-for-this-field-should-be-less-than-36-chars", "")
            status, body := PostJSON(a, "/games", payload)

            Expect(status).To(Equal(http.StatusInternalServerError))
            var result map[string]interface{}
            json.Unmarshal([]byte(body), &result)
            Expect(result["success"]).To(BeFalse())
            Expect(result["reason"]).To(Equal("pq: value too long for type character varying(36)"))
        })
    })

    Describe("Update Game Handler", func() {
        It("Should update game", func() {
            game := fixtures.GameFactory.MustCreate().(*models.Game)
            err := db.Insert(game)
            Expect(err).NotTo(HaveOccurred())

            metadata := map[string]interface{}{"y": "10"}
            payload := getGamePayload(game.PublicID, game.Name)
            payload["metadata"] = metadata
            payload["cooldownAfterDeny"] = 0

            route := fmt.Sprintf("/games/%s", game.PublicID)
            status, body := PutJSON(a, route, payload)
            Expect(status).To(Equal(http.StatusOK))
            var result map[string]interface{}
            json.Unmarshal([]byte(body), &result)
            Expect(result["success"]).To(BeTrue())

            dbGame, err := models.GetGameByPublicID(db, game.PublicID)
            Expect(err).NotTo(HaveOccurred())

            Expect(dbGame.Metadata).To(Equal(metadata))
            membershipLevels := payload["membershipLevels"].(map[string]interface{})
            for k, v := range membershipLevels {
                Expect(v.(int)).To(Equal(int(dbGame.MembershipLevels[k].(float64))))
            }
            Expect(dbGame.PublicID).To(Equal(game.PublicID))
            Expect(dbGame.Name).To(Equal(game.Name))
            Expect(dbGame.MinMembershipLevel).To(Equal(1))
            Expect(dbGame.MaxMembershipLevel).To(Equal(3))
            Expect(dbGame.MinLevelToAcceptApplication).To(Equal(payload["minLevelToAcceptApplication"]))
            Expect(dbGame.MinLevelToCreateInvitation).To(Equal(payload["minLevelToCreateInvitation"]))
            Expect(dbGame.MinLevelOffsetToPromoteMember).To(Equal(payload["minLevelOffsetToPromoteMember"]))
            Expect(dbGame.MinLevelOffsetToDemoteMember).To(Equal(payload["minLevelOffsetToDemoteMember"]))
            Expect(dbGame.MaxMembers).To(Equal(payload["maxMembers"]))
            Expect(dbGame.MaxClansPerPlayer).To(Equal(payload["maxClansPerPlayer"]))
            Expect(dbGame.CooldownAfterDeny).To(Equal(payload["cooldownAfterDeny"]))
            Expect(dbGame.CooldownAfterDelete).To(Equal(payload["cooldownAfterDelete"]))
        })

        It("Should insert if game does not exist", func() {
            gameID := uuid.NewV4().String()
            payload := getGamePayload(gameID, gameID)

            route := fmt.Sprintf("/games/%s", gameID)
            status, body := PutJSON(a, route, payload)

            Expect(status).To(Equal(http.StatusOK))
            var result map[string]interface{}
            json.Unmarshal([]byte(body), &result)
            Expect(result["success"]).To(BeTrue())

            dbGame, err := models.GetGameByPublicID(db, gameID)
            Expect(err).NotTo(HaveOccurred())
            Expect(dbGame.Metadata).To(Equal(payload["metadata"]))
            Expect(dbGame.PublicID).To(Equal(gameID))
            Expect(dbGame.Name).To(Equal(payload["name"]))
        })

        It("Should not update game if missing parameters", func() {
            game := fixtures.GameFactory.MustCreate().(*models.Game)
            err := db.Insert(game)
            Expect(err).NotTo(HaveOccurred())

            metadata := map[string]interface{}{"y": "10"}
            payload := getGamePayload(game.PublicID, game.Name)
            payload["metadata"] = metadata
            delete(payload, "name")
            delete(payload, "minLevelOffsetToPromoteMember")
            delete(payload, "maxMembers")

            route := fmt.Sprintf("/games/%s", game.PublicID)
            status, body := PutJSON(a, route, payload)
            Expect(status).To(Equal(http.StatusBadRequest))
            var result map[string]interface{}
            json.Unmarshal([]byte(body), &result)
            Expect(result["success"]).To(BeFalse())
            Expect(result["reason"]).To(Equal("name is required, minLevelOffsetToPromoteMember is required, maxMembers is required"))
        })

        It("Should not update game if bad payload", func() {
            game := fixtures.GameFactory.MustCreate().(*models.Game)
            err := db.Insert(game)
            Expect(err).NotTo(HaveOccurred())

            payload := getGamePayload(game.PublicID, game.Name)
            payload["minLevelToCreateInvitation"] = 0

            route := fmt.Sprintf("/games/%s", game.PublicID)
            status, body := PutJSON(a, route, payload)

            Expect(status).To(Equal(400))
            var result map[string]interface{}
            json.Unmarshal([]byte(body), &result)
            Expect(result["success"]).To(BeFalse())
            Expect(result["reason"]).To(Equal("minLevelToCreateInvitation should be greater or equal to minMembershipLevel"))
        })

        It("Should not update game and not panic if nil MembershipLevels", func() {
            game := fixtures.GameFactory.MustCreate().(*models.Game)
            err := db.Insert(game)
            Expect(err).NotTo(HaveOccurred())

            payload := getGamePayload(game.PublicID, game.Name)
            payload["membershipLevels"] = nil

            route := fmt.Sprintf("/games/%s", game.PublicID)
            status, body := PutJSON(a, route, payload)

            Expect(status).To(Equal(400))
            var result map[string]interface{}
            json.Unmarshal([]byte(body), &result)
            Expect(result["success"]).To(BeFalse())
            Expect(result["reason"]).To(Equal("membershipLevels is required"))
        })

        It("Should not update game and not panic if empty MembershipLevels", func() {
            game := fixtures.GameFactory.MustCreate().(*models.Game)
            err := db.Insert(game)
            Expect(err).NotTo(HaveOccurred())

            payload := getGamePayload(game.PublicID, game.Name)
            payload["membershipLevels"] = make(map[string]interface{}, 0)

            route := fmt.Sprintf("/games/%s", game.PublicID)
            status, body := PutJSON(a, route, payload)

            Expect(status).To(Equal(400))
            var result map[string]interface{}
            json.Unmarshal([]byte(body), &result)
            Expect(result["success"]).To(BeFalse())
            Expect(result["reason"]).To(Equal("membershipLevels is required"))
        })

        It("Should not update game if invalid payload", func() {
            status, body := Put(a, "/games/game-id", "invalid")

            Expect(status).To(Equal(http.StatusBadRequest))
            var result map[string]interface{}
            json.Unmarshal([]byte(body), &result)
            Expect(result["success"]).To(BeFalse())
            Expect(result["reason"].(string)).To(ContainSubstring(InvalidJSONError))
        })

        It("Should not update game if invalid data", func() {
            game := fixtures.GameFactory.MustCreate().(*models.Game)
            err := db.Insert(game)
            Expect(err).NotTo(HaveOccurred())

            payload := getGamePayload(game.PublicID, strings.Repeat("a", 256))

            route := fmt.Sprintf("/games/%s", game.PublicID)
            status, body := PutJSON(a, route, payload)

            Expect(status).To(Equal(http.StatusInternalServerError))
            var result map[string]interface{}
            json.Unmarshal([]byte(body), &result)
            Expect(result["success"]).To(BeFalse())
            Expect(result["reason"]).To(Equal("pq: value too long for type character varying(255)"))
        })
    })

    Describe("Game Hooks", func() {
        Describe("Update Game Hook", func() {
            It("Should call update game hook", func() {
                hooks, err := fixtures.GetHooksForRoutes(testDb, []string{
                    "http://localhost:52525/update",
                }, models.GameUpdatedHook)
                Expect(err).NotTo(HaveOccurred())
                responses := startRouteHandler([]string{"/update"}, 52525)

                gameID := hooks[0].GameID

                payload := getGamePayload(gameID, uuid.NewV4().String())

                route := fmt.Sprintf("/games/%s", gameID)
                status, body := PutJSON(a, route, payload)
                Expect(status).To(Equal(http.StatusOK))
                var result map[string]interface{}
                json.Unmarshal([]byte(body), &result)
                Expect(result["success"]).To(BeTrue())

                Eventually(func() int {
                    return len(*responses)
                }).Should(Equal(1))
            })
        })
    })
})