diff --git a/db/db.go b/db/db.go index fffda8a02..d2af69fb5 100644 --- a/db/db.go +++ b/db/db.go @@ -1249,8 +1249,8 @@ func (db database) UpdateBountyBoolColumn(b NewBounty, column string) NewBounty return b } -func (db database) DeleteBounty(pubkey string, created string) (Bounty, error) { - m := Bounty{} +func (db database) DeleteBounty(pubkey string, created string) (NewBounty, error) { + m := NewBounty{} db.db.Where("owner_id", pubkey).Where("created", created).Delete(&m) return m, nil } diff --git a/db/interface.go b/db/interface.go index 0a2371294..e3cc95163 100644 --- a/db/interface.go +++ b/db/interface.go @@ -47,7 +47,7 @@ type Database interface { CreateOrEditBounty(b NewBounty) (NewBounty, error) UpdateBountyNullColumn(b NewBounty, column string) NewBounty UpdateBountyBoolColumn(b NewBounty, column string) NewBounty - DeleteBounty(pubkey string, created string) (Bounty, error) + DeleteBounty(pubkey string, created string) (NewBounty, error) GetBountyByCreated(created uint) (NewBounty, error) GetBounty(id uint) NewBounty UpdateBounty(b NewBounty) (NewBounty, error) diff --git a/db/structs.go b/db/structs.go index 7a34d2520..7f1bf8ea6 100644 --- a/db/structs.go +++ b/db/structs.go @@ -736,6 +736,13 @@ type WithdrawBudgetRequest struct { OrgUuid string `json:"org_uuid"` } +// change back to WithdrawBudgetReques +type NewWithdrawBudgetRequest struct { + PaymentRequest string `json:"payment_request"` + Websocket_token string `json:"websocket_token,omitempty"` + WorkspaceUuid string `json:"workspace_uuid"` +} + type PaymentDateRange struct { StartDate string `json:"start_date"` EndDate string `json:"end_date"` diff --git a/handlers/bounty.go b/handlers/bounty.go index 7306b3a01..1e8caedb6 100644 --- a/handlers/bounty.go +++ b/handlers/bounty.go @@ -600,8 +600,6 @@ func (h *bountyHandler) BountyBudgetWithdraw(w http.ResponseWriter, r *http.Requ body, err := io.ReadAll(r.Body) r.Body.Close() - fmt.Println("WithdraW Request ===", request) - err = json.Unmarshal(body, &request) if err != nil { w.WriteHeader(http.StatusNotAcceptable) @@ -649,6 +647,71 @@ func (h *bountyHandler) BountyBudgetWithdraw(w http.ResponseWriter, r *http.Requ m.Unlock() } +// Todo: change back to NewBountyBudgetWithdraw +func (h *bountyHandler) NewBountyBudgetWithdraw(w http.ResponseWriter, r *http.Request) { + var m sync.Mutex + m.Lock() + + ctx := r.Context() + pubKeyFromAuth, _ := ctx.Value(auth.ContextKey).(string) + + if pubKeyFromAuth == "" { + fmt.Println("no pubkey from auth") + w.WriteHeader(http.StatusUnauthorized) + return + } + + request := db.NewWithdrawBudgetRequest{} + body, err := io.ReadAll(r.Body) + r.Body.Close() + + err = json.Unmarshal(body, &request) + if err != nil { + w.WriteHeader(http.StatusNotAcceptable) + return + } + + // check if user is the admin of the workspace + // or has a withdraw bounty budget role + hasRole := h.userHasAccess(pubKeyFromAuth, request.WorkspaceUuid, db.WithdrawBudget) + if !hasRole { + w.WriteHeader(http.StatusUnauthorized) + errMsg := formatPayError("You don't have appropriate permissions to withdraw bounty budget") + json.NewEncoder(w).Encode(errMsg) + return + } + + amount := utils.GetInvoiceAmount(request.PaymentRequest) + + if err == nil && amount > 0 { + // check if the workspace bounty balance + // is greater than the amount + orgBudget := h.db.GetWorkspaceBudget(request.WorkspaceUuid) + if amount > orgBudget.TotalBudget { + w.WriteHeader(http.StatusForbidden) + errMsg := formatPayError("Workspace budget is not enough to withdraw the amount") + json.NewEncoder(w).Encode(errMsg) + return + } + paymentSuccess, paymentError := h.PayLightningInvoice(request.PaymentRequest) + if paymentSuccess.Success { + // withdraw amount from workspace budget + h.db.WithdrawBudget(pubKeyFromAuth, request.WorkspaceUuid, amount) + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(paymentSuccess) + } else { + w.WriteHeader(http.StatusBadRequest) + json.NewEncoder(w).Encode(paymentError) + } + } else { + w.WriteHeader(http.StatusForbidden) + errMsg := formatPayError("Could not pay lightning invoice") + json.NewEncoder(w).Encode(errMsg) + } + + m.Unlock() +} + func formatPayError(errorMsg string) db.InvoicePayError { return db.InvoicePayError{ Success: false, diff --git a/handlers/bounty_test.go b/handlers/bounty_test.go index fb0750a20..bfed8dc93 100644 --- a/handlers/bounty_test.go +++ b/handlers/bounty_test.go @@ -261,6 +261,7 @@ func TestCreateOrEditBounty(t *testing.T) { Title: "first bounty", Description: "first bounty description", WorkspaceUuid: "work-1", + OrgUuid: "org-1", OwnerID: "test-key", } mockDb.On("UpdateBountyNullColumn", mock.AnythingOfType("db.NewBounty"), "assignee").Return(db.Bounty{Assignee: "test-key"}) @@ -446,7 +447,7 @@ func TestDeleteBounty(t *testing.T) { t.Run("should return error if failed to delete from db", func(t *testing.T) { rr := httptest.NewRecorder() handler := http.HandlerFunc(bHandler.DeleteBounty) - mockDb.On("DeleteBounty", "pub-key", "1111").Return(db.Bounty{}, errors.New("some-error")).Once() + mockDb.On("DeleteBounty", "pub-key", "1111").Return(db.NewBounty{}, errors.New("some-error")).Once() rctx := chi.NewRouteContext() rctx.URLParams.Add("pubkey", "pub-key") @@ -464,7 +465,7 @@ func TestDeleteBounty(t *testing.T) { t.Run("should successfully delete bounty from db", func(t *testing.T) { rr := httptest.NewRecorder() handler := http.HandlerFunc(bHandler.DeleteBounty) - existingBounty := db.Bounty{ + existingBounty := db.NewBounty{ OwnerID: "pub-key", Created: 1111, } @@ -479,7 +480,7 @@ func TestDeleteBounty(t *testing.T) { } handler.ServeHTTP(rr, req) - var returnedBounty db.Bounty + var returnedBounty db.NewBounty _ = json.Unmarshal(rr.Body.Bytes(), &returnedBounty) assert.Equal(t, http.StatusOK, rr.Code) assert.EqualValues(t, existingBounty, returnedBounty) @@ -879,6 +880,7 @@ func TestGetBountyById(t *testing.T) { Tribe: "development", Assignee: "user1", TicketUrl: "http://example.com/issues/1", + OrgUuid: "org-789", WorkspaceUuid: "work-789", Description: "This bounty is for fixing a critical bug in the payment system that causes transactions to fail under certain conditions.", WantedType: "immediate", @@ -904,7 +906,7 @@ func TestGetBountyById(t *testing.T) { mockDb.On("GetBountyById", mock.Anything).Return([]db.NewBounty{bounty}, nil).Once() mockDb.On("GetPersonByPubkey", "owner123").Return(db.Person{}).Once() mockDb.On("GetPersonByPubkey", "user1").Return(db.Person{}).Once() - mockDb.On("GetWorkspaceByUuid", "org-789").Return(db.Workspace{}).Once() + mockDb.On("GetWorkspaceByUuid", "work-789").Return(db.Workspace{}).Once() handler.ServeHTTP(rr, req) @@ -1298,7 +1300,7 @@ func TestMakeBountyPayment(t *testing.T) { Assignee: "assignee-1", Paid: false, }, nil) - mockDb.On("GetWorkspaceBudget", "org-1").Return(db.BountyBudget{ + mockDb.On("GetWorkspaceBudget", "work-1").Return(db.NewBountyBudget{ TotalBudget: 500, }, nil) @@ -1335,9 +1337,9 @@ func TestMakeBountyPayment(t *testing.T) { } mockDb.On("GetBounty", bountyID).Return(bounty, nil) - mockDb.On("GetWorkspaceBudget", bounty.OrgUuid).Return(db.BountyBudget{TotalBudget: 2000}, nil) + mockDb.On("GetWorkspaceBudget", bounty.WorkspaceUuid).Return(db.NewBountyBudget{TotalBudget: 2000}, nil) mockDb.On("GetPersonByPubkey", bounty.Assignee).Return(db.Person{OwnerPubKey: "assignee-1", OwnerRouteHint: "OwnerRouteHint"}, nil) - mockDb.On("AddPaymentHistory", mock.AnythingOfType("db.PaymentHistory")).Return(db.PaymentHistory{ID: 1}) + mockDb.On("AddPaymentHistory", mock.AnythingOfType("db.NewPaymentHistory")).Return(db.NewPaymentHistory{ID: 1}) mockDb.On("UpdateBounty", mock.AnythingOfType("db.NewBounty")).Run(func(args mock.Arguments) { updatedBounty := args.Get(0).(db.NewBounty) assert.True(t, updatedBounty.Paid) @@ -1384,7 +1386,7 @@ func TestMakeBountyPayment(t *testing.T) { bHandler2.userHasAccess = mockUserHasAccessTrue mockDb2.On("GetBounty", bountyID).Return(bounty, nil) - mockDb2.On("GetWorkspaceBudget", bounty.OrgUuid).Return(db.BountyBudget{TotalBudget: 2000}, nil) + mockDb2.On("GetWorkspaceBudget", bounty.WorkspaceUuid).Return(db.NewBountyBudget{TotalBudget: 2000}, nil) mockDb2.On("GetPersonByPubkey", bounty.Assignee).Return(db.Person{OwnerPubKey: "assignee-1", OwnerRouteHint: "OwnerRouteHint"}, nil) expectedUrl := fmt.Sprintf("%s/payment", config.RelayUrl) @@ -1484,7 +1486,7 @@ func TestBountyBudgetWithdraw(t *testing.T) { bHandler := NewBountyHandler(mockHttpClient, mockDb) bHandler.userHasAccess = mockUserHasAccessTrue - mockDb.On("GetWorkspaceBudget", "org-1").Return(db.BountyBudget{ + mockDb.On("GetWorkspaceBudget", "org-1").Return(db.NewBountyBudget{ TotalBudget: 500, }, nil) invoice := "lnbc15u1p3xnhl2pp5jptserfk3zk4qy42tlucycrfwxhydvlemu9pqr93tuzlv9cc7g3sdqsvfhkcap3xyhx7un8cqzpgxqzjcsp5f8c52y2stc300gl6s4xswtjpc37hrnnr3c9wvtgjfuvqmpm35evq9qyyssqy4lgd8tj637qcjp05rdpxxykjenthxftej7a2zzmwrmrl70fyj9hvj0rewhzj7jfyuwkwcg9g2jpwtk3wkjtwnkdks84hsnu8xps5vsq4gj5hs" @@ -1516,7 +1518,7 @@ func TestBountyBudgetWithdraw(t *testing.T) { paymentAmount := uint(1500) - mockDb.On("GetWorkspaceBudget", "org-1").Return(db.BountyBudget{ + mockDb.On("GetWorkspaceBudget", "org-1").Return(db.NewBountyBudget{ TotalBudget: 5000, }, nil) mockDb.On("WithdrawBudget", "valid-key", "org-1", paymentAmount).Return(nil) @@ -1553,7 +1555,7 @@ func TestBountyBudgetWithdraw(t *testing.T) { bHandler := NewBountyHandler(mockHttpClient, mockDb) bHandler.userHasAccess = mockUserHasAccessTrue - mockDb.On("GetWorkspaceBudget", "org-1").Return(db.BountyBudget{ + mockDb.On("GetWorkspaceBudget", "org-1").Return(db.NewBountyBudget{ TotalBudget: 5000, }, nil) mockHttpClient.On("Do", mock.AnythingOfType("*http.Request")).Return(&http.Response{ @@ -1602,7 +1604,7 @@ func TestBountyBudgetWithdraw(t *testing.T) { mockHttpClient.ExpectedCalls = nil mockHttpClient.Calls = nil - mockDb.On("GetWorkspaceBudget", "org-1").Return(db.BountyBudget{ + mockDb.On("GetWorkspaceBudget", "org-1").Return(db.NewBountyBudget{ TotalBudget: expectedFinalBudget, }, nil) mockDb.On("WithdrawBudget", "valid-key", "org-1", paymentAmount).Return(nil) @@ -1733,16 +1735,18 @@ func TestPollInvoice(t *testing.T) { }, nil).Once() bountyID := uint(1) - bounty := db.Bounty{ - ID: bountyID, - OrgUuid: "org-1", - Assignee: "assignee-1", - Price: uint(1000), + bounty := db.NewBounty{ + ID: bountyID, + WorkspaceUuid: "work-1", + OrgUuid: "org-1", + Assignee: "assignee-1", + Price: uint(1000), } now := time.Now() - expectedBounty := db.Bounty{ + expectedBounty := db.NewBounty{ ID: bountyID, + WorkspaceUuid: "work-1", OrgUuid: "org-1", Assignee: "assignee-1", Price: uint(1000), @@ -1751,15 +1755,15 @@ func TestPollInvoice(t *testing.T) { CompletionDate: &now, } - mockDb.On("GetInvoice", "1").Return(db.InvoiceList{Type: "KEYSEND"}) + mockDb.On("GetInvoice", "1").Return(db.NewInvoiceList{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("GetInvoice", "1").Return(db.NewInvoiceList{Status: false}) mockDb.On("GetBountyByCreated", uint(1234)).Return(bounty, nil) mockDb.On("UpdateBounty", mock.AnythingOfType("db.NewBounty")).Run(func(args mock.Arguments) { - updatedBounty := args.Get(0).(db.Bounty) + updatedBounty := args.Get(0).(db.NewBounty) assert.True(t, updatedBounty.Paid) }).Return(expectedBounty, nil).Once() - mockDb.On("UpdateInvoice", "1").Return(db.InvoiceList{}).Once() + mockDb.On("UpdateInvoice", "1").Return(db.NewInvoiceList{}).Once() expectedPaymentUrl := fmt.Sprintf("%s/payment", config.RelayUrl) expectedPaymentBody := `{"amount": 1000, "destination_key": "UserPubkey", "route_hint": "RouteHint", "text": "memotext added for notification"}` @@ -1804,11 +1808,11 @@ func TestPollInvoice(t *testing.T) { Body: r, }, nil).Once() - mockDb.On("GetInvoice", "1").Return(db.InvoiceList{Type: "BUDGET"}) + mockDb.On("GetInvoice", "1").Return(db.NewInvoiceList{Type: "BUDGET"}) 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("AddAndUpdateBudget", mock.Anything).Return(db.PaymentHistory{}) - mockDb.On("UpdateInvoice", "1").Return(db.InvoiceList{}).Once() + mockDb.On("GetInvoice", "1").Return(db.NewInvoiceList{Status: false}) + mockDb.On("AddAndUpdateBudget", mock.Anything).Return(db.NewPaymentHistory{}) + mockDb.On("UpdateInvoice", "1").Return(db.NewInvoiceList{}).Once() ro := chi.NewRouter() ro.Post("/poll/invoice/{paymentRequest}", bHandler.PollInvoice) diff --git a/handlers/tribes_test.go b/handlers/tribes_test.go index 8b1a6e96a..fabe3cf7e 100644 --- a/handlers/tribes_test.go +++ b/handlers/tribes_test.go @@ -4,12 +4,13 @@ import ( "bytes" "context" "encoding/json" - "github.com/stakwork/sphinx-tribes/config" "net/http" "net/http/httptest" "strings" "testing" + "github.com/stakwork/sphinx-tribes/config" + "github.com/go-chi/chi" "github.com/lib/pq" "github.com/stakwork/sphinx-tribes/auth" @@ -636,8 +637,8 @@ func TestGenerateBudgetInvoice(t *testing.T) { t.Run("Should mock a call to relay /invoices with the correct body", func(t *testing.T) { - mockDb.On("AddPaymentHistory", mock.AnythingOfType("db.PaymentHistory")).Return(db.PaymentHistory{}, nil) - mockDb.On("AddInvoice", mock.AnythingOfType("db.InvoiceList")).Return(db.InvoiceList{}, nil) + mockDb.On("AddPaymentHistory", mock.AnythingOfType("db.NewPaymentHistory")).Return(db.NewPaymentHistory{}, nil) + mockDb.On("AddInvoice", mock.AnythingOfType("db.NewInvoiceList")).Return(db.NewInvoiceList{}, nil) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -672,8 +673,8 @@ func TestGenerateBudgetInvoice(t *testing.T) { userAmount := float64(1000) - mockDb.On("AddPaymentHistory", mock.AnythingOfType("db.PaymentHistory")).Return(db.PaymentHistory{}, nil) - mockDb.On("AddInvoice", mock.AnythingOfType("db.InvoiceList")).Return(db.InvoiceList{}, nil) + mockDb.On("AddPaymentHistory", mock.AnythingOfType("db.NewPaymentHistory")).Return(db.NewPaymentHistory{}, nil) + mockDb.On("AddInvoice", mock.AnythingOfType("db.NewInvoiceList")).Return(db.NewInvoiceList{}, nil) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var body map[string]interface{} @@ -703,11 +704,11 @@ func TestGenerateBudgetInvoice(t *testing.T) { }) t.Run("Should add payments to the payment history and invoice to the invoice list upon successful relay call", func(t *testing.T) { - expectedPaymentHistory := db.PaymentHistory{Amount: userAmount} - expectedInvoice := db.InvoiceList{PaymentRequest: invoiceResponse.Response.Invoice} + expectedPaymentHistory := db.NewPaymentHistory{Amount: userAmount} + expectedInvoice := db.NewInvoiceList{PaymentRequest: invoiceResponse.Response.Invoice} - mockDb.On("AddPaymentHistory", mock.AnythingOfType("db.PaymentHistory")).Return(expectedPaymentHistory, nil) - mockDb.On("AddInvoice", mock.AnythingOfType("db.InvoiceList")).Return(expectedInvoice, nil) + mockDb.On("AddPaymentHistory", mock.AnythingOfType("db.NewPaymentHistory")).Return(expectedPaymentHistory, nil) + mockDb.On("AddInvoice", mock.AnythingOfType("db.NewInvoiceList")).Return(expectedInvoice, nil) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) @@ -734,7 +735,7 @@ func TestGenerateBudgetInvoice(t *testing.T) { assert.True(t, response.Succcess, "Invoice generation should be successful") assert.Equal(t, "example_invoice", response.Response.Invoice, "The invoice in the response should match the mock") - mockDb.AssertCalled(t, "AddPaymentHistory", mock.AnythingOfType("db.PaymentHistory")) - mockDb.AssertCalled(t, "AddInvoice", mock.AnythingOfType("db.InvoiceList")) + mockDb.AssertCalled(t, "AddPaymentHistory", mock.AnythingOfType("db.NewPaymentHistory")) + mockDb.AssertCalled(t, "AddInvoice", mock.AnythingOfType("db.NewInvoiceList")) }) } diff --git a/handlers/workspaces_test.go b/handlers/workspaces_test.go index 4447d4a47..09f291a5a 100644 --- a/handlers/workspaces_test.go +++ b/handlers/workspaces_test.go @@ -101,7 +101,7 @@ func TestUnitCreateOrEditWorkspace(t *testing.T) { assert.Equal(t, http.StatusBadRequest, rr.Code) }) - t.Run("should trim spaces from organization name", func(t *testing.T) { + t.Run("should trim spaces from workspace name", func(t *testing.T) { rr := httptest.NewRecorder() handler := http.HandlerFunc(oHandler.CreateOrEditWorkspace) @@ -130,7 +130,7 @@ func TestUnitCreateOrEditWorkspace(t *testing.T) { assert.Equal(t, "Abdul", responseOrg.Name) }) - t.Run("should successfully add organization if request is valid", func(t *testing.T) { + t.Run("should successfully add workspace if request is valid", func(t *testing.T) { rr := httptest.NewRecorder() handler := http.HandlerFunc(oHandler.CreateOrEditWorkspace) @@ -184,13 +184,13 @@ func TestDeleteWorkspace(t *testing.T) { oHandler := NewWorkspaceHandler(mockDb) t.Run("should return error if not authorized", func(t *testing.T) { - orgUUID := "org-uuid" + workspaceUUID := "org-uuid" rr := httptest.NewRecorder() handler := http.HandlerFunc(oHandler.DeleteWorkspace) rctx := chi.NewRouteContext() - rctx.URLParams.Add("uuid", orgUUID) - req, err := http.NewRequestWithContext(context.WithValue(context.Background(), chi.RouteCtxKey, rctx), http.MethodDelete, "/delete/"+orgUUID, nil) + rctx.URLParams.Add("uuid", workspaceUUID) + req, err := http.NewRequestWithContext(context.WithValue(context.Background(), chi.RouteCtxKey, rctx), http.MethodDelete, "/delete/"+workspaceUUID, nil) if err != nil { t.Fatal(err) } @@ -200,21 +200,21 @@ func TestDeleteWorkspace(t *testing.T) { assert.Equal(t, http.StatusUnauthorized, rr.Code) }) - t.Run("should set organization fields to null and delete users on successful delete", func(t *testing.T) { - orgUUID := "org-uuid" + t.Run("should set workspace fields to null and delete users on successful delete", func(t *testing.T) { + workspaceUUID := "org-uuid" // Mock expected database interactions - mockDb.On("GetWorkspaceByUuid", orgUUID).Return(db.Workspace{OwnerPubKey: "test-key"}).Once() - mockDb.On("UpdateWorkspaceForDeletion", orgUUID).Return(nil).Once() - mockDb.On("DeleteAllUsersFromWorkspace", orgUUID).Return(nil).Once() - mockDb.On("ChangeWorkspaceDeleteStatus", orgUUID, true).Return(db.Workspace{Uuid: orgUUID, Deleted: true}).Once() + mockDb.On("GetWorkspaceByUuid", workspaceUUID).Return(db.Workspace{OwnerPubKey: "test-key"}).Once() + mockDb.On("UpdateWorkspaceForDeletion", workspaceUUID).Return(nil).Once() + mockDb.On("DeleteAllUsersFromWorkspace", workspaceUUID).Return(nil).Once() + mockDb.On("ChangeWorkspaceDeleteStatus", workspaceUUID, true).Return(db.Workspace{Uuid: workspaceUUID, Deleted: true}).Once() rr := httptest.NewRecorder() handler := http.HandlerFunc(oHandler.DeleteWorkspace) rctx := chi.NewRouteContext() - rctx.URLParams.Add("uuid", orgUUID) - req, err := http.NewRequestWithContext(context.WithValue(ctx, chi.RouteCtxKey, rctx), http.MethodDelete, "/delete/"+orgUUID, nil) + rctx.URLParams.Add("uuid", workspaceUUID) + req, err := http.NewRequestWithContext(context.WithValue(ctx, chi.RouteCtxKey, rctx), http.MethodDelete, "/delete/"+workspaceUUID, nil) if err != nil { t.Fatal(err) } @@ -226,18 +226,18 @@ func TestDeleteWorkspace(t *testing.T) { }) t.Run("should handle failures in database updates", func(t *testing.T) { - orgUUID := "org-uuid" + workspaceUUID := "org-uuid" // Mock database interactions with error - mockDb.On("GetWorkspaceByUuid", orgUUID).Return(db.Workspace{OwnerPubKey: "test-key"}).Once() - mockDb.On("UpdateWorkspaceForDeletion", orgUUID).Return(errors.New("update error")).Once() + mockDb.On("GetWorkspaceByUuid", workspaceUUID).Return(db.Workspace{OwnerPubKey: "test-key"}).Once() + mockDb.On("UpdateWorkspaceForDeletion", workspaceUUID).Return(errors.New("update error")).Once() rr := httptest.NewRecorder() handler := http.HandlerFunc(oHandler.DeleteWorkspace) rctx := chi.NewRouteContext() - rctx.URLParams.Add("uuid", orgUUID) - req, err := http.NewRequestWithContext(context.WithValue(ctx, chi.RouteCtxKey, rctx), http.MethodDelete, "/delete/"+orgUUID, nil) + rctx.URLParams.Add("uuid", workspaceUUID) + req, err := http.NewRequestWithContext(context.WithValue(ctx, chi.RouteCtxKey, rctx), http.MethodDelete, "/delete/"+workspaceUUID, nil) if err != nil { t.Fatal(err) } @@ -248,21 +248,21 @@ func TestDeleteWorkspace(t *testing.T) { mockDb.AssertExpectations(t) }) - t.Run("should set organization's deleted column to true", func(t *testing.T) { - orgUUID := "org-uuid" + t.Run("should set workspace's deleted column to true", func(t *testing.T) { + workspaceUUID := "org-uuid" // Mock the database interactions - mockDb.On("GetWorkspaceByUuid", orgUUID).Return(db.Workspace{OwnerPubKey: "test-key"}).Once() - mockDb.On("UpdateWorkspaceForDeletion", orgUUID).Return(nil).Once() - mockDb.On("DeleteAllUsersFromWorkspace", orgUUID).Return(nil).Once() - mockDb.On("ChangeWorkspaceDeleteStatus", orgUUID, true).Return(db.Workspace{Uuid: orgUUID, Deleted: true}).Once() + mockDb.On("GetWorkspaceByUuid", workspaceUUID).Return(db.Workspace{OwnerPubKey: "test-key"}).Once() + mockDb.On("UpdateWorkspaceForDeletion", workspaceUUID).Return(nil).Once() + mockDb.On("DeleteAllUsersFromWorkspace", workspaceUUID).Return(nil).Once() + mockDb.On("ChangeWorkspaceDeleteStatus", workspaceUUID, true).Return(db.Workspace{Uuid: workspaceUUID, Deleted: true}).Once() rr := httptest.NewRecorder() handler := http.HandlerFunc(oHandler.DeleteWorkspace) rctx := chi.NewRouteContext() - rctx.URLParams.Add("uuid", orgUUID) - req, err := http.NewRequestWithContext(context.WithValue(ctx, chi.RouteCtxKey, rctx), http.MethodDelete, "/delete/"+orgUUID, nil) + rctx.URLParams.Add("uuid", workspaceUUID) + req, err := http.NewRequestWithContext(context.WithValue(ctx, chi.RouteCtxKey, rctx), http.MethodDelete, "/delete/"+workspaceUUID, nil) if err != nil { t.Fatal(err) } @@ -284,27 +284,27 @@ func TestDeleteWorkspace(t *testing.T) { }) t.Run("should set Website, Github, and Description to empty strings", func(t *testing.T) { - orgUUID := "org-uuid" + workspaceUUID := "org-uuid" updatedOrg := db.Workspace{ - Uuid: orgUUID, + Uuid: workspaceUUID, OwnerPubKey: "test-key", Website: "", Github: "", Description: "", } - mockDb.On("GetWorkspaceByUuid", orgUUID).Return(db.Workspace{OwnerPubKey: "test-key"}).Once() - mockDb.On("UpdateWorkspaceForDeletion", orgUUID).Return(nil).Once() - mockDb.On("DeleteAllUsersFromWorkspace", orgUUID).Return(nil).Once() - mockDb.On("ChangeWorkspaceDeleteStatus", orgUUID, true).Return(updatedOrg).Once() + mockDb.On("GetWorkspaceByUuid", workspaceUUID).Return(db.Workspace{OwnerPubKey: "test-key"}).Once() + mockDb.On("UpdateWorkspaceForDeletion", workspaceUUID).Return(nil).Once() + mockDb.On("DeleteAllUsersFromWorkspace", workspaceUUID).Return(nil).Once() + mockDb.On("ChangeWorkspaceDeleteStatus", workspaceUUID, true).Return(updatedOrg).Once() rr := httptest.NewRecorder() handler := http.HandlerFunc(oHandler.DeleteWorkspace) rctx := chi.NewRouteContext() - rctx.URLParams.Add("uuid", orgUUID) - req, err := http.NewRequestWithContext(context.WithValue(ctx, chi.RouteCtxKey, rctx), http.MethodDelete, "/delete/"+orgUUID, nil) + rctx.URLParams.Add("uuid", workspaceUUID) + req, err := http.NewRequestWithContext(context.WithValue(ctx, chi.RouteCtxKey, rctx), http.MethodDelete, "/delete/"+workspaceUUID, nil) if err != nil { t.Fatal(err) } @@ -324,21 +324,21 @@ func TestDeleteWorkspace(t *testing.T) { mockDb.AssertExpectations(t) }) - t.Run("should delete all users from the organization", func(t *testing.T) { - orgUUID := "org-uuid" + t.Run("should delete all users from the workspace", func(t *testing.T) { + workspaceUUID := "org-uuid" // Setting up the expected behavior of the mock database - mockDb.On("GetWorkspaceByUuid", orgUUID).Return(db.Workspace{OwnerPubKey: "test-key"}).Once() - mockDb.On("UpdateWorkspaceForDeletion", orgUUID).Return(nil).Once() - mockDb.On("DeleteAllUsersFromWorkspace", orgUUID).Return(nil).Run(func(args mock.Arguments) {}).Once() - mockDb.On("ChangeWorkspaceDeleteStatus", orgUUID, true).Return(db.Workspace{Uuid: orgUUID, Deleted: true}).Once() + mockDb.On("GetWorkspaceByUuid", workspaceUUID).Return(db.Workspace{OwnerPubKey: "test-key"}).Once() + mockDb.On("UpdateWorkspaceForDeletion", workspaceUUID).Return(nil).Once() + mockDb.On("DeleteAllUsersFromWorkspace", workspaceUUID).Return(nil).Run(func(args mock.Arguments) {}).Once() + mockDb.On("ChangeWorkspaceDeleteStatus", workspaceUUID, true).Return(db.Workspace{Uuid: workspaceUUID, Deleted: true}).Once() rr := httptest.NewRecorder() handler := http.HandlerFunc(oHandler.DeleteWorkspace) rctx := chi.NewRouteContext() - rctx.URLParams.Add("uuid", orgUUID) - req, err := http.NewRequestWithContext(context.WithValue(ctx, chi.RouteCtxKey, rctx), http.MethodDelete, "/delete/"+orgUUID, nil) + rctx.URLParams.Add("uuid", workspaceUUID) + req, err := http.NewRequestWithContext(context.WithValue(ctx, chi.RouteCtxKey, rctx), http.MethodDelete, "/delete/"+workspaceUUID, nil) if err != nil { t.Fatal(err) } @@ -359,19 +359,19 @@ func TestGetWorkspaceBounties(t *testing.T) { } oHandler := NewWorkspaceHandler(mockDb) - t.Run("Should test that an organization's bounties can be listed without authentication", func(t *testing.T) { - orgUUID := "valid-uuid" + t.Run("Should test that a workspace's bounties can be listed without authentication", func(t *testing.T) { + workspaceUUID := "valid-uuid" oHandler.generateBountyHandler = mockGenerateBountyHandler - expectedBounties := []db.Bounty{{}, {}} // Mocked response - mockDb.On("GetWorkspaceBounties", mock.AnythingOfType("*http.Request"), orgUUID).Return(expectedBounties).Once() + expectedBounties := []db.NewBounty{{}, {}} // Mocked response + mockDb.On("GetWorkspaceBounties", mock.AnythingOfType("*http.Request"), workspaceUUID).Return(expectedBounties).Once() rr := httptest.NewRecorder() handler := http.HandlerFunc(oHandler.GetWorkspaceBounties) rctx := chi.NewRouteContext() - rctx.URLParams.Add("uuid", orgUUID) - req, err := http.NewRequestWithContext(context.WithValue(context.Background(), chi.RouteCtxKey, rctx), http.MethodGet, "/bounties/"+orgUUID, nil) + rctx.URLParams.Add("uuid", workspaceUUID) + req, err := http.NewRequestWithContext(context.WithValue(context.Background(), chi.RouteCtxKey, rctx), http.MethodGet, "/bounties/"+workspaceUUID, nil) if err != nil { t.Fatal(err) } @@ -381,17 +381,17 @@ func TestGetWorkspaceBounties(t *testing.T) { assert.Equal(t, http.StatusOK, rr.Code) }) - t.Run("should return empty array when wrong organization UUID is passed", func(t *testing.T) { - orgUUID := "wrong-uuid" + t.Run("should return empty array when wrong workspace UUID is passed", func(t *testing.T) { + workspaceUUID := "wrong-uuid" - mockDb.On("GetWorkspaceBounties", mock.AnythingOfType("*http.Request"), orgUUID).Return([]db.Bounty{}).Once() + mockDb.On("GetWorkspaceBounties", mock.AnythingOfType("*http.Request"), workspaceUUID).Return([]db.NewBounty{}).Once() rr := httptest.NewRecorder() handler := http.HandlerFunc(oHandler.GetWorkspaceBounties) rctx := chi.NewRouteContext() - rctx.URLParams.Add("uuid", orgUUID) - req, err := http.NewRequestWithContext(context.WithValue(ctx, chi.RouteCtxKey, rctx), http.MethodGet, "/bounties/"+orgUUID+"?limit=10&sortBy=created&search=test&page=1&resetPage=true", nil) + rctx.URLParams.Add("uuid", workspaceUUID) + req, err := http.NewRequestWithContext(context.WithValue(ctx, chi.RouteCtxKey, rctx), http.MethodGet, "/bounties/"+workspaceUUID+"?limit=10&sortBy=created&search=test&page=1&resetPage=true", nil) if err != nil { t.Fatal(err) } @@ -414,12 +414,12 @@ func TestGetWorkspaceBudget(t *testing.T) { } oHandler := NewWorkspaceHandler(mockDb) - t.Run("Should test that a 401 is returned when trying to view an organization's budget without a token", func(t *testing.T) { - orgUUID := "valid-uuid" + t.Run("Should test that a 401 is returned when trying to view an workspace's budget without a token", func(t *testing.T) { + workspaceUUID := "valid-uuid" rctx := chi.NewRouteContext() - rctx.URLParams.Add("uuid", orgUUID) - req, err := http.NewRequestWithContext(context.WithValue(context.Background(), chi.RouteCtxKey, rctx), http.MethodGet, "/budget/"+orgUUID, nil) + rctx.URLParams.Add("uuid", workspaceUUID) + req, err := http.NewRequestWithContext(context.WithValue(context.Background(), chi.RouteCtxKey, rctx), http.MethodGet, "/budget/"+workspaceUUID, nil) if err != nil { t.Fatal(err) } @@ -430,10 +430,10 @@ func TestGetWorkspaceBudget(t *testing.T) { assert.Equal(t, http.StatusUnauthorized, rr.Code) }) - t.Run("Should test that the right workspace budget is returned, if the user is the organization admin or has the ViewReport role", func(t *testing.T) { - orgUUID := "valid-uuid" + t.Run("Should test that the right workspace budget is returned, if the user is the workspace admin or has the ViewReport role", func(t *testing.T) { + workspaceUUID := "valid-uuid" statusBudget := db.StatusBudget{ - OrgUuid: orgUUID, + OrgUuid: workspaceUUID, CurrentBudget: 10000, OpenBudget: 1000, OpenCount: 10, @@ -444,11 +444,11 @@ func TestGetWorkspaceBudget(t *testing.T) { } oHandler.userHasAccess = mockUserHasAccess - mockDb.On("GetWorkspaceStatusBudget", orgUUID).Return(statusBudget).Once() + mockDb.On("GetWorkspaceStatusBudget", workspaceUUID).Return(statusBudget).Once() rctx := chi.NewRouteContext() - rctx.URLParams.Add("uuid", orgUUID) - req, err := http.NewRequestWithContext(context.WithValue(ctx, chi.RouteCtxKey, rctx), http.MethodGet, "/budget/"+orgUUID, nil) + rctx.URLParams.Add("uuid", workspaceUUID) + req, err := http.NewRequestWithContext(context.WithValue(ctx, chi.RouteCtxKey, rctx), http.MethodGet, "/budget/"+workspaceUUID, nil) if err != nil { t.Fatal(err) } @@ -473,8 +473,8 @@ func TestGetWorkspaceBudgetHistory(t *testing.T) { mockDb := mocks.NewDatabase(t) oHandler := NewWorkspaceHandler(mockDb) - t.Run("Should test that a 401 is returned when trying to view an organization's budget history without a token", func(t *testing.T) { - orgUUID := "valid-uuid" + t.Run("Should test that a 401 is returned when trying to view an workspace's budget history without a token", func(t *testing.T) { + workspaceUUID := "valid-uuid" mockUserHasAccess := func(pubKeyFromAuth string, uuid string, role string) bool { return false @@ -482,8 +482,8 @@ func TestGetWorkspaceBudgetHistory(t *testing.T) { oHandler.userHasAccess = mockUserHasAccess rctx := chi.NewRouteContext() - rctx.URLParams.Add("uuid", orgUUID) - req, err := http.NewRequestWithContext(context.WithValue(context.Background(), chi.RouteCtxKey, rctx), http.MethodGet, "/budget/history/"+orgUUID, nil) + rctx.URLParams.Add("uuid", workspaceUUID) + req, err := http.NewRequestWithContext(context.WithValue(context.Background(), chi.RouteCtxKey, rctx), http.MethodGet, "/budget/history/"+workspaceUUID, nil) if err != nil { t.Fatal(err) } @@ -494,11 +494,11 @@ func TestGetWorkspaceBudgetHistory(t *testing.T) { assert.Equal(t, http.StatusUnauthorized, rr.Code) }) - t.Run("Should test that the right budget history is returned, if the user is the organization admin or has the ViewReport role", func(t *testing.T) { - orgUUID := "valid-uuid" + t.Run("Should test that the right budget history is returned, if the user is the workspace admin or has the ViewReport role", func(t *testing.T) { + workspaceUUID := "valid-uuid" expectedBudgetHistory := []db.BudgetHistoryData{ - {BudgetHistory: db.BudgetHistory{ID: 1, OrgUuid: orgUUID, Created: nil, Updated: nil}, SenderName: "Sender1"}, - {BudgetHistory: db.BudgetHistory{ID: 2, OrgUuid: orgUUID, Created: nil, Updated: nil}, SenderName: "Sender2"}, + {BudgetHistory: db.BudgetHistory{ID: 1, OrgUuid: workspaceUUID, Created: nil, Updated: nil}, SenderName: "Sender1"}, + {BudgetHistory: db.BudgetHistory{ID: 2, OrgUuid: workspaceUUID, Created: nil, Updated: nil}, SenderName: "Sender2"}, } mockUserHasAccess := func(pubKeyFromAuth string, uuid string, role string) bool { @@ -506,11 +506,11 @@ func TestGetWorkspaceBudgetHistory(t *testing.T) { } oHandler.userHasAccess = mockUserHasAccess - mockDb.On("GetWorkspaceBudgetHistory", orgUUID).Return(expectedBudgetHistory).Once() + mockDb.On("GetWorkspaceBudgetHistory", workspaceUUID).Return(expectedBudgetHistory).Once() rctx := chi.NewRouteContext() - rctx.URLParams.Add("uuid", orgUUID) - req, err := http.NewRequestWithContext(context.WithValue(ctx, chi.RouteCtxKey, rctx), http.MethodGet, "/budget/history/"+orgUUID, nil) + rctx.URLParams.Add("uuid", workspaceUUID) + req, err := http.NewRequestWithContext(context.WithValue(ctx, chi.RouteCtxKey, rctx), http.MethodGet, "/budget/history/"+workspaceUUID, nil) if err != nil { t.Fatal(err) } @@ -535,15 +535,15 @@ func TestGetWorkspaceBountiesCount(t *testing.T) { mockDb := mocks.NewDatabase(t) oHandler := NewWorkspaceHandler(mockDb) - t.Run("should return the count of organization bounties", func(t *testing.T) { - orgUUID := "valid-uuid" + t.Run("should return the count of workspace bounties", func(t *testing.T) { + workspaceUUID := "valid-uuid" expectedCount := int64(5) - mockDb.On("GetWorkspaceBountiesCount", mock.AnythingOfType("*http.Request"), orgUUID).Return(expectedCount).Once() + mockDb.On("GetWorkspaceBountiesCount", mock.AnythingOfType("*http.Request"), workspaceUUID).Return(expectedCount).Once() rctx := chi.NewRouteContext() - rctx.URLParams.Add("uuid", orgUUID) - req, err := http.NewRequestWithContext(context.WithValue(ctx, chi.RouteCtxKey, rctx), http.MethodGet, "/bounties/"+orgUUID+"/count/", nil) + rctx.URLParams.Add("uuid", workspaceUUID) + req, err := http.NewRequestWithContext(context.WithValue(ctx, chi.RouteCtxKey, rctx), http.MethodGet, "/bounties/"+workspaceUUID+"/count/", nil) if err != nil { t.Fatal(err) } diff --git a/mocks/Database.go b/mocks/Database.go index 1de316131..784a2492a 100644 --- a/mocks/Database.go +++ b/mocks/Database.go @@ -1284,22 +1284,22 @@ func (_c *Database_DeleteAllUsersFromWorkspace_Call) RunAndReturn(run func(strin } // DeleteBounty provides a mock function with given fields: pubkey, created -func (_m *Database) DeleteBounty(pubkey string, created string) (db.Bounty, error) { +func (_m *Database) DeleteBounty(pubkey string, created string) (db.NewBounty, error) { ret := _m.Called(pubkey, created) if len(ret) == 0 { panic("no return value specified for DeleteBounty") } - var r0 db.Bounty + var r0 db.NewBounty var r1 error - if rf, ok := ret.Get(0).(func(string, string) (db.Bounty, error)); ok { + if rf, ok := ret.Get(0).(func(string, string) (db.NewBounty, error)); ok { return rf(pubkey, created) } - if rf, ok := ret.Get(0).(func(string, string) db.Bounty); ok { + if rf, ok := ret.Get(0).(func(string, string) db.NewBounty); ok { r0 = rf(pubkey, created) } else { - r0 = ret.Get(0).(db.Bounty) + r0 = ret.Get(0).(db.NewBounty) } if rf, ok := ret.Get(1).(func(string, string) error); ok { @@ -1330,12 +1330,12 @@ func (_c *Database_DeleteBounty_Call) Run(run func(pubkey string, created string return _c } -func (_c *Database_DeleteBounty_Call) Return(_a0 db.Bounty, _a1 error) *Database_DeleteBounty_Call { +func (_c *Database_DeleteBounty_Call) Return(_a0 db.NewBounty, _a1 error) *Database_DeleteBounty_Call { _c.Call.Return(_a0, _a1) return _c } -func (_c *Database_DeleteBounty_Call) RunAndReturn(run func(string, string) (db.Bounty, error)) *Database_DeleteBounty_Call { +func (_c *Database_DeleteBounty_Call) RunAndReturn(run func(string, string) (db.NewBounty, error)) *Database_DeleteBounty_Call { _c.Call.Return(run) return _c } diff --git a/routes/bounty.go b/routes/bounty.go index 6f743d479..a4a82ef9e 100644 --- a/routes/bounty.go +++ b/routes/bounty.go @@ -35,6 +35,7 @@ func BountyRoutes() chi.Router { r.Use(auth.PubKeyContext) r.Post("/pay/{id}", bountyHandler.MakeBountyPayment) r.Post("/budget/withdraw", bountyHandler.BountyBudgetWithdraw) + r.Post("/budget_workspace/withdraw", bountyHandler.NewBountyBudgetWithdraw) r.Post("/", bountyHandler.CreateOrEditBounty) r.Delete("/assignee", handlers.DeleteBountyAssignee)