diff --git a/xapi-client/src/main/java/dev/learning/xapi/client/AttachmentHttpMessageWriter.java b/xapi-client/src/main/java/dev/learning/xapi/client/AttachmentHttpMessageWriter.java new file mode 100644 index 00000000..376b30f4 --- /dev/null +++ b/xapi-client/src/main/java/dev/learning/xapi/client/AttachmentHttpMessageWriter.java @@ -0,0 +1,66 @@ +/* + * Copyright 2016-2023 Berry Cloud Ltd. All rights reserved. + */ + +package dev.learning.xapi.client; + +import dev.learning.xapi.model.Attachment; +import java.util.List; +import java.util.Map; +import org.reactivestreams.Publisher; +import org.springframework.core.ResolvableType; +import org.springframework.core.io.buffer.DataBuffer; +import org.springframework.core.io.buffer.DataBufferFactory; +import org.springframework.core.io.buffer.DataBufferUtils; +import org.springframework.http.MediaType; +import org.springframework.http.ReactiveHttpOutputMessage; +import org.springframework.http.codec.HttpMessageWriter; +import org.springframework.http.codec.multipart.MultipartWriterSupport; +import org.springframework.lang.Nullable; +import reactor.core.publisher.Mono; + +/** + * {@link HttpMessageWriter} for writing {@link Attachment} data into multipart/mixed requests. + * + * @author István Rátkai (Selindek) + */ +public class AttachmentHttpMessageWriter extends MultipartWriterSupport + implements HttpMessageWriter { + + public AttachmentHttpMessageWriter() { + super(List.of(MediaType.MULTIPART_MIXED)); + } + + @Override + public boolean canWrite(ResolvableType elementType, @Nullable MediaType mediaType) { + return Attachment.class.isAssignableFrom(elementType.toClass()); + } + + @Override + public Mono write(Publisher parts, ResolvableType elementType, + @Nullable MediaType mediaType, ReactiveHttpOutputMessage outputMessage, + Map hints) { + + return Mono.from(parts).flatMap(part -> { + // set attachment part headers + outputMessage.getHeaders().setContentType(MediaType.valueOf(part.getContentType())); + outputMessage.getHeaders().set("Content-Transfer-Encoding", "binary"); + outputMessage.getHeaders().set("X-Experience-API-Hash", part.getSha2()); + + // write attachment content + return outputMessage.writeWith(encodePart(part, outputMessage.bufferFactory())); + }).doOnDiscard(DataBuffer.class, DataBufferUtils::release); + + } + + private Mono encodePart(Attachment part, DataBufferFactory bufferFactory) { + + return Mono.fromCallable(() -> { + final var buffer = bufferFactory.allocateBuffer(part.getContent().length); + buffer.write(part.getContent()); + return buffer; + }); + + } + +} diff --git a/xapi-client/src/main/java/dev/learning/xapi/client/MultipartService.java b/xapi-client/src/main/java/dev/learning/xapi/client/MultipartService.java deleted file mode 100644 index 8e5bb97e..00000000 --- a/xapi-client/src/main/java/dev/learning/xapi/client/MultipartService.java +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Copyright 2016-2023 Berry Cloud Ltd. All rights reserved. - */ - -package dev.learning.xapi.client; - -import com.fasterxml.jackson.databind.ObjectMapper; -import dev.learning.xapi.model.Attachment; -import dev.learning.xapi.model.Statement; -import dev.learning.xapi.model.SubStatement; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.util.FastByteArrayOutputStream; -import org.springframework.web.reactive.function.client.WebClient.RequestBodySpec; - -/** - * Helper methods for creating multipart message from statements. - * - * @author István Rátkai (Selindek) - */ -@Slf4j -@RequiredArgsConstructor -public final class MultipartService { - - private static final String MULTIPART_BOUNDARY = "xapi-learning-dev-boundary"; - private static final String MULTIPART_CONTENT_TYPE = "multipart/mixed; boundary=" - + MULTIPART_BOUNDARY; - private static final String CRLF = "\r\n"; - private static final String BOUNDARY_PREFIX = "--"; - private static final String BODY_SEPARATOR = BOUNDARY_PREFIX + MULTIPART_BOUNDARY + CRLF; - private static final String BODY_FOOTER = BOUNDARY_PREFIX + MULTIPART_BOUNDARY + BOUNDARY_PREFIX; - private static final String CONTENT_TYPE = HttpHeaders.CONTENT_TYPE + ":"; - - private static final byte[] BA_APP_JSON_HEADER = (CONTENT_TYPE + MediaType.APPLICATION_JSON_VALUE - + CRLF + CRLF).getBytes(StandardCharsets.UTF_8); - private static final byte[] BA_CRLF = CRLF.getBytes(StandardCharsets.UTF_8); - private static final byte[] BA_BODY_SEPARATOR = BODY_SEPARATOR.getBytes(StandardCharsets.UTF_8); - private static final byte[] BA_BODY_FOOTER = BODY_FOOTER.getBytes(StandardCharsets.UTF_8); - private static final byte[] BA_CONTENT_TYPE = CONTENT_TYPE.getBytes(StandardCharsets.UTF_8); - private static final byte[] BA_ENCODING_HEADER = ("Content-Transfer-Encoding:binary" + CRLF) - .getBytes(StandardCharsets.UTF_8); - private static final byte[] BA_X_API_HASH = "X-Experience-API-Hash:" - .getBytes(StandardCharsets.UTF_8); - - public static final MediaType MULTIPART_MEDIATYPE = MediaType.valueOf(MULTIPART_CONTENT_TYPE); - - private final ObjectMapper objectMapper; - - /** - *

- * Add a Statement to the request. - *

- * This method adds the statement and its attachments if there are any to the request body. Also - * sets the content-type to multipart/mixed if needed. - * - * @param requestSpec a {@link RequestBodySpec} object. - * @param statement a {@link Statement} to add. - */ - public void addBody(RequestBodySpec requestSpec, Statement statement) { - - addBody(requestSpec, statement, getRealAttachments(statement)); - - } - - /** - *

- * Adds a List of {@link Statement}s to the request. - *

- * This method adds the statements and their attachments if there are any to the request body. - * Also sets the content-type to multipart/mixed if needed. - * - * @param requestSpec a {@link RequestBodySpec} object. - * @param statements list of {@link Statement}s to add. - */ - public void addBody(RequestBodySpec requestSpec, List statements) { - - addBody(requestSpec, statements, statements.stream().flatMap(this::getRealAttachments)); - - } - - private void addBody(RequestBodySpec requestSpec, Object statements, - Stream attachments) { - - final var attachmentsBody = writeAttachments(attachments); - - if (attachmentsBody.length == 0) { - // add body directly, content-type is default application/json - requestSpec.bodyValue(statements); - } else { - // has at least one attachment with actual data -> set content-type - requestSpec.contentType(MULTIPART_MEDIATYPE); - // construct whole multipart body manually - requestSpec.bodyValue(createMultipartBody(statements, attachmentsBody)); - } - - } - - /** - * Gets {@link Attachment}s of a {@link Statement} which has data property as a {@link Stream}. - * - * @param statement a {@link Statement} object - * @return {@link Attachment} of a {@link Statement} which has data property as a {@link Stream}. - */ - private Stream getRealAttachments(Statement statement) { - - // handle the rare scenario when a sub-statement has an attachment - Stream stream = statement.getObject() instanceof final SubStatement substatement - && substatement.getAttachments() != null ? substatement.getAttachments().stream() - : Stream.empty(); - - if (statement.getAttachments() != null) { - stream = Stream.concat(stream, statement.getAttachments().stream()); - } - - return stream.filter(a -> a.getContent() != null); - } - - private byte[] createMultipartBody(Object statements, byte[] attachments) { - - try (var stream = new FastByteArrayOutputStream()) { - // Multipart Boundary - stream.write(BA_BODY_SEPARATOR); - - // Header of first part - stream.write(BA_APP_JSON_HEADER); - - // Body of first part - stream.write(objectMapper.writeValueAsBytes(statements)); - stream.write(BA_CRLF); - - // Body of attachments - stream.write(attachments); - - // Footer - stream.write(BA_BODY_FOOTER); - - return stream.toByteArrayUnsafe(); - } catch (final IOException e) { - log.error("Cannot create multipart body", e); - return new byte[] {}; - } - } - - /* - * Writes attachments to a byte array. If there are no attachments in the stream then returns an - * empty array. - */ - private static byte[] writeAttachments(Stream attachments) { - - try (var stream = new FastByteArrayOutputStream()) { - - // Write each sha2-identical attachments only once - attachments.collect(Collectors.toMap(Attachment::getSha2, v -> v, (k, v) -> v)).values() - .forEach(a -> { - try { - // Multipart Boundary - stream.write(BA_BODY_SEPARATOR); - - // Multipart headers - stream.write(BA_CONTENT_TYPE); - stream.write(a.getContentType().getBytes(StandardCharsets.UTF_8)); - stream.write(BA_CRLF); - - stream.write(BA_ENCODING_HEADER); - - stream.write(BA_X_API_HASH); - stream.write(a.getSha2().getBytes(StandardCharsets.UTF_8)); - stream.write(BA_CRLF); - stream.write(BA_CRLF); - - // Multipart body - stream.write(a.getContent()); - stream.write(BA_CRLF); - } catch (final IOException e) { - log.error("Cannot create multipart body", e); - } - - }); - - return stream.toByteArrayUnsafe(); - } - } - -} diff --git a/xapi-client/src/main/java/dev/learning/xapi/client/StatementHttpMessageWriter.java b/xapi-client/src/main/java/dev/learning/xapi/client/StatementHttpMessageWriter.java new file mode 100644 index 00000000..31e21d49 --- /dev/null +++ b/xapi-client/src/main/java/dev/learning/xapi/client/StatementHttpMessageWriter.java @@ -0,0 +1,264 @@ +/* + * Copyright 2016-2023 Berry Cloud Ltd. All rights reserved. + */ + +package dev.learning.xapi.client; + +import dev.learning.xapi.model.Attachment; +import dev.learning.xapi.model.Statement; +import dev.learning.xapi.model.SubStatement; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Supplier; +import java.util.stream.Stream; +import org.reactivestreams.Publisher; +import org.springframework.core.ResolvableType; +import org.springframework.core.codec.CodecException; +import org.springframework.core.io.buffer.DataBuffer; +import org.springframework.core.io.buffer.DataBufferFactory; +import org.springframework.core.io.buffer.DataBufferUtils; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ReactiveHttpOutputMessage; +import org.springframework.http.codec.HttpMessageWriter; +import org.springframework.http.codec.multipart.MultipartHttpMessageWriter; +import org.springframework.http.codec.multipart.MultipartWriterSupport; +import org.springframework.lang.Nullable; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +/** + * {@link HttpMessageWriter} for writing a {@link Statement} or {@link StatementList}. + *

+ * If any of the provided statements contains an {@link Attachment} with real data, then this writer + * creates a multipart/mixed output otherwise it writes the data as application/json. + *

+ *

+ * This message-writer accepts ALL objects, so all the default (and any other + * custom) {@link HttpMessageWriter} must be passed to its constructor. If the object to be written + * is not a {@link Statement} or List of Statements with real {@link Attachment}s, then this list of + * writers will be used. + *

+ * + * @author István Rátkai (Selindek) + * + * @see AttachmentHttpMessageWriter + */ +public class StatementHttpMessageWriter extends MultipartWriterSupport + implements HttpMessageWriter { + + private final List> writers = new ArrayList<>(); + + /** + * Constructor. + * + * @param list list of the original {@link HttpMessageWriter}s. This list is used if the object to + * write is not a {@link Statement} or list of statements or there are no any + * {@link Attachment}s with real data in the statements. + */ + public StatementHttpMessageWriter(List> list) { + + super(List.of(MediaType.MULTIPART_MIXED, MediaType.APPLICATION_JSON)); + + // Add special writer for attachments + this.writers.add(new AttachmentHttpMessageWriter()); + // ... but otherwise use the default list of writers + this.writers.addAll(list); + + } + + @Override + public boolean canWrite(ResolvableType elementType, @Nullable MediaType mediaType) { + return true; + } + + @Override + @SuppressWarnings("unchecked") + public Mono write(Publisher inputStream, ResolvableType elementType, + @Nullable MediaType mediaType, ReactiveHttpOutputMessage outputMessage, + Map hints) { + + return Mono.from(inputStream).flatMap(object -> { + final var list = getParts(object); + if (list.size() > 1) { + // Has attachments -> process as multipart + return writeMultipart(list, outputMessage, hints); + + } else { + // No attachments -> pass the original object to the default list of writers + + return ((HttpMessageWriter) writers.stream() + .filter(partWriter -> partWriter.canWrite(elementType, mediaType)).findFirst().get()) + .write(inputStream, elementType, mediaType, outputMessage, hints); + + } + }); + } + + private Mono writeMultipart(List list, ReactiveHttpOutputMessage outputMessage, + Map hints) { + + final var boundary = generateMultipartBoundary(); + + final var mediaType = getMultipartMediaType(MediaType.MULTIPART_MIXED, boundary); + outputMessage.getHeaders().setContentType(mediaType); + + final var bufferFactory = outputMessage.bufferFactory(); + + final Flux body = Flux.fromIterable(list) + .concatMap(element -> encodePart(boundary, element, bufferFactory, hints)) + .concatWith(generateLastLine(boundary, bufferFactory)) + .doOnDiscard(DataBuffer.class, DataBufferUtils::release); + + return outputMessage.writeWith(body); + } + + @SuppressWarnings("unchecked") + private Flux encodePart(byte[] boundary, Object body, DataBufferFactory factory, + Map hints) { + final var message = new MultipartHttpOutputMessage(factory); + final var headers = message.getHeaders(); + + final var resolvableType = ResolvableType.forClass(body.getClass()); + + final var contentType = headers.getContentType(); + + final var writer = this.writers.stream() + .filter(partWriter -> partWriter.canWrite(resolvableType, contentType)).findFirst(); + + if (!writer.isPresent()) { + return Flux.error( + new CodecException("No suitable writer found for part: " + resolvableType.toClass())); + } + + final var bodyPublisher = body instanceof Publisher ? (Publisher) body : Mono.just(body); + + // The writer will call MultipartHttpOutputMessage#write which doesn't actually write + // but only stores the body Flux and returns Mono.empty(). + + final var partContentReady = ((HttpMessageWriter) writer.get()).write(bodyPublisher, + resolvableType, contentType, message, hints); + + // After partContentReady, we can access the part content from MultipartHttpOutputMessage + // and use it for writing to the actual request body + + final Flux partContent = partContentReady.thenMany(Flux.defer(message::getBody)); + + return Flux.concat(generateBoundaryLine(boundary, factory), partContent, + generateNewLine(factory)); + } + + + @SuppressWarnings("unchecked") + private List getParts(Object object) { + + final var list = new ArrayList<>(); + + Stream attachments; + if (object instanceof final Statement statement) { + attachments = getRealAttachments(statement); + } else if (object instanceof final List statements && !statements.isEmpty() + && statements.get(0) instanceof Statement) { + attachments = ((List) statements).stream().flatMap(this::getRealAttachments); + } else { + // The object is not a statement or list of statements + return list; + } + + // first part is the statement / list of statements + list.add(object); + + list.addAll(attachments.distinct().toList()); + return list; + } + + /** + * Gets {@link Attachment}s of a {@link Statement} which has data property as a {@link Stream}. + * + * @param statement a {@link Statement} object + * + * @return {@link Attachment} of a {@link Statement} which has data property as a {@link Stream}. + */ + private Stream getRealAttachments(Statement statement) { + + // handle the rare scenario when a sub-statement has an attachment + var stream = statement.getObject() instanceof final SubStatement substatement + && substatement.getAttachments() != null ? substatement.getAttachments().stream() + : Stream.empty(); + + if (statement.getAttachments() != null) { + stream = Stream.concat(stream, statement.getAttachments().stream()); + } + + return stream.filter(a -> a.getContent() != null); + } + + /** + * This class was copied from the {@link MultipartHttpMessageWriter} class. Unfortunately it's a + * private class, so I cannot use it directly. + */ + private class MultipartHttpOutputMessage implements ReactiveHttpOutputMessage { + + private final DataBufferFactory bufferFactory; + + private final HttpHeaders headers = new HttpHeaders(); + + private final AtomicBoolean committed = new AtomicBoolean(); + + @Nullable + private Flux body; + + public MultipartHttpOutputMessage(DataBufferFactory bufferFactory) { + this.bufferFactory = bufferFactory; + } + + @Override + public HttpHeaders getHeaders() { + return this.body != null ? HttpHeaders.readOnlyHttpHeaders(this.headers) : this.headers; + } + + @Override + public DataBufferFactory bufferFactory() { + return this.bufferFactory; + } + + @Override + public void beforeCommit(Supplier> action) { + this.committed.set(true); + } + + @Override + public boolean isCommitted() { + return this.committed.get(); + } + + @Override + public Mono writeWith(Publisher body) { + if (this.body != null) { + return Mono.error(new IllegalStateException("Multiple calls to writeWith() not supported")); + } + this.body = generatePartHeaders(this.headers, this.bufferFactory).concatWith(body); + + // We don't actually want to write (just save the body Flux) + return Mono.empty(); + } + + @Override + public Mono writeAndFlushWith(Publisher> body) { + return Mono.error(new UnsupportedOperationException()); + } + + public Flux getBody() { + return this.body != null ? this.body + : Flux.error(new IllegalStateException("Body has not been written yet")); + } + + @Override + public Mono setComplete() { + return Mono.error(new UnsupportedOperationException()); + } + } + +} diff --git a/xapi-client/src/main/java/dev/learning/xapi/client/XapiClient.java b/xapi-client/src/main/java/dev/learning/xapi/client/XapiClient.java index 7eeb9361..1c122478 100644 --- a/xapi-client/src/main/java/dev/learning/xapi/client/XapiClient.java +++ b/xapi-client/src/main/java/dev/learning/xapi/client/XapiClient.java @@ -4,7 +4,6 @@ package dev.learning.xapi.client; -import com.fasterxml.jackson.databind.ObjectMapper; import dev.learning.xapi.model.About; import dev.learning.xapi.model.Activity; import dev.learning.xapi.model.Person; @@ -34,7 +33,6 @@ public class XapiClient { private final WebClient webClient; - private final MultipartService multipartService; private static final ParameterizedTypeReference> LIST_UUID_TYPE = new ParameterizedTypeReference<>() {}; @@ -48,13 +46,16 @@ public class XapiClient { * @param builder a {@link WebClient.Builder} object. The caller must set the baseUrl and the * authorization header. */ - public XapiClient(WebClient.Builder builder, ObjectMapper objectMapper) { - this.multipartService = new MultipartService(objectMapper); + public XapiClient(WebClient.Builder builder) { this.webClient = builder .defaultHeader("X-Experience-API-Version", "1.0.3") - .build(); + .codecs(configurer -> + + configurer.customCodecs().register(new StatementHttpMessageWriter(configurer.getWriters())) + + ).build(); } // Statement Resource @@ -114,15 +115,15 @@ public Mono> postStatement(PostStatementRequest request) { final Map queryParams = new HashMap<>(); - final var requestSpec = this.webClient + return this.webClient .method(request.getMethod()) - .uri(u -> request.url(u, queryParams).build(queryParams)); + .uri(u -> request.url(u, queryParams).build(queryParams)) - multipartService.addBody(requestSpec, request.getStatement()); + .bodyValue(request.getStatement()) - return requestSpec.retrieve() + .retrieve() .toEntity(LIST_UUID_TYPE) @@ -161,15 +162,15 @@ public Mono>> postStatements(PostStatementsRequest req final Map queryParams = new HashMap<>(); - final var requestSpec = this.webClient + return this.webClient .method(request.getMethod()) - .uri(u -> request.url(u, queryParams).build(queryParams)); + .uri(u -> request.url(u, queryParams).build(queryParams)) - multipartService.addBody(requestSpec, request.getStatements()); + .bodyValue(request.getStatements()) - return requestSpec.retrieve() + .retrieve() .toEntity(LIST_UUID_TYPE); diff --git a/xapi-client/src/main/java/dev/learning/xapi/client/configuration/XapiClientAutoConfiguration.java b/xapi-client/src/main/java/dev/learning/xapi/client/configuration/XapiClientAutoConfiguration.java index 9e83e8fd..ab2438ac 100644 --- a/xapi-client/src/main/java/dev/learning/xapi/client/configuration/XapiClientAutoConfiguration.java +++ b/xapi-client/src/main/java/dev/learning/xapi/client/configuration/XapiClientAutoConfiguration.java @@ -4,7 +4,6 @@ package dev.learning.xapi.client.configuration; -import com.fasterxml.jackson.databind.ObjectMapper; import dev.learning.xapi.client.XapiClient; import java.util.List; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -29,7 +28,7 @@ public class XapiClientAutoConfiguration { @Bean @ConditionalOnMissingBean public XapiClient xapiClient(XapiClientProperties properties, WebClient.Builder builder, - List configurers, ObjectMapper objectMapper) { + List configurers) { if (properties.getAuthorization() != null) { builder.defaultHeader(HttpHeaders.AUTHORIZATION, properties.getAuthorization()); @@ -45,7 +44,7 @@ public XapiClient xapiClient(XapiClientProperties properties, WebClient.Builder configurers.forEach(c -> c.accept(builder)); - return new XapiClient(builder, objectMapper); + return new XapiClient(builder); } diff --git a/xapi-client/src/test/java/dev/learning/xapi/client/XapiClientMultipartTests.java b/xapi-client/src/test/java/dev/learning/xapi/client/XapiClientMultipartTests.java index c89b9666..63ed5895 100644 --- a/xapi-client/src/test/java/dev/learning/xapi/client/XapiClientMultipartTests.java +++ b/xapi-client/src/test/java/dev/learning/xapi/client/XapiClientMultipartTests.java @@ -8,7 +8,6 @@ import static org.hamcrest.core.StringStartsWith.startsWith; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import com.fasterxml.jackson.databind.ObjectMapper; import dev.learning.xapi.model.Activity; import dev.learning.xapi.model.Agent; import dev.learning.xapi.model.Statement; @@ -39,9 +38,6 @@ class XapiClientMultipartTests { @Autowired private WebClient.Builder webClientBuilder; - @Autowired - private ObjectMapper objectMapper; - private MockWebServer mockWebServer; private XapiClient client; @@ -52,7 +48,7 @@ void setUp() throws Exception { webClientBuilder.baseUrl(mockWebServer.url("").toString()); - client = new XapiClient(webClientBuilder, objectMapper); + client = new XapiClient(webClientBuilder); } @@ -113,8 +109,13 @@ void whenPostingStatementWithTextAttachmentThenBodyIsExpected() throws Interrupt final var recordedRequest = mockWebServer.takeRequest(); // Then Body Is Expected - assertThat(recordedRequest.getBody().readUtf8(), is( - "--xapi-learning-dev-boundary\r\nContent-Type:application/json\r\n\r\n{\"actor\":{\"objectType\":\"Agent\",\"name\":\"A N Other\",\"mbox\":\"mailto:another@example.com\"},\"verb\":{\"id\":\"http://adlnet.gov/expapi/verbs/attempted\",\"display\":{\"und\":\"attempted\"}},\"object\":{\"objectType\":\"Activity\",\"id\":\"https://example.com/activity/simplestatement\",\"definition\":{\"name\":{\"en\":\"Simple Statement\"}}},\"attachments\":[{\"usageType\":\"http://adlnet.gov/expapi/attachments/text\",\"display\":{\"en\":\"text attachment\"},\"contentType\":\"text/plain\",\"length\":17,\"sha2\":\"b154d3fd46a5068da42ba05a8b9c971688ab5a57eb5c3a0e50a23c42a86786e5\"}]}\r\n--xapi-learning-dev-boundary\r\nContent-Type:text/plain\r\nContent-Transfer-Encoding:binary\r\nX-Experience-API-Hash:b154d3fd46a5068da42ba05a8b9c971688ab5a57eb5c3a0e50a23c42a86786e5\r\n\r\nSimple attachment\r\n--xapi-learning-dev-boundary--")); + final var boundary = "--" + recordedRequest.getHeader("content-type").substring(25); + + assertThat(recordedRequest.getBody().readUtf8(), is(boundary + + "\r\nContent-Type: application/json\r\nContent-Length: 531\r\n\r\n{\"actor\":{\"objectType\":\"Agent\",\"name\":\"A N Other\",\"mbox\":\"mailto:another@example.com\"},\"verb\":{\"id\":\"http://adlnet.gov/expapi/verbs/attempted\",\"display\":{\"und\":\"attempted\"}},\"object\":{\"objectType\":\"Activity\",\"id\":\"https://example.com/activity/simplestatement\",\"definition\":{\"name\":{\"en\":\"Simple Statement\"}}},\"attachments\":[{\"usageType\":\"http://adlnet.gov/expapi/attachments/text\",\"display\":{\"en\":\"text attachment\"},\"contentType\":\"text/plain\",\"length\":17,\"sha2\":\"b154d3fd46a5068da42ba05a8b9c971688ab5a57eb5c3a0e50a23c42a86786e5\"}]}\r\n" + + boundary + + "\r\nContent-Type: text/plain\r\nContent-Transfer-Encoding: binary\r\nX-Experience-API-Hash: b154d3fd46a5068da42ba05a8b9c971688ab5a57eb5c3a0e50a23c42a86786e5\r\n\r\nSimple attachment\r\n" + + boundary + "--\r\n")); } @Test @@ -142,8 +143,13 @@ void whenPostingStatementWithBinaryAttachmentThenBodyIsExpected() throws Interru final var recordedRequest = mockWebServer.takeRequest(); // Then Body Is Expected - assertThat(recordedRequest.getBody().readUtf8(), is( - "--xapi-learning-dev-boundary\r\nContent-Type:application/json\r\n\r\n{\"actor\":{\"objectType\":\"Agent\",\"name\":\"A N Other\",\"mbox\":\"mailto:another@example.com\"},\"verb\":{\"id\":\"http://adlnet.gov/expapi/verbs/attempted\",\"display\":{\"und\":\"attempted\"}},\"object\":{\"objectType\":\"Activity\",\"id\":\"https://example.com/activity/simplestatement\",\"definition\":{\"name\":{\"en\":\"Simple Statement\"}}},\"attachments\":[{\"usageType\":\"http://adlnet.gov/expapi/attachments/code\",\"display\":{\"en\":\"binary attachment\"},\"contentType\":\"application/octet-stream\",\"length\":6,\"sha2\":\"0f4b9b79ad9e0572dbc7ce7d4dd38b96dc66d28ca87d7fd738ec8f9a30935bf6\"}]}\r\n--xapi-learning-dev-boundary\r\nContent-Type:application/octet-stream\r\nContent-Transfer-Encoding:binary\r\nX-Experience-API-Hash:0f4b9b79ad9e0572dbc7ce7d4dd38b96dc66d28ca87d7fd738ec8f9a30935bf6\r\n\r\n@ABCD�\r\n--xapi-learning-dev-boundary--")); + final var boundary = "--" + recordedRequest.getHeader("content-type").substring(25); + + assertThat(recordedRequest.getBody().readUtf8(), is(boundary + + "\r\nContent-Type: application/json\r\nContent-Length: 546\r\n\r\n{\"actor\":{\"objectType\":\"Agent\",\"name\":\"A N Other\",\"mbox\":\"mailto:another@example.com\"},\"verb\":{\"id\":\"http://adlnet.gov/expapi/verbs/attempted\",\"display\":{\"und\":\"attempted\"}},\"object\":{\"objectType\":\"Activity\",\"id\":\"https://example.com/activity/simplestatement\",\"definition\":{\"name\":{\"en\":\"Simple Statement\"}}},\"attachments\":[{\"usageType\":\"http://adlnet.gov/expapi/attachments/code\",\"display\":{\"en\":\"binary attachment\"},\"contentType\":\"application/octet-stream\",\"length\":6,\"sha2\":\"0f4b9b79ad9e0572dbc7ce7d4dd38b96dc66d28ca87d7fd738ec8f9a30935bf6\"}]}\r\n" + + boundary + + "\r\nContent-Type: application/octet-stream\r\nContent-Transfer-Encoding: binary\r\nX-Experience-API-Hash: 0f4b9b79ad9e0572dbc7ce7d4dd38b96dc66d28ca87d7fd738ec8f9a30935bf6\r\n\r\n@ABCD�\r\n" + + boundary + "--\r\n")); } @Test @@ -208,8 +214,13 @@ void whenPostingSubStatementWithTextAttachmentThenBodyIsExpected() throws Interr final var recordedRequest = mockWebServer.takeRequest(); // Then Body Is Expected - assertThat(recordedRequest.getBody().readUtf8(), is( - "--xapi-learning-dev-boundary\r\nContent-Type:application/json\r\n\r\n{\"actor\":{\"objectType\":\"Agent\",\"name\":\"A N Other\",\"mbox\":\"mailto:another@example.com\"},\"verb\":{\"id\":\"https://w3id.org/xapi/adl/verbs/abandoned\",\"display\":{\"und\":\"abandoned\"}},\"object\":{\"objectType\":\"SubStatement\",\"actor\":{\"objectType\":\"Agent\",\"name\":\"A N Other\",\"mbox\":\"mailto:another@example.com\"},\"verb\":{\"id\":\"http://adlnet.gov/expapi/verbs/attended\",\"display\":{\"und\":\"attended\"}},\"object\":{\"objectType\":\"Activity\",\"id\":\"https://example.com/activity/simplestatement\",\"definition\":{\"name\":{\"en\":\"Simple Statement\"}}},\"attachments\":[{\"usageType\":\"http://adlnet.gov/expapi/attachments/text\",\"display\":{\"en\":\"text attachment\"},\"contentType\":\"text/plain\",\"length\":17,\"sha2\":\"b154d3fd46a5068da42ba05a8b9c971688ab5a57eb5c3a0e50a23c42a86786e5\"}]}}\r\n--xapi-learning-dev-boundary\r\nContent-Type:text/plain\r\nContent-Transfer-Encoding:binary\r\nX-Experience-API-Hash:b154d3fd46a5068da42ba05a8b9c971688ab5a57eb5c3a0e50a23c42a86786e5\r\n\r\nSimple attachment\r\n--xapi-learning-dev-boundary--")); + final var boundary = "--" + recordedRequest.getHeader("content-type").substring(25); + + assertThat(recordedRequest.getBody().readUtf8(), is(boundary + + "\r\nContent-Type: application/json\r\nContent-Length: 742\r\n\r\n{\"actor\":{\"objectType\":\"Agent\",\"name\":\"A N Other\",\"mbox\":\"mailto:another@example.com\"},\"verb\":{\"id\":\"https://w3id.org/xapi/adl/verbs/abandoned\",\"display\":{\"und\":\"abandoned\"}},\"object\":{\"objectType\":\"SubStatement\",\"actor\":{\"objectType\":\"Agent\",\"name\":\"A N Other\",\"mbox\":\"mailto:another@example.com\"},\"verb\":{\"id\":\"http://adlnet.gov/expapi/verbs/attended\",\"display\":{\"und\":\"attended\"}},\"object\":{\"objectType\":\"Activity\",\"id\":\"https://example.com/activity/simplestatement\",\"definition\":{\"name\":{\"en\":\"Simple Statement\"}}},\"attachments\":[{\"usageType\":\"http://adlnet.gov/expapi/attachments/text\",\"display\":{\"en\":\"text attachment\"},\"contentType\":\"text/plain\",\"length\":17,\"sha2\":\"b154d3fd46a5068da42ba05a8b9c971688ab5a57eb5c3a0e50a23c42a86786e5\"}]}}\r\n" + + boundary + + "\r\nContent-Type: text/plain\r\nContent-Transfer-Encoding: binary\r\nX-Experience-API-Hash: b154d3fd46a5068da42ba05a8b9c971688ab5a57eb5c3a0e50a23c42a86786e5\r\n\r\nSimple attachment\r\n" + + boundary + "--\r\n")); } @Test @@ -224,7 +235,7 @@ void whenPostingStatementsWithAttachmentsThenBodyIsExpected() throws Interrupted .actor(a -> a.name("A N Other").mbox("mailto:another@example.com")) - .addAttachment(a -> a.content(new byte[] {64, 65, 66, 67, 68, 69}).length(6) + .addAttachment(a -> a.content(new byte[] {64, 65, 66, 67, 68, (byte) 255}).length(6) .contentType("application/octet-stream") .usageType(URI.create("http://adlnet.gov/expapi/attachments/code")) .addDisplay(Locale.ENGLISH, "binary attachment")) @@ -240,7 +251,7 @@ void whenPostingStatementsWithAttachmentsThenBodyIsExpected() throws Interrupted .actor(a -> a.name("A N Other").mbox("mailto:another@example.com")) - .addAttachment(a -> a.content(new byte[] {64, 65, 66, 67, 68, 69}).length(6) + .addAttachment(a -> a.content(new byte[] {64, 65, 66, 67, 68, (byte) 255}).length(6) .contentType("application/octet-stream") .usageType(URI.create("http://adlnet.gov/expapi/attachments/code")) .addDisplay(Locale.ENGLISH, "binary attachment")) @@ -262,11 +273,18 @@ void whenPostingStatementsWithAttachmentsThenBodyIsExpected() throws Interrupted final var recordedRequest = mockWebServer.takeRequest(); // Then Body Is Expected - assertThat(recordedRequest.getBody().readUtf8(), is( - "--xapi-learning-dev-boundary\r\nContent-Type:application/json\r\n\r\n[{\"actor\":{\"objectType\":\"Agent\",\"name\":\"A N Other\",\"mbox\":\"mailto:another@example.com\"},\"verb\":{\"id\":\"http://adlnet.gov/expapi/verbs/attempted\",\"display\":{\"und\":\"attempted\"}},\"object\":{\"objectType\":\"Activity\",\"id\":\"https://example.com/activity/simplestatement\",\"definition\":{\"name\":{\"en\":\"Simple Statement\"}}},\"attachments\":[{\"usageType\":\"http://adlnet.gov/expapi/attachments/code\",\"display\":{\"en\":\"binary attachment\"},\"contentType\":\"application/octet-stream\",\"length\":6,\"sha2\":\"0ff3c6749b3eeaae17254fdf0e2de1f32b21c592f474bf39b62b398e8a787eef\"}]},{\"actor\":{\"objectType\":\"Agent\",\"name\":\"A N Other\",\"mbox\":\"mailto:another@example.com\"},\"verb\":{\"id\":\"http://adlnet.gov/expapi/verbs/attempted\",\"display\":{\"und\":\"attempted\"}},\"object\":{\"objectType\":\"Activity\",\"id\":\"https://example.com/activity/simplestatement\",\"definition\":{\"name\":{\"en\":\"Simple Statement\"}}},\"attachments\":[{\"usageType\":\"http://adlnet.gov/expapi/attachments/code\",\"display\":{\"en\":\"binary attachment\"},\"contentType\":\"application/octet-stream\",\"length\":6,\"sha2\":\"0ff3c6749b3eeaae17254fdf0e2de1f32b21c592f474bf39b62b398e8a787eef\"},{\"usageType\":\"http://adlnet.gov/expapi/attachments/text\",\"display\":{\"en\":\"text attachment\"},\"contentType\":\"text/plain\",\"length\":17,\"sha2\":\"b154d3fd46a5068da42ba05a8b9c971688ab5a57eb5c3a0e50a23c42a86786e5\"}]}]\r\n--xapi-learning-dev-boundary\r\nContent-Type:text/plain\r\nContent-Transfer-Encoding:binary\r\nX-Experience-API-Hash:b154d3fd46a5068da42ba05a8b9c971688ab5a57eb5c3a0e50a23c42a86786e5\r\n\r\nSimple attachment\r\n--xapi-learning-dev-boundary\r\nContent-Type:application/octet-stream\r\nContent-Transfer-Encoding:binary\r\nX-Experience-API-Hash:0ff3c6749b3eeaae17254fdf0e2de1f32b21c592f474bf39b62b398e8a787eef\r\n\r\n@ABCDE\r\n--xapi-learning-dev-boundary--")); - } + final var boundary = "--" + recordedRequest.getHeader("content-type").substring(25); + + assertThat(recordedRequest.getBody().readUtf8(), is(boundary + + "\r\nContent-Type: application/json\r\nContent-Length: 1301\r\n\r\n[{\"actor\":{\"objectType\":\"Agent\",\"name\":\"A N Other\",\"mbox\":\"mailto:another@example.com\"},\"verb\":{\"id\":\"http://adlnet.gov/expapi/verbs/attempted\",\"display\":{\"und\":\"attempted\"}},\"object\":{\"objectType\":\"Activity\",\"id\":\"https://example.com/activity/simplestatement\",\"definition\":{\"name\":{\"en\":\"Simple Statement\"}}},\"attachments\":[{\"usageType\":\"http://adlnet.gov/expapi/attachments/code\",\"display\":{\"en\":\"binary attachment\"},\"contentType\":\"application/octet-stream\",\"length\":6,\"sha2\":\"0f4b9b79ad9e0572dbc7ce7d4dd38b96dc66d28ca87d7fd738ec8f9a30935bf6\"}]},{\"actor\":{\"objectType\":\"Agent\",\"name\":\"A N Other\",\"mbox\":\"mailto:another@example.com\"},\"verb\":{\"id\":\"http://adlnet.gov/expapi/verbs/attempted\",\"display\":{\"und\":\"attempted\"}},\"object\":{\"objectType\":\"Activity\",\"id\":\"https://example.com/activity/simplestatement\",\"definition\":{\"name\":{\"en\":\"Simple Statement\"}}},\"attachments\":[{\"usageType\":\"http://adlnet.gov/expapi/attachments/code\",\"display\":{\"en\":\"binary attachment\"},\"contentType\":\"application/octet-stream\",\"length\":6,\"sha2\":\"0f4b9b79ad9e0572dbc7ce7d4dd38b96dc66d28ca87d7fd738ec8f9a30935bf6\"},{\"usageType\":\"http://adlnet.gov/expapi/attachments/text\",\"display\":{\"en\":\"text attachment\"},\"contentType\":\"text/plain\",\"length\":17,\"sha2\":\"b154d3fd46a5068da42ba05a8b9c971688ab5a57eb5c3a0e50a23c42a86786e5\"}]}]\r\n" + + boundary + + "\r\nContent-Type: application/octet-stream\r\nContent-Transfer-Encoding: binary\r\nX-Experience-API-Hash: 0f4b9b79ad9e0572dbc7ce7d4dd38b96dc66d28ca87d7fd738ec8f9a30935bf6\r\n\r\n@ABCD�\r\n" + + boundary + + "\r\nContent-Type: text/plain\r\nContent-Transfer-Encoding: binary\r\nX-Experience-API-Hash: b154d3fd46a5068da42ba05a8b9c971688ab5a57eb5c3a0e50a23c42a86786e5\r\n\r\nSimple attachment\r\n" + + boundary + "--\r\n")); + } @Test void whenPostingStatementsWithTimestampAndAttachmentThenNoExceptionIsThrown() diff --git a/xapi-client/src/test/java/dev/learning/xapi/client/XapiClientTests.java b/xapi-client/src/test/java/dev/learning/xapi/client/XapiClientTests.java index bce73ac9..d06ab622 100644 --- a/xapi-client/src/test/java/dev/learning/xapi/client/XapiClientTests.java +++ b/xapi-client/src/test/java/dev/learning/xapi/client/XapiClientTests.java @@ -7,7 +7,6 @@ import static org.hamcrest.core.Is.is; import static org.hamcrest.core.IsInstanceOf.instanceOf; -import com.fasterxml.jackson.databind.ObjectMapper; import dev.learning.xapi.model.About; import dev.learning.xapi.model.Activity; import dev.learning.xapi.model.Person; @@ -23,7 +22,6 @@ import lombok.Getter; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; -import okhttp3.mockwebserver.RecordedRequest; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -45,9 +43,6 @@ class XapiClientTests { @Autowired private WebClient.Builder webClientBuilder; - @Autowired - private ObjectMapper objectMapper; - private MockWebServer mockWebServer; private XapiClient client; @@ -58,7 +53,7 @@ void setUp() throws Exception { webClientBuilder.baseUrl(mockWebServer.url("").toString()); - client = new XapiClient(webClientBuilder, objectMapper); + client = new XapiClient(webClientBuilder); } @@ -77,7 +72,7 @@ void whenGettingStatementThenMethodIsGet() throws InterruptedException { // When Getting Statements client.getStatement(r -> r.id("4df42866-40e7-45b6-bf7c-8d5fccbdccd6")).block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Method Is Get assertThat(recordedRequest.getMethod(), is("GET")); @@ -91,7 +86,7 @@ void whenGettingStatementThenPathIsExpected() throws InterruptedException { // When Getting Statement client.getStatement(r -> r.id("4df42866-40e7-45b6-bf7c-8d5fccbdccd6")).block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Path Is Expected assertThat(recordedRequest.getPath(), @@ -108,8 +103,8 @@ void whenGettingStatementThenBodyIsInstanceOfStatement() throws InterruptedExcep .addHeader("Content-Type", "application/json; charset=utf-8")); // When Getting Statement - final var response = client.getStatement(r -> r.id("4df42866-40e7-45b6-bf7c-8d5fccbdccd6")) - .block(); + final var response = + client.getStatement(r -> r.id("4df42866-40e7-45b6-bf7c-8d5fccbdccd6")).block(); // Then Body Is Instance Of Statement assertThat(response.getBody(), instanceOf(Statement.class)); @@ -124,7 +119,7 @@ void whenGettingStatementWithAttachmentsThenPathIsExpected() throws InterruptedE client.getStatement(r -> r.id("4df42866-40e7-45b6-bf7c-8d5fccbdccd6").attachments(true)) .block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Path Is Expected assertThat(recordedRequest.getPath(), @@ -142,7 +137,7 @@ void whenGettingStatementWithCanonicalFormatThenPathIsExpected() throws Interrup r -> r.id("4df42866-40e7-45b6-bf7c-8d5fccbdccd6").format(StatementFormat.CANONICAL)) .block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Path Is Expected assertThat(recordedRequest.getPath(), @@ -174,7 +169,7 @@ void whenPostingStatementsThenMethodIsPost() throws InterruptedException { // When posting Statements client.postStatements(r -> r.statements(statements)).block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Method Is Post assertThat(recordedRequest.getMethod(), is("POST")); @@ -201,7 +196,7 @@ void whenPostingStatementsThenBodyIsExpected() throws InterruptedException { // When Posting Statements client.postStatements(r -> r.statements(attemptedStatement, passedStatement)).block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Body Is Expected assertThat(recordedRequest.getBody().readUtf8(), is( @@ -231,7 +226,7 @@ void whenPostingStatementsArrayThenBodyIsExpected() throws InterruptedException // When Posting Statements Array client.postStatements(r -> r.statements(statements)).block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Body Is Expected assertThat(recordedRequest.getBody().readUtf8(), is( @@ -261,7 +256,7 @@ void whenPostingStatementsThenContentTypeHeaderIsApplicationJson() throws Interr // When Posting Statements client.postStatements(r -> r.statements(statements)).block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Content Type Header Is Application Json assertThat(recordedRequest.getHeader("content-type"), is("application/json")); @@ -316,7 +311,7 @@ void whenPostingStatementThenMethodIsPost() throws InterruptedException { .definition(d -> d.addName(Locale.ENGLISH, "Simple Statement"))))) .block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Method Is Post assertThat(recordedRequest.getMethod(), is("POST")); @@ -339,7 +334,7 @@ void whenPostingStatementThenBodyIsExpected() throws InterruptedException { .definition(d -> d.addName(Locale.ENGLISH, "Simple Statement"))))) .block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Body Is Expected assertThat(recordedRequest.getBody().readUtf8(), is( @@ -363,7 +358,7 @@ void whenPostingStatementThenContentTypeHeaderIsApplicationJson() throws Interru .definition(d -> d.addName(Locale.ENGLISH, "Simple Statement"))))) .block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Content Type Header Is Application Json assertThat(recordedRequest.getHeader("content-type"), is("application/json")); @@ -380,7 +375,7 @@ void whenGettingVoidedStatementThenMethodIsGet() throws InterruptedException { client.getVoidedStatement( r -> r.voidedId(UUID.fromString("4df42866-40e7-45b6-bf7c-8d5fccbdccd6"))).block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Method Is Get assertThat(recordedRequest.getMethod(), is("GET")); @@ -394,7 +389,7 @@ void whenGettingVoidedStatementThenPathIsExpected() throws InterruptedException // When Getting Voided Statement client.getVoidedStatement(r -> r.voidedId("4df42866-40e7-45b6-bf7c-8d5fccbdccd6")).block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Path Is Expected assertThat(recordedRequest.getPath(), @@ -410,7 +405,7 @@ void whenGettingVoidedStatementWithAttachmentsThenPathIsExpected() throws Interr client.getStatement(r -> r.id("4df42866-40e7-45b6-bf7c-8d5fccbdccd6").attachments(true)) .block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Path Is Expected assertThat(recordedRequest.getPath(), @@ -429,7 +424,7 @@ void whenGettingVoidedStatementWithCanonicalFormatThenPathIsExpected() r -> r.id("4df42866-40e7-45b6-bf7c-8d5fccbdccd6").format(StatementFormat.CANONICAL)) .block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Path Is Expected assertThat(recordedRequest.getPath(), @@ -446,7 +441,7 @@ void whenGettingStatementsThenMethodIsGet() throws InterruptedException { // When Getting Statements client.getStatements().block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Method Is Get assertThat(recordedRequest.getMethod(), is("GET")); @@ -460,7 +455,7 @@ void whenGettingStatementsThenPathIsExpected() throws InterruptedException { // When Getting Statements client.getStatements().block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Path Is Expected assertThat(recordedRequest.getPath(), is("/statements")); @@ -500,7 +495,7 @@ void whenGettingStatementsWithAllParametersThenPathIsExpected() throws Interrupt ).block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Path Is Expected assertThat(recordedRequest.getPath(), is( @@ -519,7 +514,7 @@ void whenGettingStatementsWithAgentParameterThenPathIsExpected() throws Interrup ).block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Path Is Expected assertThat(recordedRequest.getPath(), is( @@ -538,7 +533,7 @@ void whenGettingStatementsWithVerbParameterThenPathIsExpected() throws Interrupt ).block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Path Is Expected assertThat(recordedRequest.getPath(), @@ -557,7 +552,7 @@ void whenGettingStatementsWithActivityParameterThenPathIsExpected() throws Inter ).block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Path Is Expected assertThat(recordedRequest.getPath(), @@ -576,7 +571,7 @@ void whenGettingMoreStatementsThenRequestMethodIsGet() throws InterruptedExcepti ).block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Method Is Get assertThat(recordedRequest.getMethod(), is("GET")); @@ -594,7 +589,7 @@ void whenGettingMoreStatementsThenRequestURLExpected() throws InterruptedExcepti ).block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Request URL Is Expected assertThat(recordedRequest.getRequestUrl(), @@ -617,7 +612,7 @@ void whenGettingASingleStateThenMethodIsGet() throws InterruptedException { .stateId("bookmark"), String.class).block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Method Is Get assertThat(recordedRequest.getMethod(), is("GET")); @@ -637,7 +632,7 @@ void whenGettingASingleStateThenPathIsExpected() throws InterruptedException { .stateId("bookmark"), String.class).block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Path Is Expected assertThat(recordedRequest.getPath(), is( @@ -658,7 +653,7 @@ void whenGettingASingleStateWithoutRegistrationThenMethodIsGet() throws Interrup .block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Method Is Get assertThat(recordedRequest.getMethod(), is("GET")); @@ -678,7 +673,7 @@ void whenGettingASingleStateWithoutRegistrationThenPathIsExpected() throws Inter .block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Path Is Expected assertThat(recordedRequest.getPath(), is( @@ -751,7 +746,7 @@ void whenPostingASingleStateThenMethodIsPost() throws InterruptedException { .block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Method Is Post assertThat(recordedRequest.getMethod(), is("POST")); @@ -775,7 +770,7 @@ void whenPostingASingleStateThenPathIsExpected() throws InterruptedException { .block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Path Is Expected assertThat(recordedRequest.getPath(), is( @@ -803,7 +798,7 @@ void whenPostingASingleStateWithContentTypeTextPlainThenContentTypeHeaderIsTextP .block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Content Type Header Is Text Plain assertThat(recordedRequest.getHeader("content-type"), is("text/plain")); @@ -828,7 +823,7 @@ void whenPostingASingleStateWithoutContentTypeThenContentTypeHeaderIsApplication .block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Content Type Header Is Application Json assertThat(recordedRequest.getHeader("content-type"), is("application/json")); @@ -850,7 +845,7 @@ void whenPostingASingleStateWithoutRegistrationThenMethodIsPost() throws Interru .block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Method Is Post assertThat(recordedRequest.getMethod(), is("POST")); @@ -872,7 +867,7 @@ void whenPostingASingleStateWithoutRegistrationThenPathIsExpected() throws Inter .block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Path Is Expected assertThat(recordedRequest.getPath(), is( @@ -899,7 +894,7 @@ void whenPuttingASingleStateThenMethodIsPut() throws InterruptedException { .block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Method Is Post assertThat(recordedRequest.getMethod(), is("PUT")); @@ -923,7 +918,7 @@ void whenPuttingASingleStateThenPathIsExpected() throws InterruptedException { .block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Path Is Expected assertThat(recordedRequest.getPath(), is( @@ -951,7 +946,7 @@ void whenPuttingASingleStateWithContentTypeTextPlainThenContentTypeHeaderIsTextP .block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Content Type Header Is Text Plain assertThat(recordedRequest.getHeader("content-type"), is("text/plain")); @@ -976,7 +971,7 @@ void whenPuttingASingleStateWithoutContentTypeThenContentTypeHeaderIsApplication .block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Content Type Header Is Application Json assertThat(recordedRequest.getHeader("content-type"), is("application/json")); @@ -998,7 +993,7 @@ void whenPuttingASingleStateWithoutRegistrationThenMethodIsPut() throws Interrup .block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Method Is Post assertThat(recordedRequest.getMethod(), is("PUT")); @@ -1020,7 +1015,7 @@ void whenPuttingASingleStateWithoutRegistrationThenPathIsExpected() throws Inter .block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Path Is Expected assertThat(recordedRequest.getPath(), is( @@ -1043,7 +1038,7 @@ void whenDeletingASingleStateThenMethodIsDelete() throws InterruptedException { .stateId("bookmark")).block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Method Is Delete assertThat(recordedRequest.getMethod(), is("DELETE")); @@ -1063,7 +1058,7 @@ void whenDeletingASingleStateThenPathIsExpected() throws InterruptedException { .stateId("bookmark")).block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Path Is Expected assertThat(recordedRequest.getPath(), is( @@ -1082,7 +1077,7 @@ void whenDeletingASingleStateWithoutRegistrationThenMethodIsDelete() throws Inte .stateId("bookmark")).block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Method Is Delete assertThat(recordedRequest.getMethod(), is("DELETE")); @@ -1102,7 +1097,7 @@ void whenDeletingASingleStateWithoutRegistrationThenPathIsExpected() throws Inte .stateId("bookmark")).block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Path Is Expected assertThat(recordedRequest.getPath(), is( @@ -1127,7 +1122,7 @@ void whenGettingMultipleStatesThenMethodIsGet() throws InterruptedException { .block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Method Is Get assertThat(recordedRequest.getMethod(), is("GET")); @@ -1149,7 +1144,7 @@ void whenGettingMultipleStatesThenPathIsExpected() throws InterruptedException { .block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Path Is Expected assertThat(recordedRequest.getPath(), is( @@ -1170,7 +1165,7 @@ void whenGettingMultipleStatesWithoutRegistrationThenMethodIsGet() throws Interr .block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Method Is Get assertThat(recordedRequest.getMethod(), is("GET")); @@ -1191,7 +1186,7 @@ void whenGettingMultipleStatesWithoutRegistrationThenPathIsExpected() .block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Path Is Expected assertThat(recordedRequest.getPath(), is( @@ -1258,7 +1253,7 @@ void whenDeletingMultipleStatesThenMethodIsDelete() throws InterruptedException ).block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Method Is Delete assertThat(recordedRequest.getMethod(), is("DELETE")); @@ -1278,7 +1273,7 @@ void whenDeletingMultipleStatesThenPathIsExpected() throws InterruptedException ).block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Path Is Expected assertThat(recordedRequest.getPath(), is( @@ -1298,7 +1293,7 @@ void whenDeletingMultipleStatesWithoutRegistrationThenMethodIsDelete() ).block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Method Is Delete assertThat(recordedRequest.getMethod(), is("DELETE")); @@ -1317,7 +1312,7 @@ void whenDeletingMultipleStatesWithoutRegistrationThenPathIsExpected() ).block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Path Is Expected assertThat(recordedRequest.getPath(), is( @@ -1336,7 +1331,7 @@ void whenGettingASingleAgentProfileThenMethodIsGet() throws InterruptedException .profileId("greeting"), String.class).block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Method Is Get assertThat(recordedRequest.getMethod(), is("GET")); @@ -1356,7 +1351,7 @@ void whenGettingASingleAgentProfileThenPathIsExpected() throws InterruptedExcept .block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Path Is Expected assertThat(recordedRequest.getPath(), is( @@ -1379,7 +1374,7 @@ void whenDeletingASingleAgentProfileThenMethodIsDelete() throws InterruptedExcep .block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Method Is Delete assertThat(recordedRequest.getMethod(), is("DELETE")); @@ -1397,7 +1392,7 @@ void whenDeletetingASingleAgentProfileThenPathIsExpected() throws InterruptedExc .profileId("greeting")) .block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Path Is Expected assertThat(recordedRequest.getPath(), is( @@ -1420,7 +1415,7 @@ void whenPuttingASingleAgentProfileThenMethodIsPut() throws InterruptedException .block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Method Is Put assertThat(recordedRequest.getMethod(), is("PUT")); @@ -1440,7 +1435,7 @@ void whenPuttingASingleAgentProfileThenPathIsExpected() throws InterruptedExcept .block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Path Is Expected assertThat(recordedRequest.getPath(), is( @@ -1464,7 +1459,7 @@ void whenPuttingASingleAgentProfileWithContentTypeTextPlainThenContentTypeHeader .block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Content Type Header Is Text Plain assertThat(recordedRequest.getHeader("content-type"), is("text/plain")); @@ -1485,7 +1480,7 @@ void whenPuttingASingleAgentProfileWithoutContentTypeThenContentTypeHeaderIsAppl .block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Content Type Header Is Application Json assertThat(recordedRequest.getHeader("content-type"), is("application/json")); @@ -1508,7 +1503,7 @@ void whenPuttingASingleAgentProfileWithoutContentTypeThenBodyIsExpected() .block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Body Is Expected assertThat(recordedRequest.getBody().readUtf8(), @@ -1532,7 +1527,7 @@ void whenPostingASingleAgentProfileThenMethodIsPost() throws InterruptedExceptio .block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Method Is Post assertThat(recordedRequest.getMethod(), is("POST")); @@ -1553,7 +1548,7 @@ void whenPostingASingleAgentProfileThenPathIsExpected() throws InterruptedExcept .block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Path Is Expected assertThat(recordedRequest.getPath(), is( @@ -1578,7 +1573,7 @@ void whenPostingASingleAgentProfileWithContentTypeTextPlainThenContentTypeHeader .block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Content Type Header Is Text Plain assertThat(recordedRequest.getHeader("content-type"), is("text/plain")); @@ -1600,7 +1595,7 @@ void whenPostingASingleAgentProfileWithoutContentTypeThenContentTypeHeaderIsAppl .block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Content Type Header Is Application Json assertThat(recordedRequest.getHeader("content-type"), is("application/json")); @@ -1624,7 +1619,7 @@ void whenPostingASingleAgentProfileWithoutContentTypeThenBodyIsExpected() .block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Body Is Expected assertThat(recordedRequest.getBody().readUtf8(), @@ -1644,7 +1639,7 @@ void whenGettingProfilesThenMethodIsGet() throws InterruptedException { .block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Method Is Get assertThat(recordedRequest.getMethod(), is("GET")); @@ -1663,7 +1658,7 @@ void whenGettingProfilesThenPathIsExpected() throws InterruptedException { .block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Path Is Expected assertThat(recordedRequest.getPath(), is( @@ -1685,7 +1680,7 @@ void whenGettingProfilesWithSinceParameterThenPathIsExpected() throws Interrupte .block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Path Is Expected assertThat(recordedRequest.getPath(), is( @@ -1704,7 +1699,7 @@ void whenGettingActivityByUriThenMethodIsGet() throws InterruptedException { .getActivity(r -> r.activityId(URI.create("https://example.com/activity/simplestatement"))) .block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Method Is Get assertThat(recordedRequest.getMethod(), is("GET")); @@ -1718,7 +1713,7 @@ void whenGettingActivityByStringThenMethodIsGet() throws InterruptedException { // When Getting Activity By String client.getActivity(r -> r.activityId("https://example.com/activity/simplestatement")).block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Method Is Get assertThat(recordedRequest.getMethod(), is("GET")); @@ -1734,7 +1729,7 @@ void whenGettingActivityThenPathIsExpected() throws InterruptedException { .getActivity(r -> r.activityId(URI.create("https://example.com/activity/simplestatement"))) .block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Path Is Expected assertThat(recordedRequest.getPath(), @@ -1769,7 +1764,7 @@ void whenGettingAgentsThenMethodIsGet() throws InterruptedException { // When Getting Agents client.getAgents(r -> r.agent(a -> a.mbox("mailto:another@example.com"))).block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Method Is Get assertThat(recordedRequest.getMethod(), is("GET")); @@ -1783,7 +1778,7 @@ void whenGettingAgentsThenPathIsExpected() throws InterruptedException { // When Getting Agents client.getAgents(r -> r.agent(a -> a.mbox("mailto:another@example.com"))).block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Path Is Expected assertThat(recordedRequest.getPath(), is( @@ -1800,8 +1795,8 @@ void whenGettingAgentsThenBodyIsInstanceOfPerson() throws InterruptedException { .addHeader("Content-Type", "application/json; charset=utf-8")); // When Getting Agents - final var response = client.getAgents(r -> r.agent(a -> a.mbox("mailto:another@example.com"))) - .block(); + final var response = + client.getAgents(r -> r.agent(a -> a.mbox("mailto:another@example.com"))).block(); // Then Body Is Instance Of Activity assertThat(response.getBody(), instanceOf(Person.class)); @@ -1821,7 +1816,7 @@ void whenGettingAboutThenMethodIsGet() throws InterruptedException { // When Getting About client.getAbout().block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Method Is Get assertThat(recordedRequest.getMethod(), is("GET")); @@ -1839,7 +1834,7 @@ void whenGettingAboutThenPathIsExpected() throws InterruptedException { // When Getting About client.getAbout().block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Path Is Expected assertThat(recordedRequest.getPath(), is("/about")); @@ -1873,7 +1868,7 @@ void whenGettingASingleActivityProfileThenMethodIsGet() throws InterruptedExcept .profileId("bookmark"), String.class).block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Method Is Get assertThat(recordedRequest.getMethod(), is("GET")); @@ -1889,7 +1884,7 @@ void whenGettingASingleActivityProfileThenPathIsExpected() throws InterruptedExc .profileId("bookmark"), String.class).block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Path Is Expected assertThat(recordedRequest.getPath(), is( @@ -1952,7 +1947,7 @@ void whenPostingASingleActivityProfileThenMethodIsPost() throws InterruptedExcep .block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Method Is Post assertThat(recordedRequest.getMethod(), is("POST")); @@ -1972,7 +1967,7 @@ void whenPostingASingleActivityProfileThenPathIsExpected() throws InterruptedExc .block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Path Is Expected assertThat(recordedRequest.getPath(), is( @@ -1996,7 +1991,7 @@ void whenPostingASingleActivityProfileWithContentTypeTextPlainThenContentTypeHea .block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Content Type Header Is Text Plain assertThat(recordedRequest.getHeader("content-type"), is("text/plain")); @@ -2017,7 +2012,7 @@ void whenPostingASingleActivityProfileWithoutContentTypeThenContentTypeHeaderIsA .block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Content Type Header Is Application Json assertThat(recordedRequest.getHeader("content-type"), is("application/json")); @@ -2039,7 +2034,7 @@ void whenPuttingASingleActivityProfileThenMethodIsPut() throws InterruptedExcept .block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Method Is Post assertThat(recordedRequest.getMethod(), is("PUT")); @@ -2059,7 +2054,7 @@ void whenPuttingASingleActivityProfileThenPathIsExpected() throws InterruptedExc .block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Path Is Expected assertThat(recordedRequest.getPath(), is( @@ -2083,7 +2078,7 @@ void whenPuttingASingleActivityProfileWithContentTypeTextPlainThenContentTypeHea .block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Content Type Header Is Text Plain assertThat(recordedRequest.getHeader("content-type"), is("text/plain")); @@ -2104,7 +2099,7 @@ void whenPuttingASingleActivityProfileWithoutContentTypeThenContentTypeHeaderIsA .block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Content Type Header Is Application Json assertThat(recordedRequest.getHeader("content-type"), is("application/json")); @@ -2122,7 +2117,7 @@ void whenDeletingASingleActivityProfileThenMethodIsDelete() throws InterruptedEx .profileId("bookmark")).block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Method Is Delete assertThat(recordedRequest.getMethod(), is("DELETE")); @@ -2138,7 +2133,7 @@ void whenDeletingASingleActivityProfileThenPathIsExpected() throws InterruptedEx .profileId("bookmark")).block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Path Is Expected assertThat(recordedRequest.getPath(), is( @@ -2158,7 +2153,7 @@ void whenGettingActivityProfilesWithSinceParameterThenMethodIsGet() throws Inter .block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Method Is Get assertThat(recordedRequest.getMethod(), is("GET")); @@ -2178,7 +2173,7 @@ void whenGettingActivityProfilesWithSinceParameterThenPathIsExpected() .block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Path Is Expected assertThat(recordedRequest.getPath(), is( @@ -2197,7 +2192,7 @@ void whenGettingActivityProfilesWithoutSinceParameterThenPathIsExpected() .block(); - final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + final var recordedRequest = mockWebServer.takeRequest(); // Then Path Is Expected assertThat(recordedRequest.getPath(), diff --git a/xapi-model/src/main/java/dev/learning/xapi/model/Attachment.java b/xapi-model/src/main/java/dev/learning/xapi/model/Attachment.java index 45b7dfb9..f40d697c 100644 --- a/xapi-model/src/main/java/dev/learning/xapi/model/Attachment.java +++ b/xapi-model/src/main/java/dev/learning/xapi/model/Attachment.java @@ -14,6 +14,7 @@ import java.security.NoSuchAlgorithmException; import java.util.Locale; import lombok.Builder; +import lombok.EqualsAndHashCode; import lombok.Value; /** @@ -27,6 +28,7 @@ @Value @Builder @JsonInclude(Include.NON_EMPTY) +@EqualsAndHashCode(of = "sha2") public class Attachment { /**