You are viewing a plain text version of this content. The canonical link for it is here.
Posted to server-dev@james.apache.org by bt...@apache.org on 2019/08/30 03:42:18 UTC

[james-project] 02/03: JAMES-2865 An POJO carrying mutable information of MockSMTPBehavior

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 90cab57c4101f903652b38784bdee5e9d173b1cc
Author: Tran Tien Duc <dt...@linagora.com>
AuthorDate: Wed Aug 28 10:45:00 2019 +0700

    JAMES-2865 An POJO carrying mutable information of MockSMTPBehavior
    
    For example:
     - number of answers remaining
    
    The SMTPBehaviorRepository should provide some additional information
    (number of answers remaining) rather than just the behavior
---
 .../mock/smtp/server/HTTPConfigurationServer.java  |   8 +-
 .../mock/smtp/server/SMTPBehaviorRepository.java   |  64 +++++---
 .../server/model/MockSMTPBehaviorInformation.java  | 148 ++++++++++++++++++
 .../org/apache/james/mock/smtp/server/Fixture.java |  16 ++
 .../smtp/server/SMTPBehaviorRepositoryTest.java    |  77 +++++++---
 .../model/MockSMTPBehaviorInformationTest.java     | 169 +++++++++++++++++++++
 6 files changed, 442 insertions(+), 40 deletions(-)

diff --git a/server/mailet/mock-smtp-server/src/main/java/org/apache/james/mock/smtp/server/HTTPConfigurationServer.java b/server/mailet/mock-smtp-server/src/main/java/org/apache/james/mock/smtp/server/HTTPConfigurationServer.java
index c25011f..95d65a1 100644
--- a/server/mailet/mock-smtp-server/src/main/java/org/apache/james/mock/smtp/server/HTTPConfigurationServer.java
+++ b/server/mailet/mock-smtp-server/src/main/java/org/apache/james/mock/smtp/server/HTTPConfigurationServer.java
@@ -31,13 +31,14 @@ import javax.servlet.http.HttpServletResponse;
 
 import org.apache.james.http.jetty.Configuration;
 import org.apache.james.http.jetty.JettyHttpServer;
+import org.apache.james.mock.smtp.server.model.MockSMTPBehaviorInformation;
 import org.apache.james.mock.smtp.server.model.MockSmtpBehaviors;
 import org.apache.james.util.Port;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.datatype.guava.GuavaModule;
 import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
