Skip to content

Commit 8b2b583

Browse files
tallysmartinsPragTob
authored andcommitted
Avoid job duplication by Github webhooks (#34)
* Avoid job duplication by Github webhooks - closes #29 Signed-off-by: Tallys Martins <[email protected]> * Apply changes from first review Signed-off-by: Tallys Martins <[email protected]> * Apply review changes * Small fix in WebhooksControllerTest
1 parent 87507a3 commit 8b2b583

5 files changed

Lines changed: 130 additions & 18 deletions

File tree

lib/elixir_bench/benchmarks/benchmarks.ex

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,16 @@ defmodule ElixirBench.Benchmarks do
5151
Repo.fetch(where(Job, uuid: ^uuid))
5252
end
5353

54+
defp fetch_job_by_repo_and_commit(repo, commit_sha) do
55+
query =
56+
Job
57+
|> Job.filter_by_repo(repo.id)
58+
|> where(commit_sha: ^commit_sha)
59+
|> last()
60+
61+
Repo.fetch(query)
62+
end
63+
5464
def list_benchmarks_by_repo_id(repo_ids) do
5565
Repo.all(from(b in Benchmark, where: b.repo_id in ^repo_ids))
5666
end
@@ -63,6 +73,16 @@ defmodule ElixirBench.Benchmarks do
6373
Repo.all(Job)
6474
end
6575

76+
def get_or_create_job(repo, %{commit_sha: commit_sha} = attrs) do
77+
case fetch_job_by_repo_and_commit(repo, commit_sha) do
78+
{:ok, job} ->
79+
{:ok, job}
80+
81+
{:error, :not_found} ->
82+
create_job(repo, attrs)
83+
end
84+
end
85+
6686
def create_job(repo, attrs) do
6787
changeset = Job.create_changeset(%Job{repo_id: repo.id}, attrs)
6888

lib/elixir_bench/benchmarks/job.ex

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ defmodule ElixirBench.Benchmarks.Job do
22
use Ecto.Schema
33

44
import Ecto.Changeset
5+
import Ecto.Query, only: [from: 2]
56

67
alias ElixirBench.Repos
78
alias ElixirBench.Benchmarks.{Runner, Job, Config}
@@ -66,4 +67,8 @@ defmodule ElixirBench.Benchmarks.Job do
6667
|> validate_required(@submit_fields)
6768
|> put_change(:completed_at, DateTime.utc_now())
6869
end
70+
71+
def filter_by_repo(query, repo_id) do
72+
from(j in query, where: j.repo_id == ^repo_id)
73+
end
6974
end

lib/elixir_bench_web/controllers/github/webhooks_controller.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ defmodule ElixirBenchWeb.Github.WebHooks do
4545

4646
with {:ok, %Repos.Repo{} = repo} <- Repos.fetch_repo_by_slug(slug),
4747
{:ok, job} <-
48-
Benchmarks.create_job(repo, %{branch_name: branch_name, commit_sha: commit_sha}) do
48+
Benchmarks.get_or_create_job(repo, %{branch_name: branch_name, commit_sha: commit_sha}) do
4949
conn
5050
|> render(ElixirBenchWeb.JobView, "show.json", job: job, repo: repo)
5151
else

test/elixir_bench/benchmarks/benchmarks_test.exs

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,15 +101,78 @@ defmodule ElixirBench.BenchmarksTest do
101101
end
102102
end
103103

104+
describe "get_or_create_job/2" do
105+
test "insert new jobs if no job exist for a given commit-sha and repo" do
106+
repo = insert(:repo)
107+
108+
assert_difference(Job, 2) do
109+
assert {:ok, job} =
110+
Benchmarks.get_or_create_job(repo, %{
111+
branch_name: "master",
112+
commit_sha: "ABC123"
113+
})
114+
115+
assert {:ok, _other_job} =
116+
Benchmarks.get_or_create_job(repo, %{
117+
branch_name: "master",
118+
commit_sha: "dingdong"
119+
})
120+
end
121+
122+
assert %Job{branch_name: "master", commit_sha: "ABC123"} = job
123+
end
124+
125+
test "return last existent job for a given commit-sha and repo" do
126+
repo = insert(:repo)
127+
attrs = %{repo: repo, branch_name: "master", commit_sha: "ABC123"}
128+
129+
%{id: id} = insert(:job, attrs)
130+
%{id: id2} = insert(:job, attrs)
131+
132+
refute id == id2
133+
134+
assert_difference(Job, 0) do
135+
assert {:ok, job} = Benchmarks.get_or_create_job(repo, attrs)
136+
end
137+
138+
assert %{id: ^id2, commit_sha: "ABC123"} = job
139+
end
140+
141+
test "insert new job if exists a job with same commit-sha but its from another repo" do
142+
repo = insert(:repo)
143+
other_repo = insert(:repo)
144+
145+
assert_difference(Job, 2) do
146+
assert {:ok, job} =
147+
Benchmarks.get_or_create_job(repo, %{
148+
branch_name: "master",
149+
commit_sha: "ABC123"
150+
})
151+
152+
assert {:ok, other_job} =
153+
Benchmarks.get_or_create_job(other_repo, %{
154+
branch_name: "master",
155+
commit_sha: "ABC123"
156+
})
157+
end
158+
159+
assert %Job{branch_name: "master", commit_sha: "ABC123"} = job
160+
assert %Job{branch_name: "master", commit_sha: "ABC123"} = other_job
161+
end
162+
end
163+
104164
describe "create_job/2" do
105-
test "insert new Job given correct params" do
165+
test "insert new job given correct params" do
106166
repo = insert(:repo)
107167

108168
assert_difference(Job, 1) do
109169
assert {:ok, job} =
110-
Benchmarks.create_job(repo, %{branch_name: "mm/benchee", commit_sha: "ABC123"})
170+
Benchmarks.create_job(repo, %{
171+
branch_name: "master",
172+
commit_sha: "ABC123"
173+
})
111174

112-
assert %Job{branch_name: "mm/benchee", commit_sha: "ABC123"} = job
175+
assert %Job{branch_name: "master", commit_sha: "ABC123"} = job
113176
end
114177
end
115178

@@ -171,7 +234,7 @@ defmodule ElixirBench.BenchmarksTest do
171234
assert @samples_count == ElixirBench.Repo.aggregate(query, :count, :id)
172235
end
173236

174-
test "do not duplicata benchmark in multiple runs" do
237+
test "do not duplicate benchmark in multiple runs" do
175238
job = insert(:job)
176239

177240
assert_difference(Benchmarks.Benchmark, 4) do

test/elixir_bench_web/controllers/github/webhooks_controller_test.exs

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -47,21 +47,9 @@ defmodule ElixirBenchWeb.Github.WebHooksControllerTest do
4747

4848
payload = push_payload() |> Jason.decode!()
4949

50-
refs_head = %{payload | "ref" => "refs/heads/mybranch"}
5150
empty_string = %{payload | "ref" => ""}
5251
nil_value = %{payload | "ref" => nil}
53-
54-
assert_difference(Repo, 0) do
55-
assert_difference(Job, 1) do
56-
{:ok, %{"data" => data}} =
57-
context.conn
58-
|> set_headers("push")
59-
|> post("/hooks/handle", %{"payload" => Jason.encode!(refs_head)})
60-
|> decode_response_body
61-
end
62-
end
63-
64-
assert %{"branch_name" => "mybranch", "repo_slug" => "baxterthehacker/public-repo"} = data
52+
refs_head = %{payload | "ref" => "refs/heads/mybranch"}
6553

6654
{:ok, %{"errors" => errors}} =
6755
context.conn
@@ -78,6 +66,18 @@ defmodule ElixirBenchWeb.Github.WebHooksControllerTest do
7866
|> decode_response_body
7967

8068
assert %{"branch_name" => ["can't be blank"]} = errors
69+
70+
assert_difference(Repo, 0) do
71+
assert_difference(Job, 1) do
72+
{:ok, %{"data" => data}} =
73+
context.conn
74+
|> set_headers("push")
75+
|> post("/hooks/handle", %{"payload" => Jason.encode!(refs_head)})
76+
|> decode_response_body
77+
end
78+
end
79+
80+
assert %{"branch_name" => "mybranch", "repo_slug" => "baxterthehacker/public-repo"} = data
8181
end
8282

8383
test "respond to ping event", context do
@@ -90,6 +90,30 @@ defmodule ElixirBenchWeb.Github.WebHooksControllerTest do
9090
assert 200 = response.status
9191
end
9292

93+
# This scenario happens when there is a branch with an open pull request
94+
# So Github sends requests for both events, pull request and push.
95+
test "not duplicate job for same branch and commit references", context do
96+
insert(:repo, @github_repo_attrs)
97+
push_params = %{"payload" => push_payload()}
98+
pull_request = %{"payload" => pull_request_payload()}
99+
100+
assert_difference(Job, 1) do
101+
{:ok, %{"data" => data}} =
102+
context.conn
103+
|> set_headers("pull_request")
104+
|> post("/hooks/handle", pull_request)
105+
|> decode_response_body
106+
107+
{:ok, %{"data" => ^data}} =
108+
context.conn
109+
|> set_headers("push")
110+
|> post("/hooks/handle", push_params)
111+
|> decode_response_body
112+
end
113+
114+
assert %{"branch_name" => "changes", "repo_slug" => "baxterthehacker/public-repo"} = data
115+
end
116+
93117
test "not break given unexpected pull request event payload scheme", context do
94118
insert(:repo, @github_repo_attrs)
95119
params = %{"payload" => ~s({"data": "some data"})}

0 commit comments

Comments
 (0)