diff --git a/build.gradle b/build.gradle index 904fff623..fbf638b28 100644 --- a/build.gradle +++ b/build.gradle @@ -166,8 +166,9 @@ jacocoTestReport { } afterEvaluate { // only report on main library not examples classDirectories.setFrom(files(classDirectories.files.collect { - fileTree(dir: it, - exclude: ['**/examples**']) + fileTree(dir: it, exclude: [ + '**/examples/**', 'io/nats/client/support/Debug*' + ]) })) } } diff --git a/src/main/java/io/nats/client/Options.java b/src/main/java/io/nats/client/Options.java index c4970aa83..45c67c1e2 100644 --- a/src/main/java/io/nats/client/Options.java +++ b/src/main/java/io/nats/client/Options.java @@ -1685,7 +1685,7 @@ public Builder serverPool(ServerPool serverPool) { } /** - * Set the DispatcherFactory implementation for connections to use instead of the default implementation + * Set the DispatcherFactory implementation for connections to use instead of the default implementation * @param dispatcherFactory the implementation * @return the Builder for chaining */ diff --git a/src/main/java/io/nats/client/api/MessageBatchGetRequest.java b/src/main/java/io/nats/client/api/MessageBatchGetRequest.java deleted file mode 100644 index 860497b2d..000000000 --- a/src/main/java/io/nats/client/api/MessageBatchGetRequest.java +++ /dev/null @@ -1,322 +0,0 @@ -// Copyright 2024 The NATS Authors -// Licensed 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 io.nats.client.api; - -import io.nats.client.support.JsonSerializable; - -import java.time.Duration; -import java.time.ZonedDateTime; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; - -import static io.nats.client.support.ApiConstants.*; -import static io.nats.client.support.JsonUtils.*; -import static io.nats.client.support.Validator.*; - -/** - * Object used to make a request for message batch get requests. - */ -public class MessageBatchGetRequest implements JsonSerializable { - - private final int batch; - private final int maxBytes; - private final long sequence; - private final ZonedDateTime startTime; - private final String nextBySubject; - private final List multiLastFor; - private final long upToSequence; - private final ZonedDateTime upToTime; - - MessageBatchGetRequest(Builder b) { - this.batch = b.batch; - this.maxBytes = b.maxBytes; - this.sequence = b.sequence; - this.startTime = b.startTime; - this.nextBySubject = b.nextBySubject; - this.multiLastFor = b.multiLastFor; - this.upToSequence = b.upToSequence; - this.upToTime = b.upToTime; - } - - /** - * Maximum amount of messages to be returned for this request. - * - * @return batch size - */ - public int getBatch() { - return batch; - } - - /** - * Maximum amount of returned bytes for this request. - * Limits the amount of returned messages to not exceed this. - * - * @return maximum bytes - */ - public int getMaxBytes() { - return maxBytes; - } - - /** - * Minimum sequence for returned messages. - * All returned messages will have a sequence equal to or higher than this. - * - * @return minimum message sequence - */ - public long getSequence() { - return sequence; - } - - /** - * Minimum start time for returned messages. - * All returned messages will have a start time equal to or higher than this. - * - * @return minimum message start time - */ - public ZonedDateTime getStartTime() { - return startTime; - } - - /** - * Subject used to filter messages that should be returned. - * - * @return the subject to filter - */ - public String getSubject() { - return nextBySubject; - } - - /** - * Subjects filter used, these can include wildcards. - * Will get the last messages matching the subjects. - * - * @return the subjects to get the last messages for - */ - public List getMultiLastForSubjects() { - return multiLastFor; - } - - /** - * Only return messages up to this sequence. - * - * @return the maximum message sequence to return results for - */ - public long getUpToSequence() { - return upToSequence; - } - - /** - * Only return messages up to this time. - * - * @return the maximum message time to return results for - */ - public ZonedDateTime getUpToTime() { - return upToTime; - } - - @Override - public String toJson() { - StringBuilder sb = beginJson(); - addField(sb, BATCH, batch); - addField(sb, MAX_BYTES, maxBytes); - addField(sb, SEQ, sequence); - addField(sb, START_TIME, startTime); - addField(sb, NEXT_BY_SUBJECT, nextBySubject); - addStrings(sb, MULTI_LAST, multiLastFor); - addField(sb, UP_TO_SEQ, upToSequence); - addField(sb, UP_TO_TIME, upToTime); - return endJson(sb).toString(); - } - - /** - * Creates a builder for the request. - * - * @return Builder - */ - public static Builder builder() { - return new Builder(); - } - - /** - * Creates a builder for the request. - * - * @param req the {@link MessageBatchGetRequest} - * @return Builder - */ - public static Builder builder(MessageBatchGetRequest req) { - return req == null ? new Builder() : new Builder(req); - } - - /** - * {@link MessageBatchGetRequest} is created using a Builder. The builder supports chaining and will - * create a default set of options if no methods are calls. - * - *

{@code MessageBatchGetRequest.builder().build()} will create a default {@link MessageBatchGetRequest}. - */ - public static class Builder { - private int batch = -1; - private int maxBytes = -1; - private long sequence = -1; - private ZonedDateTime startTime = null; - private String nextBySubject = null; - private List multiLastFor = new ArrayList<>(); - private long upToSequence = -1; - private ZonedDateTime upToTime = null; - - /** - * Construct the builder - */ - public Builder() { - } - - /** - * Construct the builder and initialize values with the existing {@link MessageBatchGetRequest} - * - * @param req the {@link MessageBatchGetRequest} to clone - */ - public Builder(MessageBatchGetRequest req) { - if (req != null) { - this.batch = req.batch; - this.maxBytes = req.maxBytes; - this.sequence = req.sequence; - this.startTime = req.startTime; - this.nextBySubject = req.nextBySubject; - this.multiLastFor = req.multiLastFor; - this.upToSequence = req.upToSequence; - this.upToTime = req.upToTime; - } - } - - /** - * Set the maximum amount of messages to be returned for this request. - * - * @param batch the batch size - * @return Builder - */ - public Builder batch(int batch) { - validateGtZero(batch, "Request batch size"); - this.batch = batch; - return this; - } - - /** - * Maximum amount of returned bytes for this request. - * Limits the amount of returned messages to not exceed this. - * - * @param maxBytes the maximum bytes - * @return Builder - */ - public Builder maxBytes(int maxBytes) { - this.maxBytes = maxBytes; - return this; - } - - /** - * Minimum sequence for returned messages. - * All returned messages will have a sequence equal to or higher than this. - * - * @param sequence the minimum message sequence - * @return Builder - */ - public Builder sequence(long sequence) { - validateGtEqZero(sequence, "Sequence"); - this.sequence = sequence; - return this; - } - - /** - * Minimum start time for returned messages. - * All returned messages will have a start time equal to or higher than this. - * - * @param startTime the minimum message start time - * @return Builder - */ - public Builder startTime(ZonedDateTime startTime) { - this.startTime = startTime; - return this; - } - - /** - * Subject used to filter messages that should be returned. - * - * @param subject the subject to filter - * @return Builder - */ - public Builder subject(String subject) { - this.nextBySubject = subject; - return this; - } - - /** - * Subjects filter used, these can include wildcards. - * Will get the last messages matching the subjects. - * - * @param subjects the subjects to get the last messages for - * @return Builder - */ - public Builder multiLastForSubjects(String... subjects) { - this.multiLastFor.clear(); - this.multiLastFor.addAll(Arrays.asList(subjects)); - return this; - } - - /** - * Subjects filter used, these can include wildcards. - * Will get the last messages matching the subjects. - * - * @param subjects the subjects to get the last messages for - * @return Builder - */ - public Builder multiLastForSubjects(Collection subjects) { - this.multiLastFor.clear(); - this.multiLastFor.addAll(subjects); - return this; - } - - /** - * Only return messages up to this sequence. - * If not set, will be last sequence for the stream. - * - * @param upToSequence the maximum message sequence to return results for - * @return Builder - */ - public Builder upToSequence(long upToSequence) { - validateGtZero(upToSequence, "Up to sequence"); - this.upToSequence = upToSequence; - return this; - } - - /** - * Only return messages up to this time. - * - * @param upToTime the maximum message time to return results for - * @return Builder - */ - public Builder upToTime(ZonedDateTime upToTime) { - this.upToTime = upToTime; - return this; - } - - /** - * Build the {@link MessageBatchGetRequest}. - * - * @return MessageBatchGetRequest - */ - public MessageBatchGetRequest build() { - return new MessageBatchGetRequest(this); - } - } -} diff --git a/src/main/java/io/nats/client/support/Validator.java b/src/main/java/io/nats/client/support/Validator.java index e650da816..6340da589 100644 --- a/src/main/java/io/nats/client/support/Validator.java +++ b/src/main/java/io/nats/client/support/Validator.java @@ -175,8 +175,9 @@ public static String required(String s, String label) { return s; } + @Deprecated public static String required(String s1, String s2, String label) { - if (emptyAsNull(s1) == null && emptyAsNull(s2) == null) { + if (emptyAsNull(s1) == null || emptyAsNull(s2) == null) { throw new IllegalArgumentException(label + " cannot be null or empty."); } return s1; diff --git a/src/test/java/io/nats/client/api/StreamConfigurationTests.java b/src/test/java/io/nats/client/api/StreamConfigurationTests.java index 71ed2a36d..57e491c6c 100644 --- a/src/test/java/io/nats/client/api/StreamConfigurationTests.java +++ b/src/test/java/io/nats/client/api/StreamConfigurationTests.java @@ -345,6 +345,10 @@ public void testSourceBase() { assertEquals(m1, mb.build()); assertEquals(s1.hashCode(), sb.build().hashCode()); assertEquals(m1.hashCode(), mb.build().hashCode()); + assertEquals(sb.subjectTransforms, m1.getSubjectTransforms()); + + // coverage + m.getSubjectTransforms().get(0).toString(); } private void assertNotEqualsEqualsHashcode(Source s, Mirror m, Source.Builder sb, Mirror.Builder mb) { diff --git a/src/test/java/io/nats/client/impl/DispatcherTests.java b/src/test/java/io/nats/client/impl/DispatcherTests.java index 623e9f2c3..39907d074 100644 --- a/src/test/java/io/nats/client/impl/DispatcherTests.java +++ b/src/test/java/io/nats/client/impl/DispatcherTests.java @@ -18,10 +18,7 @@ import java.io.IOException; import java.time.Duration; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; +import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; @@ -916,7 +913,7 @@ public void testThrowOnUnsubWhenClosed() { public void testThrowOnWrongSubscription() { assertThrows(IllegalStateException.class, () -> { try (NatsTestServer ts = new NatsTestServer(false); - Connection nc = Nats.connect(ts.getURI())) { + Connection nc = Nats.connect(ts.getURI())) { Dispatcher d = nc.createDispatcher((msg) -> {}); Subscription sub2 = nc.subscribe("test"); d.unsubscribe(sub2); @@ -924,4 +921,19 @@ public void testThrowOnWrongSubscription() { } }); } + + @Test + public void testDispatcherFactoryCoverage() throws Exception { + try (NatsTestServer ts = new NatsTestServer(false); + Connection nc = Nats.connect(Options.builder().server(ts.getURI()).useDispatcherWithExecutor().build())) + { + CountDownLatch latch = new CountDownLatch(1); + Dispatcher d = nc.createDispatcher((msg) -> latch.countDown()); + assertInstanceOf(NatsDispatcherWithExecutor.class, d); + String subject = NUID.nextGlobalSequence(); + d.subscribe(subject); + nc.publish(subject, null); + assertTrue(latch.await(1, TimeUnit.SECONDS)); + } + } } diff --git a/src/test/java/io/nats/client/impl/ErrorListenerTests.java b/src/test/java/io/nats/client/impl/ErrorListenerTests.java index 6f2f913c2..5613baaac 100644 --- a/src/test/java/io/nats/client/impl/ErrorListenerTests.java +++ b/src/test/java/io/nats/client/impl/ErrorListenerTests.java @@ -337,6 +337,9 @@ public void testCoverage() { // exercises the default implementation _cover(new ErrorListenerLoggerImpl()); + // exercises the console implementation + _cover(new ErrorListenerConsoleImpl()); + // exercises a little more than the defaults AtomicBoolean errorOccurredFlag = new AtomicBoolean(); AtomicBoolean exceptionOccurredFlag = new AtomicBoolean(); diff --git a/src/test/java/io/nats/client/impl/ReconnectTests.java b/src/test/java/io/nats/client/impl/ReconnectTests.java index 40c95c805..20bcb1b15 100644 --- a/src/test/java/io/nats/client/impl/ReconnectTests.java +++ b/src/test/java/io/nats/client/impl/ReconnectTests.java @@ -715,6 +715,46 @@ private static Thread getReconnectOnConnectTestThread(AtomicReference", occ.getFilterSubject()); + assertNotNull(occ.getFilterSubject()); + assertFalse(occ.hasMultipleFilterSubjects()); assertNull(occ.getDeliverPolicy()); assertEquals(ConsumerConfiguration.LONG_UNSET, occ.getStartSequence()); assertNull(occ.getStartTime()); diff --git a/src/test/java/io/nats/client/support/JsonUtilsTests.java b/src/test/java/io/nats/client/support/JsonUtilsTests.java index 993876246..b10bbea00 100644 --- a/src/test/java/io/nats/client/support/JsonUtilsTests.java +++ b/src/test/java/io/nats/client/support/JsonUtilsTests.java @@ -27,6 +27,7 @@ import static io.nats.client.support.ApiConstants.DESCRIPTION; import static io.nats.client.support.DateTimeUtils.DEFAULT_TIME; +import static io.nats.client.support.DateTimeUtils.ZONE_ID_GMT; import static io.nats.client.support.JsonUtils.*; import static io.nats.client.support.JsonValueUtils.mapBuilder; import static io.nats.client.utils.ResourceUtils.dataAsString; @@ -440,6 +441,22 @@ public void testMiscCoverage() { assertEquals("Foobar", normalize("fooBar")); assertEquals("deprecated", objectString("name", "deprecated")); assertEquals("name=null", objectString("name", null)); + + // coverage of deprecated + byte[] byte64 = json.getBytes(); + String base64 = Base64.getEncoder().encodeToString(byte64); + ZonedDateTime zdt = ZonedDateTime.now().withZoneSameInstant(ZONE_ID_GMT); + StringBuilder sb = JsonUtils.beginJson(); + addField(sb, "foo64", base64); + addField(sb, "zdt", zdt); + endJson(sb); + System.out.println(sb); + + bytes = readBase64(sb.toString(), string_pattern("foo64")); + assertArrayEquals(byte64, bytes); + + ZonedDateTime zdtRead = JsonUtils.readDate(sb.toString(), string_pattern("zdt")); + assertEquals(zdt, zdtRead); } @Test diff --git a/src/test/java/io/nats/client/support/ValidatorTests.java b/src/test/java/io/nats/client/support/ValidatorTests.java index 594c902dc..d089c3974 100644 --- a/src/test/java/io/nats/client/support/ValidatorTests.java +++ b/src/test/java/io/nats/client/support/ValidatorTests.java @@ -104,6 +104,40 @@ public void testValidatePrintable() { assertThrows(IllegalArgumentException.class, () -> validatePrintable(HAS_SPACE, "label", true)); assertThrows(IllegalArgumentException.class, () -> validatePrintable(HAS_127, "label", true)); assertThrows(IllegalArgumentException.class, () -> validatePrintable(HAS_LOW, "label", true)); + + validatePrintableExceptWildDotGt(PLAIN, "label", true); + validatePrintableExceptWildDotGt(HAS_PRINTABLE, "label", true); + validatePrintableExceptWildDotGt(HAS_DASH, "label", true); + validatePrintableExceptWildDotGt(HAS_UNDER, "label", true); + validatePrintableExceptWildDotGt(HAS_DOLLAR, "label", true); + validatePrintableExceptWildDotGt(HAS_FWD_SLASH, "label", true); + validatePrintableExceptWildDotGt(HAS_BACK_SLASH, "label", true); + validatePrintableExceptWildDotGt(HAS_EQUALS, "label", true); + validatePrintableExceptWildDotGt(HAS_TIC, "label", true); + + assertThrows(IllegalArgumentException.class, () -> validatePrintableExceptWildDotGt(HAS_DOT, "label", true)); + assertThrows(IllegalArgumentException.class, () -> validatePrintableExceptWildDotGt(STAR_NOT_SEGMENT, "label", true)); + assertThrows(IllegalArgumentException.class, () -> validatePrintableExceptWildDotGt(GT_NOT_SEGMENT, "label", true)); + assertThrows(IllegalArgumentException.class, () -> validatePrintableExceptWildDotGt(HAS_SPACE, "label", true)); + assertThrows(IllegalArgumentException.class, () -> validatePrintableExceptWildDotGt(HAS_127, "label", true)); + assertThrows(IllegalArgumentException.class, () -> validatePrintableExceptWildDotGt(HAS_LOW, "label", true)); + + validatePrintableExceptWildDotGtSlashes(PLAIN, "label", true); + validatePrintableExceptWildDotGtSlashes(HAS_PRINTABLE, "label", true); + validatePrintableExceptWildDotGtSlashes(HAS_DASH, "label", true); + validatePrintableExceptWildDotGtSlashes(HAS_UNDER, "label", true); + validatePrintableExceptWildDotGtSlashes(HAS_DOLLAR, "label", true); + validatePrintableExceptWildDotGtSlashes(HAS_EQUALS, "label", true); + validatePrintableExceptWildDotGtSlashes(HAS_TIC, "label", true); + + assertThrows(IllegalArgumentException.class, () -> validatePrintableExceptWildDotGtSlashes(HAS_FWD_SLASH, "label", true)); + assertThrows(IllegalArgumentException.class, () -> validatePrintableExceptWildDotGtSlashes(HAS_BACK_SLASH, "label", true)); + assertThrows(IllegalArgumentException.class, () -> validatePrintableExceptWildDotGtSlashes(HAS_DOT, "label", true)); + assertThrows(IllegalArgumentException.class, () -> validatePrintableExceptWildDotGtSlashes(STAR_NOT_SEGMENT, "label", true)); + assertThrows(IllegalArgumentException.class, () -> validatePrintableExceptWildDotGtSlashes(GT_NOT_SEGMENT, "label", true)); + assertThrows(IllegalArgumentException.class, () -> validatePrintableExceptWildDotGtSlashes(HAS_SPACE, "label", true)); + assertThrows(IllegalArgumentException.class, () -> validatePrintableExceptWildDotGtSlashes(HAS_127, "label", true)); + assertThrows(IllegalArgumentException.class, () -> validatePrintableExceptWildDotGtSlashes(HAS_LOW, "label", true)); } @Test @@ -332,11 +366,14 @@ public void testValidateMustMatchIfBothSupplied() { @Test public void testValidateRequired() { required("required", "label"); + required("required1", "required2", "label"); required(new Object(), "label"); required(Collections.singletonList("list"), "label"); required(Collections.singletonMap("key", "value"), "label"); assertThrows(IllegalArgumentException.class, () -> required((String)null, "label")); + assertThrows(IllegalArgumentException.class, () -> required("no-second", null, "label")); + assertThrows(IllegalArgumentException.class, () -> required(null, "no-first", "label")); assertThrows(IllegalArgumentException.class, () -> required(EMPTY, "label")); assertThrows(IllegalArgumentException.class, () -> required((Object)null, "label")); assertThrows(IllegalArgumentException.class, () -> required((List)null, "label")); @@ -373,6 +410,9 @@ public void testValidateGtZero() { assertEquals(1, validateGtZero(1, "test")); assertThrows(IllegalArgumentException.class, () -> validateGtZero(0, "test")); assertThrows(IllegalArgumentException.class, () -> validateGtZero(-1, "test")); + assertEquals(1, validateGtZero(1L, "test")); + assertThrows(IllegalArgumentException.class, () -> validateGtZero(0L, "test")); + assertThrows(IllegalArgumentException.class, () -> validateGtZero(-1L, "test")); } @Test