Skip to content

Commit 5050fe2

Browse files
authored
fix(github_hooks): populate payloads with info about commit author (#260)
## 📝 Description This update enhances the payloads to include additional information required to populate `SEMAPHORE_GIT_COMMIT_AUTHOR` when a workflow is triggered via the API. It addresses [this issue](renderedtext/tasks#7744) and is needed for [this task](renderedtext/on-prem-overview#235). ## ✅ Checklist - [x] I have tested this change - [x] This change requires documentation update - N/A
1 parent 61c7d6d commit 5050fe2

File tree

6 files changed

+360
-12
lines changed

6 files changed

+360
-12
lines changed

github_hooks/lib/internal_api/repo_proxy/branch_payload.rb

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,23 @@ def call(project, user)
1212

1313
reference = repo_host.reference(project.repo_owner_and_name, ref.delete_prefix("refs/"))
1414

15-
commit = repo_host.commit(project.repo_owner_and_name, commit_sha(sha, reference))
15+
branch_commit = repo_host.commit(project.repo_owner_and_name, commit_sha(sha, reference))
1616

17-
repo_url = commit[:html_url].split("/").first(5).join("/")
17+
repo_url = branch_commit[:html_url].split("/").first(5).join("/")
1818
author_name = user.github_repo_host_account.name
1919
author_email = user.email
2020
github_uid = user.github_repo_host_account.github_uid
2121
avatar = ::Avatar.avatar_url(github_uid)
2222

2323
commit = {
24-
"message" => commit.commit.message,
25-
"id" => commit.sha,
26-
"url" => commit.html_url,
27-
"author" => { "name" => "", "email" => "" },
24+
"message" => branch_commit.commit.message,
25+
"id" => branch_commit.sha,
26+
"url" => branch_commit.html_url,
27+
"author" => {
28+
"name" => branch_commit.commit.author&.name || "",
29+
"email" => branch_commit.commit.author&.email || "",
30+
"username" => branch_commit.author&.login
31+
},
2832
"timestamp" => ""
2933
}
3034

github_hooks/lib/internal_api/repo_proxy/pr_payload.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,21 @@ def call(project, user)
1919
raise PrNotMergeableError, "Pull Request ##{number} is not mergeable (#{pr[:html_url]})"
2020
end
2121

22+
repo_host = ::RepoHost::Factory.create_from_project(project)
23+
pr_commit = repo_host.commit(project.repo_owner_and_name, pr[:head][:sha])
24+
25+
commit = {
26+
"message" => pr_commit.commit.message,
27+
"id" => pr_commit.sha,
28+
"url" => pr_commit.html_url,
29+
"author" => {
30+
"name" => pr_commit.commit.author&.name || "",
31+
"email" => pr_commit.commit.author&.email || "",
32+
"username" => pr_commit.author&.login
33+
},
34+
"timestamp" => ""
35+
}
36+
2237
repo_url = pr[:html_url].split("/").first(5).join("/")
2338
author_name = user.github_repo_host_account.name
2439
author_email = user.email
@@ -38,6 +53,7 @@ def call(project, user)
3853
"base" => pr["base"].to_h,
3954
"head" => pr["head"].to_h
4055
},
56+
"commits" => [commit],
4157
"single" => true,
4258
"created" => true,
4359
"after" => "",

github_hooks/lib/internal_api/repo_proxy/tag_payload.rb

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,23 @@ def call(project, user)
99

1010
reference = repo_host.reference(project.repo_owner_and_name, ref.delete_prefix("refs/"))
1111

12-
commit = repo_host.commit(project.repo_owner_and_name, commit_sha(reference, repo_host, project))
12+
tag_commit = repo_host.commit(project.repo_owner_and_name, commit_sha(reference, repo_host, project))
1313

14-
repo_url = commit[:html_url].split("/").first(5).join("/")
14+
repo_url = tag_commit[:html_url].split("/").first(5).join("/")
1515
author_name = user.github_repo_host_account.name
1616
author_email = user.email
1717
github_uid = user.github_repo_host_account.github_uid
1818
avatar = ::Avatar.avatar_url(github_uid)
1919

