Skip to content

Commit

Permalink
Merge pull request stakwork#1597 from AbdulWahab3181/bounty-handler-U…
Browse files Browse the repository at this point in the history
…Ts-V4

Backend [Integration Test] bounty.go handlers PollInvoice For Bounty Keysend Payment
  • Loading branch information
elraphty authored Mar 7, 2024
2 parents f34449a + 2ad2734 commit 2a77241
Show file tree
Hide file tree
Showing 6 changed files with 145 additions and 25 deletions.
30 changes: 14 additions & 16 deletions handlers/bounty.go
Original file line number Diff line number Diff line change
Expand Up @@ -606,15 +606,14 @@ func formatPayError(errorMsg string) db.InvoicePayError {
}
}

func GetLightningInvoice(payment_request string) (db.InvoiceResult, db.InvoiceError) {
func (h *bountyHandler) GetLightningInvoice(payment_request string) (db.InvoiceResult, db.InvoiceError) {
url := fmt.Sprintf("%s/invoice?payment_request=%s", config.RelayUrl, payment_request)

client := &http.Client{}
req, err := http.NewRequest(http.MethodGet, url, nil)

req.Header.Set("x-user-token", config.RelayAuthKey)
req.Header.Set("Content-Type", "application/json")
res, _ := client.Do(req)
res, _ := h.httpClient.Do(req)

if err != nil {
log.Printf("Request Failed: %s", err)
Expand Down Expand Up @@ -693,9 +692,9 @@ func (h *bountyHandler) PayLightningInvoice(payment_request string) (db.InvoiceP
}
}

func GetInvoiceData(w http.ResponseWriter, r *http.Request) {
func (h *bountyHandler) GetInvoiceData(w http.ResponseWriter, r *http.Request) {
paymentRequest := chi.URLParam(r, "paymentRequest")
invoiceData, invoiceErr := GetLightningInvoice(paymentRequest)
invoiceData, invoiceErr := h.GetLightningInvoice(paymentRequest)

if invoiceErr.Error != "" {
w.WriteHeader(http.StatusForbidden)
Expand All @@ -707,7 +706,7 @@ func GetInvoiceData(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(invoiceData)
}

func PollInvoice(w http.ResponseWriter, r *http.Request) {
func (h *bountyHandler) PollInvoice(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
pubKeyFromAuth, _ := ctx.Value(auth.ContextKey).(string)
paymentRequest := chi.URLParam(r, "paymentRequest")
Expand All @@ -719,7 +718,7 @@ func PollInvoice(w http.ResponseWriter, r *http.Request) {
return
}

invoiceRes, invoiceErr := GetLightningInvoice(paymentRequest)
invoiceRes, invoiceErr := h.GetLightningInvoice(paymentRequest)

if invoiceErr.Error != "" {
w.WriteHeader(http.StatusForbidden)
Expand All @@ -729,14 +728,14 @@ func PollInvoice(w http.ResponseWriter, r *http.Request) {

if invoiceRes.Response.Settled {
// Todo if an invoice is settled
invoice := db.DB.GetInvoice(paymentRequest)
invData := db.DB.GetUserInvoiceData(paymentRequest)
dbInvoice := db.DB.GetInvoice(paymentRequest)
invoice := h.db.GetInvoice(paymentRequest)
invData := h.db.GetUserInvoiceData(paymentRequest)
dbInvoice := h.db.GetInvoice(paymentRequest)

// Make any change only if the invoice has not been settled
if !dbInvoice.Status {
if invoice.Type == "BUDGET" {
db.DB.AddAndUpdateBudget(invoice)
h.db.AddAndUpdateBudget(invoice)
} else if invoice.Type == "KEYSEND" {
url := fmt.Sprintf("%s/payment", config.RelayUrl)

Expand All @@ -746,12 +745,11 @@ func PollInvoice(w http.ResponseWriter, r *http.Request) {

jsonBody := []byte(bodyData)

client := &http.Client{}
req, _ := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(jsonBody))

req.Header.Set("x-user-token", config.RelayAuthKey)
req.Header.Set("Content-Type", "application/json")
res, _ := client.Do(req)
res, _ := h.httpClient.Do(req)

if err != nil {
log.Printf("Request Failed: %s", err)
Expand All @@ -767,13 +765,13 @@ func PollInvoice(w http.ResponseWriter, r *http.Request) {
keysendRes := db.KeysendSuccess{}
err = json.Unmarshal(body, &keysendRes)

bounty, err := db.DB.GetBountyByCreated(uint(invData.Created))
bounty, err := h.db.GetBountyByCreated(uint(invData.Created))

if err == nil {
bounty.Paid = true
}

db.DB.UpdateBounty(bounty)
h.db.UpdateBounty(bounty)
} else {
// Unmarshal result
keysendError := db.KeysendError{}
Expand All @@ -782,7 +780,7 @@ func PollInvoice(w http.ResponseWriter, r *http.Request) {
}
}
// Update the invoice status
db.DB.UpdateInvoice(paymentRequest)
h.db.UpdateInvoice(paymentRequest)
}

}
Expand Down
121 changes: 120 additions & 1 deletion handlers/bounty_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/stakwork/sphinx-tribes/utils"
"io"
"net/http"
"net/http/httptest"
Expand All @@ -16,6 +15,8 @@ import (
"testing"
"time"

"github.com/stakwork/sphinx-tribes/utils"

"github.com/go-chi/chi"
"github.com/lib/pq"
"github.com/stakwork/sphinx-tribes/auth"
Expand Down Expand Up @@ -1431,3 +1432,121 @@ func TestBountyBudgetWithdraw(t *testing.T) {
mockHttpClient.AssertCalled(t, "Do", mock.AnythingOfType("*http.Request"))
})
}

func TestPollInvoice(t *testing.T) {
ctx := context.Background()
mockDb := &dbMocks.Database{}
mockHttpClient := &mocks.HttpClient{}
bHandler := NewBountyHandler(mockHttpClient, mockDb)

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

t.Run("Should test that a 401 error is returned if a user is unauthorized", func(t *testing.T) {
r := chi.NewRouter()
r.Post("/poll/invoice/{paymentRequest}", bHandler.PollInvoice)

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

r.ServeHTTP(rr, req)

assert.Equal(t, http.StatusUnauthorized, rr.Code, "Expected 401 error if a user is unauthorized")
})

t.Run("Should test that a 403 error is returned if there is an invoice error", func(t *testing.T) {
expectedUrl := fmt.Sprintf("%s/invoice?payment_request=%s", config.RelayUrl, "1")

r := io.NopCloser(bytes.NewReader([]byte(`{"success": false, "error": "Internel server error"}`)))
mockHttpClient.On("Do", mock.MatchedBy(func(req *http.Request) bool {
return req.Method == http.MethodGet && expectedUrl == req.URL.String() && req.Header.Get("x-user-token") == config.RelayAuthKey
})).Return(&http.Response{
StatusCode: 500,
Body: r,
}, nil).Once()

ro := chi.NewRouter()
ro.Post("/poll/invoice/{paymentRequest}", bHandler.PollInvoice)

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

ro.ServeHTTP(rr, req)

assert.Equal(t, http.StatusForbidden, rr.Code, "Expected 403 error if there is an invoice error")
mockHttpClient.AssertExpectations(t)
})

t.Run("Should mock relay payment is successful update the bounty associated with the invoice and set the paid as true", func(t *testing.T) {
expectedUrl := fmt.Sprintf("%s/invoice?payment_request=%s", config.RelayUrl, "1")

r := io.NopCloser(bytes.NewReader([]byte(`{"success": true, "response": { "settled": true, "payment_request": "1", "payment_hash": "payment_hash", "preimage": "preimage", "Amount": "1000"}}`)))
mockHttpClient.On("Do", mock.MatchedBy(func(req *http.Request) bool {
return req.Method == http.MethodGet && expectedUrl == req.URL.String() && req.Header.Get("x-user-token") == config.RelayAuthKey
})).Return(&http.Response{
StatusCode: 200,
Body: r,
}, nil).Once()

bountyID := uint(1)
bounty := db.Bounty{
ID: bountyID,
OrgUuid: "org-1",
Assignee: "assignee-1",
Price: uint(1000),
}

now := time.Now()
expectedBounty := db.Bounty{
ID: bountyID,
OrgUuid: "org-1",
Assignee: "assignee-1",
Price: uint(1000),
Paid: true,
PaidDate: &now,
CompletionDate: &now,
}

mockDb.On("GetInvoice", "1").Return(db.InvoiceList{Type: "KEYSEND"})
mockDb.On("GetUserInvoiceData", "1").Return(db.UserInvoiceData{Amount: 1000, UserPubkey: "UserPubkey", RouteHint: "RouteHint", Created: 1234})
mockDb.On("GetInvoice", "1").Return(db.InvoiceList{Status: false})
mockDb.On("GetBountyByCreated", uint(1234)).Return(bounty, nil)
mockDb.On("UpdateBounty", mock.AnythingOfType("db.Bounty")).Run(func(args mock.Arguments) {
updatedBounty := args.Get(0).(db.Bounty)
assert.True(t, updatedBounty.Paid)
}).Return(expectedBounty, nil).Once()
mockDb.On("UpdateInvoice", "1").Return(db.InvoiceList{}).Once()

expectedPaymentUrl := fmt.Sprintf("%s/payment", config.RelayUrl)
expectedPaymentBody := `{"amount": 1000, "destination_key": "UserPubkey", "route_hint": "RouteHint", "text": "memotext added for notification"}`

r2 := io.NopCloser(bytes.NewReader([]byte(`{"success": true, "response": { "sumAmount": "1"}}`)))
mockHttpClient.On("Do", mock.MatchedBy(func(req *http.Request) bool {
bodyByt, _ := io.ReadAll(req.Body)
return req.Method == http.MethodPost && expectedPaymentUrl == req.URL.String() && req.Header.Get("x-user-token") == config.RelayAuthKey && expectedPaymentBody == string(bodyByt)
})).Return(&http.Response{
StatusCode: 200,
Body: r2,
}, nil).Once()

ro := chi.NewRouter()
ro.Post("/poll/invoice/{paymentRequest}", bHandler.PollInvoice)

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

ro.ServeHTTP(rr, req)

assert.Equal(t, http.StatusOK, rr.Code)
mockHttpClient.AssertExpectations(t)
})
}
12 changes: 7 additions & 5 deletions handlers/organizations.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@ import (
type organizationHandler struct {
db db.Database
generateBountyHandler func(bounties []db.Bounty) []db.BountyResponse
getLightningInvoice func(payment_request string) (db.InvoiceResult, db.InvoiceError)
}

func NewOrganizationHandler(db db.Database) *organizationHandler {
bHandler := NewBountyHandler(http.DefaultClient, db)
return &organizationHandler{
db: db,
generateBountyHandler: bHandler.GenerateBountyResponse,
getLightningInvoice: bHandler.GetLightningInvoice,
}
}

Expand Down Expand Up @@ -630,7 +632,7 @@ func GetPaymentHistory(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(paymentHistoryData)
}

func PollBudgetInvoices(w http.ResponseWriter, r *http.Request) {
func (oh *organizationHandler) PollBudgetInvoices(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
pubKeyFromAuth, _ := ctx.Value(auth.ContextKey).(string)
uuid := chi.URLParam(r, "uuid")
Expand All @@ -641,10 +643,10 @@ func PollBudgetInvoices(w http.ResponseWriter, r *http.Request) {
return
}

orgInvoices := db.DB.GetOrganizationInvoices(uuid)
orgInvoices := oh.db.GetOrganizationInvoices(uuid)

for _, inv := range orgInvoices {
invoiceRes, invoiceErr := GetLightningInvoice(inv.PaymentRequest)
invoiceRes, invoiceErr := oh.getLightningInvoice(inv.PaymentRequest)

if invoiceErr.Error != "" {
w.WriteHeader(http.StatusForbidden)
Expand All @@ -654,9 +656,9 @@ func PollBudgetInvoices(w http.ResponseWriter, r *http.Request) {

if invoiceRes.Response.Settled {
if !inv.Status && inv.Type == "BUDGET" {
db.DB.AddAndUpdateBudget(inv)
oh.db.AddAndUpdateBudget(inv)
// Update the invoice status
db.DB.UpdateInvoice(inv.PaymentRequest)
oh.db.UpdateInvoice(inv.PaymentRequest)
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion routes/bounty.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func BountyRoutes() chi.Router {
r.Get("/created/{created}", bountyHandler.GetBountyByCreated)
r.Get("/count/{personKey}/{tabType}", handlers.GetUserBountyCount)
r.Get("/count", handlers.GetBountyCount)
r.Get("/invoice/{paymentRequest}", handlers.GetInvoiceData)
r.Get("/invoice/{paymentRequest}", bountyHandler.GetInvoiceData)
r.Get("/filter/count", handlers.GetFilterCount)

})
Expand Down
3 changes: 2 additions & 1 deletion routes/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ func NewRouter() *http.Server {
authHandler := handlers.NewAuthHandler(db.DB)
channelHandler := handlers.NewChannelHandler(db.DB)
botHandler := handlers.NewBotHandler(db.DB)
bHandler := handlers.NewBountyHandler(http.DefaultClient, db.DB)

r.Mount("/tribes", TribeRoutes())
r.Mount("/bots", BotsRoutes())
Expand Down Expand Up @@ -75,7 +76,7 @@ func NewRouter() *http.Server {
r.Post("/badges", handlers.AddOrRemoveBadge)
r.Delete("/channel/{id}", channelHandler.DeleteChannel)
r.Delete("/ticket/{pubKey}/{created}", handlers.DeleteTicketByAdmin)
r.Get("/poll/invoice/{paymentRequest}", handlers.PollInvoice)
r.Get("/poll/invoice/{paymentRequest}", bHandler.PollInvoice)
r.Post("/meme_upload", handlers.MemeImageUpload)
r.Get("/admin/auth", authHandler.GetIsAdmin)
})
Expand Down
2 changes: 1 addition & 1 deletion routes/organizations.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func OrganizationRoutes() chi.Router {
r.Get("/budget/{uuid}", organizationHandlers.GetOrganizationBudget)
r.Get("/budget/history/{uuid}", organizationHandlers.GetOrganizationBudgetHistory)
r.Get("/payments/{uuid}", handlers.GetPaymentHistory)
r.Get("/poll/invoices/{uuid}", handlers.PollBudgetInvoices)
r.Get("/poll/invoices/{uuid}", organizationHandlers.PollBudgetInvoices)
r.Get("/invoices/count/{uuid}", handlers.GetInvoicesCount)
r.Delete("/delete/{uuid}", organizationHandlers.DeleteOrganization)
})
Expand Down

0 comments on commit 2a77241

Please sign in to comment.