Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions lib/code_corps/github/webhook/handler.ex
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ defmodule CodeCorps.GitHub.Webhook.Handler do
"""
@spec handle_supported(String.t, String.t, map) :: {:ok, GithubEvent.t}
def handle_supported(type, id, %{} = payload) do
with {:ok, %GithubEvent{} = event} <- type |> build_params(id, "unprocessed", payload) |> create_event() do
with {:ok, %GithubEvent{} = event} <- find_or_create_event(type, id, payload, "unprocessed") do
payload |> apply_handler(type) |> Event.stop_processing(event)
end
end
Expand All @@ -41,7 +41,7 @@ defmodule CodeCorps.GitHub.Webhook.Handler do
"""
@spec handle_unsupported(String.t, String.t, map) :: {:ok, GithubEvent.t}
def handle_unsupported(type, id, %{} = payload) do
type |> build_params(id, "unsupported", payload) |> create_event()
find_or_create_event(type, id, payload, "unsupported")
end

@spec build_params(String.t, String.t, String.t, map) :: map
Expand All @@ -55,6 +55,14 @@ defmodule CodeCorps.GitHub.Webhook.Handler do
}
end

@spec find_or_create_event(String.t, String.t, map, String.t) :: {:ok, GithubEvent.t}
defp find_or_create_event(type, id, payload, status) do
case GithubEvent |> Repo.get_by(github_delivery_id: id) do
nil -> type |> build_params(id, status, payload) |> create_event()
%GithubEvent{} = github_event -> {:ok, github_event}
end
end

@spec create_event(map) :: {:ok, GithubEvent.t}
defp create_event(%{} = params) do
%GithubEvent{} |> GithubEvent.changeset(params) |> Repo.insert()
Expand Down
26 changes: 25 additions & 1 deletion lib/code_corps/model/github_event.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@ defmodule CodeCorps.GithubEvent do
use CodeCorps.Model
use Scrivener, page_size: 20

alias Ecto.Changeset

@type t :: %__MODULE__{}

schema "github_events" do
field :action, :string
field :data, :string
field :error, :string
field :failure_reason, :string
field :github_delivery_id, :string
field :payload, :map
field :error, :string
field :retry, :boolean, virtual: true
field :status, :string
field :type, :string

Expand All @@ -24,5 +27,26 @@ defmodule CodeCorps.GithubEvent do
struct
|> cast(params, [:action, :data, :github_delivery_id, :payload, :error, :status, :type])
|> validate_required([:action, :github_delivery_id, :payload, :status, :type])
|> validate_inclusion(:status, statuses())
end

def update_changeset(struct, params \\ %{}) do
struct
|> cast(params, [:retry, :status])
|> validate_acceptance(:retry)
|> validate_retry()
|> validate_inclusion(:status, statuses())
end

def statuses do
~w{unprocessed processing processed errored unsupported reprocessing}
end

defp validate_retry(%Changeset{changes: %{retry: true}} = changeset) do
case changeset |> Changeset.get_field(:status) do
"errored" -> Changeset.put_change(changeset, :status, "reprocessing")
_ -> Changeset.add_error(changeset, :retry, "only possible when status is errored")
end
end
defp validate_retry(changeset), do: changeset
end
3 changes: 3 additions & 0 deletions lib/code_corps/policy/github_event.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,7 @@ defmodule CodeCorps.Policy.GithubEvent do

def show?(%User{admin: true}), do: true
def show?(%User{admin: false}), do: false

def update?(%User{admin: true}), do: true
def update?(%User{admin: false}), do: false
end
1 change: 1 addition & 0 deletions lib/code_corps/policy/policy.ex
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ defmodule CodeCorps.Policy do
# GithubEvent
defp can?(%User{} = current_user, :index, %GithubEvent{}, %{}), do: Policy.GithubEvent.index?(current_user)
defp can?(%User{} = current_user, :show, %GithubEvent{}, %{}), do: Policy.GithubEvent.show?(current_user)
defp can?(%User{} = current_user, :update, %GithubEvent{}, %{}), do: Policy.GithubEvent.update?(current_user)

