Skip to content

Commit

Permalink
Merge pull request stakwork#1997 from aliraza556/GET-tickets-by-phase
Browse files Browse the repository at this point in the history
Implement GET Endpoint for Phase-Specific Tickets
  • Loading branch information
elraphty authored Nov 28, 2024
2 parents bd9e1fc + 06b9128 commit d256b5b
Show file tree
Hide file tree
Showing 6 changed files with 316 additions and 0 deletions.
15 changes: 15 additions & 0 deletions db/features.go
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,21 @@ func (db database) GetBountiesByPhaseUuid(phaseUuid string) []Bounty {
return bounties
}

func (db database) GetTicketsByPhaseUUID(featureUUID string, phaseUUID string) ([]Tickets, error) {
var tickets []Tickets

result := db.db.
Where("feature_uuid = ? AND phase_uuid = ?", featureUUID, phaseUUID).
Order("sequence ASC").
Find(&tickets)

if result.Error != nil {
return nil, fmt.Errorf("failed to fetch tickets: %w", result.Error)
}

return tickets, nil
}

func (db database) GetProductBrief(workspaceUuid string) (string, error) {
workspace := Workspace{}
result := db.db.Model(&Workspace{}).Where("uuid = ?", workspaceUuid).First(&workspace)
Expand Down
1 change: 1 addition & 0 deletions db/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,4 +198,5 @@ type Database interface {
GetTicket(uuid string) (Tickets, error)
UpdateTicket(ticket Tickets) (Tickets, error)
DeleteTicket(uuid string) error
GetTicketsByPhaseUUID(featureUUID string, phaseUUID string) ([]Tickets, error)
}
49 changes: 49 additions & 0 deletions handlers/features.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"os"

"github.com/go-chi/chi"
"github.com/google/uuid"
"github.com/rs/xid"
"github.com/stakwork/sphinx-tribes/auth"
"github.com/stakwork/sphinx-tribes/db"
Expand Down Expand Up @@ -697,3 +698,51 @@ func (oh *featureHandler) BriefSend(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(resp.StatusCode)
w.Write(respBody)
}

func (oh *featureHandler) GetTicketsByPhaseUUID(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
pubKeyFromAuth, _ := ctx.Value(auth.ContextKey).(string)
if pubKeyFromAuth == "" {
fmt.Println("no pubkey from auth")
w.WriteHeader(http.StatusUnauthorized)
return
}

featureUUID := chi.URLParam(r, "feature_uuid")
phaseUUID := chi.URLParam(r, "phase_uuid")

if _, err := uuid.Parse(featureUUID); err != nil {
w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(map[string]string{"error": "invalid feature UUID format"})
return
}
if _, err := uuid.Parse(phaseUUID); err != nil {
w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(map[string]string{"error": "invalid phase UUID format"})
return
}

feature := oh.db.GetFeatureByUuid(featureUUID)
if feature.Uuid == "" {
w.WriteHeader(http.StatusNotFound)
json.NewEncoder(w).Encode(map[string]string{"error": "feature not found"})
return
}

_, err := oh.db.GetFeaturePhaseByUuid(featureUUID, phaseUUID)
if err != nil {
w.WriteHeader(http.StatusNotFound)
json.NewEncoder(w).Encode(map[string]string{"error": "Phase not found"})
return
}

tickets, err := oh.db.GetTicketsByPhaseUUID(featureUUID, phaseUUID)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
return
}

w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(tickets)
}
194 changes: 194 additions & 0 deletions handlers/features_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1636,3 +1636,197 @@ func TestGetFeatureStories(t *testing.T) {
assert.Equal(t, http.StatusNotAcceptable, rr.Code)
})
}

