From 2b23e7735b16825e9fc1c7f257e74d2bff658a3b Mon Sep 17 00:00:00 2001 From: Richard Ogin Date: Fri, 18 Apr 2025 19:59:01 -0500 Subject: [PATCH] DestinationSet should implement java.util.Set Signed-off-by: Richard Ogin --- .../server/userutil/DestinationSet.java | 378 ++++++++++-------- .../connect/userutil/DestinationSetTest.java | 376 +++++++++++++++++ 2 files changed, 595 insertions(+), 159 deletions(-) create mode 100644 server/test/com/mirth/connect/userutil/DestinationSetTest.java diff --git a/server/src/com/mirth/connect/server/userutil/DestinationSet.java b/server/src/com/mirth/connect/server/userutil/DestinationSet.java index 6b19594705..4322e5063c 100644 --- a/server/src/com/mirth/connect/server/userutil/DestinationSet.java +++ b/server/src/com/mirth/connect/server/userutil/DestinationSet.java @@ -1,3 +1,4 @@ + /* * Copyright (c) Mirth Corporation. All rights reserved. * @@ -7,162 +8,221 @@ * been included with this distribution in the LICENSE.txt file. */ -package com.mirth.connect.server.userutil; - -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -import org.mozilla.javascript.Context; - -import com.mirth.connect.donkey.server.Constants; -import com.mirth.connect.userutil.ImmutableConnectorMessage; - -/** - * Utility class used in the preprocessor or source filter/transformer to prevent the message from - * being sent to specific destinations. - */ -public class DestinationSet { - - private Map destinationIdMap; - private Set metaDataIds; - - /** - * DestinationSet instances should NOT be constructed manually. The instance "destinationSet" - * provided in the scope should be used. - * - * @param connectorMessage - * The delegate ImmutableConnectorMessage object. - */ - public DestinationSet(ImmutableConnectorMessage connectorMessage) { - try { - if (connectorMessage.getSourceMap().containsKey(Constants.DESTINATION_SET_KEY)) { - this.destinationIdMap = connectorMessage.getDestinationIdMap(); - this.metaDataIds = (Set) connectorMessage.getSourceMap().get(Constants.DESTINATION_SET_KEY); - } - } catch (Exception e) { - } - } - - /** - * Stop a destination from being processed for this message. - * - * @param metaDataIdOrConnectorName - * An integer representing the metaDataId of a destination connector, or the actual - * destination connector name. - * @return A boolean indicating whether at least one destination connector was actually removed - * from processing for this message. - */ - public boolean remove(Object metaDataIdOrConnectorName) { - if (metaDataIds != null) { - Integer metaDataId = convertToMetaDataId(metaDataIdOrConnectorName); - - if (metaDataId != null) { - return metaDataIds.remove(metaDataId); - } - } - - return false; - } - - /** - * Stop a destination from being processed for this message. - * - * @param metaDataIdOrConnectorNames - * A collection of integers representing the metaDataId of a destination connectors, - * or the actual destination connector names. JavaScript arrays can be used. - * @return A boolean indicating whether at least one destination connector was actually removed - * from processing for this message. - */ - public boolean remove(Collection metaDataIdOrConnectorNames) { - boolean removed = false; - - for (Object metaDataIdOrConnectorName : metaDataIdOrConnectorNames) { - if (remove(metaDataIdOrConnectorName)) { - removed = true; - } - } - - return removed; - } - - /** - * Stop all except one destination from being processed for this message. - * - * @param metaDataIdOrConnectorName - * An integer representing the metaDataId of a destination connector, or the actual - * destination connector name. - * @return A boolean indicating whether at least one destination connector was actually removed - * from processing for this message. - */ - public boolean removeAllExcept(Object metaDataIdOrConnectorName) { - if (metaDataIds != null) { - Integer metaDataId = convertToMetaDataId(metaDataIdOrConnectorName); - - if (metaDataId != null) { - return metaDataIds.retainAll(Collections.singleton(metaDataId)); - } - } - - return false; - } - - /** - * Stop all except one destination from being processed for this message. - * - * @param metaDataIdOrConnectorNames - * A collection of integers representing the metaDataId of a destination connectors, - * or the actual destination connector names. JavaScript arrays can be used. - * @return A boolean indicating whether at least one destination connector was actually removed - * from processing for this message. - */ - public boolean removeAllExcept(Collection metaDataIdOrConnectorNames) { - if (metaDataIds != null) { - Set set = new HashSet(); - - for (Object metaDataIdOrConnectorName : metaDataIdOrConnectorNames) { - Integer metaDataId = convertToMetaDataId(metaDataIdOrConnectorName); - - if (metaDataId != null) { - set.add(metaDataId); - } - } - - return metaDataIds.retainAll(set); - } - - return false; - } - - /** - * Stop all destinations from being processed for this message. This does NOT mark the source - * message as FILTERED. - * - * @return A boolean indicating whether at least one destination connector was actually removed - * from processing for this message. - */ - public boolean removeAll() { - if (metaDataIds != null && metaDataIds.size() > 0) { - metaDataIds.clear(); - return true; - } - - return false; - } - - private Integer convertToMetaDataId(Object metaDataIdOrConnectorName) { - if (metaDataIdOrConnectorName != null) { - if (metaDataIdOrConnectorName instanceof Number) { - return ((Number) metaDataIdOrConnectorName).intValue(); - } else if (metaDataIdOrConnectorName.getClass().getName().equals("org.mozilla.javascript.NativeNumber")) { - return (Integer) Context.jsToJava(metaDataIdOrConnectorName, int.class); - } else if (destinationIdMap != null) { - return destinationIdMap.get(metaDataIdOrConnectorName.toString()); - } - } - - return null; - } -} \ No newline at end of file + package com.mirth.connect.server.userutil; + + import java.util.Collection; + import java.util.Collections; + import java.util.HashSet; + import java.util.Iterator; + import java.util.Map; + import java.util.Optional; + import java.util.Set; + import java.util.stream.Collectors; + + import org.mozilla.javascript.Context; + + import com.mirth.connect.donkey.server.Constants; + import com.mirth.connect.userutil.ImmutableConnectorMessage; + + /** + * Utility class used in the preprocessor or source filter/transformer to prevent the message from + * being sent to specific destinations. + */ + public class DestinationSet implements Set { + + private Map destinationIdMap = Collections.emptyMap(); + private Set metaDataIds; + + /** + * DestinationSet instances should NOT be constructed manually. The instance "destinationSet" + * provided in the scope should be used. + * + * @param connectorMessage + * The delegate ImmutableConnectorMessage object. + */ + public DestinationSet(ImmutableConnectorMessage connectorMessage) { + try { + if (connectorMessage.getSourceMap().containsKey(Constants.DESTINATION_SET_KEY)) { + this.destinationIdMap = connectorMessage.getDestinationIdMap(); + this.metaDataIds = (Set) connectorMessage.getSourceMap().get(Constants.DESTINATION_SET_KEY); + } + } catch (Exception e) { + metaDataIds = new HashSet<>(); + } + } + + /** + * Stop a destination from being processed for this message. + * + * @param metaDataIdOrConnectorName + * An integer representing the metaDataId of a destination connector, or the actual + * destination connector name. + * @return A boolean indicating whether at least one destination connector was actually removed + * from processing for this message. + */ + public boolean remove(Object metaDataIdOrConnectorName) { + return remove(Collections.singleton(metaDataIdOrConnectorName)); + } + + /** + * Stop a destination from being processed for this message. + * + * @param metaDataIdOrConnectorNames + * A collection of integers representing the metaDataId of a destination connectors, + * or the actual destination connector names. JavaScript arrays can be used. + * @return A boolean indicating whether at least one destination connector was actually removed + * from processing for this message. + */ + public boolean remove(Collection metaDataIdOrConnectorNames) { + if(metaDataIdOrConnectorNames == null) { return false; } + + return metaDataIdOrConnectorNames.stream() + .map(this::convertToMetaDataId) + .filter(Optional::isPresent) + .map(Optional::get) + .map(metaDataIds::remove) + .filter(Boolean::booleanValue) + .count() > 0; + } + + /** + * Stop all except one destination from being processed for this message. + * + * @param metaDataIdOrConnectorName + * An integer representing the metaDataId of a destination connector, or the actual + * destination connector name. + * @return A boolean indicating whether at least one destination connector was actually removed + * from processing for this message. + */ + public boolean removeAllExcept(Object metaDataIdOrConnectorName) { + return removeAllExcept(Collections.singleton(metaDataIdOrConnectorName)); + } + + /** + * Stop all except one destination from being processed for this message. + * + * @param metaDataIdOrConnectorNames + * A collection of integers representing the metaDataId of a destination connectors, + * or the actual destination connector names. JavaScript arrays can be used. + * @return A boolean indicating whether at least one destination connector was actually removed + * from processing for this message. + */ + public boolean removeAllExcept(Collection metaDataIdOrConnectorNames) { + if(metaDataIdOrConnectorNames == null) { return false; } + + Set set = metaDataIdOrConnectorNames.stream() + .map(this::convertToMetaDataId) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.toSet()); + + return metaDataIds.retainAll(set); + } + + /** + * Stop all destinations from being processed for this message. This does NOT mark the source + * message as FILTERED. + * + * @return A boolean indicating whether at least one destination connector was actually removed + * from processing for this message. + */ + public boolean removeAll() { + int origSize = size(); + clear(); + return origSize > 0; + } + + private Optional convertToMetaDataId(Object metaDataIdOrConnectorName) { + Integer result = null; + + if (metaDataIdOrConnectorName != null) { + if (metaDataIdOrConnectorName instanceof Number) { + result = Integer.valueOf(((Number) metaDataIdOrConnectorName).intValue()); + } else if (metaDataIdOrConnectorName.getClass().getName().equals("org.mozilla.javascript.NativeNumber")) { + result = (Integer) Context.jsToJava(metaDataIdOrConnectorName, int.class); + } else { + result = destinationIdMap.get(metaDataIdOrConnectorName.toString()); + } + } + + return Optional.ofNullable(result); + } + + @Override + public int size() { + return metaDataIds.size(); + } + + @Override + public boolean isEmpty() { + return metaDataIds.isEmpty(); + } + + @Override + public boolean contains(Object metaDataIdOrConnectorName) { + Optional m = convertToMetaDataId(metaDataIdOrConnectorName); + + return m.isPresent() && metaDataIds.contains(m.get()); + } + + @Override + public Iterator iterator() { + return Collections.unmodifiableSet(metaDataIds).iterator(); + } + + @Override + public Object[] toArray() { + return metaDataIds.toArray(); + } + + @Override + public T[] toArray(T[] a) { + return metaDataIds.toArray(a); + } + + @Override + public boolean add(Integer metaDataId) { + return metaDataId != null && metaDataIds.add(metaDataId); + } + + @Override + public boolean containsAll(Collection metaDataIdOrConnectorNames) { + if(metaDataIdOrConnectorNames == null) { return false; } + + return metaDataIdOrConnectorNames.stream() + .map(this::contains) + .allMatch(Boolean::booleanValue); + } + + @Override + public boolean addAll(Collection metaDataIdOrConnectorNames) { + boolean changed = false; + + if(metaDataIdOrConnectorNames != null) { + for(Object item : metaDataIdOrConnectorNames) { + Optional m = convertToMetaDataId(item); + + if(m.isPresent() && metaDataIds.add(m.get())) { + changed = true; + } + } + } + + return changed; + } + + @Override + public boolean retainAll(Collection metaDataIdOrConnectorNames) { + return removeAllExcept((Collection)metaDataIdOrConnectorNames); + } + + @Override + public boolean removeAll(Collection metaDataIdOrConnectorNames) { + return remove((Collection)metaDataIdOrConnectorNames); + } + + @Override + public void clear() { + metaDataIds.clear(); + } + } \ No newline at end of file diff --git a/server/test/com/mirth/connect/userutil/DestinationSetTest.java b/server/test/com/mirth/connect/userutil/DestinationSetTest.java new file mode 100644 index 0000000000..39150f679c --- /dev/null +++ b/server/test/com/mirth/connect/userutil/DestinationSetTest.java @@ -0,0 +1,376 @@ + +package com.mirth.connect.server.userutil; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import com.mirth.connect.donkey.model.message.ConnectorMessage; +import com.mirth.connect.donkey.server.Constants; +import com.mirth.connect.userutil.ImmutableConnectorMessage; + +public class DestinationSetTest { + @Rule + public ExpectedException thrown = ExpectedException.none(); + + /** + * Generate a new test object. Provides default metaDataIds as [1,2,3]. + * @return a new test object + */ + private DestinationSet getNewTestDestinationSet() { + return getNewTestDestinationSetUsing(1,2,3); + } + + /** + * Generate a new test object. + * @param metaDataIds + * @return a new test object + */ + private DestinationSet getNewTestDestinationSetUsing(Integer...metaDataIds) { + Set metaDataIdsSet = new HashSet<>(Arrays.asList(metaDataIds)); + + ConnectorMessage connMsg = new ConnectorMessage(); + + Map sourceMap = new HashMap<>(); + sourceMap.put(Constants.DESTINATION_SET_KEY, metaDataIdsSet); + connMsg.setSourceMap(sourceMap); + + Map destinationIdMap = new HashMap<>(); + destinationIdMap.put("Map to 2", 2); + + ImmutableConnectorMessage icm = new ImmutableConnectorMessage(connMsg, false, destinationIdMap); + return new DestinationSet(icm); + } + + @Test + public void testIsEmptyWithoutSourceMapKey() throws Exception { + DestinationSet ds = getNewTestDestinationSetUsing(new Integer[0]); + + assertTrue(ds.isEmpty()); + assertEquals(0, ds.size()); + } + + @Test + public void testIsEmptyWithSourceMapKey() throws Exception { + ConnectorMessage cm = new ConnectorMessage(); + Set metaDataIds = new HashSet<>(); + Map sm = new HashMap<>(); + sm.put(Constants.DESTINATION_SET_KEY, metaDataIds); + cm.setSourceMap(sm); + ImmutableConnectorMessage icm = new ImmutableConnectorMessage(cm); + DestinationSet ds = new DestinationSet(icm); + + assertTrue(ds.isEmpty()); + assertEquals(0, ds.size()); + } + + @Test + public void testPopulatedSet() throws Exception { + DestinationSet ds = getNewTestDestinationSet(); + + assertFalse(ds.isEmpty()); + assertEquals(3, ds.size()); + } + + @Test + public void testAdd() throws Exception { + DestinationSet ds = getNewTestDestinationSet(); + + assertTrue(ds.add(4)); + assertEquals(4, ds.size()); + + //already in set + assertFalse(ds.add(1)); + assertEquals(4, ds.size()); + + assertFalse(ds.add(null)); + assertEquals(4, ds.size()); + } + + @Test + public void testAddAll() throws Exception { + DestinationSet ds = getNewTestDestinationSet(); + + assertFalse(ds.addAll(Arrays.asList(1))); + assertEquals(3, ds.size()); + assertFalse(ds.addAll(Arrays.asList(2, 3))); + assertEquals(3, ds.size()); + assertTrue(ds.addAll(Arrays.asList(3, 4))); + assertEquals(4, ds.size()); + assertTrue(ds.addAll(Arrays.asList(5, 6))); + assertEquals(6, ds.size()); + assertFalse(ds.addAll(null)); + assertEquals(6, ds.size()); + } + + @Test + public void testClear() throws Exception { + DestinationSet ds = getNewTestDestinationSet(); + + ds.clear(); + + assertTrue(ds.isEmpty()); + assertEquals(0, ds.size()); + } + + @Test + public void testContains() throws Exception { + DestinationSet ds = getNewTestDestinationSet(); + + assertFalse(ds.contains(0)); + assertTrue(ds.contains(1)); + assertTrue(ds.contains("Map to 2")); + assertTrue(ds.contains(3)); + assertFalse(ds.contains(4)); + assertFalse(ds.contains("Invalid")); + assertFalse(ds.contains(null)); + } + + @Test + public void testContainsAll() throws Exception { + DestinationSet ds = getNewTestDestinationSet(); + + assertTrue(ds.containsAll(Collections.emptyList())); + assertTrue(ds.containsAll(Arrays.asList(1))); + assertTrue(ds.containsAll(Arrays.asList(2, 3))); + assertTrue(ds.containsAll(Arrays.asList("Map to 2"))); + assertFalse(ds.containsAll(Arrays.asList(3, 4))); + assertFalse(ds.containsAll(Arrays.asList(4))); + assertFalse(ds.containsAll(Arrays.asList(4, 5))); + assertFalse(ds.containsAll(Arrays.asList("Invalid"))); + assertFalse(ds.containsAll(Collections.singleton(null))); + assertFalse(ds.containsAll(null)); + } + + @Test + public void testIterator() throws Exception { + DestinationSet ds = getNewTestDestinationSet(); + + Iterator iter = ds.iterator(); + + assertTrue(iter.hasNext()); + assertEquals(Integer.valueOf(1), iter.next()); + assertTrue(iter.hasNext()); + assertEquals(Integer.valueOf(2), iter.next()); + assertTrue(iter.hasNext()); + assertEquals(Integer.valueOf(3), iter.next()); + assertFalse(iter.hasNext()); + } + + @Test + public void testIteratorEmpty() throws Exception { + DestinationSet ds = getNewTestDestinationSetUsing(new Integer[0]); + + assertFalse(ds.iterator().hasNext()); + } + + @Test + public void testRemoveCollection() throws Exception { + DestinationSet ds = getNewTestDestinationSetUsing(1,2,3,4); + + assertTrue(ds.remove(Arrays.asList(3))); + assertEquals(3, ds.size()); + assertTrue(ds.remove(Arrays.asList(0,1))); + assertEquals(2, ds.size()); + assertFalse(ds.remove(Arrays.asList(5))); + assertEquals(2, ds.size()); + assertFalse(ds.remove(Arrays.asList(0,5))); + assertEquals(2, ds.size()); + assertTrue(ds.containsAll(Arrays.asList(4,2))); + assertTrue(ds.remove(Arrays.asList(2,4))); + assertEquals(0, ds.size()); + assertFalse(ds.remove(Arrays.asList(6))); + assertEquals(0, ds.size()); + assertFalse(ds.iterator().hasNext()); + assertFalse(ds.remove((Collection)null)); + assertEquals(0, ds.size()); + assertFalse(ds.iterator().hasNext()); + } + + @Test + public void testRemoveObject() throws Exception { + DestinationSet ds = getNewTestDestinationSet(); + + Integer toRemove = 2; + assertTrue(ds.remove(toRemove)); + assertEquals(2, ds.size()); + assertFalse(ds.contains(toRemove)); + + toRemove = null; + assertFalse(ds.remove(toRemove)); + assertEquals(2, ds.size()); + assertFalse(ds.contains(toRemove)); + } + + @Test + public void testRemoveAll() throws Exception { + DestinationSet ds = getNewTestDestinationSet(); + + assertTrue(ds.removeAll()); + assertEquals(0, ds.size()); + assertTrue(ds.isEmpty()); + assertFalse(ds.removeAll()); + assertFalse(ds.iterator().hasNext()); + } + + @Test + public void testRemoveAllCollection() throws Exception { + DestinationSet ds = getNewTestDestinationSetUsing(1,2,3,4); + + assertFalse(ds.removeAll(Arrays.asList(0))); + assertEquals(4, ds.size()); + assertTrue(ds.removeAll(Arrays.asList(0,1))); + assertEquals(3, ds.size()); + assertTrue(ds.removeAll(Arrays.asList(4,5))); + assertEquals(2, ds.size()); + assertFalse(ds.removeAll(Arrays.asList(6,7,8))); + assertEquals(2, ds.size()); + assertTrue(ds.iterator().hasNext()); + assertFalse(ds.removeAll((Collection)null)); + assertEquals(2, ds.size()); + assertTrue(ds.iterator().hasNext()); + } + + @Test + public void testRemoveAllExceptCollection() throws Exception { + DestinationSet ds = getNewTestDestinationSetUsing(1,2,3,4,5); + + assertTrue(ds.removeAllExcept(Arrays.asList(1,3,5,6))); + assertEquals(3, ds.size()); + assertTrue(ds.iterator().hasNext()); + assertTrue(ds.containsAll(Arrays.asList(1,3,5))); + assertFalse(ds.contains(2)); + assertFalse(ds.contains(4)); + assertTrue(ds.removeAllExcept(Arrays.asList(3))); + assertEquals(1, ds.size()); + assertTrue(ds.iterator().hasNext()); + assertTrue(ds.removeAllExcept(Arrays.asList(0))); + assertEquals(0, ds.size()); + assertFalse(ds.iterator().hasNext()); + } + + @Test + public void testRemoveAllExceptCollectionWithNullEntry() throws Exception { + DestinationSet ds = getNewTestDestinationSet(); + + assertTrue(ds.removeAllExcept(Collections.singleton(null))); + assertEquals(0, ds.size()); + assertFalse(ds.iterator().hasNext()); + } + + @Test + public void testRemoveAllExceptCollectionWithNullCollection() throws Exception { + DestinationSet ds = getNewTestDestinationSet(); + + assertFalse(ds.removeAllExcept((Collection)null)); + assertEquals(3, ds.size()); + assertTrue(ds.iterator().hasNext()); + } + + @Test + public void testRemoveAllExceptObject() throws Exception { + DestinationSet ds = getNewTestDestinationSet(); + + assertTrue(ds.removeAllExcept(2)); + assertEquals(1, ds.size()); + assertTrue(ds.iterator().hasNext()); + assertFalse(ds.contains(1)); + assertTrue(ds.contains(2)); + assertFalse(ds.contains(3)); + assertTrue(ds.iterator().hasNext()); + assertTrue(ds.removeAllExcept(0)); + assertEquals(0, ds.size()); + assertFalse(ds.iterator().hasNext()); + } + + @Test + public void testRemoveAllExceptObjectWithNull() throws Exception { + DestinationSet ds = getNewTestDestinationSet(); + + assertTrue(ds.removeAllExcept((Object)null)); + assertEquals(0, ds.size()); + assertFalse(ds.iterator().hasNext()); + } + + @Test + public void testRetainAll() throws Exception { + DestinationSet ds = getNewTestDestinationSetUsing(1,2,3,4,5); + + assertTrue(ds.retainAll(Arrays.asList(1,3,5,6))); + assertEquals(3, ds.size()); + assertTrue(ds.iterator().hasNext()); + assertTrue(ds.containsAll(Arrays.asList(1,3,5))); + assertFalse(ds.contains(2)); + assertFalse(ds.contains(4)); + assertTrue(ds.iterator().hasNext()); + assertTrue(ds.retainAll(Arrays.asList(0))); + assertEquals(0, ds.size()); + assertFalse(ds.iterator().hasNext()); + } + + @Test + public void testRetainAllWithNullEntry() throws Exception { + DestinationSet ds = getNewTestDestinationSet(); + + assertTrue(ds.retainAll(Collections.singleton(null))); + assertEquals(0, ds.size()); + assertFalse(ds.iterator().hasNext()); + } + + @Test + public void testRetainAllWithNullCollection() throws Exception { + DestinationSet ds = getNewTestDestinationSet(); + + assertFalse(ds.retainAll((Collection)null)); + assertEquals(3, ds.size()); + assertTrue(ds.iterator().hasNext()); + } + + @Test + public void testToArray() throws Exception { + DestinationSet ds = getNewTestDestinationSet(); + + Object[] arr = ds.toArray(); + assertEquals(3, arr.length); + assertEquals(Integer.valueOf(1), arr[0]); + assertEquals(Integer.valueOf(2), arr[1]); + assertEquals(Integer.valueOf(3), arr[2]); + } + + @Test + public void testToArrayGenericProperlySized() throws Exception { + DestinationSet ds = getNewTestDestinationSet(); + + Integer[] arr = new Integer[ds.size()]; + //the input and response should be the same array + Integer[] resp = ds.toArray(arr); + assertEquals(3, resp.length); + assertTrue(Arrays.equals(arr, resp)); + assertEquals(Integer.valueOf(1), arr[0]); + assertEquals(Integer.valueOf(2), arr[1]); + assertEquals(Integer.valueOf(3), arr[2]); + } + + @Test + public void testToArrayGenericImproperlySized() throws Exception { + DestinationSet ds = getNewTestDestinationSet(); + + Integer[] resp = ds.toArray(new Integer[0]); + assertEquals(3, resp.length); + assertEquals(Integer.valueOf(1), resp[0]); + assertEquals(Integer.valueOf(2), resp[1]); + assertEquals(Integer.valueOf(3), resp[2]); + } +} \ No newline at end of file