You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by bt...@apache.org on 2023/02/21 01:16:10 UTC

[james-project] 07/09: JAMES-3885 Allow to resume username change from a given step

This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 484a3d35ddef9f9531340a869c1a27f362ea10ae
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Fri Feb 17 09:01:44 2023 +0700

    JAMES-3885 Allow to resume username change from a given step
    
    From a sysadmin perspective this eases the error management in
    case a step fails...
---
 .../webadmin/routes/UsernameChangeRoutes.java      |  7 ++-
 .../webadmin/service/UsernameChangeService.java    | 55 +++++++++++++++++-----
 .../james/webadmin/service/UsernameChangeTask.java | 26 +++++++---
 ...UsernameChangeTaskAdditionalInformationDTO.java | 16 +++++--
 .../webadmin/service/UsernameChangeTaskDTO.java    | 19 ++++++--
 .../webadmin/routes/UsernameChangeRoutesTest.java  | 42 +++++++++++++++++
 .../UsernameChangeTaskSerializationTest.java       | 41 +++++++++++++++-
 7 files changed, 177 insertions(+), 29 deletions(-)

diff --git a/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/UsernameChangeRoutes.java b/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/UsernameChangeRoutes.java
index 40bb9c6b15..f87dfecb51 100644
--- a/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/UsernameChangeRoutes.java
+++ b/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/UsernameChangeRoutes.java
@@ -19,10 +19,13 @@
 
 package org.apache.james.webadmin.routes;
 
+import java.util.Optional;
+
 import javax.inject.Inject;
 
 import org.apache.james.core.Username;
 import org.apache.james.task.TaskManager;
+import org.apache.james.user.api.UsernameChangeTaskStep.StepName;
 import org.apache.james.user.api.UsersRepository;
 import org.apache.james.webadmin.Routes;
 import org.apache.james.webadmin.service.UsernameChangeService;
@@ -73,7 +76,9 @@ public class UsernameChangeRoutes implements Routes {
             Preconditions.checkArgument(usersRepository.contains(oldUser), "'oldUser' parameter should be an existing user");
             Preconditions.checkArgument(usersRepository.contains(newUser), "'newUser' parameter should be an existing user");
 
-            return new UsernameChangeTask(service, oldUser, newUser);
+            Optional<StepName> fromStep = Optional.ofNullable(request.queryParams("fromStep")).map(StepName::new);
+
+            return new UsernameChangeTask(service, oldUser, newUser, fromStep);
         }).asRoute(taskManager);
     }
 }
diff --git a/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/service/UsernameChangeService.java b/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/service/UsernameChangeService.java
index 6e87f8aab1..a62b726818 100644
--- a/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/service/UsernameChangeService.java
+++ b/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/service/UsernameChangeService.java
@@ -21,6 +21,7 @@ package org.apache.james.webadmin.service;
 
 import java.util.Comparator;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
@@ -28,6 +29,7 @@ import javax.inject.Inject;
 
 import org.apache.james.core.Username;
 import org.apache.james.user.api.UsernameChangeTaskStep;
+import org.apache.james.user.api.UsernameChangeTaskStep.StepName;
 
 import com.google.common.collect.ImmutableMap;
 
@@ -40,33 +42,38 @@ public class UsernameChangeService {
         IN_PROGRESS,
         DONE,
         FAILED,
-        ABORTED
+        ABORTED,
+        SKIPPED
     }
 
     public static class UsernameChangeStatus {
-        private final Map<UsernameChangeTaskStep.StepName, StepState> states;
+        private final Map<StepName, StepState> states;
 
         public UsernameChangeStatus(Set<UsernameChangeTaskStep> steps) {
             states = new ConcurrentHashMap<>(steps.stream()
-                .collect(ImmutableMap.toImmutableMap(step -> step.name(), any -> StepState.WAITING)));
+                .collect(ImmutableMap.toImmutableMap(UsernameChangeTaskStep::name, any -> StepState.WAITING)));
         }
 