-import com.google.common.collect.ImmutableList;
+import com.github.steveash.guavate.Guavate;
 
 public class HTTPConfigurationServer {
     static class HTTPConfigurationServlet extends HttpServlet {
@@ -64,7 +65,10 @@ public class HTTPConfigurationServer {
 
         @Override
         protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
-            MockSmtpBehaviors mockSmtpBehaviors = smtpBehaviorRepository.getBehaviors().orElse(new MockSmtpBehaviors(ImmutableList.of()));
+            MockSmtpBehaviors mockSmtpBehaviors = new MockSmtpBehaviors(smtpBehaviorRepository.remainingBehaviors()
+                .map(MockSMTPBehaviorInformation::getBehavior)
+                .collect(Guavate.toImmutableList()));
+
             resp.setStatus(SC_OK);
             resp.setContentType("application/json");
             objectMapper.writeValue(resp.getOutputStream(), mockSmtpBehaviors);
diff --git a/server/mailet/mock-smtp-server/src/main/java/org/apache/james/mock/smtp/server/SMTPBehaviorRepository.java b/server/mailet/mock-smtp-server/src/main/java/org/apache/james/mock/smtp/server/SMTPBehaviorRepository.java
index 2a8fe9d..8dad76a 100644
--- a/server/mailet/mock-smtp-server/src/main/java/org/apache/james/mock/smtp/server/SMTPBehaviorRepository.java
+++ b/server/mailet/mock-smtp-server/src/main/java/org/apache/james/mock/smtp/server/SMTPBehaviorRepository.java
@@ -20,41 +20,65 @@
 package org.apache.james.mock.smtp.server;
 
 import java.util.Arrays;
-import java.util.List;
-import java.util.Optional;
-import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.stream.Stream;
 
 import org.apache.james.mock.smtp.server.model.MockSMTPBehavior;
+import org.apache.james.mock.smtp.server.model.MockSMTPBehaviorInformation;
 import org.apache.james.mock.smtp.server.model.MockSmtpBehaviors;
 
-public class SMTPBehaviorRepository {
-    private final AtomicReference<MockSmtpBehaviors> behaviors;
+import com.github.steveash.guavate.Guavate;
+import com.google.common.annotations.VisibleForTesting;
 
-    public SMTPBehaviorRepository() {
-        this.behaviors = new AtomicReference<>();
-    }
+class SMTPBehaviorRepository {
+
+    private final ConcurrentLinkedQueue<MockSMTPBehaviorInformation> behaviorsInformation;
 
-    public Optional<MockSmtpBehaviors> getBehaviors() {
-        return Optional.ofNullable(this.behaviors.get());
+    SMTPBehaviorRepository() {
+        this.behaviorsInformation = new ConcurrentLinkedQueue<>();
     }
 
-    public void clearBehaviors() {
-        this.behaviors.set(null);
+    void clearBehaviors() {
+        synchronized (behaviorsInformation) {
+            this.behaviorsInformation.clear();
+        }
     }
 
-    public void setBehaviors(MockSmtpBehaviors behaviors) {
-        this.behaviors.set(behaviors);
+    void setBehaviors(MockSmtpBehaviors behaviors) {
+        synchronized (behaviorsInformation) {
+            clearBehaviors();
+
+            behaviorsInformation.addAll(behaviors
+                .getBehaviorList()
+                .stream()
+                .map(MockSMTPBehaviorInformation::from)
+                .collect(Guavate.toImmutableList()));
+        }
     }
 
-    public void setBehaviors(MockSMTPBehavior... behaviors) {
+    void setBehaviors(MockSMTPBehavior... behaviors) {
         setBehaviors(new MockSmtpBehaviors(Arrays.asList(behaviors)));
     }
 
-    public Stream<MockSMTPBehavior> allBehaviors() {
-        return Optional.ofNullable(behaviors.get())
-            .map(MockSmtpBehaviors::getBehaviorList)
-            .map(List::stream)
-            .orElseGet(Stream::empty);
+    Stream<MockSMTPBehaviorInformation> remainingBehaviors() {
+        synchronized (behaviorsInformation) {
+            return behaviorsInformation.stream()
+                .filter(MockSMTPBehaviorInformation::hasRemainingAnswers);
+        }
+    }
+
+    void decreaseRemainingAnswers(MockSMTPBehavior behavior) {
+        getBehaviorInformation(behavior)
+            .decreaseRemainingAnswers();
+    }
+
+    @VisibleForTesting
+    MockSMTPBehaviorInformation getBehaviorInformation(MockSMTPBehavior behavior) {
+        synchronized (behaviorsInformation) {
+            return behaviorsInformation.stream()
+                .filter(behaviorInformation -> behaviorInformation.getBehavior().equals(behavior))
+                .findFirst()
+                .orElseThrow(() -> new RuntimeException("behavior " + behavior + " not found"));
+        }
     }
 }
diff --git a/server/mailet/mock-smtp-server/src/main/java/org/apache/james/mock/smtp/server/model/MockSMTPBehaviorInformation.java b/server/mailet/mock-smtp-server/src/main/java/org/apache/james/mock/smtp/server/model/MockSMTPBehaviorInformation.java
new file mode 100644
index 0000000..baa0d50
--- /dev/null
+++ b/server/mailet/mock-smtp-server/src/main/java/org/apache/james/mock/smtp/server/model/MockSMTPBehaviorInformation.java
@@ -0,0 +1,148 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.mock.smtp.server.model;
+
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+
+public class MockSMTPBehaviorInformation {
+
+    interface RemainingAnswersCounter {
+
+        static RemainingAnswersCounter from(MockSMTPBehavior.NumberOfAnswersPolicy answersPolicy) {
+            return answersPolicy.getNumberOfAnswers()
+                .<RemainingAnswersCounter>map(AnswersCounter::new)
+                .orElseGet(UnlimitedAnswersCounter::new);
+        }
+
+        void decrease();
+
+        boolean hasRemainingAnswers();
+
+        Optional<Integer> getValue();
+    }
+
+    static class UnlimitedAnswersCounter implements RemainingAnswersCounter {
+
+        @Override
+        public void decrease() {
+            // do nothing
+        }
+
+        @Override
+        public boolean hasRemainingAnswers() {
+            return true;
+        }
+
+        @Override
+        public Optional<Integer> getValue() {
+            return Optional.empty();
+        }
+    }
+
+    static class AnswersCounter implements RemainingAnswersCounter {
+
+        static AnswersCounter remains(int remainingCount) {
+            return new AnswersCounter(remainingCount);
+        }
+
+        private final AtomicInteger remainingCount;
+
+        AnswersCounter(int remainingCount) {
+            Preconditions.checkArgument(remainingCount > 0, "remainingCount should be positive");
+
+            this.remainingCount = new AtomicInteger(remainingCount);
+        }
+
+        @Override
+        public void decrease() {
+            remainingCount.updateAndGet(currentCount -> {
+                Preconditions.checkState(currentCount > 0,
+                    "remainingCount is already being zero, you can not decrease more");
+
+                return currentCount - 1;
+            });
+        }
+
+        @Override
+        public boolean hasRemainingAnswers() {
+            return remainingCount.get() > 0;
+        }
+
+        @Override
+        public Optional<Integer> getValue() {
+            return Optional.of(remainingCount.get());
+        }
+    }
+
+    public static MockSMTPBehaviorInformation from(MockSMTPBehavior behavior) {
+        return new MockSMTPBehaviorInformation(
+            behavior,
+            RemainingAnswersCounter.from(behavior.getNumberOfAnswer()));
+    }
+
+    private final MockSMTPBehavior behavior;
+    private final RemainingAnswersCounter remainingAnswersCounter;
+
+    MockSMTPBehaviorInformation(MockSMTPBehavior behavior, RemainingAnswersCounter remainingAnswersCounter) {
+        Preconditions.checkNotNull(behavior);
+        Preconditions.checkNotNull(remainingAnswersCounter);
+
+        this.behavior = behavior;
+        this.remainingAnswersCounter = remainingAnswersCounter;
+    }
+
+    public void decreaseRemainingAnswers() {
+        remainingAnswersCounter.decrease();
+    }
+
+    public MockSMTPBehavior getBehavior() {
+        return behavior;
+    }
+
+    public boolean hasRemainingAnswers() {
+        return remainingAnswersCounter.hasRemainingAnswers();
+    }
+
+    @VisibleForTesting
+    public Optional<Integer> remainingAnswersCounter() {
+        return remainingAnswersCounter.getValue();
+    }
+
+    @Override
+    public final boolean equals(Object o) {
+        if (o instanceof MockSMTPBehaviorInformation) {
+            MockSMTPBehaviorInformation that = (MockSMTPBehaviorInformation) o;
+
+            return Objects.equals(this.behavior, that.behavior)
+                && Objects.equals(this.remainingAnswersCounter(), that.remainingAnswersCounter());
+        }
+        return false;
+    }
+
+    @Override
+    public final int hashCode() {
+        return Objects.hash(behavior, remainingAnswersCounter());
+    }
+}
\ No newline at end of file
diff --git a/server/mailet/mock-smtp-server/src/test/java/org/apache/james/mock/smtp/server/Fixture.java b/server/mailet/mock-smtp-server/src/test/java/org/apache/james/mock/smtp/server/Fixture.java
index 8d1afdc..81e4d15 100644
--- a/server/mailet/mock-smtp-server/src/test/java/org/apache/james/mock/smtp/server/Fixture.java
+++ b/server/mailet/mock-smtp-server/src/test/java/org/apache/james/mock/smtp/server/Fixture.java
@@ -68,6 +68,22 @@ public interface Fixture {
         Response.serverAccept(Response.SMTPStatusCode.of(250), "OK"),
         MockSMTPBehavior.NumberOfAnswersPolicy.times(7));
 
+    MockSMTPBehavior BEHAVIOR_MATCHING_EVERYTIME = new MockSMTPBehavior(
+        SMTPCommand.MAIL_FROM,
+        Condition.MATCH_ALL,
+        Response.serverReject(Response.SMTPStatusCode.COMMAND_NOT_IMPLEMENTED_502, "match all messages"),
+        MockSMTPBehavior.NumberOfAnswersPolicy.anytime());
+    MockSMTPBehavior BEHAVIOR_MATCHING_2_TIMES = new MockSMTPBehavior(
+        SMTPCommand.MAIL_FROM,
+        Condition.MATCH_ALL,
+        Response.serverReject(Response.SMTPStatusCode.COMMAND_NOT_IMPLEMENTED_502, "match all messages"),
+        MockSMTPBehavior.NumberOfAnswersPolicy.times(2));
+    MockSMTPBehavior BEHAVIOR_MATCHING_3_TIMES = new MockSMTPBehavior(
+        SMTPCommand.MAIL_FROM,
+        Condition.MATCH_ALL,
+        Response.serverReject(Response.SMTPStatusCode.COMMAND_NOT_IMPLEMENTED_502, "match all messages"),
+        MockSMTPBehavior.NumberOfAnswersPolicy.times(3));
+
     String JSON_BEHAVIORS = "[" + JSON_BEHAVIOR_ALL_FIELDS + ", "
         + JSON_BEHAVIOR_COMPULSORY_FIELDS + "]";
 
diff --git a/server/mailet/mock-smtp-server/src/test/java/org/apache/james/mock/smtp/server/SMTPBehaviorRepositoryTest.java b/server/mailet/mock-smtp-server/src/test/java/org/apache/james/mock/smtp/server/SMTPBehaviorRepositoryTest.java
index 9419ebd..0730ce3 100644
--- a/server/mailet/mock-smtp-server/src/test/java/org/apache/james/mock/smtp/server/SMTPBehaviorRepositoryTest.java
+++ b/server/mailet/mock-smtp-server/src/test/java/org/apache/james/mock/smtp/server/SMTPBehaviorRepositoryTest.java
@@ -19,16 +19,17 @@
 
 package org.apache.james.mock.smtp.server;
 
-import static org.apache.james.mock.smtp.server.Fixture.BEHAVIORS;
+import static org.apache.james.mock.smtp.server.Fixture.BEHAVIOR_ALL_FIELDS;
 import static org.apache.james.mock.smtp.server.Fixture.BEHAVIOR_COMPULSORY_FIELDS;
+import static org.apache.james.mock.smtp.server.Fixture.BEHAVIOR_MATCHING_2_TIMES;
+import static org.apache.james.mock.smtp.server.Fixture.BEHAVIOR_MATCHING_3_TIMES;
+import static org.apache.james.mock.smtp.server.Fixture.BEHAVIOR_MATCHING_EVERYTIME;
 import static org.assertj.core.api.Assertions.assertThat;
 
-import org.apache.james.mock.smtp.server.model.MockSmtpBehaviors;
+import org.apache.james.mock.smtp.server.model.MockSMTPBehaviorInformation;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
-import com.google.common.collect.ImmutableList;
-
 class SMTPBehaviorRepositoryTest {
     private SMTPBehaviorRepository testee;
 
@@ -38,34 +39,74 @@ class SMTPBehaviorRepositoryTest {
     }
 
     @Test
-    void getBehaviorsShouldReturnEmptyWhenNoValueStored() {
-        assertThat(testee.getBehaviors())
+    void remainingBehaviorsShouldReturnEmptyWhenNoValueStored() {
+        assertThat(testee.remainingBehaviors())
             .isEmpty();
     }
 
     @Test
-    void getBehaviorsShouldReturnPreviouslyStoredValue() {
-        testee.setBehaviors(BEHAVIORS);
+    void remainingBehaviorsShouldReturnPreviouslyStoredValue() {
+        testee.setBehaviors(BEHAVIOR_ALL_FIELDS, BEHAVIOR_COMPULSORY_FIELDS);
 
-        assertThat(testee.getBehaviors()).contains(BEHAVIORS);
+        assertThat(testee.remainingBehaviors())
+            .containsExactly(
+                MockSMTPBehaviorInformation.from(BEHAVIOR_ALL_FIELDS),
+                MockSMTPBehaviorInformation.from(BEHAVIOR_COMPULSORY_FIELDS));
     }
 
     @Test
-    void getBehaviorsShouldReturnLatestStoredValue() {
-        MockSmtpBehaviors newPojo = new MockSmtpBehaviors(ImmutableList.of(BEHAVIOR_COMPULSORY_FIELDS));
-
-        testee.setBehaviors(BEHAVIORS);
-        testee.setBehaviors(newPojo);
+    void remainingBehaviorsShouldReturnLatestStoredValue() {
+        testee.setBehaviors(BEHAVIOR_ALL_FIELDS, BEHAVIOR_COMPULSORY_FIELDS);
+        testee.setBehaviors(BEHAVIOR_COMPULSORY_FIELDS);
 
-        assertThat(testee.getBehaviors()).contains(newPojo);
+        assertThat(testee.remainingBehaviors())
+            .containsExactly(
+                MockSMTPBehaviorInformation.from(BEHAVIOR_COMPULSORY_FIELDS));
     }
 
     @Test
-    void getBehaviorsShouldReturnEmptyWhenCleared() {
-        testee.setBehaviors(BEHAVIORS);
+    void remainingBehaviorsShouldReturnEmptyWhenCleared() {
+        testee.setBehaviors(BEHAVIOR_ALL_FIELDS, BEHAVIOR_COMPULSORY_FIELDS);
 
         testee.clearBehaviors();
 
-        assertThat(testee.getBehaviors()).isEmpty();
+        assertThat(testee.remainingBehaviors()).isEmpty();
+    }
+
+    @Test
+    void getBehaviorInformationShouldReturnEmptyOptionalOfAnswerCountWhenUnlimitedAnswers() {
+        testee.setBehaviors(BEHAVIOR_MATCHING_EVERYTIME);
+
+        testee.decreaseRemainingAnswers(BEHAVIOR_MATCHING_EVERYTIME);
+        testee.decreaseRemainingAnswers(BEHAVIOR_MATCHING_EVERYTIME);
+        testee.decreaseRemainingAnswers(BEHAVIOR_MATCHING_EVERYTIME);
+        testee.decreaseRemainingAnswers(BEHAVIOR_MATCHING_EVERYTIME);
+        testee.decreaseRemainingAnswers(BEHAVIOR_MATCHING_EVERYTIME);
+
+        assertThat(testee.getBehaviorInformation(BEHAVIOR_MATCHING_EVERYTIME)
+                .remainingAnswersCounter())
+            .isEmpty();
+    }
+
+    @Test
+    void decreaseRemainingAnswersShouldDecreaseLimitedAnswer() {
+        testee.setBehaviors(BEHAVIOR_MATCHING_2_TIMES);
+
+        testee.decreaseRemainingAnswers(BEHAVIOR_MATCHING_2_TIMES);
+
+        assertThat(testee.getBehaviorInformation(BEHAVIOR_MATCHING_2_TIMES)
+                .remainingAnswersCounter())
+            .contains(1);
+    }
+
+    @Test
+    void decreaseRemainingAnswersShouldNotDecreaseOtherBehavior() {
+        testee.setBehaviors(BEHAVIOR_MATCHING_2_TIMES, BEHAVIOR_MATCHING_3_TIMES);
+
+        testee.decreaseRemainingAnswers(BEHAVIOR_MATCHING_2_TIMES);
+
+        assertThat(testee.getBehaviorInformation(BEHAVIOR_MATCHING_3_TIMES)
+                .remainingAnswersCounter())
+            .contains(3);
     }
 }
\ No newline at end of file
diff --git a/server/mailet/mock-smtp-server/src/test/java/org/apache/james/mock/smtp/server/model/MockSMTPBehaviorInformationTest.java b/server/mailet/mock-smtp-server/src/test/java/org/apache/james/mock/smtp/server/model/MockSMTPBehaviorInformationTest.java
new file mode 100644
index 0000000..a90f579
--- /dev/null
+++ b/server/mailet/mock-smtp-server/src/test/java/org/apache/james/mock/smtp/server/model/MockSMTPBehaviorInformationTest.java
@@ -0,0 +1,169 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.mock.smtp.server.model;
+
+import static org.apache.james.mock.smtp.server.model.MockSMTPBehaviorInformation.RemainingAnswersCounter;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import org.apache.james.mock.smtp.server.model.MockSMTPBehaviorInformation.AnswersCounter;
+import org.apache.james.mock.smtp.server.model.MockSMTPBehaviorInformation.UnlimitedAnswersCounter;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+import nl.jqno.equalsverifier.EqualsVerifier;
+import nl.jqno.equalsverifier.Warning;
+
+class MockSMTPBehaviorInformationTest {
+
+    @Test
+    void shouldMatchBeanContract() {
+        EqualsVerifier.forClass(MockSMTPBehaviorInformation.class)
+            .suppress(Warning.NULL_FIELDS)
+            .withIgnoredFields("remainingAnswersCounter")
+            .withPrefabValues(RemainingAnswersCounter.class, new UnlimitedAnswersCounter(), new UnlimitedAnswersCounter())
+            .verify();
+    }
+
+    @Nested
+    class UnlimitedAnswersTest {
+
+        @Test
+        void isRemainingShouldReturnTrue() {
+            RemainingAnswersCounter unlimited = new UnlimitedAnswersCounter();
+
+            assertThat(unlimited.hasRemainingAnswers())
+                .isTrue();
+        }
+
+        @Test
+        void isRemainingShouldReturnTrueAfterDecreasing() {
+            RemainingAnswersCounter unlimited = new UnlimitedAnswersCounter();
+            unlimited.decrease();
+            unlimited.decrease();
+            unlimited.decrease();
+
+            assertThat(unlimited.hasRemainingAnswers())
+                .isTrue();
+        }
+
+        @Test
+        void remainedShouldReturnEmptyOptional() {
+            RemainingAnswersCounter unlimited = new UnlimitedAnswersCounter();
+
+            assertThat(unlimited.getValue())
+                .isEmpty();
+        }
+
+        @Test
+        void remainedShouldReturnEmptyOptionalAfterDecreasing() {
+            RemainingAnswersCounter unlimited = new UnlimitedAnswersCounter();
+            unlimited.decrease();
+            unlimited.decrease();
+
+            assertThat(unlimited.getValue())
+                .isEmpty();
+        }
+    }
+
+    @Nested
+    class LimitedAnswersTest {
+
+        @Test
+        void constructorShouldThrowWhenPassingNegativeAnswersCount() {
+            assertThatThrownBy(() -> new AnswersCounter(-100))
+                .isInstanceOf(IllegalArgumentException.class);
+        }
+
+        @Test
+        void constructorShouldThrowWhenPassingZeroAnswersCount() {
+            assertThatThrownBy(() -> new AnswersCounter(0))
+                .isInstanceOf(IllegalArgumentException.class);
+        }
+
+        @Test
+        void isRemainingShouldReturnTrueWhenNoDecrease() {
+            RemainingAnswersCounter limited = AnswersCounter.remains(1);
+
+            assertThat(limited.hasRemainingAnswers())
+                .isTrue();
+        }
+
+        @Test
+        void isRemainingShouldReturnTrueWhenNotEnoughDecremental() {
+            RemainingAnswersCounter limited = AnswersCounter.remains(3);
+            limited.decrease();
+            limited.decrease();
+
+            assertThat(limited.hasRemainingAnswers())
+                .isTrue();
+        }
+
+        @Test
+        void isRemainingShouldReturnFalseWhenEnoughDecremental() {
+            RemainingAnswersCounter limited = AnswersCounter.remains(3);
+            limited.decrease();
+            limited.decrease();
+            limited.decrease();
+
+            assertThat(limited.hasRemainingAnswers())
+                .isFalse();
+        }
+
+        @Test
+        void isRemainingShouldThrowWhenExceededDecremental() {
+            RemainingAnswersCounter limited = AnswersCounter.remains(3);
+            limited.decrease();
+            limited.decrease();
+            limited.decrease();
+
+            assertThatThrownBy(limited::decrease)
+                .isInstanceOf(IllegalStateException.class);
+        }
+
+        @Test
+        void remainedShouldReturnWhenNoDecrease() {
+            RemainingAnswersCounter limited = AnswersCounter.remains(3);
+
+            assertThat(limited.getValue())
+                .contains(3);
+        }
+
+        @Test
+        void remainedShouldReturnWhenDecrease() {
+            RemainingAnswersCounter limited = AnswersCounter.remains(3);
+            limited.decrease();
+
+            assertThat(limited.getValue())
+                .contains(2);
+        }
+
+        @Test
+        void remainedShouldReturnZeroWhenEnoughDecremental() {
+            RemainingAnswersCounter limited = AnswersCounter.remains(3);
+            limited.decrease();
+            limited.decrease();
+            limited.decrease();
+
+            assertThat(limited.getValue())
+                .contains(0);
+        }
+    }
+}
\ No newline at end of file


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