func TestGetTicketsByPhaseUUID(t *testing.T) {
teardownSuite := SetupSuite(t)
defer teardownSuite(t)

fHandler := NewFeatureHandler(db.TestDB)

person := db.Person{
Uuid: uuid.New().String(),
OwnerAlias: "test-alias",
UniqueName: "test-unique-name",
OwnerPubKey: "test-pubkey",
PriceToMeet: 0,
Description: "test-description",
}
db.TestDB.CreateOrEditPerson(person)

workspace := db.Workspace{
Uuid: uuid.New().String(),
Name: "test-workspace" + uuid.New().String(),
OwnerPubKey: person.OwnerPubKey,
Github: "https://github.com/test",
Website: "https://www.testwebsite.com",
Description: "test-description",
}
db.TestDB.CreateOrEditWorkspace(workspace)

feature := db.WorkspaceFeatures{
Uuid: uuid.New().String(),
WorkspaceUuid: workspace.Uuid,
Name: "test-feature",
Url: "https://github.com/test-feature",
Priority: 0,
}
db.TestDB.CreateOrEditFeature(feature)

featurePhase := db.FeaturePhase{
Uuid: uuid.New().String(),
FeatureUuid: feature.Uuid,
Name: "test-phase",
Priority: 0,
}
db.TestDB.CreateOrEditFeaturePhase(featurePhase)

ticket := db.Tickets{
UUID: uuid.New(),
FeatureUUID: feature.Uuid,
PhaseUUID: featurePhase.Uuid,
Name: "Test Ticket",
Sequence: 1,
Description: "Test Description",
Status: db.DraftTicket,
}

db.TestDB.UpdateTicket(ticket)

ctx := context.WithValue(context.Background(), auth.ContextKey, person.OwnerPubKey)

t.Run("should return 401 if user is not authorized", func(t *testing.T) {
rctx := chi.NewRouteContext()
rctx.URLParams.Add("feature_uuid", feature.Uuid)
rctx.URLParams.Add("phase_uuid", featurePhase.Uuid)
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet,
"/features/"+feature.Uuid+"/phase/"+featurePhase.Uuid+"/tickets", nil)
if err != nil {
t.Fatal(err)
}

rr := httptest.NewRecorder()
http.HandlerFunc(fHandler.GetTicketsByPhaseUUID).ServeHTTP(rr, req)

assert.Equal(t, http.StatusUnauthorized, rr.Code)
})

t.Run("should return 400 if feature UUID is invalid", func(t *testing.T) {
rctx := chi.NewRouteContext()
rctx.URLParams.Add("feature_uuid", "invalid-uuid")
rctx.URLParams.Add("phase_uuid", featurePhase.Uuid)
req, err := http.NewRequestWithContext(context.WithValue(ctx, chi.RouteCtxKey, rctx),
http.MethodGet, "/features/invalid-uuid/phase/"+featurePhase.Uuid+"/tickets", nil)
if err != nil {
t.Fatal(err)
}

rr := httptest.NewRecorder()
http.HandlerFunc(fHandler.GetTicketsByPhaseUUID).ServeHTTP(rr, req)

assert.Equal(t, http.StatusBadRequest, rr.Code)
})

t.Run("should return 400 if phase UUID is invalid", func(t *testing.T) {
rctx := chi.NewRouteContext()
rctx.URLParams.Add("feature_uuid", feature.Uuid)
rctx.URLParams.Add("phase_uuid", "invalid-uuid")
req, err := http.NewRequestWithContext(context.WithValue(ctx, chi.RouteCtxKey, rctx),
http.MethodGet, "/features/"+feature.Uuid+"/phase/invalid-uuid/tickets", nil)
if err != nil {
t.Fatal(err)
}

rr := httptest.NewRecorder()
http.HandlerFunc(fHandler.GetTicketsByPhaseUUID).ServeHTTP(rr, req)

assert.Equal(t, http.StatusBadRequest, rr.Code)
})

t.Run("should return 404 if feature not found", func(t *testing.T) {
nonExistentUUID := uuid.New().String()
rctx := chi.NewRouteContext()
rctx.URLParams.Add("feature_uuid", nonExistentUUID)
rctx.URLParams.Add("phase_uuid", featurePhase.Uuid)
req, err := http.NewRequestWithContext(context.WithValue(ctx, chi.RouteCtxKey, rctx),
http.MethodGet, "/features/"+nonExistentUUID+"/phase/"+featurePhase.Uuid+"/tickets", nil)
if err != nil {
t.Fatal(err)
}

rr := httptest.NewRecorder()
http.HandlerFunc(fHandler.GetTicketsByPhaseUUID).ServeHTTP(rr, req)

assert.Equal(t, http.StatusNotFound, rr.Code)
})

