From 50005b172f621ca95febed5abea7053d710542de Mon Sep 17 00:00:00 2001 From: Fog3211 <23151576+Fog3211@users.noreply.github.com> Date: Fri, 1 Mar 2024 02:15:11 +0800 Subject: [PATCH] test: add bots unit test --- handlers/bots.go | 36 ++++-- handlers/bots_test.go | 259 ++++++++++++++++++++++++++++++++++++++++++ routes/bot.go | 6 +- routes/bots.go | 9 +- 4 files changed, 293 insertions(+), 17 deletions(-) create mode 100644 handlers/bots_test.go diff --git a/handlers/bots.go b/handlers/bots.go index e1700bbfc..b12937ef3 100644 --- a/handlers/bots.go +++ b/handlers/bots.go @@ -15,7 +15,19 @@ import ( "github.com/stakwork/sphinx-tribes/db" ) -func CreateOrEditBot(w http.ResponseWriter, r *http.Request) { +type botHandler struct { + db db.Database + verifyTribeUUID func(uuid string, checkTimestamp bool) (string, error) +} + +func NewBotHandler(db db.Database) *botHandler { + return &botHandler{ + db: db, + verifyTribeUUID: auth.VerifyTribeUUID, + } +} + +func (h *botHandler) CreateOrEditBot(w http.ResponseWriter, r *http.Request) { ctx := r.Context() pubKeyFromAuth, _ := ctx.Value(auth.ContextKey).(string) @@ -36,7 +48,7 @@ func CreateOrEditBot(w http.ResponseWriter, r *http.Request) { now := time.Now() - extractedPubkey, err := auth.VerifyTribeUUID(bot.UUID, false) + extractedPubkey, err := h.verifyTribeUUID(bot.UUID, false) if err != nil { fmt.Println(err) w.WriteHeader(http.StatusUnauthorized) @@ -54,9 +66,9 @@ func CreateOrEditBot(w http.ResponseWriter, r *http.Request) { bot.OwnerPubKey = extractedPubkey bot.Updated = &now - bot.UniqueName, _ = BotUniqueNameFromName(bot.Name) + bot.UniqueName, _ = h.BotUniqueNameFromName(bot.Name) - _, err = db.DB.CreateOrEditBot(bot) + _, err = h.db.CreateOrEditBot(bot) if err != nil { fmt.Println("=> ERR createOrEditBot", err) w.WriteHeader(http.StatusBadRequest) @@ -67,22 +79,22 @@ func CreateOrEditBot(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(bot) } -func GetListedBots(w http.ResponseWriter, r *http.Request) { - bots := db.DB.GetListedBots(r) +func (h *botHandler) GetListedBots(w http.ResponseWriter, r *http.Request) { + bots := h.db.GetListedBots(r) w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(bots) } -func GetBot(w http.ResponseWriter, r *http.Request) { +func (h *botHandler) GetBot(w http.ResponseWriter, r *http.Request) { uuid := chi.URLParam(r, "uuid") - bot := db.DB.GetBot(uuid) + bot := h.db.GetBot(uuid) w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(bot) } -func GetBotByUniqueName(w http.ResponseWriter, r *http.Request) { +func (h *botHandler) GetBotByUniqueName(w http.ResponseWriter, r *http.Request) { name := chi.URLParam(r, "name") - bot := db.DB.GetBotByUniqueName(name) + bot := h.db.GetBotByUniqueName(name) w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(bot) } @@ -141,7 +153,7 @@ func DeleteBot(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(true) } -func BotUniqueNameFromName(name string) (string, error) { +func (h *botHandler) BotUniqueNameFromName(name string) (string, error) { pathOne := strings.ToLower(strings.Join(strings.Fields(name), "")) reg, err := regexp.Compile("[^a-zA-Z0-9]+") if err != nil { @@ -154,7 +166,7 @@ func BotUniqueNameFromName(name string) (string, error) { if n > 0 { uniquepath = path + strconv.Itoa(n) } - existing := db.DB.GetBotByUniqueName(uniquepath) + existing := h.db.GetBotByUniqueName(uniquepath) if existing.UUID != "" { n = n + 1 } else { diff --git a/handlers/bots_test.go b/handlers/bots_test.go new file mode 100644 index 000000000..163cb996c --- /dev/null +++ b/handlers/bots_test.go @@ -0,0 +1,259 @@ +package handlers + +import ( + "bytes" + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/go-chi/chi" + "github.com/stakwork/sphinx-tribes/auth" + "github.com/stakwork/sphinx-tribes/db" + mocks "github.com/stakwork/sphinx-tribes/mocks" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +func TestCreateOrEditBot(t *testing.T) { + mockDb := mocks.NewDatabase(t) + bHandler := NewBotHandler(mockDb) + + t.Run("should test that a 401 error is returned during bot creation if there is no bot uuid", func(t *testing.T) { + mockUUID := "valid_uuid" + + requestBody := map[string]interface{}{ + "UUID": mockUUID, + } + + requestBodyBytes, err := json.Marshal(requestBody) + if err != nil { + t.Fatal(err) + } + + req, err := http.NewRequest("POST", "/", bytes.NewBuffer(requestBodyBytes)) + if err != nil { + t.Fatal(err) + } + + rr := httptest.NewRecorder() + handler := http.HandlerFunc(bHandler.CreateOrEditBot) + + handler.ServeHTTP(rr, req) + + assert.Equal(t, http.StatusUnauthorized, rr.Code) + }) + + t.Run("should test that a 401 error is returned if the user public key can't be verified during bot creation", func(t *testing.T) { + ctx := context.WithValue(context.Background(), auth.ContextKey, "pubkey") + mockPubKey := "valid_pubkey" + mockUUID := "valid_uuid" + + requestBody := map[string]interface{}{ + "UUID": mockUUID, + } + + requestBodyBytes, err := json.Marshal(requestBody) + if err != nil { + t.Fatal(err) + } + + mockVerifyTribeUUID := func(uuid string, checkTimestamp bool) (string, error) { + return mockPubKey, nil + } + + bHandler.verifyTribeUUID = mockVerifyTribeUUID + + rr := httptest.NewRecorder() + handler := http.HandlerFunc(bHandler.CreateOrEditBot) + + req, err := http.NewRequestWithContext(ctx, "POST", "/", bytes.NewBuffer(requestBodyBytes)) + if err != nil { + t.Fatal(err) + } + + chiCtx := chi.NewRouteContext() + chiCtx.URLParams.Add("uuid", mockUUID) + + req = req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, chiCtx)) + handler.ServeHTTP(rr, req) + + assert.Equal(t, http.StatusUnauthorized, rr.Code) + }) + + t.Run("should test that a bot gets created successfully if an authenticated user sends the right data", func(t *testing.T) { + mockPubKey := "valid_pubkey" + mockUUID := "valid_uuid" + mockName := "Test Bot" + mockUniqueName := "unique test bot" + + mockVerifyTribeUUID := func(uuid string, checkTimestamp bool) (string, error) { + return mockPubKey, nil + } + + bHandler.verifyTribeUUID = mockVerifyTribeUUID + + requestBody := map[string]interface{}{ + "UUID": mockUUID, + "Name": mockName, + } + requestBodyBytes, err := json.Marshal(requestBody) + if err != nil { + t.Fatal(err) + } + + mockDb.On("GetBotByUniqueName", mock.Anything).Return(db.Bot{ + UniqueName: mockUniqueName, + }, nil) + + mockDb.On("CreateOrEditBot", mock.Anything).Return(db.Bot{ + UUID: mockUUID, + }, nil) + + req, err := http.NewRequest("POST", "/", bytes.NewBuffer(requestBodyBytes)) + if err != nil { + t.Fatal(err) + } + + ctx := context.WithValue(req.Context(), auth.ContextKey, mockPubKey) + req = req.WithContext(ctx) + + rr := httptest.NewRecorder() + handler := http.HandlerFunc(bHandler.CreateOrEditBot) + handler.ServeHTTP(rr, req) + + assert.Equal(t, http.StatusOK, rr.Code) + var responseData map[string]interface{} + err = json.Unmarshal(rr.Body.Bytes(), &responseData) + if err != nil { + t.Fatalf("Error decoding JSON response: %s", err) + } + assert.Equal(t, mockUUID, responseData["uuid"]) + }) + + t.Run("should test that an existing bot gets updated when passed to POST bots", func(t *testing.T) { + mockPubKey := "valid_pubkey" + mockUUID := "valid_uuid" + mockName := "Updated Test Bot" + mockUniqueName := "unique test bot" + + mockVerifyTribeUUID := func(uuid string, checkTimestamp bool) (string, error) { + return mockPubKey, nil + } + bHandler.verifyTribeUUID = mockVerifyTribeUUID + + requestBody := map[string]interface{}{ + "UUID": mockUUID, + "Name": mockName, + } + requestBodyBytes, err := json.Marshal(requestBody) + if err != nil { + t.Fatal(err) + } + + mockDb.On("GetBotByUniqueName", mock.Anything).Return(db.Bot{ + UUID: mockUUID, + UniqueName: mockUniqueName, + Name: "Original Test Bot", + }, nil) + + mockDb.On("CreateOrEditBot", mock.Anything).Return(db.Bot{ + UUID: mockUUID, + Name: mockName, + }, nil) + + req, err := http.NewRequest("POST", "/", bytes.NewBuffer(requestBodyBytes)) + if err != nil { + t.Fatal(err) + } + ctx := context.WithValue(req.Context(), auth.ContextKey, mockPubKey) + req = req.WithContext(ctx) + + rr := httptest.NewRecorder() + handler := http.HandlerFunc(bHandler.CreateOrEditBot) + handler.ServeHTTP(rr, req) + + assert.Equal(t, http.StatusOK, rr.Code) + var responseData map[string]interface{} + err = json.Unmarshal(rr.Body.Bytes(), &responseData) + if err != nil { + t.Fatalf("Error decoding JSON response: %s", err) + } + assert.Equal(t, mockUUID, responseData["uuid"]) + assert.Equal(t, mockName, responseData["name"]) + }) + +} + +func TestGetBot(t *testing.T) { + mockDb := mocks.NewDatabase(t) + bHandler := NewBotHandler(mockDb) + + t.Run("should test that a bot can be fetched with its uuid", func(t *testing.T) { + + mockUUID := "valid_uuid" + mockBot := db.Bot{UUID: mockUUID, Name: "Test Bot"} + mockDb.On("GetBot", mock.Anything).Return(mockBot).Once() + + rr := httptest.NewRecorder() + rctx := chi.NewRouteContext() + rctx.URLParams.Add("uuid", mockUUID) + req, err := http.NewRequestWithContext(context.WithValue(context.Background(), chi.RouteCtxKey, rctx), http.MethodGet, "/"+mockUUID, nil) + + if err != nil { + t.Fatal(err) + } + + handler := http.HandlerFunc(bHandler.GetBot) + handler.ServeHTTP(rr, req) + + assert.Equal(t, http.StatusOK, rr.Code) + var returnedBot db.Bot + json.Unmarshal(rr.Body.Bytes(), &returnedBot) + assert.Equal(t, mockBot, returnedBot) + }) +} + +func TestGetListedBots(t *testing.T) { + mockDb := mocks.NewDatabase(t) + bHandler := NewBotHandler(mockDb) + + t.Run("should test that all bots that are not unlisted or deleted get listed", func(t *testing.T) { + rr := httptest.NewRecorder() + handler := http.HandlerFunc(bHandler.GetListedBots) + + allBots := []db.Bot{ + {UUID: "uuid1", Name: "Bot1", Unlisted: false, Deleted: false}, + {UUID: "uuid2", Name: "Bot2", Unlisted: false, Deleted: true}, + {UUID: "uuid3", Name: "Bot3", Unlisted: true, Deleted: false}, + {UUID: "uuid4", Name: "Bot4", Unlisted: true, Deleted: true}, + } + + expectedBots := []db.Bot{ + {UUID: "uuid1", Name: "Bot1", Unlisted: false, Deleted: false}, + } + + rctx := chi.NewRouteContext() + req, err := http.NewRequestWithContext(context.WithValue(context.Background(), chi.RouteCtxKey, rctx), http.MethodGet, "/", nil) + assert.NoError(t, err) + + mockDb.On("GetListedBots", mock.Anything).Return(allBots) + handler.ServeHTTP(rr, req) + var returnedBots []db.Bot + err = json.Unmarshal(rr.Body.Bytes(), &returnedBots) + assert.NoError(t, err) + assert.Equal(t, http.StatusOK, rr.Code) + + var filteredBots []db.Bot + for _, bot := range returnedBots { + if !bot.Deleted && !bot.Unlisted { + filteredBots = append(filteredBots, bot) + } + } + + assert.ElementsMatch(t, expectedBots, filteredBots) + mockDb.AssertExpectations(t) + }) + +} diff --git a/routes/bot.go b/routes/bot.go index 21f894565..120b60e63 100644 --- a/routes/bot.go +++ b/routes/bot.go @@ -3,18 +3,20 @@ package routes import ( "github.com/go-chi/chi" "github.com/stakwork/sphinx-tribes/auth" + "github.com/stakwork/sphinx-tribes/db" "github.com/stakwork/sphinx-tribes/handlers" ) func BotRoutes() chi.Router { r := chi.NewRouter() + botHandler := handlers.NewBotHandler(db.DB) r.Group(func(r chi.Router) { - r.Get("/{name}", handlers.GetBotByUniqueName) + r.Get("/{name}", botHandler.GetBotByUniqueName) }) r.Group(func(r chi.Router) { r.Use(auth.PubKeyContext) - r.Put("/", handlers.CreateOrEditBot) + r.Put("/", botHandler.CreateOrEditBot) r.Delete("/{uuid}", handlers.DeleteBot) }) return r diff --git a/routes/bots.go b/routes/bots.go index 50228bd63..32508aa1d 100644 --- a/routes/bots.go +++ b/routes/bots.go @@ -2,16 +2,19 @@ package routes import ( "github.com/go-chi/chi" + "github.com/stakwork/sphinx-tribes/db" "github.com/stakwork/sphinx-tribes/handlers" ) func BotsRoutes() chi.Router { r := chi.NewRouter() + botHandler := handlers.NewBotHandler(db.DB) + r.Group(func(r chi.Router) { - r.Post("/", handlers.CreateOrEditBot) - r.Get("/", handlers.GetListedBots) + r.Post("/", botHandler.CreateOrEditBot) + r.Get("/", botHandler.GetListedBots) r.Get("/owner/{pubkey}", handlers.GetBotsByOwner) - r.Get("/{uuid}", handlers.GetBot) + r.Get("/{uuid}", botHandler.GetBot) }) return r }