# GithubRepo
defp can?(%User{} = current_user, :update, %GithubRepo{} = github_repo, %{} = params), do: Policy.GithubRepo.update?(current_user, github_repo, params)
Expand Down
57 changes: 48 additions & 9 deletions lib/code_corps_web/controllers/github_event_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,20 @@ defmodule CodeCorpsWeb.GithubEventController do
type = conn |> get_event_type
delivery_id = conn |> get_delivery_id()
action = payload |> Map.get("action", "")
event_support = type |> EventSupport.status(action)
event_support |> process_event(type, delivery_id, payload)
conn |> respond_to_webhook(event_support)
end

case type |> EventSupport.status(action) do
:supported ->
Processor.process(fn -> Handler.handle_supported(type, delivery_id, payload) end)
conn |> send_resp(200, "")
:unsupported ->
Processor.process(fn -> Handler.handle_unsupported(type, delivery_id, payload) end)
conn |> send_resp(200, "")
:ignored ->
conn |> send_resp(202, "")
@spec update(Conn.t, map) :: Conn.t
def update(%Conn{} = conn, %{"id" => id} = params) do
with %GithubEvent{} = github_event <- GithubEvent |> Repo.get(id),
%User{} = current_user <- conn |> Guardian.Plug.current_resource,
{:ok, :authorized} <- current_user |> Policy.authorize(:update, github_event, params),
changeset <- github_event |> GithubEvent.update_changeset(params),
{:ok, updated_github_event} <- changeset |> retry_event()
do
conn |> render("show.json-api", data: updated_github_event)
end
end

Expand All @@ -76,4 +80,39 @@ defmodule CodeCorpsWeb.GithubEventController do
defp paginate(query, _) do
query |> Repo.all()
end

@spec process_event(atom, String.t, String.t, map) :: any | :ok
defp process_event(:supported, type, delivery_id, payload) do
Processor.process(fn -> Handler.handle_supported(type, delivery_id, payload) end)
end
defp process_event(:unsupported, type, delivery_id, payload) do
Processor.process(fn -> Handler.handle_unsupported(type, delivery_id, payload) end)
end
defp process_event(:ignored, _, _, _), do: :ok

@type retry_outcome :: {:ok, GithubEvent.t} | {:error, Ecto.Changeset.t} | :ok

@spec retry_event(Ecto.Changeset.t) :: retry_outcome
defp retry_event(%Ecto.Changeset{data: %GithubEvent{action: action, type: type}} = changeset) do
type
|> EventSupport.status(action)
|> do_retry_event(changeset)
end

@spec do_retry_event(atom, Ecto.Changeset.t) :: retry_outcome
defp do_retry_event(:ignored, _changeset), do: nil
defp do_retry_event(support, %Ecto.Changeset{data: %GithubEvent{github_delivery_id: delivery_id, payload: payload, type: type}} = changeset) do
case changeset |> Repo.update() do
{:ok, %GithubEvent{} = github_event} ->
process_event(support, type, delivery_id, payload)
{:ok, github_event}
{:error, error} ->
{:error, error}
end
end

@spec respond_to_webhook(Conn.t, atom) :: Conn.t
defp respond_to_webhook(conn, :supported), do: conn |> send_resp(200, "")
defp respond_to_webhook(conn, :unsupported), do: conn |> send_resp(200, "")
defp respond_to_webhook(conn, :ignored), do: conn |> send_resp(202, "")
end
2 changes: 1 addition & 1 deletion lib/code_corps_web/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ defmodule CodeCorpsWeb.Router do
resources "/donation-goals", DonationGoalController, only: [:create, :update, :delete]
post "/oauth/github", UserController, :github_oauth
resources "/github-app-installations", GithubAppInstallationController, only: [:create]
resources "/github-events", GithubEventController, only: [:index, :show]
resources "/github-events", GithubEventController, only: [:index, :show, :update]
resources "/github-repos", GithubRepoController, only: [:update]
resources "/organization-github-app-installations", OrganizationGithubAppInstallationController, only: [:create, :delete]
resources "/organizations", OrganizationController, only: [:create, :update]
Expand Down
46 changes: 39 additions & 7 deletions test/lib/code_corps/model/github_event_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,50 @@ defmodule CodeCorps.GithubEventTest do
action: "some content",
github_delivery_id: "71aeab80-9e59-11e7-81ac-198364bececc",
payload: %{"key" => "value"},
status: "some content",
status: "processing",
type: "some content"
}
@invalid_attrs %{}

