From 388996ee2f647d8307de084f944c1e74363849dc Mon Sep 17 00:00:00 2001 From: Kai Hudalla Date: Fri, 5 Sep 2025 18:02:13 +0200 Subject: [PATCH] Adapt message builder and attributes validator to 1.6.0-alpha.4 UMessageBuilder and UAttributesValidator have been updated to comply with the latest specification changes. --- .../transport/builder/UMessageBuilder.java | 339 +++++--- .../validate/UAttributesValidator.java | 456 ---------- .../validator/UAttributesValidator.java | 582 +++++++++++++ .../builder/UMessageBuilderTest.java | 589 +++++++------ .../validator/UAttributeValidatorTest.java | 562 ------------- .../validator/UAttributesValidatorTest.java | 791 ++++++++++++++++++ .../uuid/factory/UUIDFactoryTest.java | 31 +- .../uuid/validator/UuidValidatorTest.java | 6 +- 8 files changed, 1911 insertions(+), 1445 deletions(-) delete mode 100644 src/main/java/org/eclipse/uprotocol/transport/validate/UAttributesValidator.java create mode 100644 src/main/java/org/eclipse/uprotocol/transport/validator/UAttributesValidator.java delete mode 100644 src/test/java/org/eclipse/uprotocol/transport/validator/UAttributeValidatorTest.java create mode 100644 src/test/java/org/eclipse/uprotocol/transport/validator/UAttributesValidatorTest.java diff --git a/src/main/java/org/eclipse/uprotocol/transport/builder/UMessageBuilder.java b/src/main/java/org/eclipse/uprotocol/transport/builder/UMessageBuilder.java index 5f73da0d..27e81d4c 100644 --- a/src/main/java/org/eclipse/uprotocol/transport/builder/UMessageBuilder.java +++ b/src/main/java/org/eclipse/uprotocol/transport/builder/UMessageBuilder.java @@ -25,10 +25,9 @@ import com.google.protobuf.ByteString; import org.eclipse.uprotocol.communication.UPayload; -import org.eclipse.uprotocol.transport.validate.UAttributesValidator; -import org.eclipse.uprotocol.uri.validator.UriValidator; +import org.eclipse.uprotocol.transport.validator.UAttributesValidator; import org.eclipse.uprotocol.uuid.factory.UuidFactory; -import org.eclipse.uprotocol.uuid.validate.UuidValidator; +import org.eclipse.uprotocol.uuid.factory.UuidUtils; import java.util.Objects; import java.util.Optional; @@ -36,11 +35,12 @@ /** * Builder for easy construction of the UAttributes object. */ -public class UMessageBuilder { +public final class UMessageBuilder { private final UUri source; - private final UUID id; private final UMessageType type; + + private UUID id; private UPriority priority; private Integer ttl; private String token; @@ -54,109 +54,132 @@ public class UMessageBuilder { private ByteString payload; /** - * Construct a UMessageBuilder for a publish message. - * - * @param source The topic the message is published to (a.k.a Source address). - * @return Returns the UMessageBuilder with the configured priority. + * Gets a builder for a publish message. + * + * A publish message is used to notify all interested consumers of an event that has occurred. + * Consumers usually indicate their interest by subscribing to a particular topic. + * + * @param source The topic to publish the message to. + * @return The builder. + * @throws NullPointerException if the source is {@code null}. */ public static UMessageBuilder publish(UUri source) { Objects.requireNonNull(source, "source cannot be null."); - // Validate the source - if (!UriValidator.isTopic(source)) { - throw new IllegalArgumentException("source must be a topic."); - } - return new UMessageBuilder(source, UuidFactory.Factories.UPROTOCOL.factory().create(), - UMessageType.UMESSAGE_TYPE_PUBLISH); + return new UMessageBuilder( + source, + UuidFactory.Factories.UPROTOCOL.factory().create(), + UMessageType.UMESSAGE_TYPE_PUBLISH); } /** - * Construct a UMessageBuilder for a notification message. - * - * @param source The topic the message is published to (a.k.a Source address). - * @param sink The destination address for the notification (who will receive - * the notification). - * @return Returns the UMessageBuilder with the configured priority and sink. + * Gets a builder for a notification message. + * + * A notification is used to inform a specific consumer about an event that has occurred. + * + * @param source The component that the notification originates from. + * @param sink The URI identifying the destination to send the notification to. + * @return The builder. + * @throws NullPointerException if the source or sink is {@code null}. */ public static UMessageBuilder notification(UUri source, UUri sink) { Objects.requireNonNull(source, "source cannot be null."); Objects.requireNonNull(sink, "sink cannot be null."); - - // Validate the source and sink - if (!UriValidator.isTopic(source) || !UriValidator.isRpcResponse(sink)) { - throw new IllegalArgumentException("source must be a topic and sink must be a response."); - } - return new UMessageBuilder(source, UuidFactory.Factories.UPROTOCOL.factory().create(), - UMessageType.UMESSAGE_TYPE_NOTIFICATION).withSink(sink); + return new UMessageBuilder( + source, + UuidFactory.Factories.UPROTOCOL.factory().create(), + UMessageType.UMESSAGE_TYPE_NOTIFICATION) + .withSink(sink); } /** - * Construct a UMessageBuilder for a request message. - * - * @param source Source address for the message (address of the client sending - * the request message). - * @param sink The method that is being requested (a.k.a. destination - * address). - * @param ttl The time to live in milliseconds. - * @return Returns the UMessageBuilder with the configured priority, sink and - * ttl. + * Gets a builder for an RPC request message. + *

+ * A request message is used to invoke a service's method with some input data, expecting + * the service to reply with a response message which is correlated by means of its + * {@link UAttributes#getReqid() request ID}. + *

+ * The builder will be initialized with {@link UPriority#UPRIORITY_CS4}. + * + * @param source The URI that the sender of the request expects the response message at. + * @param sink The URI identifying the method to invoke. + * @param ttl The number of milliseconds after which the request should no longer be processed + * by the target service. + * @return The builder. + * @throws NullPointerException if the source or sink is {@code null}. + * @throws IllegalArgumentException if the ttl is less than or equal to 0. */ - public static UMessageBuilder request(UUri source, UUri sink, Integer ttl) { + public static UMessageBuilder request(UUri source, UUri sink, int ttl) { Objects.requireNonNull(source, "source cannot be null."); - Objects.requireNonNull(ttl, "ttl cannot be null."); Objects.requireNonNull(sink, "sink cannot be null."); - // Validate the source and sink - if (!UriValidator.isRpcMethod(sink) || !UriValidator.isRpcResponse(source)) { - throw new IllegalArgumentException("source must be an rpc method and sink must be a request."); - } - - // Validate the ttl if (ttl <= 0) { throw new IllegalArgumentException("ttl must be greater than 0."); } - return new UMessageBuilder(source, UuidFactory.Factories.UPROTOCOL.factory().create(), - UMessageType.UMESSAGE_TYPE_REQUEST).withTtl(ttl).withSink(sink); + return new UMessageBuilder( + source, + UuidFactory.Factories.UPROTOCOL.factory().create(), + UMessageType.UMESSAGE_TYPE_REQUEST) + .withSink(sink) + .withTtl(ttl) + .withPriority(UPriority.UPRIORITY_CS4); } /** - * Construct a UMessageBuilder for a response message. - * - * @param source The source address of the method that was requested - * @param sink The destination of the client thatsend the request. - * @param reqid The original request UUID used to correlate the response to the - * request. - * @return Returns the UMessageBuilder with the configured priority, sink and - * reqid. + * Gets a builder for an RPC response message. + *

+ * A response message is used to send the outcome of processing a request message + * to the original sender of the request message. + *

+ * The builder will be initialized with {@link UPriority#UPRIORITY_CS4}. + * + * # Arguments + * + * * `reply_to_address` - The URI that the sender of the request expects to receive the response message at. + * * `request_id` - The identifier of the request that this is the response to. + * * `invoked_method` - The URI identifying the method that has been invoked and which the created message is + * the outcome of. + * + * @param source The URI identifying the method that has been invoked and which the created message is + * the outcome of. + * @param sink The URI that the sender of the request expects to receive the response message at. + * @param reqid The identifier of the request that this is the response to. + * @return The builder. + * @throws NullPointerException if any of the parameters are {@code null}. */ public static UMessageBuilder response(UUri source, UUri sink, UUID reqid) { Objects.requireNonNull(source, "source cannot be null."); Objects.requireNonNull(sink, "sink cannot be null for Response."); Objects.requireNonNull(reqid, "reqid cannot be null."); - // Validate the source and sink - if (!UriValidator.isRpcResponse(sink) || !UriValidator.isRpcMethod(source)) { - throw new IllegalArgumentException("sink must be a response and source must be an rpc method."); - } - - try { - UuidValidator.Validators.UPROTOCOL.validator().validate(reqid); - } catch (ValidationException e) { - throw new IllegalArgumentException("reqid is not a valid UUID.", e); - } - - return new UMessageBuilder(source, UuidFactory.Factories.UPROTOCOL.factory().create(), - UMessageType.UMESSAGE_TYPE_RESPONSE).withSink(sink).withReqId(reqid); + return new UMessageBuilder( + source, + UuidFactory.Factories.UPROTOCOL.factory().create(), + UMessageType.UMESSAGE_TYPE_RESPONSE) + .withSink(sink) + .withReqId(reqid) + .withPriority(UPriority.UPRIORITY_CS4); } /** - * Construct a UMessageBuilder for a response message using an existing request. + * Gets a builder for creating an RPC response message in reply to a request. + *

+ * A response message is used to send the outcome of processing a request message + * to the original sender of the request message. + *

+ * The builder will be initialized with values from the given request attributes. + * + * # Arguments + * + * * `request_attributes` - The attributes from the request message. The response message + * builder will be initialized with the corresponding attribute values. * - * @param request The original request {@code UAttributes} used to correlate the - * response to the request. - * @return Returns the UMessageBuilder with the configured source, sink, - * priority, and reqid. + * @param request The attributes from the request message. The response message + * builder will be initialized with the corresponding attribute values. + * @return The builder. + * @throws NullPointerException if request is {@code null}. + * @throws IllegalArgumentException if the request does not contain valid request attributes. */ public static UMessageBuilder response(UAttributes request) { Objects.requireNonNull(request, "request cannot be null."); @@ -172,14 +195,14 @@ public static UMessageBuilder response(UAttributes request) { request.getSink(), UuidFactory.Factories.UPROTOCOL.factory().create(), UMessageType.UMESSAGE_TYPE_RESPONSE) - .withPriority(request.getPriority()) - .withSink(request.getSource()) - .withReqId(request.getId()); + .withPriority(request.getPriority()) + .withSink(request.getSource()) + .withReqId(request.getId()) + .withTtl(request.getTtl()); } /** - * Construct the UMessageBuilder with the configurations that are required for - * every payload transport. + * Creates a builder for attribute values required for all types of messages. * * @param source Source address of the message. * @param id Unique identifier for the message. @@ -193,88 +216,135 @@ private UMessageBuilder(UUri source, UUID id, UMessageType type) { } /** - * Add the time to live in milliseconds. + * Sets the message's identifier. + * + * Every message must have an identifier. If this method is not used, an identifier will be + * generated and set on the message when one of the build methods is invoked. * - * @param ttl the time to live in milliseconds. - * @return Returns the UMessageBuilder with the configured ttl. + * @param id The custom message ID. + * @return The builder with the custom message ID. + * @throws NullPointerException if the id is {@code null}. + * @throws IllegalArgumentException if the id is not a {@link UuidUtils#isUProtocol(UUID) valid uProtocol UUID}. */ - public UMessageBuilder withTtl(Integer ttl) { + public UMessageBuilder withMessageId(UUID id) { + Objects.requireNonNull(id, "id cannot be null."); + if (!UuidUtils.isUProtocol(id)) { + throw new IllegalArgumentException("id must be a valid uProtocol UUID."); + } + this.id = id; + return this; + } + + /** + * Sets the message's time-to-live. + * + * @param ttl The time-to-live in milliseconds. + * @return The builder with the configured ttl. + * @throws IllegalArgumentException if the ttl is negative. + */ + public UMessageBuilder withTtl(int ttl) { + if (ttl < 0) { + throw new IllegalArgumentException("TTL must be a non-negative integer."); + } this.ttl = ttl; return this; } /** - * Add the authorization token used for TAP. + * Sets the message's authorization token used for TAP. * - * @param token the authorization token used for TAP. - * @return Returns the UMessageBuilder with the configured token. + * @param token The token. + * @return The builder with the configured token. + * @throws NullPointerException if the token is {@code null}. + * @throws IllegalStateException if the message is not an RPC request message. */ public UMessageBuilder withToken(String token) { + // [impl->dsn~up-attributes-request-token~1] + Objects.requireNonNull(token, "token cannot be null."); + if (this.type != UMessageType.UMESSAGE_TYPE_REQUEST) { + throw new IllegalStateException("Token can only be set for RPC request messages."); + } this.token = token; return this; } /** - * Add the priority of the message. - * - * @param priority the priority of the message. - * @return Returns the UMessageBuilder with the configured priority. + * Sets the priority of the message. + *

+ * If not set explicitly, the priority will be {@link UPriority#UPRIORITY_UNSPECIFIED}. + * + * @param priority The priority to be used for sending the message. + * @return The builder with the configured priority. + * @throws NullPointerException if the priority is {@code null}. + * @throws IllegalArgumentException if the builder is used for creating an RPC message + * but the priority is less than {@link UPriority#UPRIORITY_CS4}. */ public UMessageBuilder withPriority(UPriority priority) { + // [impl->dsn~up-attributes-request-priority~1] + Objects.requireNonNull(priority, "priority cannot be null."); + if (priority.getNumber() < UPriority.UPRIORITY_CS4_VALUE && + (this.type == UMessageType.UMESSAGE_TYPE_REQUEST || this.type == UMessageType.UMESSAGE_TYPE_RESPONSE)) { + throw new IllegalArgumentException("priority must be at least CS4 for RPC messages"); + } this.priority = priority; return this; } /** - * Add the permission level of the message. + * Sets the message's permission level. * - * @param plevel the permission level of the message. - * @return Returns the UMessageBuilder with the configured plevel. + * @param plevel The level. + * @return The builder with the configured permission level. + * @throws IllegalArgumentException if the permission level is less than 0. + * @throws IllegalStateException if the message is not an RPC request message. */ - public UMessageBuilder withPermissionLevel(Integer plevel) { + public UMessageBuilder withPermissionLevel(int plevel) { + // [impl->dsn~up-attributes-permission-level~1] + if (plevel < 0) { + throw new IllegalArgumentException("Permission level must be greater than or equal to 0."); + } + if (this.type != UMessageType.UMESSAGE_TYPE_REQUEST) { + throw new IllegalStateException("Permission level can only be set for RPC request messages."); + } this.plevel = plevel; return this; } /** - * Add the traceprent. + * Sets the identifier of the W3C Trace Context to convey in the message. * - * @param traceparent the trace parent. - * @return Returns the UMessageBuilder with the configured traceparent. + * @param traceparent The identifier. + * @return The builder with the configured traceparent. + * @throws NullPointerException if the traceparent is {@code null}. */ public UMessageBuilder withTraceparent(String traceparent) { + // [impl->dsn~up-attributes-traceparent~1] + Objects.requireNonNull(traceparent, "traceparent cannot be null."); this.traceparent = traceparent; return this; } /** - * Add the communication status of the message. + * Sets the message's communication status. * - * @param commstatus the communication status of the message. - * @return Returns the UMessageBuilder with the configured commstatus. + * @param commstatus The status. + * @return The builder with the configured commstatus. + * @throws IllegalStateException if the message is not an RPC response message. */ public UMessageBuilder withCommStatus(UCode commstatus) { + Objects.requireNonNull(commstatus, "commstatus cannot be null."); + if (this.type != UMessageType.UMESSAGE_TYPE_RESPONSE) { + throw new IllegalStateException("Communication status can only be set for RPC response messages."); + } this.commstatus = commstatus; return this; } - /** - * Add the request ID. - * - * @param reqid the request ID. - * @return Returns the UMessageBuilder with the configured reqid. - */ private UMessageBuilder withReqId(UUID reqid) { this.reqid = reqid; return this; } - /** - * Add the explicit destination URI. - * - * @param sink the explicit destination URI. - * @return Returns the UMessageBuilder with the configured sink. - */ private UMessageBuilder withSink(UUri sink) { this.sink = sink; return this; @@ -285,56 +355,49 @@ private UMessageBuilder withSink(UUri sink) { * * @param payload The payload to be packed into the message. * @return Returns the UMessage with the configured payload. + * @throws NullPointerException if the payload is {@code null}. */ public UMessage build(UPayload payload) { - if (payload != null ) { - this.format = payload.format(); - this.payload = payload.data(); - } + Objects.requireNonNull(payload, "payload cannot be null."); + this.format = payload.format(); + this.payload = payload.data(); return build(); } /** - * Construct the UMessage from the builder. + * Creates the message based on the builder's state. + * + * # Errors + * + * If the properties set on the builder do not represent a consistent set of [`UAttributes`], + * a [`UMessageError::AttributesValidationError`] is returned. * - * @return Returns a constructed + * @return A message ready to be sent using a transport implementation. + * @throws ValidationException if the properties set on the builder do not represent a + * consistent set of attributes as determined by {@link UAttributesValidator#validate(UAttributes)}. */ public UMessage build() { - UMessage.Builder messageBuilder = UMessage.newBuilder(); - UAttributes.Builder attributesBuilder = UAttributes.newBuilder() .setSource(source) .setId(id) .setType(type); - Optional priority = Optional.ofNullable(this.priority); - switch (type) { - case UMESSAGE_TYPE_REQUEST: - case UMESSAGE_TYPE_RESPONSE: - attributesBuilder.setPriority( - priority - .filter(v -> v.getNumber() >= UPriority.UPRIORITY_CS4.getNumber()) - .orElse(UPriority.UPRIORITY_CS4)); - break; - default: - attributesBuilder.setPriority( - priority - .filter(v -> v.getNumber() >= UPriority.UPRIORITY_CS1.getNumber()) - .orElse(UPriority.UPRIORITY_CS1)); - break; - } - Optional.ofNullable(sink).ifPresent(attributesBuilder::setSink); + Optional.ofNullable(priority).ifPresent(attributesBuilder::setPriority); Optional.ofNullable(ttl).ifPresent(attributesBuilder::setTtl); Optional.ofNullable(plevel).ifPresent(attributesBuilder::setPermissionLevel); Optional.ofNullable(commstatus).ifPresent(attributesBuilder::setCommstatus); Optional.ofNullable(reqid).ifPresent(attributesBuilder::setReqid); Optional.ofNullable(token).ifPresent(attributesBuilder::setToken); Optional.ofNullable(traceparent).ifPresent(attributesBuilder::setTraceparent); - Optional.ofNullable(payload).ifPresent(messageBuilder::setPayload); Optional.ofNullable(format).ifPresent(attributesBuilder::setPayloadFormat); - return messageBuilder.setAttributes(attributesBuilder).build(); + final var attributes = attributesBuilder.build(); + UAttributesValidator.getValidator(attributes).validate(attributes); + + UMessage.Builder messageBuilder = UMessage.newBuilder(); + Optional.ofNullable(payload).ifPresent(messageBuilder::setPayload); + return messageBuilder.setAttributes(attributes).build(); } } diff --git a/src/main/java/org/eclipse/uprotocol/transport/validate/UAttributesValidator.java b/src/main/java/org/eclipse/uprotocol/transport/validate/UAttributesValidator.java deleted file mode 100644 index f6ca727b..00000000 --- a/src/main/java/org/eclipse/uprotocol/transport/validate/UAttributesValidator.java +++ /dev/null @@ -1,456 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2024 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.eclipse.uprotocol.transport.validate; - -import org.eclipse.uprotocol.uri.validator.UriValidator; -import org.eclipse.uprotocol.uuid.factory.UuidUtils; -import org.eclipse.uprotocol.v1.UAttributes; -import org.eclipse.uprotocol.v1.UMessageType; -import org.eclipse.uprotocol.v1.UPriority; -import org.eclipse.uprotocol.v1.UUri; -import org.eclipse.uprotocol.v1.UUID; -import org.eclipse.uprotocol.validation.ValidationException; -import org.eclipse.uprotocol.validation.ValidationUtils; - -import java.util.Objects; -import java.util.Optional; - -/** - * {@link UAttributes} is the class that defines the Payload. It is the place - * for configuring time to live, priority, - * security tokens and more. - * Each UAttributes class defines a different type of message payload. The - * payload can represent a simple published - * payload with some state change, - * Payload representing an RPC request or Payload representing an RPC response. - * UAttributesValidator is a base class for all UAttribute validators, that can - * help validate that the - * {@link UAttributes} object is correctly defined - * to define the Payload correctly. - */ -public abstract class UAttributesValidator { - - /** - * Static factory method for getting a validator according to the - * {@link UMessageType} defined in the - * {@link UAttributes}. - * - * @param attribute UAttributes containing the UMessageType. - * @return returns a UAttributesValidator according to the {@link UMessageType} - * defined in the {@link UAttributes}. - */ - public static UAttributesValidator getValidator(UAttributes attribute) { - - switch (attribute.getType()) { - case UMESSAGE_TYPE_RESPONSE: - return Validators.RESPONSE.validator(); - case UMESSAGE_TYPE_REQUEST: - return Validators.REQUEST.validator(); - case UMESSAGE_TYPE_NOTIFICATION: - return Validators.NOTIFICATION.validator(); - default: - return Validators.PUBLISH.validator(); - } - } - - /** - * Checks if a given set of attributes complies with the rules specified for - * the type of message they describe. - * - * @param attributes The attributes to validate. - * @throws ValidationException if the attributes are not consistent with the rules specified for the message type. - */ - public void validate(UAttributes attributes) { - final var errors = ValidationUtils.collectErrors(attributes, - this::validateType, - this::validateTtl, - this::validateSink, - this::validatePriority, - this::validatePermissionLevel, - this::validateReqId, - this::validateId - ); - if (!errors.isEmpty()) { - throw new ValidationException(errors); - } - } - - /** - * Check the time-to-live attribute to see if it has expired.
- * The message has expired when the current time is greater than the original - * UUID time - * plus the ttl attribute. - * - * @param uAttributes UAttributes with time to live value. - * @return Returns a true if the original time plus the ttl is less than the - * current time - */ - public boolean isExpired(UAttributes uAttributes) { - final int ttl = uAttributes.getTtl(); - final Optional maybeTime = UuidUtils.getTime(uAttributes.getId()); - - // if the message does not have a ttl or the original time is not present or the - // ttl is less than 0 - if (maybeTime.isEmpty() || ttl <= 0) { - return false; - } - - // the original time plus the ttl is less than the current time, the message has - // expired - return (maybeTime.get() + ttl) < System.currentTimeMillis(); - } - - /** - * Validate the time to live configuration. If the UAttributes does not contain - * a time to live then the - * ValidationResult is ok. - * - * @param attributes UAttributes object containing the message time to live - * configuration to validate. - * @throws ValidationException if TTL <= 0. - */ - public void validateTtl(UAttributes attributes) { - int ttl = attributes.getTtl(); - if (attributes.hasTtl() && ttl <= 0) { - throw new ValidationException(String.format("Invalid TTL [%s]", ttl)); - } - - } - - /** - * Validate the sink UriPart. - * - * @param attributes UAttributes object containing the sink to validate. - * @throws ValidationException if sink is invalid. - */ - public abstract void validateSink(UAttributes attributes); - - /** - * Validate the permissionLevel for the default case. If the UAttributes does - * not contain a permission level then - * the ValidationResult is ok. - * - * @param attributes UAttributes object containing the permission level to - * validate. - * @throws ValidationException if the permission level is invalid. - */ - public void validatePermissionLevel(UAttributes attributes) { - if (attributes.hasPermissionLevel() && attributes.getPermissionLevel() <= 0) { - throw new ValidationException("Invalid Permission Level"); - } - } - - /** - * Validate the correlationId for the default case. Only the response message - * should have a reqid. - * - * @param attributes Attributes object containing the request id to validate. - * @throws ValidationException if the request id is invalid. - */ - public void validateReqId(UAttributes attributes) { - if (attributes.hasReqid()) { - throw new ValidationException("Message should not have a reqid"); - } - } - - /** - * Validate the priority value to ensure it is one of the known CS values. - * - * @param attributes Attributes object containing the Priority to validate. - * @throws ValidationException if the priority is invalid. - */ - public void validatePriority(UAttributes attributes) { - if (attributes.getPriority().getNumber() < UPriority.UPRIORITY_CS1_VALUE) { - throw new ValidationException(String.format("Invalid UPriority [%s]", attributes.getPriority().name())); - } - } - - /** - * Validate the Id for the default case. If the UAttributes object does not - * contain an Id, - * the ValidationResult is failed. - * - * @param attributes Attributes object containing the id to validate. - * @throws ValidationException if the message ID is invalid. - */ - public void validateId(UAttributes attributes) { - if (!attributes.hasId()) { - throw new ValidationException("Missing id"); - } - if (!UuidUtils.isUuid(attributes.getId())) { - throw new ValidationException("Attributes must contain valid uProtocol UUID in id property"); - } - } - - /** - * Validate the {@link UMessageType} attribute, it is required. - * - * @param attributes UAttributes object containing the message type to validate. - * @throws ValidationException if this validator is inappropriate for the message's type. - */ - public abstract void validateType(UAttributes attributes); - - /** - * Validators Factory. Example: - * UAttributesValidator validateForPublishMessageType = - * UAttributesValidator.Validators.PUBLISH.validator() - */ - public enum Validators { - PUBLISH(new Publish()), - REQUEST(new Request()), - RESPONSE(new Response()), - NOTIFICATION(new Notification()); - - private final UAttributesValidator uattributesValidator; - - Validators(UAttributesValidator uattributesValidator) { - this.uattributesValidator = uattributesValidator; - } - - public UAttributesValidator validator() { - return uattributesValidator; - } - } - - /** - * Implements validations for UAttributes that define a message that is meant - * for publishing state changes. - */ - private static class Publish extends UAttributesValidator { - - /** - * Validates that attributes for a message meant to publish state changes has - * the correct type. - * - * @param attributes UAttributes object containing the message type to validate. - * @return Returns a {@link ValidationException} that is success or failed with a - * failure message. - */ - @Override - public void validateType(UAttributes attributes) { - if (UMessageType.UMESSAGE_TYPE_PUBLISH != attributes.getType()) { - throw new ValidationException( - String.format("Wrong Attribute Type [%s]", attributes.getType())); - } - } - - /** - * Validate the sink UriPart for Publish events. Publish should not have a sink. - * - * @param attributes UAttributes object containing the sink to validate. - * @return Returns a {@link ValidationException} that is success or failed with a - * failure message. - */ - @Override - public void validateSink(UAttributes attributes) { - if (attributes.hasSink()) { - throw new ValidationException("Sink should not be present"); - } - } - - @Override - public String toString() { - return "UAttributesValidator.Publish"; - } - } - - /** - * Implements validations for UAttributes that define a message that is meant - * for an RPC request. - */ - private static class Request extends UAttributesValidator { - - /** - * {@inheritDoc} - * - * Validates that attributes for a message meant for an RPC request has the - * correct type. - */ - @Override - public void validateType(UAttributes attributes) { - if (UMessageType.UMESSAGE_TYPE_REQUEST != attributes.getType()) { - throw new ValidationException( - String.format("Wrong Attribute Type [%s]", attributes.getType())); - } - } - - /** - * {@inheritDoc} - * - * Validates that attributes for a message meant for an RPC request has a - * destination sink. - * In the case of an RPC request, the sink is required. - */ - @Override - public void validateSink(UAttributes attributes) { - if (!attributes.hasSink()) { - throw new ValidationException("Missing Sink"); - } - if (!UriValidator.isRpcMethod(attributes.getSink())) { - throw new ValidationException("Invalid Sink Uri"); - } - } - - /** - * {@inheritDoc} - * - * Validate the time to live configuration. - * In the case of an RPC request, the time to live is required. - */ - @Override - public void validateTtl(UAttributes attributes) { - if (!attributes.hasTtl()) { - throw new ValidationException("Missing TTL"); - } - int ttl = attributes.getTtl(); - if (ttl <= 0) { - throw new ValidationException(String.format("Invalid TTL [%s]", ttl)); - } - } - - /** - * {@inheritDoc} - * - * Validate the priority value to ensure it is one of the known CS values - */ - @Override - public void validatePriority(UAttributes attributes) { - if (attributes.getPriority().getNumber() < UPriority.UPRIORITY_CS4_VALUE) { - throw new ValidationException( - String.format("Invalid UPriority [%s]", attributes.getPriority().name())); - } - } - - @Override - public String toString() { - return "UAttributesValidator.Request"; - } - } - - /** - * Implements validations for UAttributes that define a message that is meant - * for an RPC response. - */ - private static class Response extends UAttributesValidator { - - /** - * {@inheritDoc} - * - * Validates that attributes for a message meant for an RPC response has the - * correct type. - */ - @Override - public void validateType(UAttributes attributes) { - if (UMessageType.UMESSAGE_TYPE_RESPONSE != attributes.getType()) { - throw new ValidationException( - String.format("Wrong Attribute Type [%s]", attributes.getType())); - } - } - - /** - * {@inheritDoc} - * - * Validates that attributes for a message meant for an RPC response has a - * destination sink. - * In the case of an RPC response, the sink is required. - */ - @Override - public void validateSink(UAttributes attributes) { - Objects.requireNonNull(attributes, "UAttributes cannot be null."); - if (!attributes.hasSink() || attributes.getSink() == UUri.getDefaultInstance()) { - throw new ValidationException("Missing Sink"); - } - if (!UriValidator.isRpcResponse(attributes.getSink())) { - throw new ValidationException("Invalid Sink Uri"); - } - } - - /** - * {@inheritDoc} - * - * Validate the correlationId. n the case of an RPC response, the correlation id - * is required. - */ - @Override - public void validateReqId(UAttributes attributes) { - if (!attributes.hasReqid() || attributes.getReqid() == UUID.getDefaultInstance()) { - throw new ValidationException("Missing correlationId"); - } - if (!UuidUtils.isUuid(attributes.getReqid())) { - throw new ValidationException("Invalid correlation UUID"); - } - } - - /** - * {@inheritDoc} - * - * Validate the priority value to ensure it is one of the known CS values - */ - @Override - public void validatePriority(UAttributes attributes) { - if (attributes.getPriority().getNumber() < UPriority.UPRIORITY_CS4_VALUE) { - throw new ValidationException( - String.format("Invalid UPriority [%s]", attributes.getPriority().name())); - } - } - - @Override - public String toString() { - return "UAttributesValidator.Response"; - } - } - - /** - * Implements validations for UAttributes that define a message that is meant - * for notifications. - */ - private static class Notification extends UAttributesValidator { - - /** - * {@inheritDoc} - * - * Validates that attributes for a message meant to Notification state changes - * has the correct type. - */ - @Override - public void validateType(UAttributes attributes) { - if (UMessageType.UMESSAGE_TYPE_NOTIFICATION != attributes.getType()) { - throw new ValidationException( - String.format("Wrong Attribute Type [%s]", attributes.getType())); - } - } - - /** - * {@inheritDoc} - * - * Validates that attributes for a message meant for notifications has a - * destination sink. - * In the case of a notification, the sink is required. - */ - @Override - public void validateSink(UAttributes attributes) { - Objects.requireNonNull(attributes, "UAttributes cannot be null."); - if (!attributes.hasSink() || attributes.getSink() == UUri.getDefaultInstance()) { - throw new ValidationException("Missing Sink"); - } - if (!UriValidator.isNotificationDestination(attributes.getSink())) { - throw new ValidationException("Invalid Sink Uri"); - } - } - - @Override - public String toString() { - return "UAttributesValidator.Notification"; - } - } -} diff --git a/src/main/java/org/eclipse/uprotocol/transport/validator/UAttributesValidator.java b/src/main/java/org/eclipse/uprotocol/transport/validator/UAttributesValidator.java new file mode 100644 index 00000000..9cb9b0b3 --- /dev/null +++ b/src/main/java/org/eclipse/uprotocol/transport/validator/UAttributesValidator.java @@ -0,0 +1,582 @@ +/** + * SPDX-FileCopyrightText: 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.eclipse.uprotocol.transport.validator; + +import org.eclipse.uprotocol.uri.validator.UriValidator; +import org.eclipse.uprotocol.uuid.factory.UuidUtils; +import org.eclipse.uprotocol.v1.UAttributes; +import org.eclipse.uprotocol.v1.UCode; +import org.eclipse.uprotocol.v1.UMessageType; +import org.eclipse.uprotocol.v1.UPriority; +import org.eclipse.uprotocol.validation.ValidationException; +import org.eclipse.uprotocol.validation.ValidationUtils; + +/** + * A validator that checks if a given set of {@link UAttributes uProtocol + * message attributes} are compliant with the uProtocol specification. + *

+ * UAttributes contain a message's metadata like message ID, type, + * priority, time-to-live, security tokens and more. + *

+ * Each message contains some standard attributes like ID and type as well as + * some attributes that are specific to the given type of message, such as + * the client's permission level in an RPC request message or a status code in + * an RPC response message. + *

+ * {@code UAttributesValidator} is a base class that contains the functionality + * shared by all type specific validators. + */ +public abstract class UAttributesValidator { + + /** + * Gets a validator that can be used to check a given set of attributes.. + * + * @param attributes The attributes to get a validator for. + * @return A validator matching the type of message that the attributes + * belong to. + */ + public static UAttributesValidator getValidator(UAttributes attributes) { + return getValidator(attributes.getType()); + } + + /** + * Gets a validator that can be used to check attributes of a given type of message. + * + * @param type The type of message to get a validator for. + * @return A validator matching the type of message. + */ + public static UAttributesValidator getValidator(UMessageType type) { + switch (type) { + case UMESSAGE_TYPE_RESPONSE: + return Validators.RESPONSE.validator(); + case UMESSAGE_TYPE_REQUEST: + return Validators.REQUEST.validator(); + case UMESSAGE_TYPE_NOTIFICATION: + return Validators.NOTIFICATION.validator(); + default: + return Validators.PUBLISH.validator(); + } + } + + /** + * Verifies that a set of attributes contains a valid message ID. + * + * @param attributes The attributes to check. + * @throws ValidationException if the message ID is invalid. + */ + public final void validateId(UAttributes attributes) { + if (!attributes.hasId()) { + throw new ValidationException("Missing message ID"); + } + if (!UuidUtils.isUProtocol(attributes.getId())) { + throw new ValidationException("Attributes must contain valid uProtocol UUID in id property"); + } + } + + /** + * Verifies that this validator is appropriate for a set of attributes. + * + * @param attributes The attributes to check. + * @throws ValidationException if the attributes are not of the type returned by + * {@link #messageType()}. + */ + public final void validateType(UAttributes attributes) { + if (attributes.getType() != messageType()) { + throw new ValidationException(String.format( + "Invalid message type [%s], expected [%s]", + attributes.getType().name(), messageType().name())); + } + } + + /** + * Checks if a given set of attributes contains a valid priority. + * + * @param attributes The attributes to check. + * @throws ValidationException if the priority contained in the attributes is {@link UPriority#UNRECOGNIZED}. + */ + public final void validatePriority(UAttributes attributes) { + if (attributes.getPriority() == UPriority.UNRECOGNIZED) { + throw new ValidationException(String.format( + "Invalid UPriority [%s]", attributes.getPriority().name())); + } + } + + /** + * Verifies that a set of attributes contains a priority that is appropriate for an RPC request message. + * + * @param attributes The attributes to check. + * @throws ValidationException if the priority contained in the attributes is not at + * least {@link UPriority#UPRIORITY_CS4}. + */ + public final void validateRpcPriority(UAttributes attributes) { + if (attributes.getPriority().getNumber() < UPriority.UPRIORITY_CS4_VALUE) { + throw new ValidationException(String.format( + "RPC Request message must have at least priority [%s] but has [%s]", + UPriority.UPRIORITY_CS4.name(), attributes.getPriority().name())); + } + } + + /** + * Checks if a given set of attributes belong to a message that has expired. + *

+ * The message is considered expired if the message's creation time plus the + * duration indicated by the ttl attribute is before the current + * instant in time. + * + * @param attributes The attributes to check. + * @return {@code true} if the given attributes should be considered expired. + */ + public final boolean isExpired(UAttributes attributes) { + final int ttl = attributes.getTtl(); + return ttl > 0 && UuidUtils.isExpired(attributes.getId(), ttl); + } + + /* + * Gets the type of message that this validator can be used with. + * + * @return The message type. + */ + abstract UMessageType messageType(); + + /** + * Verifies that a set of attributes contains a valid source URI. + * + * @param attributes The attributes to check. + * @throws ValidationException if the attributes do not contain a valid source URI. + */ + public abstract void validateSource(UAttributes attributes); + + /** + * Verifies that a set of attributes contains a valid sink URI. + * + * @param attributes The attributes to check. + * @throws ValidationException if the attributes do not contain a valid sink URI. + */ + public abstract void validateSink(UAttributes attributes); + + + /** + * Checks if a given set of attributes complies with the rules specified for + * the type of message they describe. + * + * @param attributes The attributes to check. + * @throws ValidationException if the attributes are not consistent with the rules + * specified for the message type. + */ + public abstract void validate(UAttributes attributes); + + /** + * Validates the time-to-live configuration. + *

+ * If the UAttributes does not contain a time to live then the ValidationResult is ok. + * + * @param attributes The attributes to check. + * @throws ValidationException if the attributes contain a negative TTL. + */ + public void validateTtl(UAttributes attributes) { + if (!attributes.hasTtl()) { + return; + } + int ttl = attributes.getTtl(); + if (ttl < 0) { + throw new ValidationException(String.format("TTL must be a non-negative integer [%s]", ttl)); + } + } + + /** + * Validate the permissionLevel for the default case. If the UAttributes does + * not contain a permission level then + * the ValidationResult is ok. + * + * @param attributes The attributes to check. + * @throws ValidationException if the attributes contain a negative permission level. + */ + public final void validatePermissionLevel(UAttributes attributes) { + if (!attributes.hasPermissionLevel()) { + return; + } + final var level = attributes.getPermissionLevel(); + if (level < 0) { + throw new ValidationException( + String.format("Permission level must be a non-negative integer [%d]", level)); + } + } + + /** + * Validators for the message types defined by uProtocol. + */ + public enum Validators { + PUBLISH(new Publish()), + REQUEST(new Request()), + RESPONSE(new Response()), + NOTIFICATION(new Notification()); + + private final UAttributesValidator uattributesValidator; + + Validators(UAttributesValidator uattributesValidator) { + this.uattributesValidator = uattributesValidator; + } + + /** + * Gets the validator to use for checking attributes. + * + * @return The validator. + */ + public UAttributesValidator validator() { + return uattributesValidator; + } + } + + /** + * Validates attributes describing a Publish message. + */ + private static final class Publish extends UAttributesValidator { + + @Override + protected UMessageType messageType() { + return UMessageType.UMESSAGE_TYPE_PUBLISH; + } + + /** + * Verifies that attributes for a publish message contain a valid source URI. + * + * @param attributes The attributes to check. + * @throws ValidationException if the attributes do not contain a source URI, + * or if the source URI contains any wildcards, or if the source URI has an + * invalid resource ID. + */ + @Override + public void validateSource(UAttributes attributes) { + // [impl->dsn~up-attributes-publish-source~1] + if (!attributes.hasSource()) { + throw new ValidationException("Attributes for a publish message must contain a source URI"); + } + final var source = attributes.getSource(); + if (UriValidator.hasWildcard(source)) { + throw new ValidationException("Source URI must not contain wildcards"); + } + if (!UriValidator.isTopic(source)) { + throw new ValidationException("Source is not a valid topic URI"); + } + } + + /** + * Verifies that attributes for a publish message do not contain a sink URI. + * + * @param attributes The attributes to check. + * @throws ValidationException if the sink attribute contains any URI. + */ + @Override + public void validateSink(UAttributes attributes) { + if (attributes.hasSink()) { + throw new ValidationException("Attributes for a publish message must not contain a sink URI"); + } + } + + @Override + public void validate(UAttributes attributes) { + final var errors = ValidationUtils.collectErrors(attributes, + this::validateType, + this::validateId, + this::validateSource, + this::validateSink, + this::validateTtl, + this::validatePriority + ); + if (!errors.isEmpty()) { + throw new ValidationException(errors); + } + } + } + + /** + * Validates attributes describing a Notification message. + */ + private static final class Notification extends UAttributesValidator { + + @Override + protected UMessageType messageType() { + return UMessageType.UMESSAGE_TYPE_NOTIFICATION; + } + + /** + * Verifies that attributes for a notification message contain a valid source URI. + * + * @param attributes The attributes to check. + * @throws ValidationException if the attributes do not contain a source URI, + * or if the source URI contains any wildcards, or if the source URI has an invalid + * resource ID. + */ + @Override + public void validateSource(UAttributes attributes) { + // [impl->dsn~up-attributes-notification-source~1] + if (!attributes.hasSource()) { + throw new ValidationException("Attributes for a notification message must contain a source URI"); + } + final var source = attributes.getSource(); + if (UriValidator.hasWildcard(source)) { + throw new ValidationException("Source URI must not contain wildcards"); + } + if (!UriValidator.isTopic(source)) { + throw new ValidationException("Source is not a valid topic URI"); + } + } + + /** + * Verifies that attributes for a notification message contain a sink URI. + * + * @param attributes The attributes to check. + * @throws ValidationException if the attributes do not contain a sink URI, + * or if the sink URI contains any wildcards, or if the sink URI has a resource + * ID != 0. + */ + @Override + public void validateSink(UAttributes attributes) { + // [impl->dsn~up-attributes-notification-sink~1] + if (!attributes.hasSink()) { + throw new ValidationException("Attributes for a notification message must contain a sink URI"); + } + final var sink = attributes.getSink(); + if (UriValidator.hasWildcard(sink)) { + throw new ValidationException("Sink URI must not contain wildcards"); + } + if (!UriValidator.isNotificationDestination(sink)) { + throw new ValidationException("Sink's resource ID must be 0"); + } + } + + @Override + public void validate(UAttributes attributes) { + final var errors = ValidationUtils.collectErrors(attributes, + this::validateType, + this::validateId, + this::validateSource, + this::validateSink, + this::validateTtl, + this::validatePriority + ); + if (!errors.isEmpty()) { + throw new ValidationException(errors); + } + } + } + + /** + Validates attributes describing an RPC Request message. + */ + private static final class Request extends UAttributesValidator { + + @Override + protected UMessageType messageType() { + return UMessageType.UMESSAGE_TYPE_REQUEST; + } + + /** + * Verifies that attributes for a message representing an RPC request contain a reply-to-address. + * + * @param attributes The attributes to check. + * @throws ValidationException if the attributes do not contain a reply-to-address, + * or if the reply-to-address contains any wildcards, or if the reply-to-address has a + * resource ID != 0. + */ + @Override + public void validateSource(UAttributes attributes) { + // [impl->dsn~up-attributes-request-source~1] + if (!attributes.hasSource()) { + throw new ValidationException(""" + Attributes for a request message must contain a reply-to address in the source property + """); + } + final var source = attributes.getSource(); + if (UriValidator.hasWildcard(source)) { + throw new ValidationException("Source URI must not contain wildcards"); + } + if (!UriValidator.isRpcResponse(source)) { + throw new ValidationException("Source is not a valid reply-to address"); + } + } + + /** + * Verifies that attributes for a message representing an RPC request indicate the method to invoke. + * + * @param attributes The attributes to check. + * @throws ValidationException if the attributes do not contain a method-to-invoke, + * or if the method-to-invoke contains any wildcards, or if the method-to-invoke + * has an invalid resource ID. + */ + @Override + public void validateSink(UAttributes attributes) { + // [impl->dsn~up-attributes-request-sink~1] + if (!attributes.hasSink()) { + throw new ValidationException(""" + Attributes for a request message must contain a method-to-invoke in the sink property + """); + } + final var sink = attributes.getSink(); + if (UriValidator.hasWildcard(sink)) { + throw new ValidationException("Sink URI must not contain wildcards"); + } + if (!UriValidator.isRpcMethod(sink)) { + throw new ValidationException("Sink is not a valid method-to-invoke"); + } + } + + /** + * Verifies that a set of attributes representing an RPC request contain a valid time-to-live. + * + * @param attributes The attributes to check. + * @throws ValidationException if the attributes do not contain a time-to-live, + * or if the time-to-live is <= 0. + */ + @Override + public void validateTtl(UAttributes attributes) { + if (!attributes.hasTtl()) { + throw new ValidationException("RPC request message must contain a TTL"); + } + int ttl = attributes.getTtl(); + if (ttl <= 0) { + throw new ValidationException(String.format( + "RPC request message's TTL must be a positive integer [%d]", + ttl)); + } + } + + @Override + public void validate(UAttributes attributes) { + final var errors = ValidationUtils.collectErrors(attributes, + this::validateType, + this::validateId, + this::validateSource, + this::validateSink, + this::validateTtl, + this::validateRpcPriority, + this::validatePermissionLevel + ); + if (!errors.isEmpty()) { + throw new ValidationException(errors); + } + } + } + + /** + * Implements validations for UAttributes that define a message that is meant + * for an RPC response. + */ + private static final class Response extends UAttributesValidator { + + @Override + protected UMessageType messageType() { + return UMessageType.UMESSAGE_TYPE_RESPONSE; + } + + /** + * Verifies that attributes for a message representing an RPC response contain the invoked method. + * + * @param attributes The attributes to check. + * @throws ValidationException if the attributes do not contain the invoked method, + * or if the invoked method contains any wildcards, or if the invoked method has an invalid resource ID. + */ + @Override + public void validateSource(UAttributes attributes) { + // [impl->dsn~up-attributes-response-source~1] + if (!attributes.hasSource()) { + throw new ValidationException(""" + Attributes for a response message must contain the invoked method in the source property + """); + } + final var source = attributes.getSource(); + if (UriValidator.hasWildcard(source)) { + throw new ValidationException("Source URI must not contain wildcards"); + } + if (!UriValidator.isRpcMethod(source)) { + throw new ValidationException("Source is not a valid method-to-invoke"); + } + } + + /** + * Verifies that attributes for a message representing an RPC response contain a valid + * reply-to-address. + * + * @param attributes The attributes to check. + * @throws ValidationException if the attributes do not contain a reply-to-address, + * or if the reply-to-address contains any wildcards, or if the reply-to-address + * has a resource ID != 0. + */ + @Override + public void validateSink(UAttributes attributes) { + // [impl->dsn~up-attributes-response-sink~1] + if (!attributes.hasSink()) { + throw new ValidationException(""" + Attributes for a response message must contain a reply-to address in the sink property + """); + } + final var sink = attributes.getSink(); + if (UriValidator.hasWildcard(sink)) { + throw new ValidationException("Sink URI must not contain wildcards"); + } + if (!UriValidator.isRpcResponse(sink)) { + throw new ValidationException("Sink is not a valid reply-to address"); + } + } + + /** + * Verifies that the attributes contain a valid request ID. + * + * @param attributes The attributes to check. + * @throws ValidationException if the attributes do not contain a request ID, + * or if the request ID is not a valid uProtocol UUID. + */ + public void validateReqId(UAttributes attributes) { + if (!attributes.hasReqid()) { + throw new ValidationException("RPC response message must contain a request ID"); + } + final var reqid = attributes.getReqid(); + if (!UuidUtils.isUProtocol(reqid)) { + throw new ValidationException("Request ID is not a valid uProtocol UUID"); + } + } + + /** + * Verifies that a set of attributes contains a valid communication status. + * + * @param attributes The attributes to check. + * @throws ValidationException if the attributes contain an unsupported status code. + */ + public void validateCommstatus(UAttributes attributes) { + if (!attributes.hasCommstatus()) { + return; + } + final var commstatus = attributes.getCommstatus(); + if (commstatus == UCode.UNRECOGNIZED) { + throw new ValidationException("Unsupported communication status"); + } + } + + @Override + public void validate(UAttributes attributes) { + final var errors = ValidationUtils.collectErrors(attributes, + this::validateType, + this::validateId, + this::validateSource, + this::validateSink, + this::validateReqId, + this::validateTtl, + this::validateRpcPriority, + this::validateCommstatus + ); + if (!errors.isEmpty()) { + throw new ValidationException(errors); + } + } + } +} diff --git a/src/test/java/org/eclipse/uprotocol/transport/builder/UMessageBuilderTest.java b/src/test/java/org/eclipse/uprotocol/transport/builder/UMessageBuilderTest.java index d81b5e6c..b17e97a6 100644 --- a/src/test/java/org/eclipse/uprotocol/transport/builder/UMessageBuilderTest.java +++ b/src/test/java/org/eclipse/uprotocol/transport/builder/UMessageBuilderTest.java @@ -12,315 +12,388 @@ */ package org.eclipse.uprotocol.transport.builder; +import org.eclipse.uprotocol.v1.UAttributes; import org.eclipse.uprotocol.v1.UCode; import org.eclipse.uprotocol.v1.UPriority; import org.eclipse.uprotocol.v1.UUri; import org.eclipse.uprotocol.v1.UMessage; import org.eclipse.uprotocol.v1.UMessageType; +import org.eclipse.uprotocol.v1.UPayloadFormat; import org.eclipse.uprotocol.v1.UUID; +import org.eclipse.uprotocol.communication.UPayload; +import org.eclipse.uprotocol.transport.validator.UAttributesValidator; import org.eclipse.uprotocol.uuid.factory.UuidFactory; -import org.eclipse.uprotocol.v1.UAttributes; -import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; + +import com.google.protobuf.ByteString; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; -public class UMessageBuilderTest { - - @Test - public void testPublish() { - UMessage publish = UMessageBuilder.publish(buildTopic()).build(); - assertNotNull(publish); - assertEquals(UMessageType.UMESSAGE_TYPE_PUBLISH, publish.getAttributes().getType()); - assertEquals(UPriority.UPRIORITY_CS1, publish.getAttributes().getPriority()); - } - - @Test - public void testNotification() { - UUri sink = buildSink(); - UMessage notification = UMessageBuilder.notification(buildTopic(), sink).build(); - assertNotNull(notification); - assertEquals(UMessageType.UMESSAGE_TYPE_NOTIFICATION, notification.getAttributes().getType()); - assertEquals(UPriority.UPRIORITY_CS1, notification.getAttributes().getPriority()); - assertEquals(sink, notification.getAttributes().getSink()); - } +class UMessageBuilderTest { - @Test - public void testRequest() { - UUri sink = buildMethod(); - Integer ttl = 1000; - UMessage request = UMessageBuilder.request(buildSource(), sink, ttl).build(); - assertNotNull(request); - assertEquals(UMessageType.UMESSAGE_TYPE_REQUEST, request.getAttributes().getType()); - assertEquals(UPriority.UPRIORITY_CS4, request.getAttributes().getPriority()); - assertEquals(sink, request.getAttributes().getSink()); - assertEquals(ttl, request.getAttributes().getTtl()); - } - - @Test - public void testResponse() { - UUri sink = buildSink(); - UUID reqId = UuidFactory.Factories.UPROTOCOL.factory().create(); - UMessage response = UMessageBuilder.response(buildMethod(), sink, reqId).build(); - assertNotNull(response); - assertEquals(UMessageType.UMESSAGE_TYPE_RESPONSE, response.getAttributes().getType()); - assertEquals(UPriority.UPRIORITY_CS4, response.getAttributes().getPriority()); - assertEquals(sink, response.getAttributes().getSink()); - assertEquals(reqId, response.getAttributes().getReqid()); - } - - @Test - @DisplayName("Test response with existing request") - public void testResponseWithExistingRequest() { - UMessage request = UMessageBuilder.request(buildSource(), buildMethod(), 1000).build(); - UMessage response = UMessageBuilder.response(request.getAttributes()).build(); - assertNotNull(response); - assertEquals(UMessageType.UMESSAGE_TYPE_RESPONSE, response.getAttributes().getType()); - assertEquals(UPriority.UPRIORITY_CS4, request.getAttributes().getPriority()); - assertEquals(UPriority.UPRIORITY_CS4, response.getAttributes().getPriority()); - assertEquals(request.getAttributes().getSource(), response.getAttributes().getSink()); - assertEquals(request.getAttributes().getSink(), response.getAttributes().getSource()); - assertEquals(request.getAttributes().getId(), response.getAttributes().getReqid()); - } + private static final UUri UURI_DEFAULT = UUri.newBuilder() + .setUeId(1) + .setUeVersionMajor(1) + .setResourceId(0) + .build(); - @Test - public void testBuild() { - UMessageBuilder builder = UMessageBuilder.publish(buildTopic()) - .withToken("test_token") - .withPermissionLevel(2) - .withCommStatus(UCode.CANCELLED) - .withTraceparent("myParents"); - UMessage message = builder.build(); - UAttributes attributes = message.getAttributes(); - assertNotNull(message); - assertNotNull(attributes); - assertEquals(UMessageType.UMESSAGE_TYPE_PUBLISH, attributes.getType()); - assertEquals(UPriority.UPRIORITY_CS1, attributes.getPriority()); - assertEquals("test_token", attributes.getToken()); - assertEquals(2, attributes.getPermissionLevel()); - assertEquals(UCode.CANCELLED, attributes.getCommstatus()); - assertEquals("myParents", attributes.getTraceparent()); - } + private static final UUri UURI_METHOD = UUri.newBuilder() + .setUeId(0x001a_1a5b) + .setUeVersionMajor(0x04) + .setResourceId(0x7a5f) + .build(); + private static final UUri UURI_TOPIC = UUri.newBuilder() + .setUeId(1) + .setUeVersionMajor(1) + .setResourceId(0x8000) + .build(); - @Test - @DisplayName("Test building UMessage with empty payload") - public void testBuildWithAnyPayload() { - UMessage message = UMessageBuilder.publish(buildTopic()) - .build(); - assertNotNull(message); - assertFalse(message.hasPayload()); + void testWithMessageIdPanicsForInvalidUuid() { + UUID invalidMessageId = UUID.newBuilder() + .setMsb(0x00000000000000abL) + .setLsb(0x0000000000018000L) + .build(); + assertThrows( + IllegalArgumentException.class, () -> { + UMessageBuilder.publish(UURI_TOPIC).withMessageId(invalidMessageId); + }); } - - @Test - @DisplayName("Test building response message with the wrong priority value of UPRIORITY_CS3") - public void testBuildResponseWithWrongPriority() { - UUri sink = buildSink(); - UUID reqId = UuidFactory.Factories.UPROTOCOL.factory().create(); - UMessage response = UMessageBuilder.response(buildMethod(), sink, reqId) - .withPriority(UPriority.UPRIORITY_CS3) - .build(); - assertNotNull(response); - assertEquals(UMessageType.UMESSAGE_TYPE_RESPONSE, response.getAttributes().getType()); - assertEquals(UPriority.UPRIORITY_CS4, response.getAttributes().getPriority()); - assertEquals(sink, response.getAttributes().getSink()); - assertEquals(reqId, response.getAttributes().getReqid()); + @ParameterizedTest + @CsvSource(useHeadersInDisplayName = true, textBlock = """ + permLevel, commStatus, token + 5, , + , NOT_FOUND, + , , my-token + """) + void testPublishMessageBuilderRejectsInvalidAttributes( + Integer permLevel, + UCode commStatus, + String token + ) { + var builder = UMessageBuilder.publish(UURI_TOPIC); + assertBuilderPanics(builder, permLevel, commStatus, token); } - @Test - @DisplayName("Test building request message with the wrong priority value of UPRIORITY_CS3") - public void testBuildRequestWithWrongPriority() { - UUri sink = buildMethod(); - Integer ttl = 1000; - UMessage request = UMessageBuilder.request(buildSource(), sink, ttl) - .withPriority(UPriority.UPRIORITY_CS3) - .build(); - assertNotNull(request); - assertEquals(UMessageType.UMESSAGE_TYPE_REQUEST, request.getAttributes().getType()); - assertEquals(UPriority.UPRIORITY_CS4, request.getAttributes().getPriority()); - assertEquals(sink, request.getAttributes().getSink()); - assertEquals(ttl, request.getAttributes().getTtl()); + @ParameterizedTest + @CsvSource(useHeadersInDisplayName = true, textBlock = """ + permLevel, commStatus, token + 5, , + , NOT_FOUND, + , , my-token + """) + void testNotificationMessageBuilderRejectsInvalidAttributes( + Integer permLevel, + UCode commStatus, + String token + ) { + var builder = UMessageBuilder.notification(UURI_TOPIC, UURI_DEFAULT); + assertBuilderPanics(builder, permLevel, commStatus, token); } - @Test - @DisplayName("Test building notification message with the wrong priority value of UPRIORITY_CS0") - public void testBuildNotificationWithWrongPriority() { - UUri sink = buildSink(); - UMessage notification = UMessageBuilder.notification(buildTopic(), sink) - .withPriority(UPriority.UPRIORITY_CS0) - .build(); - assertNotNull(notification); - assertEquals(UMessageType.UMESSAGE_TYPE_NOTIFICATION, notification.getAttributes().getType()); - assertEquals(UPriority.UPRIORITY_CS1, notification.getAttributes().getPriority()); - assertEquals(sink, notification.getAttributes().getSink()); + void assertBuilderPanics( + UMessageBuilder builder, + Integer permLevel, + UCode commStatus, + String token + ) { + if (permLevel != null) { + assertThrows( + IllegalStateException.class, + () -> builder.withPermissionLevel(permLevel) + ); + } else if (commStatus != null) { + assertThrows( + IllegalStateException.class, + () -> builder.withCommStatus(commStatus) + ); + } else if (token != null) { + assertThrows( + IllegalStateException.class, + () -> builder.withToken(token) + ); + } } - @Test - @DisplayName("Test building publish message with the wrong priority value of UPRIORITY_CS0") - public void testBuildPublishWithWrongPriority() { - UMessage publish = UMessageBuilder.publish(buildTopic()) - .withPriority(UPriority.UPRIORITY_CS0) - .build(); - assertNotNull(publish); - assertEquals(UMessageType.UMESSAGE_TYPE_PUBLISH, publish.getAttributes().getType()); - assertEquals(UPriority.UPRIORITY_CS1, publish.getAttributes().getPriority()); + @ParameterizedTest + @ValueSource(ints = { -1, 0 }) + void testRequestRejectsInvalidTtl(int ttl) { + assertThrows( + IllegalArgumentException.class, + () -> UMessageBuilder.request(UURI_DEFAULT, UURI_METHOD, ttl) + ); } - @Test - @DisplayName("Test building publish message with the priority value of UPRIORITY_CS4") - public void testBuildPublishWithPriority() { - UMessage publish = UMessageBuilder.publish(buildTopic()) - .withPriority(UPriority.UPRIORITY_CS4) - .build(); - assertNotNull(publish); - assertEquals(UMessageType.UMESSAGE_TYPE_PUBLISH, publish.getAttributes().getType()); - assertEquals(UPriority.UPRIORITY_CS4, publish.getAttributes().getPriority()); + @ParameterizedTest + @CsvSource(useHeadersInDisplayName = true, textBlock = """ + permLevel, commStatus, priority + -1, , + , NOT_FOUND, + , , UPRIORITY_UNSPECIFIED + , , UPRIORITY_CS0 + , , UPRIORITY_CS1 + , , UPRIORITY_CS2 + , , UPRIORITY_CS3 + """) + void testRequestMessageBuilderRejectsInvalidAttributes( + Integer permLevel, + UCode commStatus, + UPriority priority + ) { + var builder = UMessageBuilder.request(UURI_DEFAULT, UURI_METHOD, 5000); + + if (permLevel != null) { + assertThrows( + IllegalArgumentException.class, + () -> builder.withPermissionLevel(permLevel) + ); + } else if (commStatus != null) { + assertThrows( + IllegalStateException.class, + () -> builder.withCommStatus(commStatus) + ); + } else if (priority != null) { + assertThrows( + IllegalArgumentException.class, + () -> builder.withPriority(priority) + ); + } } - - private UUri buildSink() { - return UUri.newBuilder().setAuthorityName("vcu.someVin.veh.ultifi.gm.com") - .setUeId(1) - .setUeVersionMajor(1) - .setResourceId(0).build(); + @ParameterizedTest + @CsvSource(useHeadersInDisplayName = true, textBlock = """ + ttl, permLevel, token, priority + -1, , , + , 5, , + , , my-token, + , , , UPRIORITY_UNSPECIFIED + , , , UPRIORITY_CS0 + , , , UPRIORITY_CS1 + , , , UPRIORITY_CS2 + , , , UPRIORITY_CS3 + """) + void testResponseMessageBuilderRejectsInvalidAttributes( + Integer ttl, + Integer permLevel, + String token, + UPriority priority + ) { + var builder = UMessageBuilder.response( + UURI_METHOD, + UURI_DEFAULT, + UuidFactory.Factories.UPROTOCOL.factory().create()); + + if (ttl != null) { + assertThrows( + IllegalArgumentException.class, + () -> builder.withTtl(ttl) + ); + } else if (permLevel != null) { + assertThrows( + IllegalStateException.class, + () -> builder.withPermissionLevel(permLevel) + ); + } else if (token != null) { + assertThrows( + IllegalStateException.class, + () -> builder.withToken(token) + ); + } else if (priority != null) { + assertThrows( + IllegalArgumentException.class, + () -> builder.withPriority(priority) + ); + } } - @Test - @DisplayName("Test publish when source is not a valid topic") - public void testPublishWithInvalidSource() { - assertThrows(IllegalArgumentException.class, - () -> UMessageBuilder.publish(buildSource()).build()); + void testResponseRejectsNonRequestAttributes() { + UAttributes attributes = UAttributes.newBuilder() + .setType(UMessageType.UMESSAGE_TYPE_PUBLISH) + .setId(UuidFactory.Factories.UPROTOCOL.factory().create()) + .setSource(UURI_TOPIC) + .build(); + UAttributesValidator.getValidator(attributes).validate(attributes); + + assertThrows( + IllegalArgumentException.class, + () -> UMessageBuilder.response(attributes) + ); } - @Test - @DisplayName("Test notification when source is not a valid topic") - public void testNotificationWithInvalidSource() { - assertThrows(IllegalArgumentException.class, - () -> UMessageBuilder.notification(buildSource(), buildSink()).build()); + void testBuildSupportsRepeatedInvocation() { + UMessageBuilder builder = UMessageBuilder.publish(UURI_TOPIC); + UMessage messageOne = builder + .withMessageId(UuidFactory.Factories.UPROTOCOL.factory().create()) + .build(UPayload.pack(ByteString.copyFromUtf8("locked"), UPayloadFormat.UPAYLOAD_FORMAT_TEXT)); + UMessage messageTwo = builder + .withMessageId(UuidFactory.Factories.UPROTOCOL.factory().create()) + .build(UPayload.pack(ByteString.copyFromUtf8("unlocked"), UPayloadFormat.UPAYLOAD_FORMAT_TEXT)); + assertEquals(messageOne.getAttributes().getType(), messageTwo.getAttributes().getType()); + assertNotEquals(messageOne.getAttributes().getId(), messageTwo.getAttributes().getId()); + assertEquals(messageOne.getAttributes().getSource(), messageTwo.getAttributes().getSource()); + assertNotEquals(messageOne.getPayload(), messageTwo.getPayload()); } - @Test - @DisplayName("Test notification when sink is not a valid UUri") - public void testNotificationWithInvalidSink() { - assertThrows(IllegalArgumentException.class, - () -> UMessageBuilder.notification(buildTopic(), buildTopic()).build()); + void testBuildRetainsAllPublishAttributes() { + var messageId = UuidFactory.Factories.UPROTOCOL.factory().create(); + String traceparent = "traceparent"; + UUri topic = UURI_TOPIC; + UMessage message = UMessageBuilder.publish(topic) + .withMessageId(messageId) + .withTtl(5000) + .withTraceparent(traceparent) + .build(UPayload.pack(ByteString.copyFromUtf8("locked"), UPayloadFormat.UPAYLOAD_FORMAT_TEXT)); + + assertEquals(messageId, message.getAttributes().getId()); + assertEquals(UPriority.UPRIORITY_UNSPECIFIED, message.getAttributes().getPriority()); + assertEquals(topic, message.getAttributes().getSource()); + assertFalse(message.getAttributes().hasSink()); + assertEquals(5000, message.getAttributes().getTtl()); + assertEquals(traceparent, message.getAttributes().getTraceparent()); + assertEquals(UMessageType.UMESSAGE_TYPE_PUBLISH, message.getAttributes().getType()); + assertEquals(UPayloadFormat.UPAYLOAD_FORMAT_TEXT, message.getAttributes().getPayloadFormat()); + + var proto = message.toByteString(); + assertDoesNotThrow(() -> { + var deserializedMessage = UMessage.parseFrom(proto); + assertEquals(message, deserializedMessage); + }); } - @Test - @DisplayName("Test request when source is not valid") - public void testRequestWithInvalidSource() { - assertThrows(IllegalArgumentException.class, - () -> UMessageBuilder.request(buildMethod(), buildMethod(), 1000).build()); + void testBuildRetainsAllNotificationAttributes() { + var messageId = UuidFactory.Factories.UPROTOCOL.factory().create(); + String traceparent = "traceparent"; + var origin = UURI_TOPIC; + var destination = UURI_DEFAULT; + UMessage message = UMessageBuilder.notification(origin, destination) + .withMessageId(messageId) + .withPriority(UPriority.UPRIORITY_CS2) + .withTtl(5000) + .withTraceparent(traceparent) + .build(UPayload.pack(ByteString.copyFromUtf8("locked"), UPayloadFormat.UPAYLOAD_FORMAT_TEXT)); + + assertEquals(messageId, message.getAttributes().getId()); + assertEquals(UPriority.UPRIORITY_CS2, message.getAttributes().getPriority()); + assertEquals(origin, message.getAttributes().getSource()); + assertEquals(destination, message.getAttributes().getSink()); + assertEquals(5000, message.getAttributes().getTtl()); + assertEquals(traceparent, message.getAttributes().getTraceparent()); + assertEquals(UMessageType.UMESSAGE_TYPE_NOTIFICATION, message.getAttributes().getType()); + assertEquals(UPayloadFormat.UPAYLOAD_FORMAT_TEXT, message.getAttributes().getPayloadFormat()); + + var proto = message.toByteString(); + assertDoesNotThrow(() -> { + var deserializedMessage = UMessage.parseFrom(proto); + assertEquals(message, deserializedMessage); + }); } @Test - @DisplayName("Test request when sink is not valid") - public void testRequestWithInvalidSink() { - assertThrows(IllegalArgumentException.class, - () -> UMessageBuilder.request(buildSource(), buildSource(), 1000).build()); + void testBuildRetainsAllRequestAttributes() { + var messageId = UuidFactory.Factories.UPROTOCOL.factory().create(); + var token = "token"; + String traceparent = "traceparent"; + var methodToInvoke = UURI_METHOD; + var replyToAddress = UURI_DEFAULT; + UMessage message = UMessageBuilder.request(replyToAddress, methodToInvoke, 5000) + .withMessageId(messageId) + .withPermissionLevel(5) + .withPriority(UPriority.UPRIORITY_CS4) + .withToken(token) + .withTraceparent(traceparent) + .build(UPayload.pack(ByteString.copyFromUtf8("locked"), UPayloadFormat.UPAYLOAD_FORMAT_TEXT)); + + assertEquals(messageId, message.getAttributes().getId()); + assertEquals(5, message.getAttributes().getPermissionLevel()); + assertEquals(UPriority.UPRIORITY_CS4, message.getAttributes().getPriority()); + assertEquals(replyToAddress, message.getAttributes().getSource()); + assertEquals(methodToInvoke, message.getAttributes().getSink()); + assertEquals(token, message.getAttributes().getToken()); + assertEquals(5000, message.getAttributes().getTtl()); + assertEquals(traceparent, message.getAttributes().getTraceparent()); + assertEquals(UMessageType.UMESSAGE_TYPE_REQUEST, message.getAttributes().getType()); + assertEquals(UPayloadFormat.UPAYLOAD_FORMAT_TEXT, message.getAttributes().getPayloadFormat()); + + var proto = message.toByteString(); + assertDoesNotThrow(() -> { + var deserializedMessage = UMessage.parseFrom(proto); + assertEquals(message, deserializedMessage); + }); } @Test - @DisplayName("Test request when source and sink are not valid") - public void testRequestWithInvalidSourceAndSink() { - assertThrows(IllegalArgumentException.class, - () -> UMessageBuilder.request(buildMethod(), buildSource(), 1000).build()); + void testBuilderCopiesRequestAttributes() { + var requestMessageId = UuidFactory.Factories.UPROTOCOL.factory().create(); + var responseMessageId = UuidFactory.Factories.UPROTOCOL.factory().create(); + var methodToInvoke = UURI_METHOD; + var replyToAddress = UURI_DEFAULT; + UMessage requestMessage = UMessageBuilder.request(replyToAddress, methodToInvoke, 5000) + .withMessageId(requestMessageId) + .withPriority(UPriority.UPRIORITY_CS5) + .build(); + UMessage message = UMessageBuilder.response(requestMessage.getAttributes()) + .withMessageId(responseMessageId) + .withCommStatus(UCode.DEADLINE_EXCEEDED) + .build(); + + assertEquals(responseMessageId, message.getAttributes().getId()); + assertEquals(UCode.DEADLINE_EXCEEDED, message.getAttributes().getCommstatus()); + assertEquals(UPriority.UPRIORITY_CS5, message.getAttributes().getPriority()); + assertEquals(requestMessageId, message.getAttributes().getReqid()); + assertEquals(methodToInvoke, message.getAttributes().getSource()); + assertEquals(replyToAddress, message.getAttributes().getSink()); + assertEquals(5000, message.getAttributes().getTtl()); + assertEquals(UMessageType.UMESSAGE_TYPE_RESPONSE, message.getAttributes().getType()); + assertEquals(UPayloadFormat.UPAYLOAD_FORMAT_UNSPECIFIED, message.getAttributes().getPayloadFormat()); + assertTrue(message.getPayload().isEmpty()); } - @Test - @DisplayName("Test request when the ttl is null") - public void testRequestWithNullTtl() { - assertThrows(NullPointerException.class, - () -> UMessageBuilder.request(buildSource(), buildMethod(), null).build()); - } - - - @Test - @DisplayName("Test request when ttl is negative") - public void testRequestWithNegativeTtl() { - assertThrows(IllegalArgumentException.class, - () -> UMessageBuilder.request(buildSource(), buildMethod(), -1).build()); - } - - - @Test - @DisplayName("Test response when source is not valid") - public void testResponseWithInvalidSource() { - assertThrows(IllegalArgumentException.class, - () -> UMessageBuilder.response(buildSink(), buildSink(), - UuidFactory.Factories.UPROTOCOL.factory().create()).build()); - } - - - @Test - @DisplayName("Test response when sink is not valid") - public void testResponseWithInvalidSink() { - assertThrows(IllegalArgumentException.class, - () -> UMessageBuilder.response(buildMethod(), buildMethod(), - UuidFactory.Factories.UPROTOCOL.factory().create()).build()); - } - - @Test - @DisplayName("Test response when source and sink are not valid") - public void testResponseWithInvalidSourceAndSink() { - assertThrows(IllegalArgumentException.class, - () -> UMessageBuilder.response(buildSource(), buildSource(), - UuidFactory.Factories.UPROTOCOL.factory().create()).build()); - } - - - @Test - @DisplayName("Test response when reqId is null") - public void testResponseWithNullReqId() { - assertThrows(NullPointerException.class, - () -> UMessageBuilder.response(buildMethod(), buildSink(), null).build()); - } - - - @Test - @DisplayName("Test response when we pass an invalid reqid") - public void testResponseWithInvalidReqId() { - assertThrows(IllegalArgumentException.class, - () -> UMessageBuilder.response(buildMethod(), buildSink(), UUID.getDefaultInstance()).build()); - } - - - @Test - @DisplayName("Test notification when source is not a valid topic and and sink is not valid") - public void testNotificationWithInvalidSourceAndSink() { - assertThrows(IllegalArgumentException.class, - () -> UMessageBuilder.notification(buildSink(), buildSource()).build()); - } - - - @Test - @DisplayName("Test response builder when we pass UAttributes that is not a valid request type") - public void testResponseBuilderWithInvalidRequestType() { - assertThrows(IllegalArgumentException.class, - () -> UMessageBuilder.response(UAttributes.getDefaultInstance()).build()); - } - - - private UUri buildSource() { - return UUri.newBuilder().setUeId(2).setUeVersionMajor(1).setResourceId(0).build(); - } - - private UUri buildTopic() { - return UUri.newBuilder().setUeId(2).setUeVersionMajor(1).setResourceId(0x8000).build(); - } - - private UUri buildMethod() { - return UUri.newBuilder().setUeId(2).setUeVersionMajor(1).setResourceId(1).build(); + void testBuildRetainsAllResponseAttributes() { + var messageId = UuidFactory.Factories.UPROTOCOL.factory().create(); + var requestId = UuidFactory.Factories.UPROTOCOL.factory().create(); + var traceparent = "traceparent"; + var methodToInvoke = UURI_METHOD; + var replyToAddress = UURI_DEFAULT; + UMessage message = UMessageBuilder.response(methodToInvoke, replyToAddress, requestId) + .withMessageId(messageId) + .withCommStatus(UCode.DEADLINE_EXCEEDED) + .withPriority(UPriority.UPRIORITY_CS5) + .withTtl(4000) + .withTraceparent(traceparent) + .build(); + + assertEquals(messageId, message.getAttributes().getId()); + assertEquals(UCode.DEADLINE_EXCEEDED, message.getAttributes().getCommstatus()); + assertEquals(UPriority.UPRIORITY_CS5, message.getAttributes().getPriority()); + assertEquals(requestId, message.getAttributes().getReqid()); + assertEquals(methodToInvoke, message.getAttributes().getSource()); + assertEquals(replyToAddress, message.getAttributes().getSink()); + assertEquals(4000, message.getAttributes().getTtl()); + assertEquals(traceparent, message.getAttributes().getTraceparent()); + assertEquals(UMessageType.UMESSAGE_TYPE_RESPONSE, message.getAttributes().getType()); + assertEquals(UPayloadFormat.UPAYLOAD_FORMAT_UNSPECIFIED, message.getAttributes().getPayloadFormat()); + assertTrue(message.getPayload().isEmpty()); + + var proto = message.toByteString(); + assertDoesNotThrow(() -> { + var deserializedMessage = UMessage.parseFrom(proto); + assertEquals(message, deserializedMessage); + }); } } diff --git a/src/test/java/org/eclipse/uprotocol/transport/validator/UAttributeValidatorTest.java b/src/test/java/org/eclipse/uprotocol/transport/validator/UAttributeValidatorTest.java deleted file mode 100644 index c35fb49c..00000000 --- a/src/test/java/org/eclipse/uprotocol/transport/validator/UAttributeValidatorTest.java +++ /dev/null @@ -1,562 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2024 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.eclipse.uprotocol.transport.validator; - -import static org.junit.Assert.assertThrows; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.eclipse.uprotocol.v1.UAttributes; -import org.eclipse.uprotocol.v1.UMessage; -import org.eclipse.uprotocol.v1.UMessageType; -import org.eclipse.uprotocol.v1.UPriority; -import org.eclipse.uprotocol.v1.UUID; -import org.eclipse.uprotocol.transport.builder.UMessageBuilder; -import org.eclipse.uprotocol.transport.validate.UAttributesValidator; -import org.eclipse.uprotocol.uuid.factory.UuidFactory; -import org.eclipse.uprotocol.v1.UUri; -import org.eclipse.uprotocol.validation.ValidationException; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -public class UAttributeValidatorTest { - - private UUri buildDefaultUUri() { - return UUri.newBuilder().setUeId(1).setUeVersionMajor(1).setResourceId(0).build(); - } - - private UUri buildMethodUUri() { - return UUri.newBuilder().setUeId(1).setUeVersionMajor(1).setResourceId(1).build(); - } - - private UUri buildTopicUUri() { - return UUri.newBuilder().setUeId(1).setUeVersionMajor(1).setResourceId(0x8000).build(); - } - - @Test - @DisplayName(""" - Test creating a UMessage of type publish then validating it using \ - UAttributeValidator for the happy path - """) - public void testUAttributeValidatorHappyPath() { - UMessage message = UMessageBuilder.publish(buildTopicUUri()).build(); - - UAttributesValidator validator = UAttributesValidator.getValidator(message.getAttributes()); - validator.validate(message.getAttributes()); - assertEquals(validator.toString(), "UAttributesValidator.Publish"); - } - - @Test - @DisplayName("Test validation a notification message using UAttributeValidator") - public void testUAttributeValidatorNotification() { - UMessage message = UMessageBuilder.notification(buildTopicUUri(), buildDefaultUUri()).build(); - - UAttributesValidator validator = UAttributesValidator.getValidator(message.getAttributes()); - validator.validate(message.getAttributes()); - assertEquals(validator.toString(), "UAttributesValidator.Notification"); - } - - @Test - @DisplayName("Test validation a request message using UAttributeValidator") - public void testUAttributeValidatorRequest() { - UMessage message = UMessageBuilder.request(buildDefaultUUri(), buildMethodUUri(), 1000).build(); - - UAttributesValidator validator = UAttributesValidator.getValidator(message.getAttributes()); - validator.validate(message.getAttributes()); - assertEquals(validator.toString(), "UAttributesValidator.Request"); - } - - @Test - @DisplayName("Test validation a response message using UAttributeValidator") - public void testUAttributeValidatorResponse() { - UMessage request = UMessageBuilder.request(buildDefaultUUri(), buildMethodUUri(), 1000).build(); - UMessage response = UMessageBuilder.response( - buildMethodUUri(), - buildDefaultUUri(), - request.getAttributes().getId()) - .build(); - - UAttributesValidator validator = UAttributesValidator.getValidator(response.getAttributes()); - validator.validate(response.getAttributes()); - assertEquals(validator.toString(), "UAttributesValidator.Response"); - } - - @Test - @DisplayName("Test validation a response message using UAttributeValidator when passed request UAttributes") - public void testUAttributeValidatorResponseWithRequestAttributes() { - UMessage request = UMessageBuilder.request(buildDefaultUUri(), buildMethodUUri(), 1000).build(); - UMessage response = UMessageBuilder.response(request.getAttributes()).build(); - - UAttributesValidator validator = UAttributesValidator.getValidator(response.getAttributes()); - validator.validate(response.getAttributes()); - assertEquals(validator.toString(), "UAttributesValidator.Response"); - } - - @Test - @DisplayName("Test validation failed when using the publish validator to test request messages") - public void testUAttributeValidatorRequestWithPublishValidator() { - UMessage message = UMessageBuilder.request(buildDefaultUUri(), buildMethodUUri(), 1000).build(); - - UAttributesValidator validator = UAttributesValidator.Validators.PUBLISH.validator(); - ValidationException result = assertThrows( - ValidationException.class, - () -> validator.validate(message.getAttributes())); - assertEquals(validator.toString(), "UAttributesValidator.Publish"); - assertEquals( - result.getMessage(), - "Wrong Attribute Type [UMESSAGE_TYPE_REQUEST],Sink should not be present"); - } - - @Test - @DisplayName("Test validation failed when using the notification validator to test publish messages") - public void testUAttributeValidatorPublishWithNotificationValidator() { - UMessage message = UMessageBuilder.publish(buildTopicUUri()).build(); - - UAttributesValidator validator = UAttributesValidator.Validators.NOTIFICATION.validator(); - ValidationException result = assertThrows( - ValidationException.class, - () -> validator.validate(message.getAttributes())); - assertEquals(validator.toString(), "UAttributesValidator.Notification"); - assertEquals(result.getMessage(), "Wrong Attribute Type [UMESSAGE_TYPE_PUBLISH],Missing Sink"); - } - - @Test - @DisplayName("Test validation failed when using the request validator to test response messages") - public void testUAttributeValidatorResponseWithRequestValidator() { - UMessage request = UMessageBuilder.request(buildDefaultUUri(), buildMethodUUri(), 1000).build(); - UMessage response = UMessageBuilder.response(request.getAttributes()).build(); - - UAttributesValidator validator = UAttributesValidator.Validators.REQUEST.validator(); - ValidationException result = assertThrows( - ValidationException.class, - () -> validator.validate(response.getAttributes())); - assertEquals(validator.toString(), "UAttributesValidator.Request"); - assertEquals( - result.getMessage(), - """ - Wrong Attribute Type [UMESSAGE_TYPE_RESPONSE],Missing TTL,Invalid Sink Uri,Message should not have a reqid\ - """); - } - - @Test - @DisplayName("Test validation failed when using the response validator to test notification messages") - public void testUAttributeValidatorNotificationWithResponseValidator() { - UMessage message = UMessageBuilder.notification(buildTopicUUri(), buildDefaultUUri()).build(); - - UAttributesValidator validator = UAttributesValidator.Validators.RESPONSE.validator(); - ValidationException result = assertThrows( - ValidationException.class, - () -> validator.validate(message.getAttributes())); - assertEquals(validator.toString(), "UAttributesValidator.Response"); - assertEquals( - result.getMessage(), - """ - Wrong Attribute Type [UMESSAGE_TYPE_NOTIFICATION],Invalid UPriority [UPRIORITY_CS1],Missing correlationId\ - """); - } - - @Test - @DisplayName("Test validation of request message has an invalid sink attribute") - public void testUAttributeValidatorRequestMissingSink() { - UAttributes attributes = UAttributes.newBuilder() - .setSource(buildDefaultUUri()) - .setSink(buildDefaultUUri()) - .setId(UuidFactory.Factories.UPROTOCOL.factory().create()) - .setType(UMessageType.UMESSAGE_TYPE_REQUEST) - .setPriority(UPriority.UPRIORITY_CS4) - .setTtl(1000) - .build(); - - UAttributesValidator validator = UAttributesValidator.getValidator(attributes); - ValidationException result = assertThrows( - ValidationException.class, - () -> validator.validate(attributes)); - assertEquals(validator.toString(), "UAttributesValidator.Request"); - assertEquals(result.getMessage(), "Invalid Sink Uri"); - } - - @Test - @DisplayName("Test validation of request message that has a permission level that is less than 0") - public void testUAttributeValidatorRequestInvalidPermissionLevel() { - UMessage message = UMessageBuilder.request( - buildDefaultUUri(), - buildMethodUUri(), - 1000) - .withPermissionLevel(-1) - .build(); - - UAttributesValidator validator = UAttributesValidator.getValidator(message.getAttributes()); - ValidationException result = assertThrows( - ValidationException.class, - () -> validator.validate(message.getAttributes())); - assertEquals(validator.toString(), "UAttributesValidator.Request"); - assertEquals(result.getMessage(), "Invalid Permission Level"); - } - - @Test - @DisplayName("Test validation of request message that has a permission level that is greater than 0") - public void testUAttributeValidatorRequestValidPermissionLevel() { - UMessage message = UMessageBuilder.request( - buildDefaultUUri(), - buildMethodUUri(), - 1000) - .withPermissionLevel(1) - .build(); - - UAttributesValidator validator = UAttributesValidator.getValidator(message.getAttributes()); - validator.validate(message.getAttributes()); - assertFalse(validator.isExpired(message.getAttributes())); - assertEquals(validator.toString(), "UAttributesValidator.Request"); - } - - @Test - @DisplayName("Test validation of request message that has TTL that is less than 0") - public void testUAttributeValidatorRequestInvalidTTL() { - UMessage message = UMessageBuilder.publish(buildTopicUUri()).withTtl(-1).build(); - - UAttributesValidator validator = UAttributesValidator.getValidator(message.getAttributes()); - ValidationException result = assertThrows( - ValidationException.class, - () -> validator.validate(message.getAttributes())); - assertFalse(validator.isExpired(message.getAttributes())); - assertEquals(validator.toString(), "UAttributesValidator.Publish"); - assertEquals(result.getMessage(), "Invalid TTL [-1]"); - } - - @Test - @DisplayName("Test validation of request message where the message has expired") - public void testUAttributeValidatorRequestExpired() throws InterruptedException { - UMessage message = UMessageBuilder.request(buildDefaultUUri(), buildMethodUUri(), 1).build(); - Thread.sleep(100); - UAttributesValidator validator = UAttributesValidator.getValidator(message.getAttributes()); - assertTrue(validator.isExpired(message.getAttributes())); - } - - @Test - @DisplayName("Test validator isExpired() for an ID that is mall formed and doesn't have the time") - public void testUAttributeValidatorRequestExpiredMalformedId() { - UAttributes attributes = UAttributes.newBuilder() - .setId(UUID.getDefaultInstance()) - .setType(UMessageType.UMESSAGE_TYPE_REQUEST) - .build(); - UAttributesValidator validator = UAttributesValidator.getValidator(attributes); - assertFalse(validator.isExpired(attributes)); - } - - @Test - @DisplayName("Test validation fails when a publish messages has a reqid") - public void testUAttributeValidatorPublishWithReqId() { - UMessage publish = UMessageBuilder.publish(buildTopicUUri()).build(); - UAttributes attributes = UAttributes.newBuilder() - .mergeFrom(publish.getAttributes()) - .setReqid(UUID.getDefaultInstance()) - .build(); - - UAttributesValidator validator = UAttributesValidator.getValidator(attributes); - ValidationException result = assertThrows( - ValidationException.class, - () -> validator.validate(attributes)); - assertEquals(validator.toString(), "UAttributesValidator.Publish"); - assertEquals(result.getMessage(), "Message should not have a reqid"); - } - - @Test - @DisplayName("Test notification validation where the sink is missing") - public void testUAttributeValidatorNotificationMissingSink() { - final UMessage message = UMessageBuilder.notification(buildTopicUUri(), buildDefaultUUri()).build(); - final UAttributes attributes = UAttributes.newBuilder().mergeFrom(message.getAttributes()).clearSink().build(); - final UAttributesValidator validator = UAttributesValidator.getValidator(attributes); - final ValidationException result = assertThrows( - ValidationException.class, - () -> validator.validate(attributes)); - - assertEquals(validator.toString(), "UAttributesValidator.Notification"); - assertEquals(result.getMessage(), "Missing Sink"); - } - - @Test - @DisplayName("Test notification validation where the sink the default instance") - public void testUAttributeValidatorNotificationDefaultSink() { - final UMessage message = UMessageBuilder.notification(buildTopicUUri(), buildDefaultUUri()).build(); - final UAttributes attributes = UAttributes.newBuilder() - .mergeFrom(message.getAttributes()) - .setSink(UUri.getDefaultInstance()) - .build(); - final UAttributesValidator validator = UAttributesValidator.getValidator(attributes); - final ValidationException result = assertThrows( - ValidationException.class, - () -> validator.validate(attributes)); - - assertEquals(validator.toString(), "UAttributesValidator.Notification"); - assertEquals(result.getMessage(), "Missing Sink"); - } - - @Test - @DisplayName("Test notification validation where the sink is NOT the defaultResourceId") - public void testUAttributeValidatorNotificationDefaultResourceId() { - final UAttributes attributes = UAttributes.newBuilder() - .setSource(buildTopicUUri()) - .setId(UuidFactory.Factories.UPROTOCOL.factory().create()) - .setSink(buildMethodUUri()) - .setType(UMessageType.UMESSAGE_TYPE_NOTIFICATION) - .setPriority(UPriority.UPRIORITY_CS1) - .build(); - final UAttributesValidator validator = UAttributesValidator.getValidator(attributes); - final ValidationException result = assertThrows( - ValidationException.class, - () -> validator.validate(attributes)); - - assertEquals(validator.toString(), "UAttributesValidator.Notification"); - assertEquals(result.getMessage(), "Invalid Sink Uri"); - } - - @Test - @DisplayName("Test validatePriority when priority is less than CS0") - public void testUAttributeValidatorValidatePriorityLessThanCS0() { - final UMessage message = UMessageBuilder.publish(buildTopicUUri()).build(); - final UAttributes attributes = UAttributes.newBuilder() - .mergeFrom(message.getAttributes()) - .setPriority(UPriority.UPRIORITY_UNSPECIFIED) - .build(); - final UAttributesValidator validator = UAttributesValidator.getValidator(attributes); - final ValidationException result = assertThrows( - ValidationException.class, - () -> validator.validate(attributes)); - - assertEquals(validator.toString(), "UAttributesValidator.Publish"); - assertEquals(result.getMessage(), "Invalid UPriority [UPRIORITY_UNSPECIFIED]"); - } - - @Test - @DisplayName("Test validatePriority when priority CS0") - public void testUAttributeValidatorValidatePriorityIsCS0() { - final UMessage message = UMessageBuilder.publish(buildTopicUUri()).build(); - final UAttributes attributes = UAttributes.newBuilder(). - mergeFrom(message.getAttributes()).setPriority(UPriority.UPRIORITY_CS0).build(); - final UAttributesValidator validator = UAttributesValidator.getValidator(attributes); - final ValidationException result = assertThrows( - ValidationException.class, - () -> validator.validate(attributes)); - - assertEquals(validator.toString(), "UAttributesValidator.Publish"); - assertEquals(result.getMessage(), "Invalid UPriority [UPRIORITY_CS0]"); - } - - @Test - @DisplayName("Test validateId when id is missing") - public void testUAttributeValidatorValidateIdMissing() { - final UMessage message = UMessageBuilder.publish(buildTopicUUri()).build(); - final UAttributes attributes = UAttributes.newBuilder().mergeFrom(message.getAttributes()).clearId().build(); - final UAttributesValidator validator = UAttributesValidator.getValidator(attributes); - final ValidationException result = assertThrows( - ValidationException.class, - () -> validator.validate(attributes)); - - assertEquals(validator.toString(), "UAttributesValidator.Publish"); - assertEquals(result.getMessage(), "Missing id"); - } - - @Test - @DisplayName("Test validateId when id is the default instance") - public void testUAttributeValidatorValidateIdDefault() { - final UMessage message = UMessageBuilder.publish(buildTopicUUri()).build(); - final UAttributes attributes = UAttributes.newBuilder() - .mergeFrom(message.getAttributes()) - .setId(UUID.getDefaultInstance()) - .build(); - final UAttributesValidator validator = UAttributesValidator.getValidator(attributes); - final ValidationException result = assertThrows( - ValidationException.class, - () -> validator.validate(attributes)); - - assertEquals(validator.toString(), "UAttributesValidator.Publish"); - assertEquals(result.getMessage(), "Attributes must contain valid uProtocol UUID in id property"); - } - - @Test - @DisplayName("Test publish validateSink when sink is not empty") - public void testUAttributeValidatorValidateSinkNotEmpty() { - final UMessage message = UMessageBuilder.publish(buildTopicUUri()).build(); - final UAttributes attributes = UAttributes.newBuilder() - .mergeFrom(message.getAttributes()) - .setSink(buildDefaultUUri()) - .build(); - final UAttributesValidator validator = UAttributesValidator.getValidator(attributes); - final ValidationException result = assertThrows( - ValidationException.class, - () -> validator.validate(attributes)); - - assertEquals(validator.toString(), "UAttributesValidator.Publish"); - assertEquals(result.getMessage(), "Sink should not be present"); - } - - @Test - @DisplayName("Test validateSink of a request message that is missing a sink") - public void testUAttributeValidatorValidateSinkMissing() { - final UMessage message = UMessageBuilder.request(buildDefaultUUri(), buildMethodUUri(), 1000).build(); - final UAttributes attributes = UAttributes.newBuilder().mergeFrom(message.getAttributes()).clearSink().build(); - final UAttributesValidator validator = UAttributesValidator.getValidator(attributes); - final ValidationException result = assertThrows( - ValidationException.class, - () -> validator.validate(attributes)); - - assertEquals(validator.toString(), "UAttributesValidator.Request"); - assertEquals(result.getMessage(), "Missing Sink"); - } - - @Test - @DisplayName("Test validateTtl of a request message where ttl is less than 0") - public void testUAttributeValidatorValidateTtlLessThanZero() { - final UAttributes attributes = UAttributes.newBuilder() - .setSource(buildDefaultUUri()) - .setSink(buildMethodUUri()) - .setTtl(-1) - .setId(UuidFactory.Factories.UPROTOCOL.factory().create()) - .setType(UMessageType.UMESSAGE_TYPE_REQUEST) - .setPriority(UPriority.UPRIORITY_CS4).build(); - final UAttributesValidator validator = UAttributesValidator.getValidator(attributes); - final ValidationException result = assertThrows( - ValidationException.class, - () -> validator.validate(attributes)); - - assertEquals(validator.toString(), "UAttributesValidator.Request"); - assertEquals(result.getMessage(), "Invalid TTL [-1]"); - } - - @Test - @DisplayName("Test validatePriority of a request message where priority is less than CS4") - public void testUAttributeValidatorValidatePriorityLessThanCS4() { - final UMessage message = UMessageBuilder.request(buildDefaultUUri(), buildMethodUUri(), 1000).build(); - final UAttributes attributes = UAttributes.newBuilder() - .mergeFrom(message.getAttributes()) - .setPriority(UPriority.UPRIORITY_CS3) - .build(); - final UAttributesValidator validator = UAttributesValidator.getValidator(attributes); - final ValidationException result = assertThrows( - ValidationException.class, - () -> validator.validate(attributes)); - - assertEquals(validator.toString(), "UAttributesValidator.Request"); - assertEquals(result.getMessage(), "Invalid UPriority [UPRIORITY_CS3]"); - } - - @Test - @DisplayName("Test validateSink for a response message where the sink is missing") - public void testUAttributeValidatorValidateSinkResponseMissing() { - final UMessage request = UMessageBuilder.request(buildDefaultUUri(), buildMethodUUri(), 1000).build(); - final UMessage response = UMessageBuilder.response(request.getAttributes()).build(); - final UAttributes attributes = UAttributes.newBuilder().mergeFrom(response.getAttributes()).clearSink().build(); - final UAttributesValidator validator = UAttributesValidator.getValidator(attributes); - final ValidationException result = assertThrows( - ValidationException.class, - () -> validator.validate(attributes)); - - assertEquals(validator.toString(), "UAttributesValidator.Response"); - assertEquals(result.getMessage(), "Missing Sink"); - } - - @Test - @DisplayName("Test validateSink for a response message where the sink is the default instance") - public void testUAttributeValidatorValidateSinkResponseDefault() { - final UMessage request = UMessageBuilder.request(buildDefaultUUri(), buildMethodUUri(), 1000).build(); - final UMessage response = UMessageBuilder.response(request.getAttributes()).build(); - final UAttributes attributes = UAttributes.newBuilder() - .mergeFrom(response.getAttributes()) - .setSink(UUri.getDefaultInstance()) - .build(); - final UAttributesValidator validator = UAttributesValidator.getValidator(attributes); - final ValidationException result = assertThrows( - ValidationException.class, - () -> validator.validate(attributes)); - - assertEquals(validator.toString(), "UAttributesValidator.Response"); - assertEquals(result.getMessage(), "Missing Sink"); - } - - @Test - @DisplayName("Test validateSink for a response message where the sink is NOT the defaultResourceId") - public void testUAttributeValidatorValidateSinkResponseDefaultResourceId() { - final UAttributes attributes = UAttributes.newBuilder() - .setSource(buildMethodUUri()) - .setSink(buildMethodUUri()) - .setTtl(1000) - .setType(UMessageType.UMESSAGE_TYPE_RESPONSE) - .setPriority(UPriority.UPRIORITY_CS4) - .setId(UuidFactory.Factories.UPROTOCOL.factory().create()) - .setReqid(UuidFactory.Factories.UPROTOCOL.factory().create()) - .build(); - - final UAttributesValidator validator = UAttributesValidator.getValidator(attributes); - final ValidationException result = assertThrows( - ValidationException.class, - () -> validator.validate(attributes)); - - assertEquals(validator.toString(), "UAttributesValidator.Response"); - assertEquals(result.getMessage(), "Invalid Sink Uri"); - } - - @Test - @DisplayName("Test validateReqId for a response message when the reqid is missing") - public void testUAttributeValidatorValidateReqIdMissing() { - final UMessage request = UMessageBuilder.request(buildDefaultUUri(), buildMethodUUri(), 1000).build(); - final UMessage response = UMessageBuilder.response(request.getAttributes()).build(); - final UAttributes attributes = UAttributes.newBuilder() - .mergeFrom(response.getAttributes()) - .clearReqid() - .build(); - final UAttributesValidator validator = UAttributesValidator.getValidator(attributes); - final ValidationException result = assertThrows( - ValidationException.class, - () -> validator.validate(attributes)); - - assertEquals(validator.toString(), "UAttributesValidator.Response"); - assertEquals(result.getMessage(), "Missing correlationId"); - } - - @Test - @DisplayName("Test validateReqId for a response message when the reqid is the default instance") - public void testUAttributeValidatorValidateReqIdDefault() { - final UMessage request = UMessageBuilder.request(buildDefaultUUri(), buildMethodUUri(), 1000).build(); - final UMessage response = UMessageBuilder.response(request.getAttributes()).build(); - final UAttributes attributes = UAttributes.newBuilder() - .mergeFrom(response.getAttributes()) - .setReqid(UUID.getDefaultInstance()) - .build(); - final UAttributesValidator validator = UAttributesValidator.getValidator(attributes); - final ValidationException result = assertThrows( - ValidationException.class, - () -> validator.validate(attributes)); - - assertEquals(validator.toString(), "UAttributesValidator.Response"); - assertEquals(result.getMessage(), "Missing correlationId"); - } - - @Test - @DisplayName("Test validateReqId for a response message when the reqid not a valid uprotocol UUID") - public void testUAttributeValidatorValidateReqIdInvalid() { - final UMessage request = UMessageBuilder.request(buildDefaultUUri(), buildMethodUUri(), 1000).build(); - final UMessage response = UMessageBuilder.response(request.getAttributes()).build(); - final UAttributes attributes = UAttributes.newBuilder() - .mergeFrom(response.getAttributes()) - .setReqid(UUID.newBuilder().setLsb(0xbeadbeef).setMsb(0xdeadbeef)) - .build(); - final UAttributesValidator validator = UAttributesValidator.getValidator(attributes); - final ValidationException result = assertThrows( - ValidationException.class, - () -> validator.validate(attributes)); - - assertEquals(validator.toString(), "UAttributesValidator.Response"); - assertEquals(result.getMessage(), "Invalid correlation UUID"); - } -} diff --git a/src/test/java/org/eclipse/uprotocol/transport/validator/UAttributesValidatorTest.java b/src/test/java/org/eclipse/uprotocol/transport/validator/UAttributesValidatorTest.java new file mode 100644 index 00000000..9401d9dd --- /dev/null +++ b/src/test/java/org/eclipse/uprotocol/transport/validator/UAttributesValidatorTest.java @@ -0,0 +1,791 @@ +/** + * SPDX-FileCopyrightText: 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.eclipse.uprotocol.transport.validator; + +import static org.junit.Assert.assertThrows; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Optional; +import java.util.OptionalInt; +import java.util.stream.Stream; + +import org.eclipse.uprotocol.v1.UAttributes; +import org.eclipse.uprotocol.v1.UCode; +import org.eclipse.uprotocol.v1.UMessageType; +import org.eclipse.uprotocol.v1.UPriority; +import org.eclipse.uprotocol.v1.UUID; +import org.eclipse.uprotocol.uuid.factory.UuidFactory; +import org.eclipse.uprotocol.v1.UUri; +import org.eclipse.uprotocol.validation.ValidationException; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.MethodSource; + +public class UAttributesValidatorTest { + + private static final UUri UURI_DEFAULT = UUri.newBuilder().setUeId(1).setUeVersionMajor(1).setResourceId(0).build(); + + private static final UUri UURI_METHOD = UUri.newBuilder() + .setUeId(0x001a_1a5b) + .setUeVersionMajor(0x04) + .setResourceId(0x7a5f) + .build(); + + private static final UUri UURI_TOPIC = UUri.newBuilder() + .setUeId(1) + .setUeVersionMajor(1) + .setResourceId(0x8000) + .build(); + + private static final UUri UURI_WILDCARD_RESOURCE = UUri.newBuilder() + .setUeId(1) + .setUeVersionMajor(1) + .setResourceId(0xFFFF) + .build(); + + private static final UUID UUID_INVALID = UUID.newBuilder() + .setMsb(0x000000000001C000) + .setLsb(0x0000000000000000) + .build(); + + @Test + void testValidateTypeFailsForUnexpectedTypeCode() { + UAttributes attributes = UAttributes.newBuilder() + .setType(UMessageType.UMESSAGE_TYPE_UNSPECIFIED) + .build(); + assertThrows( + ValidationException.class, + () -> UAttributesValidator.Validators.PUBLISH.validator().validate(attributes)); + assertThrows( + ValidationException.class, + () -> UAttributesValidator.Validators.NOTIFICATION.validator().validate(attributes)); + assertThrows( + ValidationException.class, + () -> UAttributesValidator.Validators.REQUEST.validator().validate(attributes)); + assertThrows( + ValidationException.class, + () -> UAttributesValidator.Validators.RESPONSE.validator().validate(attributes)); + } + + @ParameterizedTest + @CsvSource(useHeadersInDisplayName = true, textBlock = """ + messageType, expectedValidatorType + # succeeds for Unspecified message + UMESSAGE_TYPE_UNSPECIFIED, UMESSAGE_TYPE_PUBLISH + # succeeds for Publish message + UMESSAGE_TYPE_PUBLISH, UMESSAGE_TYPE_PUBLISH + # succeeds for Notification message + UMESSAGE_TYPE_NOTIFICATION, UMESSAGE_TYPE_NOTIFICATION + # succeeds for Request message + UMESSAGE_TYPE_REQUEST, UMESSAGE_TYPE_REQUEST + # succeeds for Response message + UMESSAGE_TYPE_RESPONSE, UMESSAGE_TYPE_RESPONSE + """) + void testGetValidatorReturnsMatchingValidator( + UMessageType messageType, + UMessageType expectedValidatorType + ) { + var validator = UAttributesValidator.getValidator(messageType); + assertEquals(expectedValidatorType, validator.messageType()); + } + + static Stream publishMessageArgProvider() { + return Stream.of( + Arguments.of( + Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UURI_TOPIC), + Optional.empty(), + OptionalInt.empty(), + true + ), + // fails for message containing destination + Arguments.of( + Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UURI_TOPIC), + Optional.of(UURI_DEFAULT), + OptionalInt.empty(), + false + ), + // succeeds for valid attributes + Arguments.of( + Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UURI_TOPIC), + Optional.empty(), + OptionalInt.of(100), + true + ), + // fails for missing topic + Arguments.of( + Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.empty(), + Optional.empty(), + OptionalInt.empty(), + false + ), + // fails for invalid topic + Arguments.of( + Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UURI_METHOD), + Optional.empty(), + OptionalInt.empty(), + false + ), + // fails for source with wildcard + Arguments.of( + Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UURI_WILDCARD_RESOURCE), + Optional.empty(), + OptionalInt.empty(), + false + ), + // fails for missing message ID + Arguments.of( + Optional.empty(), + Optional.of(UURI_TOPIC), + Optional.empty(), + OptionalInt.empty(), + false + ), + // fails for invalid message ID + Arguments.of( + Optional.of(UUID_INVALID), + Optional.of(UURI_TOPIC), + Optional.empty(), + OptionalInt.empty(), + false + ), + // fails for invalid (negative) TTL + Arguments.of( + Optional.of(UUID_INVALID), + Optional.of(UURI_TOPIC), + Optional.empty(), + OptionalInt.of(-1), + false + )); + } + @ParameterizedTest + @MethodSource("publishMessageArgProvider") + void testValidateAttributesForPublishMessage( + Optional id, + Optional source, + Optional sink, + OptionalInt ttl, + boolean shouldSucceed + ) { + var attribsBuilder = UAttributes.newBuilder(); + attribsBuilder.setType(UMessageType.UMESSAGE_TYPE_PUBLISH); + id.ifPresent(attribsBuilder::setId); + source.ifPresent(attribsBuilder::setSource); + sink.ifPresent(attribsBuilder::setSink); + ttl.ifPresent(attribsBuilder::setTtl); + var attribs = attribsBuilder.build(); + if (shouldSucceed) { + UAttributesValidator.Validators.PUBLISH.validator().validate(attribs); + assertThrows( + ValidationException.class, + () -> UAttributesValidator.Validators.NOTIFICATION.validator().validate(attribs) + ); + assertThrows( + ValidationException.class, + () -> UAttributesValidator.Validators.REQUEST.validator().validate(attribs) + ); + assertThrows( + ValidationException.class, + () -> UAttributesValidator.Validators.RESPONSE.validator().validate(attribs) + ); + } else { + assertThrows( + ValidationException.class, + () -> UAttributesValidator.Validators.PUBLISH.validator().validate(attribs)); + } + } + + static Stream notificationMessageArgProvider() { + return Stream.of( + // succeeds for both origin and destination + Arguments.of( + Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UURI_TOPIC), + Optional.of(UURI_DEFAULT), + OptionalInt.empty(), + OptionalInt.empty(), + true + ), + // succeeds for valid attributes + Arguments.of( + Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UURI_TOPIC), + Optional.of(UURI_DEFAULT), + OptionalInt.of(100), + OptionalInt.of(UPriority.UPRIORITY_CS1_VALUE), + true + ), + // fails for missing destination + Arguments.of( + Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UURI_TOPIC), + Optional.empty(), + OptionalInt.empty(), + OptionalInt.empty(), + false + ), + // fails for missing origin + Arguments.of( + Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.empty(), + Optional.of(UURI_DEFAULT), + OptionalInt.empty(), + OptionalInt.empty(), + false + ), + // fails for invalid origin + Arguments.of( + Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UURI_METHOD), + Optional.of(UURI_DEFAULT), + OptionalInt.empty(), + OptionalInt.empty(), + false + ), + // fails for origin with wildcard + Arguments.of( + Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UURI_WILDCARD_RESOURCE), + Optional.of(UURI_DEFAULT), + OptionalInt.empty(), + OptionalInt.empty(), + false + ), + // fails for invalid destination + Arguments.of( + Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UURI_TOPIC), + Optional.of(UURI_METHOD), + OptionalInt.empty(), + OptionalInt.empty(), + false + ), + // fails for destination with wildcard + Arguments.of( + Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UURI_TOPIC), + Optional.of(UURI_WILDCARD_RESOURCE), + OptionalInt.empty(), + OptionalInt.empty(), + false + ), + // fails for neither origin nor destination + Arguments.of( + Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.empty(), + Optional.empty(), + OptionalInt.empty(), + OptionalInt.empty(), + false + ), + // fails for unknown priority + Arguments.of( + Optional.empty(), + Optional.of(UURI_TOPIC), + Optional.of(UURI_DEFAULT), + OptionalInt.empty(), + OptionalInt.of(20), + false + ), + // fails for missing message ID + Arguments.of( + Optional.empty(), + Optional.of(UURI_TOPIC), + Optional.of(UURI_DEFAULT), + OptionalInt.empty(), + OptionalInt.empty(), + false + ), + // fails for invalid message ID + Arguments.of( + Optional.of(UUID_INVALID), + Optional.of(UURI_TOPIC), + Optional.of(UURI_DEFAULT), + OptionalInt.empty(), + OptionalInt.empty(), + false + )); + } + + @ParameterizedTest + @MethodSource("notificationMessageArgProvider") + void testValidateAttributesForNotificationMessage( + Optional id, + Optional source, + Optional sink, + OptionalInt ttl, + OptionalInt priority, + boolean shouldSucceed + ) { + var attribsBuilder = UAttributes.newBuilder(); + attribsBuilder.setType(UMessageType.UMESSAGE_TYPE_NOTIFICATION); + id.ifPresent(attribsBuilder::setId); + source.ifPresent(attribsBuilder::setSource); + sink.ifPresent(attribsBuilder::setSink); + ttl.ifPresent(attribsBuilder::setTtl); + priority.ifPresent(attribsBuilder::setPriorityValue); + var attribs = attribsBuilder.build(); + if (shouldSucceed) { + UAttributesValidator.Validators.NOTIFICATION.validator().validate(attribs); + assertThrows( + ValidationException.class, + () -> UAttributesValidator.Validators.PUBLISH.validator().validate(attribs) + ); + assertThrows( + ValidationException.class, + () -> UAttributesValidator.Validators.REQUEST.validator().validate(attribs) + ); + assertThrows( + ValidationException.class, + () -> UAttributesValidator.Validators.RESPONSE.validator().validate(attribs) + ); + } else { + assertThrows( + ValidationException.class, + () -> UAttributesValidator.Validators.NOTIFICATION.validator().validate(attribs)); + } + } + + static Stream requestMessageArgProvider() { + return Stream.of( + // succeeds for mandatory attributes + Arguments.of( + Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UURI_METHOD), + Optional.of(UURI_DEFAULT), + OptionalInt.empty(), + OptionalInt.of(2000), + OptionalInt.of(UPriority.UPRIORITY_CS4_VALUE), + Optional.empty(), + true), + // succeeds for valid attributes + Arguments.of( + Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UURI_METHOD), + Optional.of(UURI_DEFAULT), + OptionalInt.of(1), + OptionalInt.of(2000), + OptionalInt.of(UPriority.UPRIORITY_CS4_VALUE), + Optional.of("mytoken"), + true), + // fails for missing message ID + Arguments.of( + Optional.empty(), + Optional.of(UURI_METHOD), + Optional.of(UURI_DEFAULT), + OptionalInt.of(1), + OptionalInt.of(2000), + OptionalInt.of(UPriority.UPRIORITY_CS4_VALUE), + Optional.of("mytoken"), + false), + // fails for invalid message ID + Arguments.of( + Optional.of(UUID_INVALID), + Optional.of(UURI_METHOD), + Optional.of(UURI_DEFAULT), + OptionalInt.empty(), + OptionalInt.of(2000), + OptionalInt.of(UPriority.UPRIORITY_CS4_VALUE), + Optional.empty(), + false), + // fails for missing reply-to-address + Arguments.of( + Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UURI_METHOD), + Optional.empty(), + OptionalInt.empty(), + OptionalInt.of(2000), + OptionalInt.of(UPriority.UPRIORITY_CS4_VALUE), + Optional.empty(), + false), + // fails for invalid reply-to-address + Arguments.of( + Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UURI_METHOD), + Optional.of(UURI_TOPIC), + OptionalInt.empty(), + OptionalInt.of(2000), + OptionalInt.of(UPriority.UPRIORITY_CS4_VALUE), + Optional.empty(), + false), + // fails for reply-to-address with wildcard + Arguments.of( + Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UURI_METHOD), + Optional.of(UURI_WILDCARD_RESOURCE), + OptionalInt.empty(), + OptionalInt.of(2000), + OptionalInt.of(UPriority.UPRIORITY_CS4_VALUE), + Optional.empty(), + false), + // fails for missing method-to-invoke + Arguments.of( + Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.empty(), + Optional.of(UURI_DEFAULT), + OptionalInt.empty(), + OptionalInt.of(2000), + OptionalInt.of(UPriority.UPRIORITY_CS4_VALUE), + Optional.empty(), + false), + // fails for invalid method-to-invoke + Arguments.of( + Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UURI_TOPIC), + Optional.of(UURI_DEFAULT), + OptionalInt.empty(), + OptionalInt.of(2000), + OptionalInt.of(UPriority.UPRIORITY_CS4_VALUE), + Optional.empty(), + false), + // fails for method-to-invoke with wildcard + Arguments.of( + Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UURI_WILDCARD_RESOURCE), + Optional.of(UURI_DEFAULT), + OptionalInt.empty(), + OptionalInt.of(2000), + OptionalInt.of(UPriority.UPRIORITY_CS4_VALUE), + Optional.empty(), + false), + // fails for missing priority + Arguments.of( + Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UURI_METHOD), + Optional.of(UURI_DEFAULT), + OptionalInt.of(1), + OptionalInt.of(2000), + OptionalInt.empty(), + Optional.empty(), + false), + // fails for invalid priority + Arguments.of( + Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UURI_METHOD), + Optional.of(UURI_DEFAULT), + OptionalInt.of(1), + OptionalInt.of(2000), + OptionalInt.of(UPriority.UPRIORITY_CS3_VALUE), + Optional.empty(), + false), + // fails for unknown priority + Arguments.of( + Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UURI_METHOD), + Optional.of(UURI_DEFAULT), + OptionalInt.of(1), + OptionalInt.of(2000), + OptionalInt.of(20), + Optional.empty(), + false), + // fails for missing ttl + Arguments.of( + Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UURI_METHOD), + Optional.of(UURI_DEFAULT), + OptionalInt.empty(), + OptionalInt.empty(), + OptionalInt.of(UPriority.UPRIORITY_CS4_VALUE), + Optional.empty(), + false), + // fails for ttl = 0 + Arguments.of( + Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UURI_METHOD), + Optional.of(UURI_DEFAULT), + OptionalInt.empty(), + OptionalInt.of(0), + OptionalInt.of(UPriority.UPRIORITY_CS4_VALUE), + Optional.empty(), + false), + // fails for invalid (negative) permission level + Arguments.of( + Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UURI_METHOD), + Optional.of(UURI_DEFAULT), + OptionalInt.of(-1), + OptionalInt.of(2000), + OptionalInt.of(UPriority.UPRIORITY_CS4_VALUE), + Optional.empty(), + false) + ); + } + + @ParameterizedTest + @MethodSource("requestMessageArgProvider") + void testValidateAttributesForRpcRequestMessage( + Optional id, + Optional methodToInvoke, + Optional replyToAddress, + OptionalInt permLevel, + OptionalInt ttl, + OptionalInt priority, + Optional token, + boolean shouldSucceed + ) { + var attribsBuilder = UAttributes.newBuilder(); + attribsBuilder.setType(UMessageType.UMESSAGE_TYPE_REQUEST); + id.ifPresent(attribsBuilder::setId); + methodToInvoke.ifPresent(attribsBuilder::setSink); + replyToAddress.ifPresent(attribsBuilder::setSource); + permLevel.ifPresent(attribsBuilder::setPermissionLevel); + ttl.ifPresent(attribsBuilder::setTtl); + priority.ifPresent(attribsBuilder::setPriorityValue); + token.ifPresent(attribsBuilder::setToken); + var attribs = attribsBuilder.build(); + if (shouldSucceed) { + UAttributesValidator.Validators.REQUEST.validator().validate(attribs); + assertThrows( + ValidationException.class, + () -> UAttributesValidator.Validators.PUBLISH.validator().validate(attribs) + ); + assertThrows( + ValidationException.class, + () -> UAttributesValidator.Validators.NOTIFICATION.validator().validate(attribs) + ); + assertThrows( + ValidationException.class, + () -> UAttributesValidator.Validators.RESPONSE.validator().validate(attribs) + ); + } else { + assertThrows( + ValidationException.class, + () -> UAttributesValidator.Validators.REQUEST.validator().validate(attribs)); + } + } + + static Stream responseMessageArgProvider() { + return Stream.of( + // succeeds for mandatory attributes + Arguments.of( + Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UURI_DEFAULT), + Optional.of(UURI_METHOD), + Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + OptionalInt.empty(), + OptionalInt.empty(), + Optional.of(UPriority.UPRIORITY_CS4), + true), + // succeeds for valid attributes + Arguments.of( + Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UURI_DEFAULT), + Optional.of(UURI_METHOD), + Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + OptionalInt.of(UCode.CANCELLED_VALUE), + OptionalInt.of(100), + Optional.of(UPriority.UPRIORITY_CS4), + true), + // fails for missing message ID + Arguments.of( + Optional.empty(), + Optional.of(UURI_DEFAULT), + Optional.of(UURI_METHOD), + Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + OptionalInt.of(UCode.CANCELLED_VALUE), + OptionalInt.of(100), + Optional.of(UPriority.UPRIORITY_CS4), + false), + // fails for invalid message ID + Arguments.of( + Optional.of(UUID_INVALID), + Optional.of(UURI_DEFAULT), + Optional.of(UURI_METHOD), + Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + OptionalInt.empty(), + OptionalInt.empty(), + Optional.of(UPriority.UPRIORITY_CS4), + false), + // fails for missing reply-to-address + Arguments.of( + Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.empty(), + Optional.of(UURI_METHOD), + Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + OptionalInt.empty(), + OptionalInt.empty(), + Optional.of(UPriority.UPRIORITY_CS4), + false), + // fails for invalid reply-to-address + Arguments.of( + Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UURI_TOPIC), + Optional.of(UURI_METHOD), + Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + OptionalInt.empty(), + OptionalInt.empty(), + Optional.of(UPriority.UPRIORITY_CS4), + false), + // fails for reply-to-address with wildcard + Arguments.of( + Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UURI_WILDCARD_RESOURCE), + Optional.of(UURI_METHOD), + Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + OptionalInt.empty(), + OptionalInt.empty(), + Optional.of(UPriority.UPRIORITY_CS4), + false), + // fails for missing invoked-method + Arguments.of( + Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UURI_DEFAULT), + Optional.empty(), + Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + OptionalInt.empty(), + OptionalInt.empty(), + Optional.of(UPriority.UPRIORITY_CS4), + false), + // fails for invalid invoked-method + Arguments.of( + Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UURI_DEFAULT), + Optional.of(UURI_TOPIC), + Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + OptionalInt.empty(), + OptionalInt.empty(), + Optional.of(UPriority.UPRIORITY_CS4), + false), + // fails for invoked-method with wildcard + Arguments.of( + Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UURI_DEFAULT), + Optional.of(UURI_WILDCARD_RESOURCE), + Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + OptionalInt.empty(), + OptionalInt.empty(), + Optional.of(UPriority.UPRIORITY_CS4), + false), + // fails for invalid commstatus + Arguments.of( + Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UURI_DEFAULT), + Optional.of(UURI_METHOD), + Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + OptionalInt.of(-189), + OptionalInt.empty(), + Optional.of(UPriority.UPRIORITY_CS4), + false), + // succeeds for ttl > 0 + Arguments.of( + Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UURI_DEFAULT), + Optional.of(UURI_METHOD), + Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + OptionalInt.empty(), + OptionalInt.of(100), + Optional.of(UPriority.UPRIORITY_CS4), + true), + // succeeds for ttl = 0 + Arguments.of( + Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UURI_DEFAULT), + Optional.of(UURI_METHOD), + Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + OptionalInt.empty(), + OptionalInt.of(0), + Optional.of(UPriority.UPRIORITY_CS4), + true), + // fails for missing priority + Arguments.of( + Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UURI_DEFAULT), + Optional.of(UURI_METHOD), + Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + OptionalInt.of(UCode.CANCELLED_VALUE), + OptionalInt.of(100), + Optional.empty(), + false), + // fails for invalid priority + Arguments.of( + Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UURI_DEFAULT), + Optional.of(UURI_METHOD), + Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + OptionalInt.of(UCode.CANCELLED_VALUE), + OptionalInt.of(100), + Optional.of(UPriority.UPRIORITY_CS3), + false), + // fails for missing request ID + Arguments.of( + Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UURI_DEFAULT), + Optional.of(UURI_METHOD), + Optional.empty(), + OptionalInt.empty(), + OptionalInt.empty(), + Optional.of(UPriority.UPRIORITY_CS4), + false), + // fails for invalid request ID + Arguments.of( + Optional.of(UuidFactory.Factories.UPROTOCOL.factory().create()), + Optional.of(UURI_DEFAULT), + Optional.of(UURI_METHOD), + Optional.of(UUID_INVALID), + OptionalInt.empty(), + OptionalInt.empty(), + Optional.of(UPriority.UPRIORITY_CS4), + false) + ); + } + + @ParameterizedTest + @MethodSource("responseMessageArgProvider") + void testValidateAttributesForRpcResponseMessage( + Optional id, + Optional replyToAddress, + Optional invokedMethod, + Optional reqid, + OptionalInt commstatus, + OptionalInt ttl, + Optional priority, + boolean shouldSucceed + ) { + var attribsBuilder = UAttributes.newBuilder(); + attribsBuilder.setType(UMessageType.UMESSAGE_TYPE_RESPONSE); + id.ifPresent(attribsBuilder::setId); + replyToAddress.ifPresent(attribsBuilder::setSink); + invokedMethod.ifPresent(attribsBuilder::setSource); + reqid.ifPresent(attribsBuilder::setReqid); + commstatus.ifPresent(attribsBuilder::setCommstatusValue); + ttl.ifPresent(attribsBuilder::setTtl); + priority.ifPresent(attribsBuilder::setPriority); + var attribs = attribsBuilder.build(); + if (shouldSucceed) { + UAttributesValidator.Validators.RESPONSE.validator().validate(attribs); + assertThrows( + ValidationException.class, + () -> UAttributesValidator.Validators.PUBLISH.validator().validate(attribs) + ); + assertThrows( + ValidationException.class, + () -> UAttributesValidator.Validators.NOTIFICATION.validator().validate(attribs) + ); + assertThrows( + ValidationException.class, + () -> UAttributesValidator.Validators.REQUEST.validator().validate(attribs) + ); + } else { + assertThrows( + ValidationException.class, + () -> UAttributesValidator.Validators.RESPONSE.validator().validate(attribs)); + } + } +} diff --git a/src/test/java/org/eclipse/uprotocol/uuid/factory/UUIDFactoryTest.java b/src/test/java/org/eclipse/uprotocol/uuid/factory/UUIDFactoryTest.java index 2e7ca849..8b00a90c 100644 --- a/src/test/java/org/eclipse/uprotocol/uuid/factory/UUIDFactoryTest.java +++ b/src/test/java/org/eclipse/uprotocol/uuid/factory/UUIDFactoryTest.java @@ -17,10 +17,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import java.time.Duration; import java.time.Instant; -import java.util.ArrayList; -import java.util.List; import java.util.Optional; import static org.junit.jupiter.api.Assertions.*; @@ -113,9 +110,9 @@ void testUuidv6CreationWithNullInstant() { @Test @DisplayName("Test UUIDUtils for Random UUID") void testUuidutilsForRandomUuid() { - final java.util.UUID uuid_java = java.util.UUID.randomUUID(); - final UUID uuid = UUID.newBuilder().setMsb(uuid_java.getMostSignificantBits()) - .setLsb(uuid_java.getLeastSignificantBits()).build(); + final var uuidJava = java.util.UUID.randomUUID(); + final UUID uuid = UUID.newBuilder().setMsb(uuidJava.getMostSignificantBits()) + .setLsb(uuidJava.getLeastSignificantBits()).build(); final Optional time = UuidUtils.getTime(uuid); final String uuidString = UuidSerializer.serialize(uuid); @@ -222,28 +219,6 @@ void testCreateUprotocolUuidWithDifferentTimeValues() throws InterruptedExceptio } - @Test - @DisplayName("Test Create both UUIDv6 and v7 to compare performance") - void testCreateBothUuidv6AndV7ToComparePerformance() throws InterruptedException { - final List uuidv6List = new ArrayList<>(); - final List uuidv7List = new ArrayList<>(); - final int MAX_COUNT = 10000; - - Instant start = Instant.now(); - for (int i = 0; i < MAX_COUNT; i++) { - uuidv7List.add(UuidFactory.Factories.UPROTOCOL.factory().create()); - } - final Duration v7Diff = Duration.between(start, Instant.now()); - - start = Instant.now(); - for (int i = 0; i < MAX_COUNT; i++) { - uuidv6List.add(UuidFactory.Factories.UUIDV6.factory().create()); - } - final Duration v6Diff = Duration.between(start, Instant.now()); - System.out.println( - "UUIDv7:[" + v7Diff.toNanos() / MAX_COUNT + "ns]" + " UUIDv6:[" + v6Diff.toNanos() / MAX_COUNT + "ns]"); - } - @Test @DisplayName("Test Create UUIDv7 with the same time to confirm the UUIDs are not the same") void testCreateUuidv7WithTheSameTimeToConfirmTheUuidsAreNotTheSame() { diff --git a/src/test/java/org/eclipse/uprotocol/uuid/validator/UuidValidatorTest.java b/src/test/java/org/eclipse/uprotocol/uuid/validator/UuidValidatorTest.java index c84d92a3..58b4658f 100644 --- a/src/test/java/org/eclipse/uprotocol/uuid/validator/UuidValidatorTest.java +++ b/src/test/java/org/eclipse/uprotocol/uuid/validator/UuidValidatorTest.java @@ -75,9 +75,9 @@ void testUuidv7WithInvalidTypes() { final UUID uuidv6 = UuidFactory.Factories.UUIDV6.factory().create(); final UUID uuid = UUID.newBuilder().setMsb(0L).setLsb(0L).build(); - final java.util.UUID uuid_java = java.util.UUID.randomUUID(); - final UUID uuidv4 = UUID.newBuilder().setMsb(uuid_java.getMostSignificantBits()) - .setLsb(uuid_java.getLeastSignificantBits()).build(); + final java.util.UUID uuidJava = java.util.UUID.randomUUID(); + final UUID uuidv4 = UUID.newBuilder().setMsb(uuidJava.getMostSignificantBits()) + .setLsb(uuidJava.getLeastSignificantBits()).build(); final UuidValidator validator = UuidValidator.Validators.UPROTOCOL.validator(); assertNotNull(validator);