diff --git a/at_client/src/examples/NotifyListExample.java b/at_client/src/examples/NotifyListExample.java new file mode 100644 index 00000000..aa63b866 --- /dev/null +++ b/at_client/src/examples/NotifyListExample.java @@ -0,0 +1,31 @@ + +import java.util.List; +import java.util.concurrent.ExecutionException; + +import org.atsign.client.api.AtClient; +import org.atsign.client.api.notification.AtNotification; +import org.atsign.client.api.notification.NotificationService; +import org.atsign.common.AtException; +import org.atsign.common.AtSign; + +public class NotifyListExample { + public static void main(String[] args) throws AtException, InterruptedException, ExecutionException { + final String ROOT_URL = "root.atsign.org:64"; + final String ATSIGN_STR = "@smoothalligator"; + final AtSign atSign = new AtSign(ATSIGN_STR); + final AtClient atClient = AtClient.withRemoteSecondary(ROOT_URL, atSign); + + final NotificationService ns = atClient.getNotificationService(); + final List notifications = ns.notifyList().get(); + + final int numNotifications = notifications.size(); + System.out.println("Notifications Listed: (" + numNotifications + ")"); + for(int i = 0; i < numNotifications; i++) { + AtNotification notification = notifications.get(i); + String id = notification.id; + String from = notification.from; + String key = notification.key; + System.out.println("[" + i + "]: " + id + " | from: " + from + " | key: " + key); + } + } +} diff --git a/at_client/src/examples/NotifyRemoveExample.java b/at_client/src/examples/NotifyRemoveExample.java new file mode 100644 index 00000000..273eb8ea --- /dev/null +++ b/at_client/src/examples/NotifyRemoveExample.java @@ -0,0 +1,30 @@ + +import java.util.Scanner; +import java.util.concurrent.ExecutionException; + + +import org.atsign.client.api.AtClient; +import org.atsign.client.api.notification.NotificationResult; +import org.atsign.client.api.notification.NotificationService; +import org.atsign.common.AtException; +import org.atsign.common.AtSign; + +public class NotifyRemoveExample { + + public static void main(String[] args) throws AtException, InterruptedException, ExecutionException { + final String ROOT_URL = "root.atsign.org:64"; + final String ATSIGN_STR = "@smoothalligator"; + final AtSign atSign = new AtSign(ATSIGN_STR); + final AtClient atClient = AtClient.withRemoteSecondary(ROOT_URL, atSign); + + final NotificationService ns = atClient.getNotificationService(); + + Scanner scanner = new Scanner(System.in); + System.out.println("Enter notification id to delete: "); + String notificationId = scanner.nextLine(); + scanner.close(); + NotificationResult result = ns.notifyRemove(notificationId).get(); + System.out.println(result.toString()); + } + +} diff --git a/at_client/src/examples/NotifySendExample.java b/at_client/src/examples/NotifySendExample.java new file mode 100644 index 00000000..6ae4e05b --- /dev/null +++ b/at_client/src/examples/NotifySendExample.java @@ -0,0 +1,35 @@ + +import java.util.concurrent.ExecutionException; + +import org.atsign.client.api.AtClient; +import org.atsign.client.api.notification.NotificationParams; +import org.atsign.client.api.notification.NotificationResult; +import org.atsign.client.api.notification.NotificationService; +import org.atsign.client.api.notification.NotificationServiceImpl; +import org.atsign.common.AtException; +import org.atsign.common.AtSign; + +public class NotifySendExample { + + public static void main(String[] args) throws AtException, InterruptedException, ExecutionException { + + // initialize AtClient instance + final String ROOT_URL = "root.atsign.org:64"; + final String ATSIGN_STR = "@soccer0"; + final AtSign atSign = new AtSign(ATSIGN_STR); + final AtClient atClient = AtClient.withRemoteSecondary(ROOT_URL, atSign); + + // get notification service + final NotificationService ns = (NotificationServiceImpl) atClient.getNotificationService(); + + final String RECIPIENT_ATSIGN_STR = "@smoothalligator"; + final AtSign recipient = new AtSign(RECIPIENT_ATSIGN_STR); + final NotificationResult result = ns.notify(NotificationParams.forText("12345", atSign, recipient, false)).get(); + + System.out.println(result.toString()); + + + + } + +} diff --git a/at_client/src/main/java/org/atsign/client/api/AtClient.java b/at_client/src/main/java/org/atsign/client/api/AtClient.java index fb89b7b2..80e4c10c 100644 --- a/at_client/src/main/java/org/atsign/client/api/AtClient.java +++ b/at_client/src/main/java/org/atsign/client/api/AtClient.java @@ -5,6 +5,7 @@ import org.atsign.client.api.impl.connections.DefaultAtConnectionFactory; import org.atsign.client.api.impl.events.SimpleAtEventBus; import org.atsign.client.api.impl.secondaries.RemoteSecondary; +import org.atsign.client.api.notification.NotificationService; import org.atsign.common.AtSign; import org.atsign.common.AtException; import org.atsign.client.util.KeysUtil; @@ -161,4 +162,6 @@ static AtClient withRemoteSecondary(Secondary.Address remoteSecondaryAddress, At CompletableFuture put(PublicKey publicKey, byte[] value); CompletableFuture> getAtKeys(String regex); + + NotificationService getNotificationService(); } diff --git a/at_client/src/main/java/org/atsign/client/api/impl/clients/AtClientImpl.java b/at_client/src/main/java/org/atsign/client/api/impl/clients/AtClientImpl.java index 93ac95db..32557c78 100644 --- a/at_client/src/main/java/org/atsign/client/api/impl/clients/AtClientImpl.java +++ b/at_client/src/main/java/org/atsign/client/api/impl/clients/AtClientImpl.java @@ -25,6 +25,7 @@ import org.atsign.client.api.AtEvents.AtEventBus; import org.atsign.client.api.AtEvents.AtEventListener; import org.atsign.client.api.AtEvents.AtEventType; +import org.atsign.client.api.notification.NotificationService; import org.atsign.client.api.Secondary; import org.atsign.client.util.EncryptionUtil; import org.atsign.client.util.KeysUtil; @@ -57,16 +58,20 @@ public class AtClientImpl implements AtClient { private final Map keys; @Override public Map getEncryptionKeys() {return keys;} + private final Secondary secondary; @Override public Secondary getSecondary() {return secondary;} + private final NotificationService notificationService; + @Override public NotificationService getNotificationService() {return notificationService;} + private final AtEventBus eventBus; public AtClientImpl(AtEventBus eventBus, AtSign atSign, Map keys, Secondary secondary) { this.eventBus = eventBus; this.atSign = atSign; this.keys = keys; this.secondary = secondary; - + this.notificationService = NotificationService.create(this); eventBus.addEventListener(this, EnumSet.allOf(AtEventType.class)); } diff --git a/at_client/src/main/java/org/atsign/client/api/notification/AtNotification.java b/at_client/src/main/java/org/atsign/client/api/notification/AtNotification.java new file mode 100644 index 00000000..27831e8c --- /dev/null +++ b/at_client/src/main/java/org/atsign/client/api/notification/AtNotification.java @@ -0,0 +1,56 @@ +package org.atsign.client.api.notification; + +import java.util.ArrayList; +import java.util.List; + +import org.atsign.client.api.Secondary; +import org.atsign.common.ResponseTransformers.NotifyListResponseTransformer; +import org.atsign.common.response_models.NotifyListResponse; + +/** + * Class represents a notification in the atProtocol. + */ +public class AtNotification { + + public String id; // id of the notification + public String key; // notification data + public String from; // the atSign the notification is sent by + public String to; // the atSign the notification is being sent to + public Long epochMillis; // when the notification was sent in epoch millis + public String value; // notification value, [OPTIONAL] + public String operation; // "update", "delete", [OPTIONAL] + public String messageType; // MessageType.Text or MessageType.Key + public Boolean isEncrypted; // notification + + @Override + public String toString() { + return "{\"id\": \"" + id + "\", \"key\": \"" + key + "\", \"from\": \"" + from + "\", \"to\": \"" + to + "\", \"epochMillis\": " + epochMillis.toString() + ", \"value\": \"" + value + "\", \"operation\": \"" + operation + "\", \"messageType\": \"" + messageType + "\", \"isEncrypted\": " + isEncrypted.toString() + "}"; + } + + /** + * Convert the Secondary.Response into a List object + * @param response The response data from `data:` after running + * `notify:list` + * @return a List where each AtNotification + */ + public static List fromResponse(Secondary.Response response) { + NotifyListResponseTransformer transformer = new NotifyListResponseTransformer(); + NotifyListResponse model = transformer.transform(response); + List notifications = new ArrayList(); + model.notifications.stream().forEach((n) -> { + AtNotification notification = new AtNotification(); + notification.id = n.id; + notification.key = n.key; + notification.from = n.from; + notification.to = n.to; + notification.epochMillis = n.epochMillis; + notification.value = n.value; + notification.operation = n.operation; + notification.messageType = n.messageType; + notification.isEncrypted = n.isEncrypted; + notifications.add(notification); + }); + return notifications; + } + +} diff --git a/at_client/src/main/java/org/atsign/client/api/notification/NotificationParams.java b/at_client/src/main/java/org/atsign/client/api/notification/NotificationParams.java new file mode 100644 index 00000000..3ce7630f --- /dev/null +++ b/at_client/src/main/java/org/atsign/client/api/notification/NotificationParams.java @@ -0,0 +1,103 @@ +package org.atsign.client.api.notification; + +import java.util.UUID; + +import org.atsign.common.AtSign; +import org.atsign.common.KeyBuilders; +import org.atsign.common.Keys; +import org.atsign.common.NotificationEnums; +import org.atsign.common.Keys.AtKey; +import org.atsign.common.Keys.SharedKey; +import org.atsign.common.NotificationEnums.Operation; + + +public class NotificationParams { + + private String id; + private AtKey atKey; + private String value; // nullable (optional) + private NotificationEnums.Operation operation; + private NotificationEnums.MessageType messageType; + private NotificationEnums.Priority priority; + private NotificationEnums.Strategy strategy; + // private Integer latestN = 1; + // private String notifier = SYSTEM; + + // only this class, children, and other classes in this package can create instances + // use static factory methods to create instances of NotificationParams. + protected NotificationParams() {} + + public String getNotificationId() { + return id; + } + + public AtKey getAtKey() { + return atKey; + } + + public String getValue() { + return value; + } + + public NotificationEnums.Operation getOperation() { + return operation; + } + + public NotificationEnums.MessageType getMessageType() { + return messageType; + } + + public NotificationEnums.Priority getPriority() { + return priority; + } + + public NotificationEnums.Strategy getStrategy() { + return strategy; + } + + /** + * + * @param atKey AtKey object which contains the sharedBy and sharedWith atSign. + * @param value nullable (optional) + * @return NotificationParams object + */ + public static NotificationParams forUpdate(AtKey atKey, String value) { + NotificationParams params = new NotificationParams(); + params.id = UUID.randomUUID().toString(); + params.atKey = atKey; + params.value = value; + params.operation = Operation.UPDATE; + params.messageType = NotificationEnums.MessageType.KEY; + params.priority = NotificationEnums.Priority.LOW; + params.strategy = NotificationEnums.Strategy.ALL; + return params; + } + + public static NotificationParams forDelete(AtKey atKey) { + NotificationParams params = new NotificationParams(); + params.id = UUID.randomUUID().toString(); + params.atKey = atKey; + params.operation = Operation.DELETE; + params.messageType = NotificationEnums.MessageType.KEY; + params.priority = NotificationEnums.Priority.LOW; + params.strategy = NotificationEnums.Strategy.ALL; + return params; + } + + public static NotificationParams forText(String text, AtSign sharedBy, AtSign sharedWith, boolean shouldEncrypt) { + + SharedKey sharedKey = new KeyBuilders.SharedKeyBuilder(sharedBy, sharedWith).key(text).build(); + sharedKey.metadata.isEncrypted = shouldEncrypt; + + + NotificationParams params = new NotificationParams(); + params.id = UUID.randomUUID().toString(); + params.atKey = sharedKey; + params.operation = Operation.UPDATE; + params.messageType = NotificationEnums.MessageType.TEXT; + params.priority = NotificationEnums.Priority.LOW; + params.strategy = NotificationEnums.Strategy.ALL; + return params; + } + +} \ No newline at end of file diff --git a/at_client/src/main/java/org/atsign/client/api/notification/NotificationResult.java b/at_client/src/main/java/org/atsign/client/api/notification/NotificationResult.java new file mode 100644 index 00000000..e29418e4 --- /dev/null +++ b/at_client/src/main/java/org/atsign/client/api/notification/NotificationResult.java @@ -0,0 +1,46 @@ +package org.atsign.client.api.notification; + +import org.atsign.common.NotificationStatus; +import org.atsign.common.Keys.AtKey; + +public class NotificationResult { + private String notificationId; + private AtKey atKey; + private NotificationStatus notificationStatus; + + public NotificationResult(String notificationId, AtKey atKey, NotificationStatus notificationStatus) { + this.notificationId = notificationId; + this.atKey = atKey; + this.notificationStatus = notificationStatus; + } + + public void setAtKey(AtKey atKey) { + this.atKey = atKey; + } + + public void setNotificationId(String notificationId) { + this.notificationId = notificationId; + } + + public void setNotificationStatus(NotificationStatus notificationStatus) { + this.notificationStatus = notificationStatus; + } + + public AtKey getAtKey() { + return this.atKey; + } + + public String getNotificationId() { + return this.notificationId; + } + + public NotificationStatus getNotificationStatus() { + return this.notificationStatus; + } + + @Override + public String toString() { + return "id: " + this.notificationId + " | status: " + this.notificationStatus.name(); + } + +} diff --git a/at_client/src/main/java/org/atsign/client/api/notification/NotificationService.java b/at_client/src/main/java/org/atsign/client/api/notification/NotificationService.java new file mode 100644 index 00000000..28abe5f4 --- /dev/null +++ b/at_client/src/main/java/org/atsign/client/api/notification/NotificationService.java @@ -0,0 +1,78 @@ +package org.atsign.client.api.notification; + +import java.util.Date; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import org.atsign.client.api.AtClient; + +public interface NotificationService { + + public static NotificationService create(AtClient atClient) { + return new NotificationServiceImpl(atClient); + } + + /** + * TODO Javadoc + * @param regex + * @param shouldDecrypt + * @return + */ + CompletableFuture subscribe(String regex, boolean shouldDecrypt); // TODO Replace "Object" with Stream or similar. See: https://github.com/atsign-foundation/at_client_sdk/blob/99707459494594fbdc3f0873769eeca29c6a3124/packages/at_client/lib/src/service/notification_service.dart#L14 + + /** + * Sends a notification (via the `notify:` verb) + * Use NotificationParams static factory methods to create an instance. + * @param params parameters containing details about the Notification that will be sent. + * @return NotificationResult object containing details about the notification that was sent. + */ + CompletableFuture notify(NotificationParams params); + + /** + * Gets the status notification (via the `notify:status:` verb) + * @param notificationId id of the notificaiton (e.g. `4a8bbbe9-3c7c-4679-9721-dfd01b5adf3f`) + * @return NotificationResult object containing details about the status of the notification. + */ + CompletableFuture getStatus(String notificationId); + + /** + * Returns a List object containing all the notifications received by the currently onboarded atSign. + * Each AtNotification object represents a notification in the notification store. + * @return List object, non-null, emptyable + */ + CompletableFuture> notifyList(); + + /** + * Returns a List object containing all the notifications received by the currently onboarded atSign. + * Each AtNotification object represents a notification in the notification store. + * @param regex regex filter when searching through notifications. Filters what is inside "key": + * @return List object, non-null, emptyable + */ + CompletableFuture> notifyList(String regex); + + /** + * Returns a List object containing all the notifications received by the currently onboarded atSign. + * Each AtNotification object represents a notification in the notification store. + * Filter notifications by regex and between a date range. + * fromDate must be before toDate. + * @param regex regex filter when searching through notifications. Filters what is inside "key": + * @param fromDate Date object, only year, month, and day are considered + * @param toDate Date object, only year, month, and day are considered + * @return List object, non-null, emptyable + */ + CompletableFuture> notifyList(String regex, Date fromDate, Date toDate); + + /** + * Removes a notification given a String notificationId + * @param notificationId id representing a notification inside the atSign's atServer notification store + * @return NotificationResult object detailing the result of running this method + */ + CompletableFuture notifyRemove(String notificationId); + + /** + * Removes all notifications in the atServer's notification store + * @return NotificationResult object detailing the result of running this method + */ + CompletableFuture notifyRemoveAll(); + +} diff --git a/at_client/src/main/java/org/atsign/client/api/notification/NotificationServiceImpl.java b/at_client/src/main/java/org/atsign/client/api/notification/NotificationServiceImpl.java new file mode 100644 index 00000000..10550836 --- /dev/null +++ b/at_client/src/main/java/org/atsign/client/api/notification/NotificationServiceImpl.java @@ -0,0 +1,194 @@ +package org.atsign.client.api.notification; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; + +import org.atsign.client.api.AtClient; +import org.atsign.client.api.Secondary; +import org.atsign.client.util.AtClientValidation; +import org.atsign.common.AtException; +import org.atsign.common.AtSign; +import org.atsign.common.NotificationEnums; +import org.atsign.common.NotificationStatus; +import org.atsign.common.ResponseTransformers.NotifyListResponseTransformer; +import org.atsign.common.VerbBuilders.NotifyRemoveVerbBuilder; +import org.atsign.common.VerbBuilders.NotifyKeyChangeBuilder; +import org.atsign.common.VerbBuilders.NotifyListVerbBuilder; +import org.atsign.common.VerbBuilders.NotifyTextVerbBuilder; +import org.atsign.common.VerbBuilders.VerbBuilder; +import org.atsign.common.response_models.NotifyListResponse; + +public class NotificationServiceImpl implements NotificationService { + + private final AtClient atClient; + + public NotificationServiceImpl(AtClient atClient) { + this.atClient = atClient; + } + + @Override + public CompletableFuture subscribe(String regex, boolean shouldDecrypt) { + throw new RuntimeException("Not implemented yet"); + } + + @Override + + public CompletableFuture notify(NotificationParams params) { + return CompletableFuture.supplyAsync(() -> { + NotificationResult result = new NotificationResult(params.getNotificationId(), params.getAtKey(), + NotificationStatus.undelivered); + + if (params.getAtKey().sharedBy == null) { + params.getAtKey().sharedBy = atClient.getAtSign(); + } + + // TODO: AtClientValidation notification + // AtClientValidation.validateNotificationRequest(params, /* get hold of rootDomain somehow */, /* get hold of root port somehow */); + + VerbBuilder builder; + switch (params.getMessageType()) { + case TEXT: + NotifyTextVerbBuilder a = new NotifyTextVerbBuilder(); + a.setRecipientAtSign(params.getAtKey().sharedWith.atSign); + a.setText(params.getAtKey().name); + builder = a; + break; + case KEY: + NotifyKeyChangeBuilder b = new NotifyKeyChangeBuilder(); + if (params.getOperation().toString().equalsIgnoreCase("update") + || params.getOperation().toString().equalsIgnoreCase("delete")) { + b.setOperation(params.getOperation().toString().toLowerCase()); + } + b.setRecipientAtSign(params.getAtKey().sharedWith.atSign); + b.setKey(params.getAtKey().toString()); + b.setSenderAtSign(params.getAtKey().sharedBy.atSign); + b.setValue(params.getValue()); + b.setTtr(params.getAtKey().metadata.ttr); + builder = b; + break; + default: + throw new IllegalArgumentException("Invalid message type"); + } + + String command = builder.build(); + try { + Secondary.Response response = (atClient).executeCommand(command, false); + // System.out.println("Executed command: " + command + " got: " + response.toString()); + if (response.data != "null") { + result.setNotificationStatus(NotificationStatus.delivered); + } + } catch (AtException e) { + result.setNotificationStatus(NotificationStatus.errored); + } + return result; + }); + } + + @Override + public CompletableFuture getStatus(String notificationId) { + throw new RuntimeException("Not implemented yet"); + } + + /** + * Get the list of notifications (notify:list verb) + * + * @return non-null List object, emptyable + */ + @Override + public CompletableFuture> notifyList() { + return this.notifyList(null); + } + + /** + * Get the list of notifications (notify:list verb) + * + * @param regex - regex to filter the notifications, nullable + * @return non-null List object, emptyable + */ + public CompletableFuture> notifyList(String regex) { + return this.notifyList(regex, null, null); + } + + /** + * Get the list of notifications (notify:list verb) + * + * @param regex - regex to filter the notifications, nullable + * @param fromDate - from date to filter the notifications yyyy-MM-dd (e.g. + * "2021-01-01"), nullable + * @param toDate - to date to filter the notifications yyyy-MM-dd (e.g. + * "2021-01-01"), nullable + * @return non-null List object, emptyable + */ + public CompletableFuture> notifyList(String regex, Date fromDate, Date toDate) { + return CompletableFuture.supplyAsync(() -> { + try { + List atNotifications = getNotifications(regex, fromDate, toDate); + return atNotifications; + } catch (AtException e) { + throw new CompletionException(e); + } + }); + } + + /** + * Remove a notification from the notification store. + * Each notification has a notificationId. Obtain these ids from notifyList(...) + * @param notificationId - notificationId to remove from the notification store + * @return NotificationResult object detailing the status of the notification. The status of the notification will be `delivered` as long as there was a response from the server. A `delivered` status does not mean the notification existed in the first place. + */ + @Override + public CompletableFuture notifyRemove(String notificationId) { + return CompletableFuture.supplyAsync(() -> { + try { + return deleteNotification(notificationId); + } catch (AtException e) { + throw new CompletionException(e); + } + }); + } + + @Override + public CompletableFuture notifyRemoveAll() { + throw new RuntimeException("Not implemented yet"); + } + + /// ****************************** + /// Private Methods + /// ****************************** + + private NotificationResult deleteNotification(String notificationId) throws AtException { + NotifyRemoveVerbBuilder b = new NotifyRemoveVerbBuilder(); + b.setNotificationId(notificationId); + String command = b.build(); + + + NotificationResult result = new NotificationResult(notificationId, null, + NotificationStatus.undelivered); + + Secondary.Response response = (atClient).executeCommand(command, false); + if (response.data.equals("success")) { + result.setNotificationStatus(NotificationStatus.delivered); + } + return result; + } + + private List getNotifications(String regex, Date fromDate, Date toDate) throws AtException { + List notifications = new ArrayList(); + + NotifyListVerbBuilder b = new NotifyListVerbBuilder(); + b.setRegex(regex); + b.setFrom(fromDate); + b.setTo(toDate); + String command = b.build(); + + Secondary.Response response = (atClient).executeCommand(command, false); + + notifications = AtNotification.fromResponse(response); + + return notifications; + } + +} diff --git a/at_client/src/main/java/org/atsign/client/util/AtClientValidation.java b/at_client/src/main/java/org/atsign/client/util/AtClientValidation.java index 39daa758..7f1787c2 100644 --- a/at_client/src/main/java/org/atsign/client/util/AtClientValidation.java +++ b/at_client/src/main/java/org/atsign/client/util/AtClientValidation.java @@ -3,10 +3,12 @@ import java.io.IOException; import org.atsign.client.api.Secondary; +import org.atsign.client.api.notification.NotificationParams; import org.atsign.common.AtException; import org.atsign.common.AtSign; import org.atsign.common.Keys.AtKey; import org.atsign.common.Keys.Metadata; +import org.atsign.common.NotificationEnums.MessageType; public class AtClientValidation { @@ -120,4 +122,21 @@ public static void validateAtKey(AtKey atKey, String rootUrl) throws AtException } + public static void validateNotificationRequest(NotificationParams params, String rootDomain, int rootPort) throws AtException{ + if(params.getAtKey().sharedBy == null) { + throw new AtException("AtKey.sharedBy cannot be null"); + } + + if(params.getAtKey().sharedWith == null) { + throw new AtException("AtKey.sharedWith cannot be null"); + } + + atSignExists(params.getAtKey().sharedWith, rootDomain, rootPort + ""); + + if(params.getMessageType() != MessageType.TEXT) { + validateAtKey(params.getAtKey(), rootDomain + ":" + rootPort); + } + + } + } \ No newline at end of file diff --git a/at_client/src/main/java/org/atsign/common/NotificationEnums.java b/at_client/src/main/java/org/atsign/common/NotificationEnums.java new file mode 100644 index 00000000..c9055b92 --- /dev/null +++ b/at_client/src/main/java/org/atsign/common/NotificationEnums.java @@ -0,0 +1,38 @@ +package org.atsign.common; + +public class NotificationEnums { + + public enum Operation { + UPDATE, DELETE, APPEND, REMOVE,; + + public String toString() { + return this.name().toLowerCase(); + } + + } + + public enum Priority { + LOW, MEDIUM, HIGH,; + + public String toString() { + return this.name().toLowerCase(); + } + + } + + public enum Strategy { + ALL, LATEST,; + + public String toString() { + return this.name().toLowerCase(); + } + } + + public enum MessageType { + KEY, TEXT,; + + public String toString() { + return "MessageType." + this.name().toLowerCase(); // MessageType.text or MessageType.key + } + } +} diff --git a/at_client/src/main/java/org/atsign/common/ResponseTransformers.java b/at_client/src/main/java/org/atsign/common/ResponseTransformers.java index dd37da49..a004cf7a 100644 --- a/at_client/src/main/java/org/atsign/common/ResponseTransformers.java +++ b/at_client/src/main/java/org/atsign/common/ResponseTransformers.java @@ -1,10 +1,12 @@ package org.atsign.common; +import java.util.ArrayList; import java.util.List; import java.util.Map; import org.atsign.client.api.Secondary.Response; import org.atsign.common.response_models.LlookupAllResponse; +import org.atsign.common.response_models.NotifyListResponse; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; @@ -71,6 +73,52 @@ public Map transform(Response value) { } } + public static class NotifyListResponseTransformer implements ResponseTransformer { + /* + * [ + * { + * "id": "8797feda-d2d6-432a-9b9d-cc23184f60eb", + * "from": "@fascinatingsnow", + * "to": "@smoothalligator", + * "key": "@smoothalligator:test@fascinatingsnow", + * "value": null, + * "operation": "update", + * "epochMillis": 1665514962777, + * "messageType": "MessageType.key", + * "isEncrypted": false + * } + * ] + */ + @Override + public NotifyListResponse transform(Response value) { + NotifyListResponse model = new NotifyListResponse(); + model.notifications = new ArrayList(); + + if(value.data == null || value.data.isEmpty() || value.data.equalsIgnoreCase("null")) { + return model; + } + + // value.data == "[{}, {}, {}]" + String s = value.data.substring(1, value.data.length() - 1); // s == "{}, {}, {}" + String[] notificationStrs = s.split(",\\s*\\{"); // notificationStrs == ["{}", "{}", "{}"] + for(int i = 1; i < notificationStrs.length; i++) { + notificationStrs[i] = "{" + notificationStrs[i]; + } + + ObjectMapper mapper = new ObjectMapper(); + for(String notificationStr : notificationStrs) { + try { + NotifyListResponse.Notification notification = mapper.readValue(notificationStr, NotifyListResponse.Notification.class); + model.notifications.add(notification); + } catch (Exception e) { + e.printStackTrace(); + } + } + + return model; + } + } + public static class NotifyResponseTransformer implements ResponseTransformer { @Override public String transform(Response value) { @@ -78,7 +126,6 @@ public String transform(Response value) { } } - public static class NotificationStatusResponseTransformer implements ResponseTransformer { @Override public NotificationStatus transform(Response value) { diff --git a/at_client/src/main/java/org/atsign/common/VerbBuilders.java b/at_client/src/main/java/org/atsign/common/VerbBuilders.java index a4823be9..888c6468 100644 --- a/at_client/src/main/java/org/atsign/common/VerbBuilders.java +++ b/at_client/src/main/java/org/atsign/common/VerbBuilders.java @@ -1,10 +1,18 @@ package org.atsign.common; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.time.Instant; +import java.time.format.DateTimeFormatter; +import java.util.Date; +import java.util.UUID; + import org.apache.commons.lang3.StringUtils; import org.atsign.common.Keys.AtKey; import org.atsign.common.Keys.Metadata; import org.atsign.common.Keys.PublicKey; import org.atsign.common.Keys.SharedKey; +import org.atsign.common.NotificationEnums.MessageType; /** * @@ -659,7 +667,6 @@ public String build() { throw new IllegalArgumentException("key cannot be null or empty"); } - if(!operation.equals("update") && !operation.equals("delete")) { throw new IllegalArgumentException("Only 'update' and 'delete' are allowed for operation"); } @@ -709,7 +716,7 @@ public String build() { } } - public static class NotificationStatusVerbBuilder implements VerbBuilder { + public static class NotifyStatusVerbBuilder implements VerbBuilder { private String notificationId; @@ -728,4 +735,67 @@ public String build() { } } + public static class NotifyListVerbBuilder implements VerbBuilder { + + // get a list of notification json objects by running `notify:list` + + private static SimpleDateFormat formatter; + + static { + formatter = new SimpleDateFormat("yyyy-MM-dd"); + } + + private String regex; // optional regex to filter the list of notifications + private Date from; // optional (yyyy-MM-dd format) e.g. "2019-01-01" + private Date to; // optional (yyyy-MM-dd format) e.g. "2019-01-01" + + public void setRegex(String regex) { + this.regex = regex; + } + + public void setFrom(Date from) { + this.from = from; + } + + public void setTo(Date to) { + this.to = to; + } + + @Override + public String build() { + String b = "notify:list"; + if(from != null && to != null) { + if(from.toInstant().toEpochMilli() > to.toInstant().toEpochMilli()) { + throw new IllegalArgumentException("from date cannot be greater than to date"); + } + b += ":" + formatter.format(from); + b += ":" + formatter.format(to); + } + if (regex != null) { + b += ":" + regex; + } + return b; + } + + } + + public static class NotifyRemoveVerbBuilder implements VerbBuilder { + + private String notificationId; // mandatory, the id of the notification to delete + + public void setNotificationId(String notificationId) { + this.notificationId = notificationId; + } + + //notify:delete:(?\S+)$'; + public String build() { + + if(notificationId == null || StringUtils.isBlank(notificationId)) { + throw new IllegalArgumentException("notificationId cannot be null or empty"); + } + + return "notify:remove:" + notificationId; + } + } + } diff --git a/at_client/src/main/java/org/atsign/common/response_models/NotifyListResponse.java b/at_client/src/main/java/org/atsign/common/response_models/NotifyListResponse.java new file mode 100644 index 00000000..aa44179f --- /dev/null +++ b/at_client/src/main/java/org/atsign/common/response_models/NotifyListResponse.java @@ -0,0 +1,43 @@ +package org.atsign.common.response_models; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class NotifyListResponse { + + @JsonProperty + public List notifications; + + public static class Notification { + @JsonProperty + public String id; + + @JsonProperty + public String from; + + @JsonProperty + public String to; + + @JsonProperty + public String key; + + @JsonProperty + public String value; + + @JsonProperty + public String operation; + + @JsonProperty + public Long epochMillis; + + @JsonProperty + public String messageType; + + @JsonProperty + public Boolean isEncrypted; + + } + + +} diff --git a/at_client/src/test/java/org/atsign/common/AtClientValidationTest.java b/at_client/src/test/java/org/atsign/common/AtClientValidationTest.java index 0ebc8121..3d7ebfe5 100644 --- a/at_client/src/test/java/org/atsign/common/AtClientValidationTest.java +++ b/at_client/src/test/java/org/atsign/common/AtClientValidationTest.java @@ -2,6 +2,7 @@ import static org.junit.Assert.assertThrows; +import org.atsign.client.api.notification.NotificationParams; import org.atsign.client.util.AtClientValidation; import org.atsign.common.Keys.Metadata; import org.atsign.common.Keys.PublicKey; @@ -243,6 +244,53 @@ public void validateAtKeyTest() { // TODO } + @Test + public void validateNotificationRequestTest() { + // AtSign dummy1 = new AtSign("@alice"); + // AtSign dummy2 = new AtSign("@bob"); + // final String ROOT_DOMAIN = "root.atsign.org"; + // final int ROOT_PORT = 64; + + + // // null params + // assertThrows(AtException.class, () -> { + // NotificationParams params = null; + // AtClientValidation.validateNotificationRequest(params, ROOT_DOMAIN, ROOT_PORT); + // }); + + // // forText with null text + // assertThrows(AtException.class, () -> { + // NotificationParams params = NotificationParams.forText(null, dummy1, dummy2, false); + // AtClientValidation.validateNotificationRequest(params, ROOT_DOMAIN, ROOT_PORT); + // }); + + // // forText with null sharedBy + // assertThrows(AtException.class, () -> { + // NotificationParams params = NotificationParams.forText("test", null, dummy2, false); + // AtClientValidation.validateNotificationRequest(params, ROOT_DOMAIN, ROOT_PORT); + // }); + + // // forText with null sharedWith + // assertThrows(AtException.class, () -> { + // NotificationParams params = NotificationParams.forText("test", dummy1, null, false); + // AtClientValidation.validateNotificationRequest(params, ROOT_DOMAIN, ROOT_PORT); + // }); + + // // forUpdate with null sharedBy + // assertThrows(AtException.class, () -> { + // SharedKey sk = new KeyBuilders.SharedKeyBuilder(null, dummy2).key("test").build(); + // NotificationParams params = NotificationParams.forUpdate(sk, "lmao"); + // AtClientValidation.validateNotificationRequest(params, ROOT_DOMAIN, ROOT_PORT); + // }); + + // // forUpdate with null sharedWith + // assertThrows(AtException.class, () -> { + // SharedKey sk = new KeyBuilders.SharedKeyBuilder(dummy1, null).key("test").build(); + // NotificationParams params = NotificationParams.forUpdate(sk, "lmao"); + // AtClientValidation.validateNotificationRequest(params, ROOT_DOMAIN, ROOT_PORT); + // }); + } + @After public void tearDown() { } diff --git a/at_client/src/test/java/org/atsign/common/ResponseTransformerTest.java b/at_client/src/test/java/org/atsign/common/ResponseTransformerTest.java index 32b96059..2eda49b2 100644 --- a/at_client/src/test/java/org/atsign/common/ResponseTransformerTest.java +++ b/at_client/src/test/java/org/atsign/common/ResponseTransformerTest.java @@ -5,13 +5,15 @@ import org.atsign.client.api.Secondary; import org.atsign.common.ResponseTransformers.LlookupAllResponseTransformer; +import org.atsign.common.ResponseTransformers.NotifyListResponseTransformer; import org.atsign.common.response_models.LlookupAllResponse; +import org.atsign.common.response_models.NotifyListResponse; import org.junit.After; import org.junit.Before; import org.junit.Test; public class ResponseTransformerTest { - + @Before public void setUp() { } @@ -24,32 +26,37 @@ public void scanResponseTransformerTest() { @Test public void llookupAllResponseTransformerTest() { String RESPONSE_STR = "{" + - "\"key\":\"public:publickey@cooking2\"," + - "\"data\":\"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCz9nTBDDLxLgSxYu4+mDF3anWuTlKysXBBLsp3glrBP9xEXDx4muOHuHZIzuNvFlsjcCDF/mLSAJcvbxUoTsOQp+QD5XMhlNS9TWGsmNks7KHylNEhcqo2Va7RZxNS6MZBRacl+OusnebVKdOXDnbuevoED5fSklOz7mvdm9Mb2wIDAQAB\"," + - "\"metaData\":{" + - "\"createdBy\":null," + - "\"updatedBy\":null," + - "\"createdAt\":\"2022-08-12 01:50:15.398Z\"," + - "\"updatedAt\":\"2022-08-12 01:50:15.398Z\"," + - "\"availableAt\":\"2022-08-12 01:50:15.398Z\"," + - "\"expiresAt\":null," + - "\"refreshAt\":null," + - "\"status\":\"active\"," + - "\"version\":0," + - "\"ttl\":0," + - "\"ttb\":0," + - "\"ttr\":null," + - "\"ccd\":null," + - "\"isBinary\":false," + - "\"isEncrypted\":false," + - "\"dataSignature\":null," + - "\"sharedKeyEnc\":null," + - "\"pubKeyCS\":null," + - "\"encoding\":null" + - "}" + - "}"; - - // System.out.println(RESPONSE_STR); //{"key":"public:publickey@cooking2","data":"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCz9nTBDDLxLgSxYu4+mDF3anWuTlKysXBBLsp3glrBP9xEXDx4muOHuHZIzuNvFlsjcCDF/mLSAJcvbxUoTsOQp+QD5XMhlNS9TWGsmNks7KHylNEhcqo2Va7RZxNS6MZBRacl+OusnebVKdOXDnbuevoED5fSklOz7mvdm9Mb2wIDAQAB","metaData":{"createdBy":null,"updatedBy":null,"createdAt":"2022-08-12 01:50:15.398Z","updatedAt":"2022-08-12 01:50:15.398Z","availableAt":"2022-08-12 01:50:15.398Z","expiresAt":null,"refreshAt":null,"status":"active","version":0,"ttl":0,"ttb":0,"ttr":null,"ccd":null,"isBinary":false,"isEncrypted":false,"dataSignature":null,"sharedKeyEnc":null,"pubKeyCS":null,"encoding":null}} + "\"key\":\"public:publickey@cooking2\"," + + "\"data\":\"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCz9nTBDDLxLgSxYu4+mDF3anWuTlKysXBBLsp3glrBP9xEXDx4muOHuHZIzuNvFlsjcCDF/mLSAJcvbxUoTsOQp+QD5XMhlNS9TWGsmNks7KHylNEhcqo2Va7RZxNS6MZBRacl+OusnebVKdOXDnbuevoED5fSklOz7mvdm9Mb2wIDAQAB\"," + + + "\"metaData\":{" + + "\"createdBy\":null," + + "\"updatedBy\":null," + + "\"createdAt\":\"2022-08-12 01:50:15.398Z\"," + + "\"updatedAt\":\"2022-08-12 01:50:15.398Z\"," + + "\"availableAt\":\"2022-08-12 01:50:15.398Z\"," + + "\"expiresAt\":null," + + "\"refreshAt\":null," + + "\"status\":\"active\"," + + "\"version\":0," + + "\"ttl\":0," + + "\"ttb\":0," + + "\"ttr\":null," + + "\"ccd\":null," + + "\"isBinary\":false," + + "\"isEncrypted\":false," + + "\"dataSignature\":null," + + "\"sharedKeyEnc\":null," + + "\"pubKeyCS\":null," + + "\"encoding\":null" + + "}" + + "}"; + + // System.out.println(RESPONSE_STR); + // //{"key":"public:publickey@cooking2","data":"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCz9nTBDDLxLgSxYu4+mDF3anWuTlKysXBBLsp3glrBP9xEXDx4muOHuHZIzuNvFlsjcCDF/mLSAJcvbxUoTsOQp+QD5XMhlNS9TWGsmNks7KHylNEhcqo2Va7RZxNS6MZBRacl+OusnebVKdOXDnbuevoED5fSklOz7mvdm9Mb2wIDAQAB","metaData":{"createdBy":null,"updatedBy":null,"createdAt":"2022-08-12 + // 01:50:15.398Z","updatedAt":"2022-08-12 + // 01:50:15.398Z","availableAt":"2022-08-12 + // 01:50:15.398Z","expiresAt":null,"refreshAt":null,"status":"active","version":0,"ttl":0,"ttb":0,"ttr":null,"ccd":null,"isBinary":false,"isEncrypted":false,"dataSignature":null,"sharedKeyEnc":null,"pubKeyCS":null,"encoding":null}} Secondary.Response response = new Secondary.Response(); response.isError = false; response.data = RESPONSE_STR; @@ -58,8 +65,10 @@ public void llookupAllResponseTransformerTest() { model = transformer.transform(response); assertEquals("public:publickey@cooking2", model.key); - assertEquals("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCz9nTBDDLxLgSxYu4+mDF3anWuTlKysXBBLsp3glrBP9xEXDx4muOHuHZIzuNvFlsjcCDF/mLSAJcvbxUoTsOQp+QD5XMhlNS9TWGsmNks7KHylNEhcqo2Va7RZxNS6MZBRacl+OusnebVKdOXDnbuevoED5fSklOz7mvdm9Mb2wIDAQAB", model.data); - assertEquals(null, model.metaData.createdBy); + assertEquals( + "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCz9nTBDDLxLgSxYu4+mDF3anWuTlKysXBBLsp3glrBP9xEXDx4muOHuHZIzuNvFlsjcCDF/mLSAJcvbxUoTsOQp+QD5XMhlNS9TWGsmNks7KHylNEhcqo2Va7RZxNS6MZBRacl+OusnebVKdOXDnbuevoED5fSklOz7mvdm9Mb2wIDAQAB", + model.data); + assertEquals(null, model.metaData.createdBy); assertEquals(null, model.metaData.updatedBy); assertEquals("2022-08-12 01:50:15.398Z", model.metaData.createdAt); assertEquals("2022-08-12 01:50:15.398Z", model.metaData.updatedAt); @@ -78,7 +87,7 @@ public void llookupAllResponseTransformerTest() { assertEquals(null, model.metaData.sharedKeyEnc); assertEquals(null, model.metaData.pubKeyCS); assertEquals(null, model.metaData.encoding); - + // System.out.println("FullKeyName: " + model.key); // System.out.println("Data: " + model.data); // System.out.println("MetaData.createdBy: " + model.metaData.createdBy); @@ -96,13 +105,102 @@ public void llookupAllResponseTransformerTest() { // System.out.println("MetaData.ccd: " + model.metaData.ccd); // System.out.println("MetaData.isBinary: " + model.metaData.isBinary); // System.out.println("MetaData.isEncrypted: " + model.metaData.isEncrypted); - // System.out.println("MetaData.dataSignature: " + model.metaData.dataSignature); + // System.out.println("MetaData.dataSignature: " + + // model.metaData.dataSignature); // System.out.println("MetaData.sharedKeyEnc: " + model.metaData.sharedKeyEnc); // System.out.println("MetaData.pubKeyCS: " + model.metaData.pubKeyCS); // System.out.println("MetaData.encoding: " + model.metaData.encoding); } + @Test + public void notifyListResponseTransformerTest() { + /** + * [ + * { + * "id":"915a5c6a-314e-437f-b464-9a8c0d80770d", + * "from":"@soccer0", + * "to":"@22easy", + * "key":"@22easy:12345", + * "value":null, + * "operation":"null" + * "epochMillis":1671491608714, + * "messageType":"MessageType.text", + * "isEncrypted":false + * }, + * { + * "id":"c18d15ef-3da9-4538-b699-4a6542666678" + * "from":"@soccer0", + * "to":"@22easy", + * "key":"@22easy:12345", + * "value":null, + * "operation":"null", + * "epochMillis":1671491611414, + * "messageType":"MessageType.text", + * "isEncrypted":false + * }, + * { + * "id":"ce872442-a3e5-4624-b27c-f37a69e6bd3e", + * "from":"@soccer0", + * "to":"@22easy", + * "key":"@22easy:12345", + * "value":null, + * "operation":"null", + * "epochMillis":1671491603639, + * "messageType":"MessageType.text", + * "isEncrypted":false + * } + * ] + */ + + final String RESPONSE_STR = "[{\"id\":\"915a5c6a-314e-437f-b464-9a8c0d80770d\",\"from\":\"@soccer0\",\"to\":\"@22easy\",\"key\":\"@22easy:12345\",\"value\":null,\"operation\":\"null\",\"epochMillis\":1671491608714,\"messageType\":\"MessageType.text\",\"isEncrypted\":false},{\"id\":\"c18d15ef-3da9-4538-b699-4a6542666678\",\"from\":\"@soccer0\",\"to\":\"@22easy\",\"key\":\"@22easy:12345\",\"value\":null,\"operation\":\"null\",\"epochMillis\":1671491611414,\"messageType\":\"MessageType.text\",\"isEncrypted\":false},{\"id\":\"ce872442-a3e5-4624-b27c-f37a69e6bd3e\",\"from\":\"@soccer0\",\"to\":\"@22easy\",\"key\":\"@22easy:12345\",\"value\":null,\"operation\":\"null\",\"epochMillis\":1671491603639,\"messageType\":\"MessageType.text\",\"isEncrypted\":false}]"; + + Secondary.Response response = new Secondary.Response(); + response.isError = false; + response.data = RESPONSE_STR; + + NotifyListResponseTransformer transformer = new NotifyListResponseTransformer(); + NotifyListResponse model = transformer.transform(response); + + assertEquals(3, model.notifications.size()); + + // check notification 1 + NotifyListResponse.Notification n1 = model.notifications.get(0); + assertEquals("915a5c6a-314e-437f-b464-9a8c0d80770d", n1.id); + assertEquals("@soccer0", n1.from); + assertEquals("@22easy", n1.to); + assertEquals("@22easy:12345", n1.key); + assertEquals(null, n1.value); + assertEquals("null", n1.operation); + assertEquals(1671491608714L, n1.epochMillis.longValue()); + assertEquals("MessageType.text", n1.messageType); + assertEquals(false, n1.isEncrypted); + + // check notification 2 + NotifyListResponse.Notification n2 = model.notifications.get(1); + assertEquals("c18d15ef-3da9-4538-b699-4a6542666678", n2.id); + assertEquals("@soccer0", n2.from); + assertEquals("@22easy", n2.to); + assertEquals("@22easy:12345", n2.key); + assertEquals(null, n2.value); + assertEquals("null", n2.operation); + assertEquals(1671491611414L, n2.epochMillis.longValue()); + assertEquals("MessageType.text", n2.messageType); + assertEquals(false, n2.isEncrypted); + + // check notification 3 + NotifyListResponse.Notification n3 = model.notifications.get(2); + assertEquals("ce872442-a3e5-4624-b27c-f37a69e6bd3e", n3.id); + assertEquals("@soccer0", n3.from); + assertEquals("@22easy", n3.to); + assertEquals("@22easy:12345", n3.key); + assertEquals(null, n3.value); + assertEquals("null", n3.operation); + assertEquals(1671491603639L, n3.epochMillis.longValue()); + assertEquals("MessageType.text", n3.messageType); + assertEquals(false, n3.isEncrypted); + } + @After public void tearDown() { } diff --git a/at_client/src/test/java/org/atsign/common/VerbBuildersTest.java b/at_client/src/test/java/org/atsign/common/VerbBuildersTest.java index ec5586b4..54b2797b 100644 --- a/at_client/src/test/java/org/atsign/common/VerbBuildersTest.java +++ b/at_client/src/test/java/org/atsign/common/VerbBuildersTest.java @@ -3,6 +3,9 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThrows; +import java.util.Calendar; +import java.util.Date; + import org.atsign.common.Keys.PublicKey; import org.atsign.common.Keys.SelfKey; import org.atsign.common.Keys.SharedKey; @@ -11,8 +14,10 @@ import org.atsign.common.VerbBuilders.FromVerbBuilder; import org.atsign.common.VerbBuilders.LlookupVerbBuilder; import org.atsign.common.VerbBuilders.LookupVerbBuilder; -import org.atsign.common.VerbBuilders.NotificationStatusVerbBuilder; +import org.atsign.common.VerbBuilders.NotifyRemoveVerbBuilder; import org.atsign.common.VerbBuilders.NotifyKeyChangeBuilder; +import org.atsign.common.VerbBuilders.NotifyListVerbBuilder; +import org.atsign.common.VerbBuilders.NotifyStatusVerbBuilder; import org.atsign.common.VerbBuilders.NotifyTextVerbBuilder; import org.atsign.common.VerbBuilders.PKAMVerbBuilder; import org.atsign.common.VerbBuilders.POLVerbBuilder; @@ -671,22 +676,63 @@ public void notifyKeyChangeBuilderTest() { } @Test - public void notificationStatusVerbBuilderTest() { - + public void notifyStatusVerbBuilderTest() { // Test not setting any parameters assertThrows("Mandatory fields are not set. Expecting a IllegalArgumentException being thrown.", IllegalArgumentException.class, () -> { - final NotificationStatusVerbBuilder notificationStatusVerbBuilder = new NotificationStatusVerbBuilder(); - // Expect build to throw Illegal argument exception for not setting mandatory - // parameters + final NotifyStatusVerbBuilder notificationStatusVerbBuilder = new NotifyStatusVerbBuilder(); + // Expect build to throw Illegal argument exception for not setting mandatory parameters notificationStatusVerbBuilder.build(); }); - final NotificationStatusVerbBuilder notificationStatusVerbBuilder = new NotificationStatusVerbBuilder(); + // Test with notification id parameter + final NotifyStatusVerbBuilder notificationStatusVerbBuilder = new NotifyStatusVerbBuilder(); notificationStatusVerbBuilder.setNotificationId("n1234"); String expectedResult = "notify:status:n1234"; assertEquals(expectedResult, notificationStatusVerbBuilder.build()); + } + + @Test + public void notifyListVerbBuilderTest() { + // test with no argument + NotifyListVerbBuilder notificationListVerbBuilder = new NotifyListVerbBuilder(); + assertEquals("notify:list", notificationListVerbBuilder.build()); + + // Test with regex + notificationListVerbBuilder = new NotifyListVerbBuilder(); + notificationListVerbBuilder.setRegex(".*"); + assertEquals("notify:list:.*", notificationListVerbBuilder.build()); + // Test with fromDate and toDate + notificationListVerbBuilder = new NotifyListVerbBuilder(); + Date fromDate = new Calendar.Builder().setDate(2020, Calendar.JANUARY, 1).build().getTime(); + Date toDate = new Calendar.Builder().setDate(2020, Calendar.JANUARY, 2).build().getTime(); + notificationListVerbBuilder.setFrom(fromDate); + notificationListVerbBuilder.setTo(toDate); + assertEquals("notify:list:2020-01-01:2020-01-02", notificationListVerbBuilder.build()); + + // test with regex, fromDate, and toDate + notificationListVerbBuilder = new NotifyListVerbBuilder(); + notificationListVerbBuilder.setFrom(fromDate); + notificationListVerbBuilder.setTo(toDate); + notificationListVerbBuilder.setRegex(".*"); + assertEquals("notify:list:2020-01-01:2020-01-02:.*", notificationListVerbBuilder.build()); + } + + @Test + public void notifyDeleteVerbBuilderTest() { + // test with no argument + assertThrows("Mandatory fields are not set. Expecting a IllegalArgumentException being thrown.", + IllegalArgumentException.class, () -> { + NotifyRemoveVerbBuilder notificationDeleteVerbBuilder = new NotifyRemoveVerbBuilder(); + // Expect build to throw Illegal argument exception for not setting mandatory parameters + notificationDeleteVerbBuilder.build(); + }); + + // Test with notification id argument + NotifyRemoveVerbBuilder notificationDeleteVerbBuilder = new NotifyRemoveVerbBuilder(); + notificationDeleteVerbBuilder.setNotificationId("n1234"); + assertEquals("notify:remove:n1234", notificationDeleteVerbBuilder.build()); } @After