test "changeset with valid attributes" do
changeset = GithubEvent.changeset(%GithubEvent{}, @valid_attrs)
assert changeset.valid?
describe "changeset/2" do
test "with valid attributes" do
changeset = GithubEvent.changeset(%GithubEvent{}, @valid_attrs)
assert changeset.valid?
end

test "with invalid attributes" do
changeset = GithubEvent.changeset(%GithubEvent{}, @invalid_attrs)
refute changeset.valid?
end

test "validates inclusion of status" do
attrs = @valid_attrs |> Map.put(:status, "foo")
changeset = GithubEvent.changeset(%GithubEvent{}, attrs)
refute changeset.valid?
assert changeset.errors[:status] == {"is invalid", [validation: :inclusion]}
end
end

test "changeset with invalid attributes" do
changeset = GithubEvent.changeset(%GithubEvent{}, @invalid_attrs)
refute changeset.valid?
describe "update_changeset/2" do
test "with retry true and status errored" do
attrs = @valid_attrs |> Map.merge(%{retry: true, status: "errored"})
changeset = GithubEvent.update_changeset(%GithubEvent{status: "errored"}, attrs)
assert changeset.valid?
assert changeset.changes[:status] == "reprocessing"
end

test "with retry true and status not errored" do
attrs = @valid_attrs |> Map.put(:retry, true)
changeset = GithubEvent.update_changeset(%GithubEvent{status: "foo"}, attrs)
refute changeset.valid?
assert_error_message(changeset, :retry, "only possible when status is errored")
end

test "with retry false" do
attrs = @valid_attrs |> Map.put(:retry, false)
changeset = GithubEvent.update_changeset(%GithubEvent{}, attrs)
refute changeset.valid?
refute changeset.changes[:status] == "reprocessing"
end
end
end
14 changes: 13 additions & 1 deletion test/lib/code_corps/policy/github_event_test.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
defmodule CodeCorps.GithubEventPolicyTest do
use CodeCorps.PolicyCase

import CodeCorps.Policy.GithubEvent, only: [index?: 1, show?: 1]
import CodeCorps.Policy.GithubEvent, only: [index?: 1, show?: 1, update?: 1]

describe "index" do
test "returns true when user is an admin" do
Expand All @@ -26,4 +26,16 @@ defmodule CodeCorps.GithubEventPolicyTest do
refute show?(user)
end
end

describe "update" do
test "returns true when user is an admin" do
user = insert(:user, admin: true)
assert update?(user)
end

test "returns false when user is not an admin" do
user = insert(:user, admin: false)
refute update?(user)
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -114,4 +114,33 @@ defmodule CodeCorpsWeb.GithubEventControllerTest do
refute Repo.get_by(GithubEvent, github_delivery_id: "foo")
end
end

describe "update" do
@valid_attrs %{retry: true}

@tag authenticated: :admin
test "updates when the status was errored", %{conn: conn} do
payload = load_event_fixture("pull_request_opened")
github_event = insert(:github_event, action: "opened", github_delivery_id: "foo", payload: payload, status: "errored", type: "pull_request")

assert conn |> request_update(github_event, @valid_attrs) |> json_response(200)
end

@tag authenticated: :admin
test "does not update for any other status", %{conn: conn} do
payload = load_event_fixture("pull_request_opened")
github_event = insert(:github_event, action: "opened", payload: payload, status: "processed", type: "pull_request")

assert conn |> request_update(github_event, @valid_attrs) |> json_response(422)
end

test "renders 401 when unauthenticated", %{conn: conn} do
assert conn |> request_update |> json_response(401)
end

@tag :authenticated
test "renders 403 when unauthorized", %{conn: conn} do
assert conn |> request_update |> json_response(403)
end
end
end