2020
commit = {
21-
"message" => commit.commit.message,
22-
"id" => commit.sha,
23-
"url" => commit.html_url,
24-
"author" => { "name" => "", "email" => "" },
21+
"message" => tag_commit.commit.message,
22+
"id" => tag_commit.sha,
23+
"url" => tag_commit.html_url,
24+
"author" => {
25+
"name" => tag_commit.commit.author&.name || "",
26+
"email" => tag_commit.commit.author&.email || "",
27+
"username" => tag_commit.author&.login
28+
},
2529
"timestamp" => ""
2630
}
2731

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
require "spec_helper"
2+
3+
GitAuthor = Struct.new(:name, :email, :login)
4+
GitCommitData = Struct.new(:message, :author)
5+
GitCommit = Struct.new(:sha, :html_url, :commit, :author) do
6+
def [](key)
7+
html_url if key == :html_url
8+
end
9+
end
10+
11+
BranchRef = Struct.new(:ref, :object) do
12+
def [](key)
13+
return ref if key == :ref
14+
return object if key == :object
15+
16+
nil
17+
end
18+
end
19+
20+
RepoHostBranchStub = Struct.new(:branch_commit) do
21+
def reference(*_args)
22+
BranchRef.new("heads/main", { sha: branch_commit.sha, type: "commit" })
23+
end
24+
25+
def commit(*_args)
26+
branch_commit
27+
end
28+
end
29+
30+
RSpec.describe InternalApi::RepoProxy::BranchPayload do
31+
let(:ref) { "refs/heads/main" }
32+
let(:sha) { "abc123" }
33+
let(:project) { instance_double(Project, repo_owner_and_name: "owner/repo") }
34+
let(:user) do
35+
host_account = Struct.new(:name, :github_uid, :login)
36+
.new("Alice", 123, "alice")
37+
Struct.new(:github_repo_host_account, :email)
38+
.new(host_account, "[email protected]")
39+
end
40+
41+
let(:branch_commit) do
42+
author = GitAuthor.new("Alice", "[email protected]", "alice")
43+
commit_data = GitCommitData.new("Branch commit", author)
44+
GitCommit.new(
45+
sha,
46+
"https://github.com/owner/repo/commit/#{sha}",
47+
commit_data,
48+
author
49+
)
50+
end
51+
52+
let(:repo_host) { RepoHostBranchStub.new(branch_commit) }
53+
54+
before do
55+
allow(::RepoHost::Factory).to receive(:create_from_project).and_return(repo_host)
56+
allow(::Avatar).to receive(:avatar_url).and_return("http://avatar.url")
57+
end
58+
59+
describe "#call" do
60+
subject(:payload) { described_class.new(ref, sha).call(project, user) }
61+
62+
it "returns a payload hash with expected keys" do
63+
expect(payload).to include(
64+
"ref" => "heads/main",
65+
"single" => true,
66+
"created" => true,
67+
"head_commit" => a_kind_of(Hash),
68+
"commits" => [a_kind_of(Hash)],
69+
"repository" => hash_including("html_url", "full_name"),
70+
"pusher" => hash_including("name", "email"),
71+
"sender" => hash_including("id", "avatar_url")
72+
)
73+
end
74+
75+
it "sets author and commit details correctly" do
76+
commit = payload["commits"].last
77+
expect(commit["author"]["name"]).to eq("Alice")
78+
expect(commit["author"]["email"]).to eq("[email protected]")
79+
expect(commit["author"]["username"]).to eq("alice")
80+
expect(commit["id"]).to eq(sha)
81+
expect(commit["message"]).to eq("Branch commit")
82+
end
83+
end
84+
85+
describe "#commit_sha" do
86+
it "returns sha if it matches SHA_REGEXP" do
87+
obj = described_class.new(ref, sha)
88+
result = obj.send(:commit_sha, sha, BranchRef.new("heads/main", { sha: sha, type: "commit" }))
89+
expect(result).to eq(sha)
90+
end
91+
92+
it "returns reference sha if type is commit and sha is not valid" do
93+
bad_sha = "notasha"
94+
ref_obj = { ref: "heads/main", object: { sha: "def456", type: "commit" } }
95+
obj = described_class.new(ref, bad_sha)
96+
result = obj.send(:commit_sha, bad_sha, ref_obj)
97+
expect(result).to eq("def456")
98+
end
99+
100+
it "returns nil if neither condition matches" do
101+
bad_sha = "notasha"
102+
ref_obj = { ref: "heads/main", object: { sha: "def456", type: "tag" } }
103+
obj = described_class.new(ref, bad_sha)
104+
result = obj.send(:commit_sha, bad_sha, ref_obj)
105+
expect(result).to be_nil
106+
end
107+
end
108+
end
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
require "spec_helper"
2+
3+
GitAuthor = Struct.new(:name, :email, :login)
4+
GitCommitData = Struct.new(:message, :author)
5+
GitCommit = Struct.new(:sha, :html_url, :commit, :author) do
6+
def [](key)
7+
html_url if key == :html_url
8+
end
9+
end
10+
11+
RepoHostStub = Struct.new(:pr_commit) do
12+
def commit(*_args)
13+
pr_commit
14+
end
15+
end
16+
17+
RSpec.describe InternalApi::RepoProxy::PrPayload do
18+
let(:ref) { "refs/pull/1/head" }
19+
let(:number) { 1 }
20+
let(:project) { instance_double(Project, repo_owner_and_name: "owner/repo") }
21+
let(:user) do
22+
host_account = Struct.new(:name, :github_uid, :login).new("Alice", 123, "alice")
23+
Struct.new(:github_repo_host_account, :email)
24+
.new(host_account, "[email protected]")
25+
end
26+
27+
let(:pr_commit) do
28+
author = GitAuthor.new("Alice", "[email protected]", "alice")
29+
commit_data = GitCommitData.new("PR commit", author)
30+
GitCommit.new(
31+
"abc123",
32+
"https://github.com/owner/repo/commit/abc123",
33+
commit_data,
34+
author
35+
)
36+
end
37+
38+
let(:repo_host) { RepoHostStub.new(pr_commit) }
39+
40+
let(:pr) do
41+
{
42+
"number" => 1,
43+
head: { sha: "abc123", ref: "feature-branch", repo: { full_name: "owner/repo" } },
44+
base: { ref: "main", repo: { full_name: "owner/repo" } },
45+
title: "A PR",
46+
commits_url: "https://api.github.com/repos/owner/repo/pulls/1/commits",
47+
html_url: "https://github.com/owner/repo/pull/1"
48+
}
49+
end
50+
let(:meta) { { pr: pr, ref: ref, merge_commit_sha: "abc123", commit_author: "Alice" } }
51+
52+
before do
53+
allow(::RepoHost::Factory).to receive(:create_from_project).and_return(repo_host)
54+
allow(::Avatar).to receive(:avatar_url).and_return("http://avatar.url")
55+
end
56+
57+
describe "#call" do
58+
subject(:payload) { described_class.new(ref, number).call(project, user) }
59+
60+
before do
61+
allow(Semaphore::RepoHost::Hooks::Handler)
62+
.to receive(:update_pr_data)
63+
.and_return([:ok, meta, ""])
64+
end
65+
66+
it "returns a payload hash with expected keys" do
67+
expect(payload).to include(
68+
"number" => 1,
69+
"pull_request" => a_kind_of(Hash),
70+
"commits" => [a_kind_of(Hash)],
71+
"repository" => hash_including("html_url", "full_name"),
72+
"pusher" => hash_including("name", "email"),
73+
"sender" => hash_including("id", "avatar_url", "login")
74+
)
75+
end
76+
77+
it "sets author and commit details correctly" do
78+
commit = payload["commits"].last
79+
expect(commit["author"]["name"]).to eq("Alice")
80+
expect(commit["author"]["email"]).to eq("[email protected]")
81+
expect(commit["author"]["username"]).to eq("alice")
82+
expect(commit["id"]).to eq("abc123")
83+
expect(commit["message"]).to eq("PR commit")
84+
end
85+
end
86+
87+
context "when PR is not found" do
88+
before do
89+
allow(Semaphore::RepoHost::Hooks::Handler)
90+
.to receive(:update_pr_data)
91+
.and_return([:not_found, {}, "not found"])
92+
end
93+
94+
it "raises PrNotMergeableError" do
95+
expect do
96+
described_class.new(ref, number).call(project, user)
97+
end.to raise_error(described_class::PrNotMergeableError, /not found/i)
98+
end
99+
end
100+
101+
context "when PR is not mergeable" do
102+
before do
103+
allow(Semaphore::RepoHost::Hooks::Handler)
104+
.to receive(:update_pr_data)
105+
.and_return([:non_mergeable, { pr: pr }, "not mergeable"])
106+
end
107+
108+
it "raises PrNotMergeableError" do
109+
expect do
110+
described_class.new(ref, number).call(project, user)
111+
end.to raise_error(described_class::PrNotMergeableError, /not mergeable/i)
112+
end
113+
end
114+
end

0 commit comments

Comments
 (0)