diff --git a/lib/algora/bounties/bounties.ex b/lib/algora/bounties/bounties.ex index 35f3a97bc..806ee0161 100644 --- a/lib/algora/bounties/bounties.ex +++ b/lib/algora/bounties/bounties.ex @@ -917,12 +917,14 @@ defmodule Algora.Bounties do |> join(:left, [t: t], r in assoc(t, :repository), as: :r) |> join(:left, [r: r], ro in assoc(r, :user), as: :ro) |> where([b], not is_nil(b.amount)) + |> where([b], b.status != :cancelled) |> apply_criteria(criteria) |> order_by([b], desc: b.amount, desc: b.inserted_at, desc: b.id) |> select([b, o: o, t: t, ro: ro, r: r], %{ id: b.id, inserted_at: b.inserted_at, amount: b.amount, + status: b.status, owner: %{ id: o.id, name: o.name, @@ -956,6 +958,7 @@ defmodule Algora.Bounties do join: user in assoc(c, :user), left_join: s in assoc(c, :source), where: t.id in ^ticket_ids, + where: c.status != :cancelled, select_merge: %{user: user, source: s} ) end diff --git a/lib/algora_web/controllers/webhooks/github_controller.ex b/lib/algora_web/controllers/webhooks/github_controller.ex index 43bc6f8fc..b81ffe47f 100644 --- a/lib/algora_web/controllers/webhooks/github_controller.ex +++ b/lib/algora_web/controllers/webhooks/github_controller.ex @@ -71,6 +71,7 @@ defmodule AlgoraWeb.Webhooks.GithubController do from c in Claim, join: s in assoc(c, :source), join: u in assoc(c, :user), + where: c.status != :cancelled, where: s.id == ^source.id, where: u.provider == "github", where: u.provider_id == ^to_string(payload["pull_request"]["user"]["id"]), @@ -82,7 +83,7 @@ defmodule AlgoraWeb.Webhooks.GithubController do %Claim{group_id: group_id} -> Repo.update_all( - from(c in Claim, where: c.group_id == ^group_id), + from(c in Claim, where: c.group_id == ^group_id, where: c.status != :cancelled), set: [status: :approved] ) @@ -93,6 +94,7 @@ defmodule AlgoraWeb.Webhooks.GithubController do join: tru in assoc(tr, :user), join: u in assoc(c, :user), where: c.group_id == ^group_id, + where: c.status != :cancelled, order_by: [desc: c.group_share, asc: c.inserted_at], select_merge: %{ target: %{t | repository: %{tr | user: tru}}, diff --git a/test/algora/bounties_test.exs b/test/algora/bounties_test.exs index 75698ba64..99d18bdd9 100644 --- a/test/algora/bounties_test.exs +++ b/test/algora/bounties_test.exs @@ -14,6 +14,14 @@ defmodule Algora.BountiesTest do alias Algora.PSP alias Bounties.Tip + setup do + repo_owner = insert!(:user) + repo = insert!(:repository, %{user: repo_owner}) + ticket = insert!(:ticket, %{repository: repo}) + + %{ticket: ticket} + end + describe "bounties" do test "create" do creator = insert!(:user) @@ -369,4 +377,30 @@ defmodule Algora.BountiesTest do assert response == String.trim(expected_response) end end + + describe "list_bounties/1" do + test "does not include cancelled bounties", %{ticket: ticket} do + insert!(:bounty, status: :open, ticket: ticket, owner: insert!(:user)) + insert!(:bounty, status: :paid, ticket: ticket, owner: insert!(:user)) + insert!(:bounty, status: :cancelled, ticket: ticket, owner: insert!(:user)) + + bounties = Bounties.list_bounties() + assert Enum.any?(bounties, &(&1.status == :open)) + assert Enum.any?(bounties, &(&1.status == :paid)) + refute Enum.any?(bounties, &(&1.status == :cancelled)) + end + end + + describe "list_claims/1" do + test "does not include cancelled claims", %{ticket: ticket} do + insert!(:claim, status: :pending, target: ticket, user: insert!(:user)) + insert!(:claim, status: :approved, target: ticket, user: insert!(:user)) + insert!(:claim, status: :cancelled, target: ticket, user: insert!(:user)) + + claims = Bounties.list_claims([ticket.id]) + assert Enum.any?(claims, &(&1.status == :pending)) + assert Enum.any?(claims, &(&1.status == :approved)) + refute Enum.any?(claims, &(&1.status == :cancelled)) + end + end end diff --git a/test/algora_web/controllers/webhooks/github_controller_test.exs b/test/algora_web/controllers/webhooks/github_controller_test.exs index 4b1a84019..ff211ee79 100644 --- a/test/algora_web/controllers/webhooks/github_controller_test.exs +++ b/test/algora_web/controllers/webhooks/github_controller_test.exs @@ -14,6 +14,7 @@ defmodule AlgoraWeb.Webhooks.GithubControllerTest do alias Algora.Github.Webhook alias Algora.Payments.Transaction alias Algora.Repo + alias Algora.Workspace.Ticket alias AlgoraWeb.Webhooks.GithubController setup do @@ -220,7 +221,7 @@ defmodule AlgoraWeb.Webhooks.GithubControllerTest do test "does not allow multiple claims in a single PR", ctx do issue_number1 = :rand.uniform(1000) - issue_number2 = :rand.uniform(1000) + issue_number2 = issue_number1 + 1 pr_number = :rand.uniform(1000) process_scenario!(ctx, [ @@ -252,7 +253,7 @@ defmodule AlgoraWeb.Webhooks.GithubControllerTest do test "cancels existing claim when attempting to claim a different bounty in the same PR", ctx do issue_number1 = :rand.uniform(1000) - issue_number2 = :rand.uniform(1000) + issue_number2 = issue_number1 + 1 pr_number = :rand.uniform(1000) process_scenario!(ctx, [ @@ -510,6 +511,79 @@ defmodule AlgoraWeb.Webhooks.GithubControllerTest do assert is_nil(transfer) end + test "handles autopay when claim is changed to a different bounty and PR is merged", ctx do + issue_number1 = :rand.uniform(1000) + issue_number2 = issue_number1 + :rand.uniform(1000) + pr_number = issue_number1 + :rand.uniform(1000) + + customer = insert!(:customer, user: ctx[:org]) + _payment_method = insert!(:payment_method, is_default: true, customer: customer) + + process_scenario!(ctx, [ + %{ + event_action: "issue_comment.created", + user_type: :admin, + body: "/bounty $100", + params: %{"issue" => %{"number" => issue_number1}} + }, + %{ + event_action: "issue_comment.created", + user_type: :admin, + body: "/bounty $200", + params: %{"issue" => %{"number" => issue_number2}} + }, + %{ + event_action: "pull_request.opened", + user_type: :unauthorized, + body: "/claim #{issue_number1}", + params: %{"pull_request" => %{"number" => pr_number}} + }, + %{ + event_action: "pull_request.edited", + user_type: :unauthorized, + body: "/claim #{issue_number2}", + params: %{"pull_request" => %{"number" => pr_number}} + }, + %{ + event_action: "pull_request.closed", + user_type: :unauthorized, + body: "/claim #{issue_number2}", + params: %{"pull_request" => %{"number" => pr_number, "merged_at" => DateTime.to_iso8601(DateTime.utc_now())}} + } + ]) + + ticket1 = Repo.get_by!(Ticket, number: issue_number1) + ticket2 = Repo.get_by!(Ticket, number: issue_number2) + + _bounty1 = Repo.get_by!(Bounty, ticket_id: ticket1.id) + bounty2 = Repo.get_by!(Bounty, ticket_id: ticket2.id) + + cancelled_claim = Repo.get_by!(Claim, target_id: ticket1.id) + active_claim = Repo.get_by!(Claim, target_id: ticket2.id) + + assert active_claim.status == :approved + assert cancelled_claim.status == :cancelled + + charge = Repo.one!(from t in Transaction, where: t.type == :charge) + assert Money.equal?(charge.net_amount, Money.new(:USD, 200)) + assert charge.status == :initialized + assert charge.user_id == ctx[:org].id + + debit = Repo.one!(from t in Transaction, where: t.type == :debit) + assert Money.equal?(debit.net_amount, Money.new(:USD, 200)) + assert debit.status == :initialized + assert debit.user_id == ctx[:org].id + assert debit.bounty_id == bounty2.id + assert debit.claim_id == active_claim.id + + credit = Repo.one!(from t in Transaction, where: t.type == :credit) + assert Money.equal?(credit.net_amount, Money.new(:USD, 200)) + assert credit.status == :initialized + assert credit.user_id == ctx[:unauthorized_user].id + assert credit.bounty_id == bounty2.id + assert credit.claim_id == active_claim.id + end + test "prevents duplicate transaction creation when receiving multiple PR closed events", ctx do issue_number = :rand.uniform(1000) pr_number = :rand.uniform(1000) diff --git a/test/support/factory.ex b/test/support/factory.ex index 08a036193..dadfca06b 100644 --- a/test/support/factory.ex +++ b/test/support/factory.ex @@ -202,7 +202,9 @@ defmodule Algora.Factory do def bounty_factory do %Algora.Bounties.Bounty{ - id: Nanoid.generate() + id: Nanoid.generate(), + status: :open, + amount: Money.new!(100, :USD) } end