-        public void beginStep(UsernameChangeTaskStep.StepName step) {
+        public void beginStep(StepName step) {
             states.put(step, StepState.IN_PROGRESS);
         }
 
-        public void endStep(UsernameChangeTaskStep.StepName step) {
+        public void endStep(StepName step) {
             states.put(step, StepState.DONE);
         }
 
-        public void failedStep(UsernameChangeTaskStep.StepName step) {
+        public void failedStep(StepName step) {
             states.put(step, StepState.FAILED);
         }
 
-        public void abortStep(UsernameChangeTaskStep.StepName step) {
+        public void abortStep(StepName step) {
             states.put(step, StepState.ABORTED);
         }
 
+        public void skipStep(StepName step) {
+            states.put(step, StepState.SKIPPED);
+        }
+
         public void abort() {
             states.entrySet()
                 .stream()
@@ -74,7 +81,7 @@ public class UsernameChangeService {
                 .forEach(entry -> abortStep(entry.getKey()));
         }
 
-        public Map<UsernameChangeTaskStep.StepName, StepState> getStates() {
+        public Map<StepName, StepState> getStates() {
             return ImmutableMap.copyOf(states);
         }
     }
@@ -82,15 +89,20 @@ public class UsernameChangeService {
     public static class Performer {
         private final Set<UsernameChangeTaskStep> steps;
         private final UsernameChangeStatus status;
+        private final Optional<Integer> correspondingPriority;
 
-        public Performer(Set<UsernameChangeTaskStep> steps, UsernameChangeStatus status) {
+        public Performer(Set<UsernameChangeTaskStep> steps, UsernameChangeStatus status, Optional<StepName> fromStep) {
             this.steps = steps;
             this.status = status;
+            this.correspondingPriority = fromStep.map(this::correspondingPriority);
         }
 
         public Mono<Void> changeUsername(Username oldUsername, Username newUsername) {
-            return Flux.fromIterable(steps)
-                .sort(Comparator.comparingInt(UsernameChangeTaskStep::priority))
+            correspondingPriority.ifPresent(priority -> steps.stream()
+                .filter(step -> step.priority() < priority)
+                .forEach(step -> status.skipStep(step.name())));
+
+            return steps()
                 .concatMap(step -> Mono.fromRunnable(() -> status.beginStep(step.name()))
                     .then(Mono.from(step.changeUsername(oldUsername, newUsername)))
                     .then(Mono.fromRunnable(() -> status.endStep(step.name())))
@@ -99,6 +111,23 @@ public class UsernameChangeService {
                 .then();
         }
 
+        private Flux<UsernameChangeTaskStep> steps() {
+            return correspondingPriority
+                .map(priority -> Flux.fromIterable(steps)
+                    .filter(step -> step.priority() >= priority)
+                    .sort(Comparator.comparingInt(UsernameChangeTaskStep::priority)))
+                .orElseGet(() -> Flux.fromIterable(steps)
+                    .sort(Comparator.comparingInt(UsernameChangeTaskStep::priority)));
+        }
+
+        private int correspondingPriority(StepName stepName) {
+            return steps.stream()
+                .filter(step -> step.name().equals(stepName))
+                .map(UsernameChangeTaskStep::priority)
+                .findAny()
+                .orElseThrow(() -> new IllegalArgumentException("Starting step not found: " + stepName.asString()));
+        }
+
         public UsernameChangeStatus getStatus() {
             return status;
         }
@@ -111,7 +140,7 @@ public class UsernameChangeService {
         this.steps = steps;
     }
 
-    public Performer performer() {
-        return new Performer(steps, new UsernameChangeStatus(steps));
+    public Performer performer(Optional<StepName> fromStep) {
+        return new Performer(steps, new UsernameChangeStatus(steps), fromStep);
     }
 }
diff --git a/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/service/UsernameChangeTask.java b/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/service/UsernameChangeTask.java
index cab6908686..677ff961d0 100644
--- a/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/service/UsernameChangeTask.java
+++ b/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/service/UsernameChangeTask.java
@@ -28,7 +28,7 @@ import org.apache.james.core.Username;
 import org.apache.james.task.Task;
 import org.apache.james.task.TaskExecutionDetails;
 import org.apache.james.task.TaskType;
-import org.apache.james.user.api.UsernameChangeTaskStep;
+import org.apache.james.user.api.UsernameChangeTaskStep.StepName;
 
 import reactor.core.publisher.Mono;
 
@@ -39,13 +39,19 @@ public class UsernameChangeTask implements Task {
         private final Instant timestamp;
         private final Username oldUser;
         private final Username newUser;
-        private final Map<UsernameChangeTaskStep.StepName, UsernameChangeService.StepState> status;
+        private final Map<StepName, UsernameChangeService.StepState> status;
+        private final Optional<StepName> fromStep;
 
-        public AdditionalInformation(Instant timestamp, Username oldUser, Username newUser, Map<UsernameChangeTaskStep.StepName, UsernameChangeService.StepState> status) {
+        public AdditionalInformation(Instant timestamp, Username oldUser, Username newUser, Map<StepName, UsernameChangeService.StepState> status, Optional<StepName> fromStep) {
             this.timestamp = timestamp;
             this.oldUser = oldUser;
             this.newUser = newUser;
             this.status = status;
+            this.fromStep = fromStep;
+        }
+
+        public Optional<StepName> getFromStep() {
+            return fromStep;
         }
 
         public Username getOldUser() {
@@ -56,7 +62,7 @@ public class UsernameChangeTask implements Task {
             return newUser;
         }
 
-        public Map<UsernameChangeTaskStep.StepName, UsernameChangeService.StepState> getStatus() {
+        public Map<StepName, UsernameChangeService.StepState> getStatus() {
             return status;
         }
 
@@ -69,12 +75,14 @@ public class UsernameChangeTask implements Task {
     private final Username oldUser;
     private final Username newUser;
     private final UsernameChangeService.Performer performer;
+    private final Optional<StepName> fromStep;
 
-    public UsernameChangeTask(UsernameChangeService service, Username oldUser, Username newUser) {
+    public UsernameChangeTask(UsernameChangeService service, Username oldUser, Username newUser, Optional<StepName> fromStep) {
         this.oldUser = oldUser;
         this.newUser = newUser;
 
-        this.performer = service.performer();
+        this.performer = service.performer(fromStep);
+        this.fromStep = fromStep;
     }
 
 
@@ -96,7 +104,7 @@ public class UsernameChangeTask implements Task {
 
     @Override
     public Optional<TaskExecutionDetails.AdditionalInformation> details() {
-        return Optional.of(new AdditionalInformation(Clock.systemUTC().instant(), oldUser, newUser, performer.getStatus().getStates()));
+        return Optional.of(new AdditionalInformation(Clock.systemUTC().instant(), oldUser, newUser, performer.getStatus().getStates(), fromStep));
     }
 
     public Username getOldUser() {
@@ -106,4 +114,8 @@ public class UsernameChangeTask implements Task {
     public Username getNewUser() {
         return newUser;
     }
+
+    public Optional<StepName> getFromStep() {
+        return fromStep;
+    }
 }
diff --git a/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/service/UsernameChangeTaskAdditionalInformationDTO.java b/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/service/UsernameChangeTaskAdditionalInformationDTO.java
index 14e41f4e0c..76f670c439 100644
--- a/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/service/UsernameChangeTaskAdditionalInformationDTO.java
+++ b/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/service/UsernameChangeTaskAdditionalInformationDTO.java
@@ -20,12 +20,13 @@ package org.apache.james.webadmin.service;
 
 import java.time.Instant;
 import java.util.Map;
+import java.util.Optional;
 
 import org.apache.james.core.Username;
 import org.apache.james.json.DTOModule;
 import org.apache.james.server.task.json.dto.AdditionalInformationDTO;
 import org.apache.james.server.task.json.dto.AdditionalInformationDTOModule;
-import org.apache.james.user.api.UsernameChangeTaskStep;
+import org.apache.james.user.api.UsernameChangeTaskStep.StepName;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.google.common.collect.ImmutableMap;
@@ -40,8 +41,9 @@ public class UsernameChangeTaskAdditionalInformationDTO implements AdditionalInf
                 Username.of(dto.newUser),
                 dto.status.entrySet().stream()
                     .collect(ImmutableMap.toImmutableMap(
-                        entry -> new UsernameChangeTaskStep.StepName(entry.getKey()),
-                        entry -> UsernameChangeService.StepState.valueOf(entry.getValue())))))
+                        entry -> new StepName(entry.getKey()),
+                        entry -> UsernameChangeService.StepState.valueOf(entry.getValue()))),
+                dto.fromStep.map(StepName::new)))
             .toDTOConverter((details, type) -> new UsernameChangeTaskAdditionalInformationDTO(
                 type,
                 details.getOldUser().asString(),
@@ -50,6 +52,7 @@ public class UsernameChangeTaskAdditionalInformationDTO implements AdditionalInf
                     .collect(ImmutableMap.toImmutableMap(
                         entry -> entry.getKey().asString(),
                         entry -> entry.getValue().toString())),
+                details.getFromStep().map(StepName::asString),
                 details.timestamp()))
             .typeName(UsernameChangeTask.TYPE.asString())
             .withFactory(AdditionalInformationDTOModule::new);
@@ -59,18 +62,21 @@ public class UsernameChangeTaskAdditionalInformationDTO implements AdditionalInf
     private final String oldUser;
     private final String newUser;
     private final Map<String, String> status;
+    private final Optional<String> fromStep;
     private final Instant timestamp;
 
     public UsernameChangeTaskAdditionalInformationDTO(@JsonProperty("type") String type,
                                                       @JsonProperty("oldUser") String oldUser,
                                                       @JsonProperty("newUser") String newUser,
                                                       @JsonProperty("status") Map<String, String> status,
+                                                      @JsonProperty("fromStep") Optional<String> fromStep,
                                                       @JsonProperty("timestamp") Instant timestamp) {
         this.type = type;
         this.oldUser = oldUser;
         this.newUser = newUser;
         this.status = status;
         this.timestamp = timestamp;
+        this.fromStep = fromStep;
     }
 
     public String getOldUser() {
@@ -89,6 +95,10 @@ public class UsernameChangeTaskAdditionalInformationDTO implements AdditionalInf
         return timestamp;
     }
 
+    public Optional<String> getFromStep() {
+        return fromStep;
+    }
+
     @Override
     public String getType() {
         return type;
diff --git a/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/service/UsernameChangeTaskDTO.java b/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/service/UsernameChangeTaskDTO.java
index b833c9f1f8..7c558f66f9 100644
--- a/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/service/UsernameChangeTaskDTO.java
+++ b/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/service/UsernameChangeTaskDTO.java
@@ -18,10 +18,13 @@
  ****************************************************************/
 package org.apache.james.webadmin.service;
 
+import java.util.Optional;
+
 import org.apache.james.core.Username;
 import org.apache.james.json.DTOModule;
 import org.apache.james.server.task.json.dto.TaskDTO;
 import org.apache.james.server.task.json.dto.TaskDTOModule;
+import org.apache.james.user.api.UsernameChangeTaskStep.StepName;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
 
@@ -38,23 +41,29 @@ public class UsernameChangeTaskDTO implements TaskDTO {
     }
 
     public static UsernameChangeTaskDTO toDTO(UsernameChangeTask domainObject, String typeName) {
-        return new UsernameChangeTaskDTO(typeName, domainObject.getOldUser().asString(), domainObject.getNewUser().asString());
+        return new UsernameChangeTaskDTO(typeName,
+            domainObject.getOldUser().asString(),
+            domainObject.getNewUser().asString(),
+            domainObject.getFromStep().map(StepName::asString));
     }
 
     private final String type;
     private final String oldUser;
     private final String newUser;
+    private final Optional<String> fromStep;
 
     public UsernameChangeTaskDTO(@JsonProperty("type") String type,
                                  @JsonProperty("oldUser") String oldUser,
-                                 @JsonProperty("newUser") String newUser) {
+                                 @JsonProperty("newUser") String newUser,
+                                 @JsonProperty("fromStep") Optional<String> fromStep) {
         this.type = type;
         this.oldUser = oldUser;
         this.newUser = newUser;
+        this.fromStep = fromStep;
     }
 
     public UsernameChangeTask fromDTO(UsernameChangeService service) {
-        return new UsernameChangeTask(service, Username.of(oldUser), Username.of(newUser));
+        return new UsernameChangeTask(service, Username.of(oldUser), Username.of(newUser), fromStep.map(StepName::new));
     }
 
     @Override
@@ -69,4 +78,8 @@ public class UsernameChangeTaskDTO implements TaskDTO {
     public String getNewUser() {
         return newUser;
     }
+
+    public Optional<String> getFromStep() {
+        return fromStep;
+    }
 }
diff --git a/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/UsernameChangeRoutesTest.java b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/UsernameChangeRoutesTest.java
index 5d441aec07..9c5598515b 100644
--- a/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/UsernameChangeRoutesTest.java
+++ b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/UsernameChangeRoutesTest.java
@@ -242,5 +242,47 @@ class UsernameChangeRoutesTest {
             assertThat(behaviour1.get()).isTrue();
             assertThat(behaviour2.get()).isFalse();
         }
+
+        @Test
+        void shouldSupportSkipWhenFailure() {
+            String taskId = with()
+                .queryParam("action", "rename")
+                .queryParam("fromStep", "B")
+                .post("/users/" + OLD_USER.asString() + "/rename/" + NEW_USER.asString())
+                .jsonPath()
+                .get("taskId");
+
+            given()
+                .basePath(TasksRoutes.BASE)
+            .when()
+                .get(taskId + "/await")
+            .then()
+                .body("type", is("UsernameChangeTask"))
+                .body("status", is("failed"))
+                .body("additionalInformation.type", is("UsernameChangeTask"))
+                .body("additionalInformation.oldUser", is("jessy.jones@domain.tld"))
+                .body("additionalInformation.newUser", is("jessy.smith@domain.tld"))
+                .body("additionalInformation.status.A", is("SKIPPED"))
+                .body("additionalInformation.status.B", is("FAILED"))
+                .body("additionalInformation.status.C", is("ABORTED"));
+
+            assertThat(behaviour1.get()).isFalse();
+            assertThat(behaviour2.get()).isFalse();
+        }
+
+        @Test
+        void shouldRejectInvalidFromStep() {
+            given()
+                .queryParam("action", "rename")
+                .queryParam("fromStep", "invalid")
+            .when()
+                .post("/users/" + OLD_USER.asString() + "/rename/" + NEW_USER.asString())
+            .then()
+                .statusCode(HttpStatus.BAD_REQUEST_400)
+                .body("statusCode", Matchers.is(400))
+                .body("type", Matchers.is(ErrorResponder.ErrorType.INVALID_ARGUMENT.getType()))
+                .body("message", Matchers.is("Invalid arguments supplied in the user request"))
+                .body("details", Matchers.is("Starting step not found: invalid"));
+        }
     }
 }
\ No newline at end of file
diff --git a/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/service/UsernameChangeTaskSerializationTest.java b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/service/UsernameChangeTaskSerializationTest.java
index d17a7c2ecb..b03e4ed3dc 100644
--- a/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/service/UsernameChangeTaskSerializationTest.java
+++ b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/service/UsernameChangeTaskSerializationTest.java
@@ -20,6 +20,7 @@
 package org.apache.james.webadmin.service;
 
 import java.time.Instant;
+import java.util.Optional;
 
 import org.apache.james.JsonSerializationVerifier;
 import org.apache.james.core.Username;
@@ -65,6 +66,7 @@ class UsernameChangeTaskSerializationTest {
     }
 
     private static final String SERIALIZED_TASK = "{\"newUser\":\"geraldine\",\"oldUser\":\"user\",\"type\":\"UsernameChangeTask\"}";
+    private static final String SERIALIZED_TASK_WITH_FROM_STEP = "{\"newUser\":\"geraldine\",\"fromStep\":\"B\",\"oldUser\":\"user\",\"type\":\"UsernameChangeTask\"}";
     private static final String SERIALIZED_ADDITIONAL_INFORMATION = "{" +
         "  \"newUser\":\"geraldine\"," +
         "  \"oldUser\":\"user\"," +
@@ -76,17 +78,37 @@ class UsernameChangeTaskSerializationTest {
         "  \"timestamp\":\"2018-11-13T12:00:55Z\"," +
         "  \"type\":\"UsernameChangeTask\"" +
         "}";
+    private static final String SERIALIZED_ADDITIONAL_INFORMATION_WITH_STEP_NAME = "{" +
+        "  \"newUser\":\"geraldine\"," +
+        "  \"oldUser\":\"user\"," +
+        "  \"fromStep\":\"B\"," +
+        "  \"status\":{" +
+        "    \"A\":\"DONE\"," +
+        "    \"B\":\"WAITING\"," +
+        "    \"C\":\"FAILED\"," +
+        "    \"D\":\"ABORTED\"}," +
+        "  \"timestamp\":\"2018-11-13T12:00:55Z\"," +
+        "  \"type\":\"UsernameChangeTask\"" +
+        "}";
 
     private static final UsernameChangeService SERVICE = new UsernameChangeService(ImmutableSet.of(A, B, C, D));
 
     @Test
     void taskShouldBeSerializable() throws Exception {
         JsonSerializationVerifier.dtoModule(UsernameChangeTaskDTO.module(SERVICE))
-            .bean(new UsernameChangeTask(SERVICE, OLD_USERNAME, NEW_USERNAME))
+            .bean(new UsernameChangeTask(SERVICE, OLD_USERNAME, NEW_USERNAME, Optional.empty()))
             .json(SERIALIZED_TASK)
             .verify();
     }
 
+    @Test
+    void taskShouldBeSerializableWithFromStep() throws Exception {
+        JsonSerializationVerifier.dtoModule(UsernameChangeTaskDTO.module(SERVICE))
+            .bean(new UsernameChangeTask(SERVICE, OLD_USERNAME, NEW_USERNAME, Optional.of(STEP_B)))
+            .json(SERIALIZED_TASK_WITH_FROM_STEP)
+            .verify();
+    }
+
     @Test
     void additionalInformationShouldBeSerializable() throws Exception {
         JsonSerializationVerifier.dtoModule(UsernameChangeTaskAdditionalInformationDTO.module())
@@ -97,8 +119,23 @@ class UsernameChangeTaskSerializationTest {
                 ImmutableMap.of(STEP_A, UsernameChangeService.StepState.DONE,
                     STEP_B, UsernameChangeService.StepState.WAITING,
                     STEP_C, UsernameChangeService.StepState.FAILED,
-                    STEP_D, UsernameChangeService.StepState.ABORTED)))
+                    STEP_D, UsernameChangeService.StepState.ABORTED), Optional.empty()))
             .json(SERIALIZED_ADDITIONAL_INFORMATION)
             .verify();
     }
+
+    @Test
+    void additionalInformationShouldBeSerializableWithStepName() throws Exception {
+        JsonSerializationVerifier.dtoModule(UsernameChangeTaskAdditionalInformationDTO.module())
+            .bean(new UsernameChangeTask.AdditionalInformation(
+                TIMESTAMP,
+                OLD_USERNAME,
+                NEW_USERNAME,
+                ImmutableMap.of(STEP_A, UsernameChangeService.StepState.DONE,
+                    STEP_B, UsernameChangeService.StepState.WAITING,
+                    STEP_C, UsernameChangeService.StepState.FAILED,
+                    STEP_D, UsernameChangeService.StepState.ABORTED), Optional.of(STEP_B)))
+            .json(SERIALIZED_ADDITIONAL_INFORMATION_WITH_STEP_NAME)
+            .verify();
+    }
 }
\ No newline at end of file


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org