Skip to content

Commit bfeba3b

Browse files
authored
Improve webhook processor code coverage for cloud tag events (#984)
1 parent e4bcc66 commit bfeba3b

File tree

11 files changed

+1182
-46
lines changed

11 files changed

+1182
-46
lines changed

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/HookProcessor.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,10 @@
2727
import hudson.security.ACL;
2828
import hudson.security.ACLContext;
2929
import java.util.List;
30+
import java.util.concurrent.TimeUnit;
3031
import java.util.logging.Level;
3132
import java.util.logging.Logger;
33+
import jenkins.scm.api.SCMHeadEvent;
3234
import jenkins.scm.api.SCMSource;
3335
import jenkins.scm.api.SCMSourceOwner;
3436
import jenkins.scm.api.SCMSourceOwners;
@@ -110,4 +112,21 @@ protected void scmSourceReIndex(final String owner, final String repository) {
110112
}
111113
}
112114
}
115+
116+
/**
117+
* Implementations have to call this method when want propagate an
118+
* {@link SCMHeadEvent} to the scm-api.
119+
*
120+
* @param event the to fire
121+
* @param delaySeconds a delay in seconds to wait before propagate the
122+
* event. If the given value is less than 0 than default will be
123+
* used.
124+
*/
125+
protected void notifyEvent(SCMHeadEvent<?> event, int delaySeconds) {
126+
if (delaySeconds == 0) {
127+
SCMHeadEvent.fireNow(event);
128+
} else {
129+
SCMHeadEvent.fireLater(event, delaySeconds > 0 ? delaySeconds : BitbucketSCMSource.getEventDelaySeconds(), TimeUnit.SECONDS);
130+
}
131+
}
113132
}

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/NativeServerPullRequestHookProcessor.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,9 @@
2828
import com.cloudbees.jenkins.plugins.bitbucket.server.events.NativeServerPullRequestEvent;
2929
import hudson.RestrictedSince;
3030
import java.io.IOException;
31-
import java.util.concurrent.TimeUnit;
3231
import java.util.logging.Level;
3332
import java.util.logging.Logger;
3433
import jenkins.scm.api.SCMEvent;
35-
import jenkins.scm.api.SCMHeadEvent;
3634
import org.kohsuke.accmod.Restricted;
3735
import org.kohsuke.accmod.restrictions.NoExternalUse;
3836

@@ -78,6 +76,6 @@ public void process(HookEventType hookEvent, String payload, BitbucketType insta
7876
return;
7977
}
8078

81-
SCMHeadEvent.fireLater(new ServerHeadEvent(serverUrl, eventType, pullRequestEvent, origin), BitbucketSCMSource.getEventDelaySeconds(), TimeUnit.SECONDS);
79+
notifyEvent(new ServerHeadEvent(serverUrl, eventType, pullRequestEvent, origin), BitbucketSCMSource.getEventDelaySeconds());
8280
}
8381
}

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/NativeServerPushHookProcessor.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,9 @@
3434
import hudson.RestrictedSince;
3535
import java.io.IOException;
3636
import java.util.List;
37-
import java.util.concurrent.TimeUnit;
3837
import java.util.logging.Level;
3938
import java.util.logging.Logger;
4039
import jenkins.scm.api.SCMEvent;
41-
import jenkins.scm.api.SCMHeadEvent;
4240
import org.kohsuke.accmod.Restricted;
4341
import org.kohsuke.accmod.restrictions.NoExternalUse;
4442

@@ -106,7 +104,7 @@ public void process(HookEventType hookEvent, String payload, BitbucketType insta
106104

107105
for (final SCMEvent.Type type : events.keySet()) {
108106
ServerPushEvent headEvent = new ServerPushEvent(serverUrl, type, events.get(type), origin, repository, mirrorId);
109-
SCMHeadEvent.fireLater(headEvent, BitbucketSCMSource.getEventDelaySeconds(), TimeUnit.SECONDS);
107+
notifyEvent(headEvent, BitbucketSCMSource.getEventDelaySeconds());
110108
}
111109
}
112110

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/PullRequestHookProcessor.java

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,7 @@
2828
import com.cloudbees.jenkins.plugins.bitbucket.client.BitbucketCloudWebhookPayload;
2929
import com.cloudbees.jenkins.plugins.bitbucket.server.client.BitbucketServerWebhookPayload;
3030
import hudson.RestrictedSince;
31-
import java.util.concurrent.TimeUnit;
3231
import jenkins.scm.api.SCMEvent;
33-
import jenkins.scm.api.SCMHeadEvent;
3432
import org.kohsuke.accmod.Restricted;
3533
import org.kohsuke.accmod.restrictions.NoExternalUse;
3634

