diff --git a/src/main/java/org/hibernate/infra/replicate/jira/service/jira/HandlerProjectContext.java b/src/main/java/org/hibernate/infra/replicate/jira/service/jira/HandlerProjectContext.java index 5dbf5f6..9fb346a 100644 --- a/src/main/java/org/hibernate/infra/replicate/jira/service/jira/HandlerProjectContext.java +++ b/src/main/java/org/hibernate/infra/replicate/jira/service/jira/HandlerProjectContext.java @@ -2,10 +2,14 @@ import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; +import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Supplier; import java.util.regex.Pattern; import org.hibernate.infra.replicate.jira.JiraConfig; @@ -16,6 +20,7 @@ import org.hibernate.infra.replicate.jira.service.jira.model.rest.JiraIssueBulkResponse; import org.hibernate.infra.replicate.jira.service.jira.model.rest.JiraIssues; import org.hibernate.infra.replicate.jira.service.jira.model.rest.JiraUser; +import org.hibernate.infra.replicate.jira.service.jira.model.rest.JiraVersion; import io.quarkus.logging.Log; @@ -26,6 +31,7 @@ public final class HandlerProjectContext implements AutoCloseable { private static final int ISSUES_PER_REQUEST = 25; private static final String SYNC_ISSUE_PLACEHOLDER_SUMMARY = "Sync issue placeholder"; private final ReentrantLock lock = new ReentrantLock(); + private final ReentrantLock versionLock = new ReentrantLock(); private final String projectName; private final String projectGroupName; @@ -43,6 +49,8 @@ public final class HandlerProjectContext implements AutoCloseable { private final Pattern sourceLabelPattern; private final DateTimeFormatter formatter; + private final Map destFixVersions; + public HandlerProjectContext(String projectName, String projectGroupName, JiraRestClient sourceJiraClient, JiraRestClient destinationJiraClient, HandlerProjectGroupContext projectGroupContext, Map allProjectsContextMap) { @@ -63,6 +71,9 @@ public HandlerProjectContext(String projectName, String projectGroupName, JiraRe this.sourceLabelPattern = Pattern .compile(projectGroupContext.projectGroup().formatting().labelTemplate().formatted(".+")); this.formatter = DateTimeFormatter.ofPattern(projectGroupContext.projectGroup().formatting().timestampFormat()); + + this.destFixVersions = getAndCreateMissingCurrentFixVersions(project, projectGroupContext, sourceJiraClient, + destinationJiraClient); } public JiraConfig.JiraProject project() { @@ -233,4 +244,107 @@ public boolean isSourceLabel(String label) { public String formatTimestamp(ZonedDateTime time) { return time != null ? time.format(formatter) : ""; } + + public JiraVersion fixVersion(JiraVersion version) { + return fixVersion(version, false); + } + + public JiraVersion fixVersion(JiraVersion version, boolean force) { + if (!force) { + JiraVersion v = destFixVersions.get(version.name); + if (v != null) { + return v; + } + } + versionLock.lock(); + try { + if (force) { + return destFixVersions.compute(version.name, (name, current) -> upsert(project, projectGroupContext, + destinationJiraClient, version, List.of())); + } else { + return destFixVersions.computeIfAbsent(version.name, + name -> upsert(project, projectGroupContext, destinationJiraClient, version, List.of())); + } + } catch (Exception e) { + Log.errorf(e, + "Couldn't create a copy of the fix version %s, version will not be synced for a particular Jira ticket.", + version.name); + return null; + } finally { + versionLock.unlock(); + } + } + + public void refreshFixVersions() { + versionLock.lock(); + try { + destFixVersions.clear(); + destFixVersions.putAll(getAndCreateMissingCurrentFixVersions(project, projectGroupContext, sourceJiraClient, + destinationJiraClient)); + } finally { + versionLock.unlock(); + } + } + + private static Map getAndCreateMissingCurrentFixVersions(JiraConfig.JiraProject project, + HandlerProjectGroupContext projectGroupContext, JiraRestClient sourceJiraClient, + JiraRestClient destinationJiraClient) { + Map result = new HashMap<>(); + + try { + List upstreamVersions = sourceJiraClient.versions(project.originalProjectKey()); + List downstreamVersions = destinationJiraClient.versions(project.projectKey()); + + for (JiraVersion upstreamVersion : upstreamVersions) { + JiraVersion downstreamVersion = upsert(project, projectGroupContext, destinationJiraClient, + upstreamVersion, downstreamVersions); + result.put(upstreamVersion.name, downstreamVersion); + } + } catch (Exception e) { + Log.errorf(e, "Encountered a problem while building the fix version map for %s: %s", project.projectKey(), + e.getMessage()); + } + + return result; + } + + private static JiraVersion upsert(JiraConfig.JiraProject project, HandlerProjectGroupContext projectGroupContext, + JiraRestClient jiraRestClient, JiraVersion upstreamVersion, List downstreamVersions) { + Optional version = JiraVersion.findVersion(upstreamVersion.id, downstreamVersions); + JiraVersion downstreamVersion = null; + if (version.isEmpty()) { + Log.infof("Creating a new fix version for project %s: %s", project.projectKey(), upstreamVersion.name); + downstreamVersion = processJiraVersion(project, projectGroupContext, upstreamVersion, + () -> jiraRestClient.create(upstreamVersion.copyForProject(project))); + } else if (versionNeedsUpdate(upstreamVersion, version.get())) { + Log.infof("Updating a fix version for project %s: %s", project.projectKey(), upstreamVersion.name); + downstreamVersion = processJiraVersion(project, projectGroupContext, upstreamVersion, + () -> jiraRestClient.update(version.get().id, upstreamVersion.copyForProject(project))); + } else { + downstreamVersion = version.get(); + } + return downstreamVersion; + } + + private static JiraVersion processJiraVersion(JiraConfig.JiraProject project, + HandlerProjectGroupContext projectGroupContext, JiraVersion upstreamVersion, Supplier action) { + try { + projectGroupContext.startProcessingEvent(); + return action.get(); + } catch (InterruptedException e) { + Log.error("Interrupted while trying to process fix version", e); + Thread.currentThread().interrupt(); + } catch (Exception e) { + Log.errorf(e, "Ignoring fix version sync. Unable to process fix %s version for project %s: %s", + upstreamVersion.name, project.projectKey(), e.getMessage()); + } + return null; + } + + private static boolean versionNeedsUpdate(JiraVersion upstreamVersion, JiraVersion downstreamVersion) { + return !Objects.equals(upstreamVersion.name, downstreamVersion.name) + || !Objects.equals(upstreamVersion.prepareVersionDescriptionForCopy(), downstreamVersion.description) + || upstreamVersion.released != downstreamVersion.released + || !Objects.equals(upstreamVersion.releaseDate, downstreamVersion.releaseDate); + } } diff --git a/src/main/java/org/hibernate/infra/replicate/jira/service/jira/JiraService.java b/src/main/java/org/hibernate/infra/replicate/jira/service/jira/JiraService.java index 2a0c92a..bd586cf 100644 --- a/src/main/java/org/hibernate/infra/replicate/jira/service/jira/JiraService.java +++ b/src/main/java/org/hibernate/infra/replicate/jira/service/jira/JiraService.java @@ -256,6 +256,18 @@ public void registerManagementRoutes(@Observes ManagementInterface mi) { triggerCommentSyncEvents(project, null, comments); rc.end(); }); + mi.router().get("/sync/fix-versions/:project").blockingHandler(rc -> { + String project = rc.pathParam("project"); + + HandlerProjectContext context = contextPerProject.get(project); + + if (context == null) { + throw new IllegalArgumentException("Unknown project '%s'".formatted(project)); + } + + context.submitTask(context::refreshFixVersions); + rc.end(); + }); } /** diff --git a/src/main/java/org/hibernate/infra/replicate/jira/service/jira/client/JiraRestClient.java b/src/main/java/org/hibernate/infra/replicate/jira/service/jira/client/JiraRestClient.java index 336daae..348ab6c 100644 --- a/src/main/java/org/hibernate/infra/replicate/jira/service/jira/client/JiraRestClient.java +++ b/src/main/java/org/hibernate/infra/replicate/jira/service/jira/client/JiraRestClient.java @@ -16,6 +16,7 @@ import org.hibernate.infra.replicate.jira.service.jira.model.rest.JiraSimpleObject; import org.hibernate.infra.replicate.jira.service.jira.model.rest.JiraTransition; import org.hibernate.infra.replicate.jira.service.jira.model.rest.JiraUser; +import org.hibernate.infra.replicate.jira.service.jira.model.rest.JiraVersion; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.ObjectMapper; @@ -148,6 +149,22 @@ JiraIssues find(@QueryParam("jql") String query, @QueryParam("startAt") int star @Path("/issue/{issueKey}/archive") void archive(@PathParam("issueKey") String issueKey); + @GET + @Path("/version/{id}") + JiraVersion version(@PathParam("id") Long id); + + @GET + @Path("/project/{projectKey}/versions") + List versions(@PathParam("projectKey") String projectKey); + + @POST + @Path("/version") + JiraVersion create(JiraVersion version); + + @PUT + @Path("/version/{id}") + JiraVersion update(@PathParam("id") String id, JiraVersion version); + @ClientObjectMapper static ObjectMapper objectMapper(ObjectMapper defaultObjectMapper) { return defaultObjectMapper.copy().setDefaultPropertyInclusion(JsonInclude.Include.NON_EMPTY); diff --git a/src/main/java/org/hibernate/infra/replicate/jira/service/jira/client/JiraRestClientBuilder.java b/src/main/java/org/hibernate/infra/replicate/jira/service/jira/client/JiraRestClientBuilder.java index a6d0145..1fd871d 100644 --- a/src/main/java/org/hibernate/infra/replicate/jira/service/jira/client/JiraRestClientBuilder.java +++ b/src/main/java/org/hibernate/infra/replicate/jira/service/jira/client/JiraRestClientBuilder.java @@ -22,6 +22,7 @@ import org.hibernate.infra.replicate.jira.service.jira.model.rest.JiraSimpleObject; import org.hibernate.infra.replicate.jira.service.jira.model.rest.JiraTransition; import org.hibernate.infra.replicate.jira.service.jira.model.rest.JiraUser; +import org.hibernate.infra.replicate.jira.service.jira.model.rest.JiraVersion; import org.jboss.resteasy.reactive.RestResponse; import org.jboss.resteasy.reactive.client.api.ClientLogger; @@ -254,6 +255,26 @@ public void archive(String issueKey) { withRetry(() -> delegate.archive(issueKey)); } + @Override + public JiraVersion version(Long id) { + return withRetry(() -> delegate.version(id)); + } + + @Override + public List versions(String projectKey) { + return withRetry(() -> delegate.versions(projectKey)); + } + + @Override + public JiraVersion create(JiraVersion version) { + return withRetry(() -> delegate.create(version)); + } + + @Override + public JiraVersion update(String id, JiraVersion version) { + return withRetry(() -> delegate.update(id, version)); + } + private static final int RETRIES = 5; private static final Duration WAIT_BETWEEN_RETRIES = Duration.of(2, ChronoUnit.SECONDS); diff --git a/src/main/java/org/hibernate/infra/replicate/jira/service/jira/handler/JiraIssueAbstractEventHandler.java b/src/main/java/org/hibernate/infra/replicate/jira/service/jira/handler/JiraIssueAbstractEventHandler.java index 4ee0069..0f21ea7 100644 --- a/src/main/java/org/hibernate/infra/replicate/jira/service/jira/handler/JiraIssueAbstractEventHandler.java +++ b/src/main/java/org/hibernate/infra/replicate/jira/service/jira/handler/JiraIssueAbstractEventHandler.java @@ -19,6 +19,7 @@ import org.hibernate.infra.replicate.jira.service.jira.model.rest.JiraSimpleObject; import org.hibernate.infra.replicate.jira.service.jira.model.rest.JiraTransition; import org.hibernate.infra.replicate.jira.service.jira.model.rest.JiraUser; +import org.hibernate.infra.replicate.jira.service.jira.model.rest.JiraVersion; import org.hibernate.infra.replicate.jira.service.reporting.ReportingConfig; abstract class JiraIssueAbstractEventHandler extends JiraEventHandler { @@ -163,6 +164,16 @@ protected JiraIssue issueToCreate(JiraIssue sourceIssue, JiraIssue downstreamIss .put(epicLinkDestinationLabelCustomFieldName, sourceEpicLabel)); } + if (sourceIssue.fields.fixVersions != null) { + destinationIssue.fields.fixVersions = new ArrayList<>(); + for (JiraVersion version : sourceIssue.fields.fixVersions) { + JiraVersion downstream = context.fixVersion(version); + if (downstream != null) { + destinationIssue.fields.fixVersions.add(downstream); + } + } + } + return destinationIssue; } @@ -175,7 +186,7 @@ private List prepareLabels(JiraIssue sourceIssue, JiraIssue downstreamIs // let's also add fix versions to the labels if (sourceIssue.fields.fixVersions != null) { - for (JiraSimpleObject fixVersion : sourceIssue.fields.fixVersions) { + for (JiraVersion fixVersion : sourceIssue.fields.fixVersions) { String fixVersionLabel = "Fix version:%s".formatted(fixVersion.name).replace(' ', '_'); labelsToSet.add(fixVersionLabel); } diff --git a/src/main/java/org/hibernate/infra/replicate/jira/service/jira/handler/JiraVersionUpsertEventHandler.java b/src/main/java/org/hibernate/infra/replicate/jira/service/jira/handler/JiraVersionUpsertEventHandler.java new file mode 100644 index 0000000..4a4889c --- /dev/null +++ b/src/main/java/org/hibernate/infra/replicate/jira/service/jira/handler/JiraVersionUpsertEventHandler.java @@ -0,0 +1,23 @@ +package org.hibernate.infra.replicate.jira.service.jira.handler; + +import org.hibernate.infra.replicate.jira.service.jira.HandlerProjectContext; +import org.hibernate.infra.replicate.jira.service.jira.model.rest.JiraVersion; +import org.hibernate.infra.replicate.jira.service.reporting.ReportingConfig; + +public class JiraVersionUpsertEventHandler extends JiraEventHandler { + + public JiraVersionUpsertEventHandler(ReportingConfig reportingConfig, HandlerProjectContext context, Long id) { + super(reportingConfig, context, id); + } + + @Override + protected void doRun() { + JiraVersion version = context.sourceJiraClient().version(objectId); + context.fixVersion(version, true); + } + + @Override + public String toString() { + return "JiraVersionUpsertEventHandler{" + "objectId=" + objectId + '}'; + } +} diff --git a/src/main/java/org/hibernate/infra/replicate/jira/service/jira/model/hook/JiraWebHookEvent.java b/src/main/java/org/hibernate/infra/replicate/jira/service/jira/model/hook/JiraWebHookEvent.java index 86df3e6..5df55a7 100644 --- a/src/main/java/org/hibernate/infra/replicate/jira/service/jira/model/hook/JiraWebHookEvent.java +++ b/src/main/java/org/hibernate/infra/replicate/jira/service/jira/model/hook/JiraWebHookEvent.java @@ -19,6 +19,7 @@ public class JiraWebHookEvent extends JiraBaseObject { public JiraWebHookObject comment; public JiraWebHookIssue issue; public JiraWebHookIssueLink issueLink; + public JiraWebHookObject version; public Optional eventType() { return JiraWebhookEventType.of(webhookEvent); @@ -27,6 +28,6 @@ public Optional eventType() { @Override public String toString() { return "JiraWebHookEvent{" + "webhookEvent='" + webhookEvent + '\'' + ", comment=" + comment + ", issue=" - + issue + ", otherProperties=" + properties() + '}'; + + issue + ", otherProperties=" + properties() + ", version=" + version + '}'; } } diff --git a/src/main/java/org/hibernate/infra/replicate/jira/service/jira/model/hook/JiraWebhookEventType.java b/src/main/java/org/hibernate/infra/replicate/jira/service/jira/model/hook/JiraWebhookEventType.java index e44aeda..0d7182a 100644 --- a/src/main/java/org/hibernate/infra/replicate/jira/service/jira/model/hook/JiraWebhookEventType.java +++ b/src/main/java/org/hibernate/infra/replicate/jira/service/jira/model/hook/JiraWebhookEventType.java @@ -12,6 +12,7 @@ import org.hibernate.infra.replicate.jira.service.jira.handler.JiraIssueLinkDeleteEventHandler; import org.hibernate.infra.replicate.jira.service.jira.handler.JiraIssueLinkUpsertEventHandler; import org.hibernate.infra.replicate.jira.service.jira.handler.JiraIssueUpsertEventHandler; +import org.hibernate.infra.replicate.jira.service.jira.handler.JiraVersionUpsertEventHandler; import org.hibernate.infra.replicate.jira.service.reporting.ReportingConfig; public enum JiraWebhookEventType { @@ -119,6 +120,28 @@ public Collection handlers(ReportingConfig reportingConfig, JiraWebHoo return List .of(new JiraCommentDeleteEventHandler(reportingConfig, context, event.comment.id, event.issue.id)); } + }, + VERSION_CREATED("jira:version_created") { + @Override + public Collection handlers(ReportingConfig reportingConfig, JiraWebHookEvent event, + HandlerProjectContext context) { + if (event.version == null || event.version.id == null) { + throw new IllegalStateException( + "Trying to handle a version event but version id is null: %s".formatted(event)); + } + return List.of(new JiraVersionUpsertEventHandler(reportingConfig, context, event.version.id)); + } + }, + VERSION_UPDATED("jira:version_updated") { + @Override + public Collection handlers(ReportingConfig reportingConfig, JiraWebHookEvent event, + HandlerProjectContext context) { + if (event.version == null || event.version.id == null) { + throw new IllegalStateException( + "Trying to handle a version event but version id is null: %s".formatted(event)); + } + return List.of(new JiraVersionUpsertEventHandler(reportingConfig, context, event.version.id)); + } }; private final String name; diff --git a/src/main/java/org/hibernate/infra/replicate/jira/service/jira/model/rest/JiraFields.java b/src/main/java/org/hibernate/infra/replicate/jira/service/jira/model/rest/JiraFields.java index 3e367d2..53e77c5 100644 --- a/src/main/java/org/hibernate/infra/replicate/jira/service/jira/model/rest/JiraFields.java +++ b/src/main/java/org/hibernate/infra/replicate/jira/service/jira/model/rest/JiraFields.java @@ -17,7 +17,7 @@ public class JiraFields extends JiraBaseObject { public JiraUser assignee; public JiraUser reporter; - public List fixVersions; + public List fixVersions; // NOTE: this one is for "read-only" purposes, to create links a different API // has to be used public List issuelinks; diff --git a/src/main/java/org/hibernate/infra/replicate/jira/service/jira/model/rest/JiraVersion.java b/src/main/java/org/hibernate/infra/replicate/jira/service/jira/model/rest/JiraVersion.java new file mode 100644 index 0000000..1b0d71c --- /dev/null +++ b/src/main/java/org/hibernate/infra/replicate/jira/service/jira/model/rest/JiraVersion.java @@ -0,0 +1,71 @@ +package org.hibernate.infra.replicate.jira.service.jira.model.rest; + +import java.net.URI; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.regex.Pattern; + +import org.hibernate.infra.replicate.jira.JiraConfig; +import org.hibernate.infra.replicate.jira.service.jira.model.JiraBaseObject; + +import jakarta.ws.rs.core.UriBuilder; + +public class JiraVersion extends JiraBaseObject { + + public URI self; + public String id; + public String name; + public String description; + public String projectId; + public String releaseDate; + public boolean released; + + public JiraVersion() { + } + + public JiraVersion(String id) { + this.id = id; + } + + public JiraVersion copyForProject(JiraConfig.JiraProject project) { + JiraVersion copy = new JiraVersion(); + copy.name = name; + copy.description = prepareVersionDescriptionForCopy(); + copy.projectId = project.projectId(); + copy.releaseDate = releaseDate; + copy.released = released; + return copy; + } + + public String prepareVersionDescriptionForCopy() { + URI verisonUri = createJiraVersionUri(this); + return """ + {quote}This [version|%s] was created as a copy of %s{quote} + + + %s""".formatted(verisonUri, this.name, Objects.toString(this.description, "")).trim(); + } + + public static Optional findVersion(String versionId, List versions) { + if (versions == null || versions.isEmpty()) { + return Optional.empty(); + } + // e.g. https://hibernate.atlassian.net/projects/HSEARCH/versions/32220/ + Pattern pattern = Pattern.compile("(?s)^\\{quote\\}This \\[version.+/versions/%s\\].*".formatted(versionId)); + for (JiraVersion check : versions) { + if (pattern.matcher(check.description).matches()) { + return Optional.of(check); + } + } + return Optional.empty(); + } + + private static URI createJiraVersionUri(JiraVersion version) { + // e.g. + // https://hibernate.atlassian.net/projects/HSEARCH/versions/32220 + return UriBuilder.fromUri(version.self).replacePath("projects").path(version.projectId).path("versions") + .path(version.id).replaceQuery("").build(); + } + +} diff --git a/src/test/java/org/hibernate/infra/replicate/jira/export/ExportProjectTest.java b/src/test/java/org/hibernate/infra/replicate/jira/export/ExportProjectTest.java index 556946f..502e50d 100644 --- a/src/test/java/org/hibernate/infra/replicate/jira/export/ExportProjectTest.java +++ b/src/test/java/org/hibernate/infra/replicate/jira/export/ExportProjectTest.java @@ -13,7 +13,7 @@ import org.hibernate.infra.replicate.jira.service.jira.client.JiraRestClientBuilder; import org.hibernate.infra.replicate.jira.service.jira.model.rest.JiraIssue; import org.hibernate.infra.replicate.jira.service.jira.model.rest.JiraIssues; -import org.hibernate.infra.replicate.jira.service.jira.model.rest.JiraSimpleObject; +import org.hibernate.infra.replicate.jira.service.jira.model.rest.JiraVersion; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -27,6 +27,7 @@ @TestProfile(ExportProjectTest.Profile.class) @QuarkusTest +@Disabled class ExportProjectTest { private static final String DEFAULT_REPORTER_NAME = "hibernate-admins@redhat.com"; @@ -153,7 +154,7 @@ private List formatLabels(JiraIssue issue) { } if (issue.fields.fixVersions != null) { - for (JiraSimpleObject fixVersion : issue.fields.fixVersions) { + for (JiraVersion fixVersion : issue.fields.fixVersions) { labels.add("Fix version: %s".formatted(fixVersion.name).replace(' ', '_')); } } diff --git a/src/test/java/org/hibernate/infra/replicate/jira/handler/IssueTest.java b/src/test/java/org/hibernate/infra/replicate/jira/handler/IssueTest.java index c7a0752..1f88ffa 100644 --- a/src/test/java/org/hibernate/infra/replicate/jira/handler/IssueTest.java +++ b/src/test/java/org/hibernate/infra/replicate/jira/handler/IssueTest.java @@ -17,6 +17,7 @@ import org.hibernate.infra.replicate.jira.service.jira.handler.JiraIssueLinkDeleteEventHandler; import org.hibernate.infra.replicate.jira.service.jira.handler.JiraIssueLinkUpsertEventHandler; import org.hibernate.infra.replicate.jira.service.jira.handler.JiraIssueUpsertEventHandler; +import org.hibernate.infra.replicate.jira.service.jira.model.rest.JiraIssue; import org.hibernate.infra.replicate.jira.service.reporting.ReportingConfig; import org.junit.jupiter.api.AfterEach; @@ -72,7 +73,7 @@ void testUpsert() { // - the downstream issue is updated, // - web link added pointing to the issue // - transition is performed - Mockito.verify(destination, Mockito.times(1)).update(eq("JIRATEST2-1"), any()); + Mockito.verify(destination, Mockito.times(1)).update(eq("JIRATEST2-1"), any(JiraIssue.class)); Mockito.verify(destination, Mockito.times(1)).upsertRemoteLink(eq("JIRATEST2-1"), any()); Mockito.verify(destination, Mockito.times(1)).transition(eq("JIRATEST2-1"), any()); } @@ -103,7 +104,7 @@ void testRemoveNonExisting() { // - we called the source jira and it throws 404 // - destination jira is updated (title) Mockito.verify(source, Mockito.times(1)).getIssue(eq("JIRATEST1-1")); - Mockito.verify(destination, Mockito.times(1)).update(eq("JIRATEST2-1"), any()); + Mockito.verify(destination, Mockito.times(1)).update(eq("JIRATEST2-1"), any(JiraIssue.class)); } finally { source.itemCannotBeFound.set(""); } diff --git a/src/test/java/org/hibernate/infra/replicate/jira/mock/SampleJiraRestClient.java b/src/test/java/org/hibernate/infra/replicate/jira/mock/SampleJiraRestClient.java index 3bde855..da52e9a 100644 --- a/src/test/java/org/hibernate/infra/replicate/jira/mock/SampleJiraRestClient.java +++ b/src/test/java/org/hibernate/infra/replicate/jira/mock/SampleJiraRestClient.java @@ -3,6 +3,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; @@ -23,6 +24,7 @@ import org.hibernate.infra.replicate.jira.service.jira.model.rest.JiraSimpleObject; import org.hibernate.infra.replicate.jira.service.jira.model.rest.JiraTransition; import org.hibernate.infra.replicate.jira.service.jira.model.rest.JiraUser; +import org.hibernate.infra.replicate.jira.service.jira.model.rest.JiraVersion; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; @@ -193,6 +195,26 @@ public void archive(String issueKey) { // do nothing } + @Override + public JiraVersion version(Long id) { + return new JiraVersion(Objects.toString(id, null)); + } + + @Override + public List versions(String projectKey) { + return List.of(new JiraVersion("version")); + } + + @Override + public JiraVersion create(JiraVersion version) { + return version; + } + + @Override + public JiraVersion update(String id, JiraVersion version) { + return version; + } + private JiraIssueLink sampleIssueLink(Long id) { try { return objectMapper.readValue("""