diff --git a/handlers/tribes.go b/handlers/tribes.go index 041698fb2..d5e34b00e 100644 --- a/handlers/tribes.go +++ b/handlers/tribes.go @@ -18,11 +18,15 @@ import ( ) type tribeHandler struct { - db db.Database + db db.Database + verifyTribeUUID func(uuid string, checkTimestamp bool) (string, error) } func NewTribeHandler(db db.Database) *tribeHandler { - return &tribeHandler{db: db} + return &tribeHandler{ + db: db, + verifyTribeUUID: auth.VerifyTribeUUID, + } } func GetAllTribes(w http.ResponseWriter, r *http.Request) { @@ -120,7 +124,7 @@ func PutTribeStats(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(true) } -func DeleteTribe(w http.ResponseWriter, r *http.Request) { +func (th *tribeHandler) DeleteTribe(w http.ResponseWriter, r *http.Request) { ctx := r.Context() pubKeyFromAuth, _ := ctx.Value(auth.ContextKey).(string) @@ -131,7 +135,7 @@ func DeleteTribe(w http.ResponseWriter, r *http.Request) { return } - extractedPubkey, err := auth.VerifyTribeUUID(uuid, false) + extractedPubkey, err := th.verifyTribeUUID(uuid, false) if err != nil { fmt.Println(err) w.WriteHeader(http.StatusUnauthorized) @@ -144,7 +148,7 @@ func DeleteTribe(w http.ResponseWriter, r *http.Request) { return } - db.DB.UpdateTribe(uuid, map[string]interface{}{ + th.db.UpdateTribe(uuid, map[string]interface{}{ "deleted": true, }) @@ -166,9 +170,9 @@ func (th *tribeHandler) GetTribe(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(theTribe) } -func GetFirstTribeByFeed(w http.ResponseWriter, r *http.Request) { +func (th *tribeHandler) GetFirstTribeByFeed(w http.ResponseWriter, r *http.Request) { url := r.URL.Query().Get("url") - tribe := db.DB.GetFirstTribeByFeedURL(url) + tribe := th.db.GetFirstTribeByFeedURL(url) if tribe.UUID == "" { w.WriteHeader(http.StatusNotFound) @@ -179,7 +183,7 @@ func GetFirstTribeByFeed(w http.ResponseWriter, r *http.Request) { j, _ := json.Marshal(tribe) json.Unmarshal(j, &theTribe) - theTribe["channels"] = db.DB.GetChannelsByTribe(tribe.UUID) + theTribe["channels"] = th.db.GetChannelsByTribe(tribe.UUID) w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(theTribe) @@ -298,7 +302,7 @@ func PutTribeActivity(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(true) } -func SetTribePreview(w http.ResponseWriter, r *http.Request) { +func (th *tribeHandler) SetTribePreview(w http.ResponseWriter, r *http.Request) { ctx := r.Context() pubKeyFromAuth, _ := ctx.Value(auth.ContextKey).(string) @@ -308,7 +312,7 @@ func SetTribePreview(w http.ResponseWriter, r *http.Request) { return } - extractedPubkey, err := auth.VerifyTribeUUID(uuid, false) + extractedPubkey, err := th.verifyTribeUUID(uuid, false) if err != nil { fmt.Println(err) w.WriteHeader(http.StatusUnauthorized) @@ -322,7 +326,7 @@ func SetTribePreview(w http.ResponseWriter, r *http.Request) { } preview := r.URL.Query().Get("preview") - db.DB.UpdateTribe(uuid, map[string]interface{}{ + th.db.UpdateTribe(uuid, map[string]interface{}{ "preview": preview, }) diff --git a/handlers/tribes_test.go b/handlers/tribes_test.go index bfc409c68..9e3ce1e47 100644 --- a/handlers/tribes_test.go +++ b/handlers/tribes_test.go @@ -8,6 +8,7 @@ import ( "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" @@ -183,3 +184,182 @@ func TestGetTribesByAppUrl(t *testing.T) { assert.ElementsMatch(t, mockTribes, responseData) }) } + +func TestDeleteTribe(t *testing.T) { + ctx := context.WithValue(context.Background(), auth.ContextKey, "owner_pubkey") + mockDb := mocks.NewDatabase(t) + tHandler := NewTribeHandler(mockDb) + + t.Run("Should test that the owner of a tribe can delete a tribe", func(t *testing.T) { + // Mock data + mockUUID := "valid_uuid" + mockOwnerPubKey := "owner_pubkey" + + mockVerifyTribeUUID := func(uuid string, checkTimestamp bool) (string, error) { + return mockOwnerPubKey, nil + } + mockDb.On("UpdateTribe", mock.Anything, map[string]interface{}{"deleted": true}).Return(true) + + tHandler.verifyTribeUUID = mockVerifyTribeUUID + + // Create and serve request + rr := httptest.NewRecorder() + handler := http.HandlerFunc(tHandler.DeleteTribe) + + req, err := http.NewRequestWithContext(ctx, "DELETE", "/tribe/"+mockUUID, nil) + 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) + + // Verify response + assert.Equal(t, http.StatusOK, rr.Code) + var responseData bool + errors := json.Unmarshal(rr.Body.Bytes(), &responseData) + assert.NoError(t, errors) + assert.True(t, responseData) + }) + + t.Run("Should test that a 401 error is returned when a tribe is attempted to be deleted by someone other than the owner", func(t *testing.T) { + // Mock data + ctx := context.WithValue(context.Background(), auth.ContextKey, "pubkey") + mockUUID := "valid_uuid" + mockOwnerPubKey := "owner_pubkey" + + mockVerifyTribeUUID := func(uuid string, checkTimestamp bool) (string, error) { + return mockOwnerPubKey, nil + } + + tHandler.verifyTribeUUID = mockVerifyTribeUUID + + // Create and serve request + rr := httptest.NewRecorder() + handler := http.HandlerFunc(tHandler.DeleteTribe) + + req, err := http.NewRequestWithContext(ctx, "DELETE", "/tribe/"+mockUUID, nil) + 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) + + // Verify response + assert.Equal(t, http.StatusUnauthorized, rr.Code) + }) +} + +func TestGetFirstTribeByFeed(t *testing.T) { + mockDb := mocks.NewDatabase(t) + tHandler := NewTribeHandler(mockDb) + + t.Run("Should test that a tribe can be gotten by passing the feed URL", func(t *testing.T) { + // Mock data + mockFeedURL := "valid_feed_url" + mockTribe := db.Tribe{ + UUID: "valid_uuid", + } + mockChannels := []db.Channel{ + {ID: 1, TribeUUID: mockTribe.UUID}, + } + + mockDb.On("GetFirstTribeByFeedURL", mockFeedURL).Return(mockTribe).Once() + mockDb.On("GetChannelsByTribe", mockTribe.UUID).Return(mockChannels).Once() + + // Create request with valid feed URL + req, err := http.NewRequest("GET", "/tribe_by_feed?url="+mockFeedURL, nil) + if err != nil { + t.Fatal(err) + } + + // Serve request + rr := httptest.NewRecorder() + handler := http.HandlerFunc(tHandler.GetFirstTribeByFeed) + handler.ServeHTTP(rr, req) + + // Verify response + 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, mockTribe.UUID, responseData["uuid"]) + }) +} + +func TestSetTribePreview(t *testing.T) { + ctx := context.WithValue(context.Background(), auth.ContextKey, "owner_pubkey") + mockDb := mocks.NewDatabase(t) + tHandler := NewTribeHandler(mockDb) + + t.Run("Should test that the owner of a tribe can set tribe preview", func(t *testing.T) { + // Mock data + mockUUID := "valid_uuid" + mockOwnerPubKey := "owner_pubkey" + + mockVerifyTribeUUID := func(uuid string, checkTimestamp bool) (string, error) { + return mockOwnerPubKey, nil + } + mockDb.On("UpdateTribe", mock.Anything, map[string]interface{}{"preview": "preview"}).Return(true) + + tHandler.verifyTribeUUID = mockVerifyTribeUUID + + // Create and serve request + rr := httptest.NewRecorder() + handler := http.HandlerFunc(tHandler.SetTribePreview) + + req, err := http.NewRequestWithContext(ctx, "PUT", "/tribepreview/"+mockUUID+"?preview=preview", nil) + 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) + + // Verify response + assert.Equal(t, http.StatusOK, rr.Code) + var responseData bool + errors := json.Unmarshal(rr.Body.Bytes(), &responseData) + assert.NoError(t, errors) + assert.True(t, responseData) + }) + + t.Run("Should test that a 401 error is returned when setting a tribe preview action by someone other than the owner", func(t *testing.T) { + // Mock data + ctx := context.WithValue(context.Background(), auth.ContextKey, "pubkey") + mockUUID := "valid_uuid" + mockOwnerPubKey := "owner_pubkey" + + mockVerifyTribeUUID := func(uuid string, checkTimestamp bool) (string, error) { + return mockOwnerPubKey, nil + } + + tHandler.verifyTribeUUID = mockVerifyTribeUUID + + // Create and serve request + rr := httptest.NewRecorder() + handler := http.HandlerFunc(tHandler.SetTribePreview) + + req, err := http.NewRequestWithContext(ctx, "PUT", "/tribepreview/"+mockUUID+"?preview=preview", nil) + 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) + + // Verify response + assert.Equal(t, http.StatusUnauthorized, rr.Code) + }) +} diff --git a/routes/index.go b/routes/index.go index 4cb494c62..1f5867d7b 100644 --- a/routes/index.go +++ b/routes/index.go @@ -34,7 +34,7 @@ func NewRouter() *http.Server { r.Mount("/metrics", MetricsRoutes()) r.Group(func(r chi.Router) { - r.Get("/tribe_by_feed", handlers.GetFirstTribeByFeed) + r.Get("/tribe_by_feed", tribeHandlers.GetFirstTribeByFeed) r.Get("/leaderboard/{tribe_uuid}", handlers.GetLeaderBoard) r.Get("/tribe_by_un/{un}", handlers.GetTribeByUniqueName) r.Get("/tribes_by_owner/{pubkey}", tribeHandlers.GetTribesByOwner) @@ -65,9 +65,9 @@ func NewRouter() *http.Server { r.Put("/leaderboard/{tribe_uuid}", handlers.UpdateLeaderBoard) r.Put("/tribe", handlers.CreateOrEditTribe) r.Put("/tribestats", handlers.PutTribeStats) - r.Delete("/tribe/{uuid}", handlers.DeleteTribe) + r.Delete("/tribe/{uuid}", tribeHandlers.DeleteTribe) r.Put("/tribeactivity/{uuid}", handlers.PutTribeActivity) - r.Put("/tribepreview/{uuid}", handlers.SetTribePreview) + r.Put("/tribepreview/{uuid}", tribeHandlers.SetTribePreview) r.Post("/verify/{challenge}", db.Verify) r.Post("/badges", handlers.AddOrRemoveBadge) r.Delete("/channel/{id}", handlers.DeleteChannel)