Skip to content

Commit

Permalink
Merge pull request #1574 from MirzaHanan/MakeBountyPayment-tests
Browse files Browse the repository at this point in the history
Backend [Integration Test]: bounty.go handlers MakeBountyPayment
  • Loading branch information
elraphty authored Feb 29, 2024
2 parents af7523b + fdaefd3 commit bbcc852
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 6 deletions.
10 changes: 5 additions & 5 deletions handlers/bounty.go
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,7 @@ func (h *bountyHandler) GenerateBountyResponse(bounties []db.Bounty) []db.Bounty
return bountyResponse
}

func MakeBountyPayment(w http.ResponseWriter, r *http.Request) {
func (h *bountyHandler) MakeBountyPayment(w http.ResponseWriter, r *http.Request) {
var m sync.Mutex
m.Lock()

Expand All @@ -429,7 +429,7 @@ func MakeBountyPayment(w http.ResponseWriter, r *http.Request) {
return
}

bounty := db.DB.GetBounty(id)
bounty := h.db.GetBounty(id)
amount := bounty.Price

if bounty.ID != id {
Expand All @@ -445,7 +445,7 @@ func MakeBountyPayment(w http.ResponseWriter, r *http.Request) {

// check if user is the admin of the organization
// or has a pay bounty role
hasRole := db.UserHasAccess(pubKeyFromAuth, bounty.OrgUuid, db.PayBounty)
hasRole := h.db.UserHasAccess(pubKeyFromAuth, bounty.OrgUuid, db.PayBounty)
if !hasRole {
w.WriteHeader(http.StatusUnauthorized)
json.NewEncoder(w).Encode("You don't have appropriate permissions to pay bounties")
Expand All @@ -454,7 +454,7 @@ func MakeBountyPayment(w http.ResponseWriter, r *http.Request) {

// check if the organization bounty balance
// is greater than the amount
orgBudget := db.DB.GetOrganizationBudget(bounty.OrgUuid)
orgBudget := h.db.GetOrganizationBudget(bounty.OrgUuid)
if orgBudget.TotalBudget < amount {
w.WriteHeader(http.StatusForbidden)
json.NewEncoder(w).Encode("organization budget is not enough to pay the amount")
Expand All @@ -474,7 +474,7 @@ func MakeBountyPayment(w http.ResponseWriter, r *http.Request) {

url := fmt.Sprintf("%s/payment", config.RelayUrl)

assignee := db.DB.GetPersonByPubkey(bounty.Assignee)
assignee := h.db.GetPersonByPubkey(bounty.Assignee)
bodyData := utils.BuildKeysendBodyData(amount, assignee.OwnerPubKey, assignee.OwnerRouteHint)

jsonBody := []byte(bodyData)
Expand Down
151 changes: 151 additions & 0 deletions handlers/bounty_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"net/http/httptest"
"strconv"
"strings"
"sync"
"testing"
"time"

Expand Down Expand Up @@ -1117,3 +1118,153 @@ func TestGetAllBounties(t *testing.T) {

})
}

func TestMakeBountyPayment(t *testing.T) {
ctx := context.Background()
mockDb := dbMocks.NewDatabase(t)
mockHttpClient := mocks.NewHttpClient(t)
bHandler := NewBountyHandler(mockHttpClient, mockDb)

unauthorizedCtx := context.WithValue(ctx, auth.ContextKey, "")
authorizedCtx := context.WithValue(ctx, auth.ContextKey, "valid-key")

var mutex sync.Mutex
var processingTimes []time.Time

t.Run("mutex lock ensures sequential access", func(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
mutex.Lock()
processingTimes = append(processingTimes, time.Now())
time.Sleep(10 * time.Millisecond)
mutex.Unlock()

bHandler.MakeBountyPayment(w, r)
}))
defer server.Close()

var wg sync.WaitGroup
for i := 0; i < 3; i++ {
wg.Add(1)
go func() {
defer wg.Done()
_, err := http.Get(server.URL)
if err != nil {
t.Errorf("Failed to send request: %v", err)
}
}()
}
wg.Wait()

for i := 1; i < len(processingTimes); i++ {
assert.True(t, processingTimes[i].After(processingTimes[i-1]),
"Expected processing times to be sequential, indicating mutex is locking effectively.")
}
})

t.Run("401 unauthorized error when unauthorized user hits endpoint", func(t *testing.T) {

r := chi.NewRouter()
r.Post("/gobounties/pay/{id}", bHandler.MakeBountyPayment)

rr := httptest.NewRecorder()
req, err := http.NewRequestWithContext(unauthorizedCtx, http.MethodPost, "/gobounties/pay/1", nil)

if err != nil {
t.Fatal(err)
}

r.ServeHTTP(rr, req)

assert.Equal(t, http.StatusUnauthorized, rr.Code, "Expected 401 Unauthorized for unauthorized access")
mockDb.AssertExpectations(t)
})

t.Run("405 when trying to pay an already-paid bounty", func(t *testing.T) {
mockDb.On("GetBounty", mock.AnythingOfType("uint")).Return(db.Bounty{
ID: 1,
Price: 1000,
OrgUuid: "org-1",
Assignee: "assignee-1",
Paid: true,
}, nil)

mockDb.On("UserHasAccess", "valid-key", "org-1", db.PayBounty).Return(true)
mockDb.On("GetOrganizationBudget", "org-1").Return(db.BountyBudget{TotalBudget: 1000}, nil)
mockDb.On("GetPersonByPubkey", "assignee-1").Return(db.Person{
OwnerPubKey: "assignee-1",
}, nil)

r := chi.NewRouter()
r.Post("/gobounties/pay/{id}", bHandler.MakeBountyPayment)

requestBody := bytes.NewBuffer([]byte("{}"))
rr := httptest.NewRecorder()
req, err := http.NewRequestWithContext(authorizedCtx, http.MethodPost, "/gobounties/pay/1", requestBody)
if err != nil {
t.Fatal(err)
}

r.ServeHTTP(rr, req)
assert.Equal(t, http.StatusMethodNotAllowed, rr.Code, "Expected 405 Method Not Allowed for an already-paid bounty")
mockDb.AssertExpectations(t)
})

t.Run("401 error if user not organization admin or does not have PAY BOUNTY role", func(t *testing.T) {
mockDb.On("GetBounty", mock.AnythingOfType("uint")).Return(db.Bounty{
ID: 1,
Price: 1000,
OrgUuid: "org-1",
Assignee: "assignee-1",
Paid: false,
}, nil)
mockDb.On("UserHasAccess", "valid-key", "org-1", db.PayBounty).Return(false)

r := chi.NewRouter()
r.Post("/gobounties/pay/{id}", bHandler.MakeBountyPayment)

rr := httptest.NewRecorder()
req, err := http.NewRequestWithContext(unauthorizedCtx, http.MethodPost, "/gobounties/pay/1", bytes.NewBufferString(`{}`))
if err != nil {
t.Fatal(err)
}

r.ServeHTTP(rr, req)

assert.Equal(t, http.StatusUnauthorized, rr.Code, "Expected 401 Unauthorized when the user lacks the PAY BOUNTY role")

})

t.Run("403 error when amount exceeds organization's budget balance", func(t *testing.T) {
ctx := context.WithValue(context.Background(), auth.ContextKey, "valid-key")

mockDb := dbMocks.NewDatabase(t)
mockHttpClient := mocks.NewHttpClient(t)
bHandler := NewBountyHandler(mockHttpClient, mockDb)
mockDb.On("GetBounty", mock.AnythingOfType("uint")).Return(db.Bounty{
ID: 1,
Price: 1000,
OrgUuid: "org-1",
Assignee: "assignee-1",
Paid: false,
}, nil)
mockDb.On("UserHasAccess", "valid-key", "org-1", db.PayBounty).Return(true)
mockDb.On("GetOrganizationBudget", "org-1").Return(db.BountyBudget{
TotalBudget: 500,
}, nil)

r := chi.NewRouter()
r.Post("/gobounties/pay/{id}", bHandler.MakeBountyPayment)

rr := httptest.NewRecorder()
req, err := http.NewRequestWithContext(ctx, http.MethodPost, "/gobounties/pay/1", nil)
if err != nil {
t.Fatal(err)
}

r.ServeHTTP(rr, req)

assert.Equal(t, http.StatusForbidden, rr.Code, "Expected 403 Forbidden when the payment exceeds the organization's budget")

})

}
2 changes: 1 addition & 1 deletion routes/bounty.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func BountyRoutes() chi.Router {
})
r.Group(func(r chi.Router) {
r.Use(auth.PubKeyContext)
r.Post("/pay/{id}", handlers.MakeBountyPayment)
r.Post("/pay/{id}", bountyHandler.MakeBountyPayment)
r.Post("/budget/withdraw", bountyHandler.BountyBudgetWithdraw)

r.Post("/", bountyHandler.CreateOrEditBounty)
Expand Down

0 comments on commit bbcc852

Please sign in to comment.