t.Run("should return 404 if phase not found", func(t *testing.T) {
nonExistentUUID := uuid.New().String()
rctx := chi.NewRouteContext()
rctx.URLParams.Add("feature_uuid", feature.Uuid)
rctx.URLParams.Add("phase_uuid", nonExistentUUID)
req, err := http.NewRequestWithContext(context.WithValue(ctx, chi.RouteCtxKey, rctx),
http.MethodGet, "/features/"+feature.Uuid+"/phase/"+nonExistentUUID+"/tickets", nil)
if err != nil {
t.Fatal(err)
}

rr := httptest.NewRecorder()
http.HandlerFunc(fHandler.GetTicketsByPhaseUUID).ServeHTTP(rr, req)

assert.Equal(t, http.StatusNotFound, rr.Code)
})

t.Run("should return tickets successfully when authorized", func(t *testing.T) {
rctx := chi.NewRouteContext()
rctx.URLParams.Add("feature_uuid", feature.Uuid)
rctx.URLParams.Add("phase_uuid", featurePhase.Uuid)
req, err := http.NewRequestWithContext(context.WithValue(ctx, chi.RouteCtxKey, rctx),
http.MethodGet, "/features/"+feature.Uuid+"/phase/"+featurePhase.Uuid+"/tickets", nil)
if err != nil {
t.Fatal(err)
}

rr := httptest.NewRecorder()
http.HandlerFunc(fHandler.GetTicketsByPhaseUUID).ServeHTTP(rr, req)

assert.Equal(t, http.StatusOK, rr.Code)

var returnedTickets []db.Tickets
err = json.Unmarshal(rr.Body.Bytes(), &returnedTickets)
assert.NoError(t, err)
assert.Equal(t, 1, len(returnedTickets))
assert.Equal(t, ticket.Name, returnedTickets[0].Name)
assert.Equal(t, ticket.Description, returnedTickets[0].Description)
assert.Equal(t, ticket.Status, returnedTickets[0].Status)
assert.Equal(t, ticket.Sequence, returnedTickets[0].Sequence)
})

t.Run("should return empty array when no tickets exist", func(t *testing.T) {
emptyPhase := db.FeaturePhase{
Uuid: uuid.New().String(),
FeatureUuid: feature.Uuid,
Name: "empty-phase",
Priority: 1,
}
db.TestDB.CreateOrEditFeaturePhase(emptyPhase)

rctx := chi.NewRouteContext()
rctx.URLParams.Add("feature_uuid", feature.Uuid)
rctx.URLParams.Add("phase_uuid", emptyPhase.Uuid)
req, err := http.NewRequestWithContext(context.WithValue(ctx, chi.RouteCtxKey, rctx),
http.MethodGet, "/features/"+feature.Uuid+"/phase/"+emptyPhase.Uuid+"/tickets", nil)
if err != nil {
t.Fatal(err)
}

rr := httptest.NewRecorder()
http.HandlerFunc(fHandler.GetTicketsByPhaseUUID).ServeHTTP(rr, req)

assert.Equal(t, http.StatusOK, rr.Code)

var returnedTickets []db.Tickets
err = json.Unmarshal(rr.Body.Bytes(), &returnedTickets)
assert.NoError(t, err)
assert.Equal(t, 0, len(returnedTickets))
})
}
56 changes: 56 additions & 0 deletions mocks/Database.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions routes/features.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ func FeatureRoutes() chi.Router {
r.Get("/{feature_uuid}/story/{story_uuid}", featureHandlers.GetStoryByUuid)
r.Delete("/{feature_uuid}/story/{story_uuid}", featureHandlers.DeleteStory)
r.Get("/{feature_uuid}/phase/{phase_uuid}/bounty", featureHandlers.GetBountiesByFeatureAndPhaseUuid)
r.Get("/{feature_uuid}/phase/{phase_uuid}/tickets", featureHandlers.GetTicketsByPhaseUUID)
r.Get("/{feature_uuid}/phase/{phase_uuid}/bounty/count", featureHandlers.GetBountiesCountByFeatureAndPhaseUuid)

})
Expand Down

0 comments on commit d256b5b

Please sign in to comment.