@@ -67,9 +65,4 @@ public void process(final HookEventType hookEvent, String payload, final Bitbuck
6765
}
6866
}
6967
}
70-
71-
/* for test purpose */
72-
protected void notifyEvent(SCMHeadEvent<?> event, int delaySeconds) {
73-
SCMHeadEvent.fireLater(event, delaySeconds, TimeUnit.SECONDS);
74-
}
7568
}

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/PushHookProcessor.java

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
import com.cloudbees.jenkins.plugins.bitbucket.client.BitbucketCloudWebhookPayload;
2929
import com.cloudbees.jenkins.plugins.bitbucket.server.client.BitbucketServerWebhookPayload;
3030
import hudson.RestrictedSince;
31-
import java.util.concurrent.TimeUnit;
3231
import java.util.logging.Level;
3332
import java.util.logging.Logger;
3433
import jenkins.scm.api.SCMEvent;
@@ -70,14 +69,10 @@ public void process(HookEventType hookEvent, String payload, BitbucketType insta
7069
type = SCMEvent.Type.UPDATED;
7170
}
7271
}
73-
SCMHeadEvent.fireLater(new PushEvent(type, push, origin), BitbucketSCMSource.getEventDelaySeconds(), TimeUnit.SECONDS);
72+
notifyEvent(new PushEvent(type, push, origin), BitbucketSCMSource.getEventDelaySeconds());
7473
}
7574
}
7675
}
7776
}
7877

79-
/* for test purpose */
80-
protected void notifyEvent(SCMHeadEvent<?> event, int delaySeconds) {
81-
SCMHeadEvent.fireLater(event, delaySeconds, TimeUnit.SECONDS);
82-
}
8378
}

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/server/client/repository/BitbucketServerRepository.java

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@ public class BitbucketServerRepository implements BitbucketRepository {
4848
@JsonProperty("slug")
4949
private String repositoryName;
5050

51-
// JSON mapping added in setter because the field can not be called "public"
52-
private Boolean public_;
51+
@JsonProperty("public")
52+
private boolean isPublic;
5353

5454
private Boolean archived;
5555

@@ -100,7 +100,7 @@ public void setProject(BitbucketProject project) {
100100

101101
@Override
102102
public boolean isPrivate() {
103-
return !public_;
103+
return !isPublic;
104104
}
105105

106106
@Override
@@ -113,9 +113,8 @@ public void setArchived(Boolean archived) {
113113
this.archived = archived;
114114
}
115115

116-
@JsonProperty("public")
117-
public void setPublic(Boolean public_) {
118-
this.public_ = public_;
116+
public void setPublic(Boolean isPublic) {
117+
this.isPublic = isPublic;
119118
}
120119

121120
@JsonIgnore

src/test/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/PullRequestHookProcessorTest.java

Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -32,37 +32,38 @@
3232
import java.nio.charset.StandardCharsets;
3333
import java.util.List;
3434
import jenkins.scm.api.SCMEvent.Type;
35+
import jenkins.scm.api.SCMHeadEvent;
3536
import jenkins.scm.api.SCMNavigator;
3637
import jenkins.scm.api.SCMSource;
3738
import org.apache.commons.io.IOUtils;
3839
import org.junit.jupiter.api.BeforeEach;
3940
import org.junit.jupiter.api.Test;
4041
import org.jvnet.hudson.test.JenkinsRule;
4142
import org.jvnet.hudson.test.junit.jupiter.WithJenkins;
42-
import org.mockito.ArgumentCaptor;
4343

4444
import static org.assertj.core.api.Assertions.assertThat;
45-
import static org.mockito.ArgumentMatchers.anyInt;
4645
import static org.mockito.Mockito.mock;
47-
import static org.mockito.Mockito.spy;
48-
import static org.mockito.Mockito.verify;
4946

5047
class PullRequestHookProcessorTest {
5148

5249
private PullRequestHookProcessor sut;
50+
protected SCMHeadEvent<?> scmEvent;
5351

5452
@BeforeEach
5553
void setup() {
56-
sut = spy(new PullRequestHookProcessor());
54+
sut = new PullRequestHookProcessor() {
55+
@Override
56+
protected void notifyEvent(SCMHeadEvent<?> event, int delaySeconds) {
57+
PullRequestHookProcessorTest.this.scmEvent = event;
58+
}
59+
};
5760
}
5861

5962
@Test
6063
void test_pullrequest_created() throws Exception {
6164
sut.process(HookEventType.PULL_REQUEST_CREATED, loadResource("pullrequest_created.json"), BitbucketType.CLOUD, "origin");
6265

63-
ArgumentCaptor<PREvent> eventCaptor = ArgumentCaptor.forClass(PREvent.class);
64-
verify(sut).notifyEvent(eventCaptor.capture(), anyInt());
65-
PREvent event = eventCaptor.getValue();
66+
PREvent event = (PREvent) scmEvent;
6667
assertThat(event).isNotNull();
6768
assertThat(event.getSourceName()).isEqualTo("test-repos");
6869
assertThat(event.getType()).isEqualTo(Type.CREATED);
@@ -73,9 +74,7 @@ void test_pullrequest_created() throws Exception {
7374
void test_pullrequest_rejected() throws Exception {
7475
sut.process(HookEventType.PULL_REQUEST_DECLINED, loadResource("pullrequest_rejected.json"), BitbucketType.CLOUD, "origin");
7576

76-
ArgumentCaptor<PREvent> eventCaptor = ArgumentCaptor.forClass(PREvent.class);
77-
verify(sut).notifyEvent(eventCaptor.capture(), anyInt());
78-
PREvent event = eventCaptor.getValue();
77+
PREvent event = (PREvent) scmEvent;
7978
assertThat(event).isNotNull();
8079
assertThat(event.getSourceName()).isEqualTo("test-repos");
8180
assertThat(event.getType()).isEqualTo(Type.REMOVED);
@@ -86,9 +85,7 @@ void test_pullrequest_rejected() throws Exception {
8685
void test_pullrequest_created_when_event_match_SCMNavigator() throws Exception {
8786
sut.process(HookEventType.PULL_REQUEST_CREATED, loadResource("pullrequest_created.json"), BitbucketType.CLOUD, "origin");
8887

89-
ArgumentCaptor<PREvent> eventCaptor = ArgumentCaptor.forClass(PREvent.class);
90-
verify(sut).notifyEvent(eventCaptor.capture(), anyInt());
91-
PREvent event = eventCaptor.getValue();
88+
PREvent event = (PREvent) scmEvent;
9289
// discard any scm navigator than bitbucket
9390
assertThat(event.isMatch(mock(SCMNavigator.class))).isFalse();
9491

@@ -112,9 +109,7 @@ void test_pullrequest_created_when_event_match_SCMNavigator() throws Exception {
112109
void test_pullrequest_created_when_event_match_SCMSource(JenkinsRule r) throws Exception {
113110
sut.process(HookEventType.PULL_REQUEST_CREATED, loadResource("pullrequest_created.json"), BitbucketType.CLOUD, "origin");
114111

115-
ArgumentCaptor<PREvent> eventCaptor = ArgumentCaptor.forClass(PREvent.class);
116-
verify(sut).notifyEvent(eventCaptor.capture(), anyInt());
117-
PREvent event = eventCaptor.getValue();
112+
PREvent event = (PREvent) scmEvent;
118113
// discard any scm navigator than bitbucket
119114
assertThat(event.isMatch(mock(SCMSource.class))).isFalse();
120115

@@ -140,11 +135,9 @@ void test_pullrequest_created_when_event_match_SCMSource(JenkinsRule r) throws E
140135
void test_pullrequest_rejected_returns_empty_pullrequests_when_event_match_SCMSource(JenkinsRule r) throws Exception {
141136
sut.process(HookEventType.PULL_REQUEST_DECLINED, loadResource("pullrequest_rejected.json"), BitbucketType.CLOUD, "origin");
142137

143-
ArgumentCaptor<PREvent> eventCaptor = ArgumentCaptor.forClass(PREvent.class);
144-
verify(sut).notifyEvent(eventCaptor.capture(), anyInt());
145-
PREvent event = eventCaptor.getValue();
138+
PREvent event = (PREvent) scmEvent;
146139

147-
BitbucketSCMSource scmSource = new BitbucketSCMSource("amuniz", "test-repos");
140+
BitbucketSCMSource scmSource = new BitbucketSCMSource("aMUNIZ", "test-repos");
148141
scmSource.setTraits(List.of(new OriginPullRequestDiscoveryTrait(2)));
149142
assertThat(event.isMatch(scmSource)).isTrue();
150143
assertThat(event.getPullRequests(scmSource)).isEmpty();
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
/*
2+
* The MIT License
3+
*
4+
* Copyright (c) 2025, Nikolas Falco
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in
14+
* all copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
* THE SOFTWARE.
23+
*/
24+
package com.cloudbees.jenkins.plugins.bitbucket.hooks;
25+
26+
import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource;
27+
import com.cloudbees.jenkins.plugins.bitbucket.BitbucketTagSCMHead;
28+
import com.cloudbees.jenkins.plugins.bitbucket.BranchSCMHead;
29+
import hudson.scm.SCM;
30+
import java.io.IOException;
31+
import java.io.InputStream;
32+
import java.nio.charset.StandardCharsets;
33+
import java.util.Map;
34+
import jenkins.plugins.git.AbstractGitSCMSource.SCMRevisionImpl;
35+
import jenkins.scm.api.SCMEvent.Type;
36+
import jenkins.scm.api.SCMHead;
37+
import jenkins.scm.api.SCMHeadEvent;
38+
import jenkins.scm.api.SCMRevision;
39+
import org.apache.commons.io.IOUtils;
40+
import org.junit.jupiter.api.BeforeEach;
41+
import org.junit.jupiter.api.Test;
42+
43+
import static org.assertj.core.api.Assertions.assertThat;
44+
import static org.mockito.Mockito.mock;
45+
46+
class PushHookProcessorTest {
47+
private PushHookProcessor sut;
48+
private SCMHeadEvent<?> scmEvent;
49+
50+
@BeforeEach
51+
void setup() {
52+
sut = new PushHookProcessor() {
53+
@Override
54+
protected void notifyEvent(SCMHeadEvent<?> event, int delaySeconds) {
55+
PushHookProcessorTest.this.scmEvent = event;
56+
}
57+
};
58+
}
59+
60+
@Test
61+
void test_tag_created() throws Exception {
62+
sut.process(HookEventType.PUSH, loadResource("tag_created.json"), BitbucketType.CLOUD, "origin");
63+
64+
PushEvent event = (PushEvent) scmEvent;
65+
assertThat(event).isNotNull();
66+
assertThat(event.getSourceName()).isEqualTo("test-repos");
67+
assertThat(event.getType()).isEqualTo(Type.CREATED);
68+
assertThat(event.isMatch(mock(SCM.class))).isFalse();
69+
70+
BitbucketSCMSource scmSource = new BitbucketSCMSource("AMUNIZ", "test-repos");
71+
Map<SCMHead, SCMRevision> heads = event.heads(scmSource);
72+
assertThat(heads.keySet())
73+
.first()
74+
.usingRecursiveComparison()
75+
.isEqualTo(new BitbucketTagSCMHead("simple-tag", 1738608795000l)); // verify is using last commit date
76+
}
77+
78+
@Test
79+
void test_annotated_tag_created() throws Exception {
80+
sut.process(HookEventType.PUSH, loadResource("annotated_tag_created.json"), BitbucketType.CLOUD, "origin");
81+
82+
PushEvent event = (PushEvent) scmEvent;
83+
assertThat(event).isNotNull();
84+
assertThat(event.getSourceName()).isEqualTo("test-repos");
85+
assertThat(event.getType()).isEqualTo(Type.CREATED);
86+
assertThat(event.isMatch(mock(SCM.class))).isFalse();
87+
88+
BitbucketSCMSource scmSource = new BitbucketSCMSource("AMUNIz", "test-repos");
89+
Map<SCMHead, SCMRevision> heads = event.heads(scmSource);
90+
assertThat(heads.keySet())
91+
.first()
92+
.usingRecursiveComparison()
93+
.isEqualTo(new BitbucketTagSCMHead("test-tag", 1738608816000l));
94+
}
95+
96+
@Test
97+
void test_commmit_created() throws Exception {
98+
sut.process(HookEventType.PUSH, loadResource("commit_created.json"), BitbucketType.CLOUD, "origin");
99+
100+
PushEvent event = (PushEvent) scmEvent;
101+
assertThat(event).isNotNull();
102+
assertThat(event.getSourceName()).isEqualTo("test-repos");
103+
assertThat(event.getType()).isEqualTo(Type.UPDATED);
104+
assertThat(event.isMatch(mock(SCM.class))).isFalse();
105+
106+
BitbucketSCMSource scmSource = new BitbucketSCMSource("aMUNIZ", "test-repos");
107+
Map<SCMHead, SCMRevision> heads = event.heads(scmSource);
108+
assertThat(heads.keySet())
109+
.first()
110+
.usingRecursiveComparison()
111+
.isEqualTo(new BranchSCMHead("feature/issue-819"));
112+
assertThat(heads.values())
113+
.first()
114+
.usingRecursiveComparison()
115+
.isEqualTo(new SCMRevisionImpl(new BranchSCMHead("feature/issue-819"), "5ecffa3874e96920f24a2b3c0d0038e47d5cd1a4"));
116+
}
117+
118+
private String loadResource(String resource) throws IOException {
119+
try (InputStream stream = this.getClass().getResourceAsStream("cloud/" + resource)) {
120+
return IOUtils.toString(stream, StandardCharsets.UTF_8);
121+
}
122+
}
123+
124+
}

0 commit comments

Comments
 (0)