From 86aae144cc04a96bd7c018fac23dc5c7c7038cfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolas=20P=C3=A9pin-Perreault?= Date: Sun, 14 Mar 2021 17:36:07 +0100 Subject: [PATCH] feat: added ImmutableRecordCopier - updated README and tests to use ImmutableRecordCopier --- README.md | 80 +++++-- pom.xml | 7 + .../immutables/ImmutableRecordCopier.java | 198 ++++++++++++++++++ .../immutables/record/ZeebeStyle.java | 3 +- .../immutables/ImmutableRecordCopierTest.java | 41 ++++ .../ImmutableRecordSerializationTest.java | 23 +- .../record/IntentTypeIdResolverTest.java | 57 +++++ .../record/ValueTypeIdResolverTest.java | 58 +++++ .../record/assertj/ImmutableAssertions.java | 26 --- .../record/assertj/ImmutableRecordAssert.java | 182 ---------------- 10 files changed, 439 insertions(+), 236 deletions(-) create mode 100644 src/main/java/io/zeebe/protocol/immutables/ImmutableRecordCopier.java create mode 100644 src/test/java/io/zeebe/protocol/immutables/ImmutableRecordCopierTest.java create mode 100644 src/test/java/io/zeebe/protocol/immutables/record/IntentTypeIdResolverTest.java create mode 100644 src/test/java/io/zeebe/protocol/immutables/record/ValueTypeIdResolverTest.java delete mode 100644 src/test/java/io/zeebe/protocol/immutables/record/assertj/ImmutableAssertions.java delete mode 100644 src/test/java/io/zeebe/protocol/immutables/record/assertj/ImmutableRecordAssert.java diff --git a/README.md b/README.md index 30a9598..5c8bc43 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,3 @@ -[![Build Status](https://travis-ci.org/zeebe-io/zeebe-protocol-immutables.svg?branch=master)](https://travis-ci.org/zeebe-io/zeebe-protocol-immutables) - # zeebe-protocol-immutables This library provides an implementation of the Zeebe protocol which can be serialized and @@ -61,9 +59,8 @@ For example, an exporter could do the following: ```java private static final ObjectMapper MAPPER = new ObjectMapper(); -public void export(Record record) { - - final Record clone = ImmutableRecord.builder().from(record); +public void export(Record record) { + final Record clone = ImmutableRecordCopier.deepCopyOf(record); try(final OutputStream out = createOutputStream()) { MAPPER.writeValue(out, clone); @@ -73,17 +70,28 @@ public void export(Record record) { You could then have configured the `ObjectMapper` to write YAML, CBOR, etc., beforehand. -### Limitations +### Copying and comparing + +If you want to compare two `Record` instances with potentially different implementations, the +recommended way is to first convert them both to `ImmutableRecord`. + +The easiest way is to use the `ImmutableRecordCopier` utility class. You can do a deep copy of any +`Record<>` as is, and it will return an equivalent `ImmutableRecord<>`. + +```java +final Record record = ...; +final ImmutableRecord copiedRecord = ImmutableRecordCopier.deepCopyOf(record); +``` -There are some known limitations regarding serialization. Serializing as shown above will not -serialize nested types, e.g. the value itself. If you want to do so recursively, you first have to -clone the value itself and set it in the builder. I'd like to improve this, but haven't gotten to it -yet, as I was mostly focused on deserialization. +If you just want to copy the record value, or if you want to have a +`ImmutableRecord`, for example, then you can copy the value first and copy +the record yourself as: -Another known limitation is that an `ImmutableRecord` is not necessarily equal to the `Record`, even -if they are logically equal. That is to say, serializing a record, then deserializing will not give -two records which are `Object#equals()`. This is expected as `Record` is just an interface, but can -be surprising nonetheless. +```java +final Record record = ...; +final ImmutableDeploymentRecordValue copiedValue = ImmutableRecordCopier.deepCopyOf(record.getValueType(), record.getValue()); +final ImmutableRecord copiedRecord = ImmutableRecord.builder().from(record).value(copiedValue).build(); +``` ## Development @@ -122,17 +130,45 @@ the generated `Immutable*` versions of these classes. ### Deserialization -The `Record` class in the protocol is typed; the value's concrete class and the intent's concrete -enum are both derived from the `Record#getValueType`. As such, both fields are annotated using -`@JsonTypeInfo` which points to that property, and are given a corresponding type resolver (see -`ValueTypeIdResolver` and `IntentTypeIdResolver`), which allows Jackson to properly deserialize a -`Record` concretely as `ImmutableRecord`. +Since `Record` is a typed interface, we need to resolve `T` during deserialization. The way to do +so in Zeebe is by using the `Record#getValueType()`. + +> You can look at `ValueTypeIdResolver` to see how we resolve the value type to the right value +> class. + +The `Intent` of the record is an interface, which also needs to be resolved to the correct type +during deserialization. + +> You can look at `IntentTypeIdResolver` to see how raw intents are mapped to the right type. + +With these out of the way, you can then easily deserialize a raw JSON payload into an +`ImmutableRecord`, where all types are properly resolved. See [usage](#usage) for more. ## Testing -Currently testing is sort of a playground - I decided to go for property based testing using jqwik, -and I'm learning as I go, so there are bound to be mistakes in how I'm doing this. -Contributions are more than welcome :) +We assume that the `immutables` library works properly, and as such focus primarily on the +serialization capabilities. + +To test this, we use two built-in exporters: specifically the `DebugHttpExporter` and +the `RecordingExporter`. + +We start a Zeebe broker, run a sample workload, then wait for all records to be exported. + +> NOTE: waiting for all records to be exported is difficult due to the black box nature of the test +> infrastructure, so we simply wait up until some seconds have passed since the last record was +> exported. This is relatively safe since the `ExporterIntegrationRule` already waits until the +> workload is finished and exported to the `RecordingExporter`, so waiting just a few seconds more +> is mostly just to be safe. + +The `DebugHttpExporter` provides an endpoint where we can get all exported records as JSON. This is +our sample data set from which we can test the deserialization capabilities of the library. + +The `RecordingExporter` provides us with the raw exported records against which we can then compare. + +To simplify the comparison, the raw records are first converted to an equivalent +`ImmutableRecord` representation. This may seem tautological, but as mentioned, we assume the +generated code is valid (which includes the builders), and just want to verify that serialization +works as expected. ## Code of Conduct diff --git a/pom.xml b/pom.xml index 85d93a5..367a1f9 100644 --- a/pom.xml +++ b/pom.xml @@ -138,6 +138,13 @@ test + + org.junit.jupiter + junit-jupiter-params + ${version.junit} + test + + org.assertj assertj-core diff --git a/src/main/java/io/zeebe/protocol/immutables/ImmutableRecordCopier.java b/src/main/java/io/zeebe/protocol/immutables/ImmutableRecordCopier.java new file mode 100644 index 0000000..181669d --- /dev/null +++ b/src/main/java/io/zeebe/protocol/immutables/ImmutableRecordCopier.java @@ -0,0 +1,198 @@ +/* + * Copyright © 2020 camunda services GmbH (info@camunda.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.zeebe.protocol.immutables; + +import io.zeebe.protocol.immutables.record.ImmutableDeployedWorkflow; +import io.zeebe.protocol.immutables.record.ImmutableDeploymentRecordValue; +import io.zeebe.protocol.immutables.record.ImmutableDeploymentResource; +import io.zeebe.protocol.immutables.record.ImmutableErrorRecordValue; +import io.zeebe.protocol.immutables.record.ImmutableIncidentRecordValue; +import io.zeebe.protocol.immutables.record.ImmutableJobBatchRecordValue; +import io.zeebe.protocol.immutables.record.ImmutableJobRecordValue; +import io.zeebe.protocol.immutables.record.ImmutableMessageRecordValue; +import io.zeebe.protocol.immutables.record.ImmutableMessageStartEventSubscriptionRecordValue; +import io.zeebe.protocol.immutables.record.ImmutableMessageSubscriptionRecordValue; +import io.zeebe.protocol.immutables.record.ImmutableRecord; +import io.zeebe.protocol.immutables.record.ImmutableRecord.Builder; +import io.zeebe.protocol.immutables.record.ImmutableTimerRecordValue; +import io.zeebe.protocol.immutables.record.ImmutableVariableDocumentRecordValue; +import io.zeebe.protocol.immutables.record.ImmutableVariableRecordValue; +import io.zeebe.protocol.immutables.record.ImmutableWorkflowInstanceCreationRecordValue; +import io.zeebe.protocol.immutables.record.ImmutableWorkflowInstanceRecordValue; +import io.zeebe.protocol.immutables.record.ImmutableWorkflowInstanceResultRecordValue; +import io.zeebe.protocol.immutables.record.ImmutableWorkflowInstanceSubscriptionRecordValue; +import io.zeebe.protocol.record.Record; +import io.zeebe.protocol.record.RecordValue; +import io.zeebe.protocol.record.ValueType; +import io.zeebe.protocol.record.value.DeploymentRecordValue; +import io.zeebe.protocol.record.value.ErrorRecordValue; +import io.zeebe.protocol.record.value.IncidentRecordValue; +import io.zeebe.protocol.record.value.JobBatchRecordValue; +import io.zeebe.protocol.record.value.JobRecordValue; +import io.zeebe.protocol.record.value.MessageRecordValue; +import io.zeebe.protocol.record.value.MessageStartEventSubscriptionRecordValue; +import io.zeebe.protocol.record.value.MessageSubscriptionRecordValue; +import io.zeebe.protocol.record.value.TimerRecordValue; +import io.zeebe.protocol.record.value.VariableDocumentRecordValue; +import io.zeebe.protocol.record.value.VariableRecordValue; +import io.zeebe.protocol.record.value.WorkflowInstanceCreationRecordValue; +import io.zeebe.protocol.record.value.WorkflowInstanceRecordValue; +import io.zeebe.protocol.record.value.WorkflowInstanceResultRecordValue; +import io.zeebe.protocol.record.value.WorkflowInstanceSubscriptionRecordValue; +import io.zeebe.protocol.record.value.deployment.DeployedWorkflow; +import io.zeebe.protocol.record.value.deployment.DeploymentResource; +import java.util.ArrayList; +import java.util.List; + +/** + * Utility class to perform deep copy of any {@link Record<>} implementation to an equivalent {@link + * ImmutableRecord<>} implementation. + * + *

This is necessary as by default the {@code copyOf()} methods generated by the library perform + * only shallow copies to some extent - that is, they will correctly detect when a member has an + * equivalent {@code Immutable*} type, but not when said member is a collection or a container. + * + *

If you want to perform deep copies, for example to compare two implementations, you can use + * the methods below. + */ +public final class ImmutableRecordCopier { + + private ImmutableRecordCopier() {} + + @SuppressWarnings("unchecked") + public static ImmutableRecord deepCopyOfRecord( + final Record record) { + final U value = (U) deepCopyOfRecordValue(record.getValueType(), record.getValue()); + final Builder originalBuilder = ImmutableRecord.builder().from(record); + final Builder convertedBuilder = (Builder) originalBuilder; + + return convertedBuilder.value(value).build(); + } + + @SuppressWarnings("unchecked") + public static T deepCopyOfRecordValue( + final ValueType type, final T value) { + switch (type) { + case JOB: + return (U) ImmutableJobRecordValue.builder().from((JobRecordValue) value).build(); + case DEPLOYMENT: + return (U) deepCopyOfDeploymentRecordValue((DeploymentRecordValue) value); + case WORKFLOW_INSTANCE: + return (U) + ImmutableWorkflowInstanceRecordValue.builder() + .from((WorkflowInstanceRecordValue) value) + .build(); + case INCIDENT: + return (U) ImmutableIncidentRecordValue.builder().from((IncidentRecordValue) value).build(); + case MESSAGE: + return (U) ImmutableMessageRecordValue.builder().from((MessageRecordValue) value).build(); + case MESSAGE_SUBSCRIPTION: + return (U) + ImmutableMessageSubscriptionRecordValue.builder() + .from((MessageSubscriptionRecordValue) value) + .build(); + case WORKFLOW_INSTANCE_SUBSCRIPTION: + return (U) + ImmutableWorkflowInstanceSubscriptionRecordValue.builder() + .from((WorkflowInstanceSubscriptionRecordValue) value) + .build(); + case JOB_BATCH: + return (U) deepCopyOfJobBatchRecordValue((JobBatchRecordValue) value); + case TIMER: + return (U) ImmutableTimerRecordValue.builder().from((TimerRecordValue) value).build(); + case MESSAGE_START_EVENT_SUBSCRIPTION: + return (U) + ImmutableMessageStartEventSubscriptionRecordValue.builder() + .from((MessageStartEventSubscriptionRecordValue) value) + .build(); + case VARIABLE: + return (U) ImmutableVariableRecordValue.builder().from((VariableRecordValue) value).build(); + case VARIABLE_DOCUMENT: + return (U) + ImmutableVariableDocumentRecordValue.builder() + .from((VariableDocumentRecordValue) value) + .build(); + case WORKFLOW_INSTANCE_CREATION: + return (U) + ImmutableWorkflowInstanceCreationRecordValue.builder() + .from((WorkflowInstanceCreationRecordValue) value) + .build(); + case ERROR: + return (U) ImmutableErrorRecordValue.builder().from((ErrorRecordValue) value).build(); + case WORKFLOW_INSTANCE_RESULT: + return (U) + ImmutableWorkflowInstanceResultRecordValue.builder() + .from((WorkflowInstanceResultRecordValue) value) + .build(); + case SBE_UNKNOWN: + case NULL_VAL: + default: + throw new IllegalArgumentException("Unknown value type " + type); + } + } + + private static ImmutableDeploymentRecordValue deepCopyOfDeploymentRecordValue( + final DeploymentRecordValue value) { + final List workflows = new ArrayList<>(); + final List resources = new ArrayList<>(); + + for (final DeployedWorkflow workflow : value.getDeployedWorkflows()) { + final ImmutableDeployedWorkflow immutableWorkflow; + if (workflow instanceof ImmutableDeployedWorkflow) { + immutableWorkflow = (ImmutableDeployedWorkflow) workflow; + } else { + immutableWorkflow = ImmutableDeployedWorkflow.builder().from(workflow).build(); + } + + workflows.add(immutableWorkflow); + } + + for (final DeploymentResource resource : value.getResources()) { + final ImmutableDeploymentResource immutableResource; + if (resource instanceof ImmutableDeploymentResource) { + immutableResource = (ImmutableDeploymentResource) resource; + } else { + immutableResource = ImmutableDeploymentResource.builder().from(resource).build(); + } + + resources.add(immutableResource); + } + + return ImmutableDeploymentRecordValue.builder() + .from(value) + .resources(resources) + .deployedWorkflows(workflows) + .build(); + } + + private static ImmutableJobBatchRecordValue deepCopyOfJobBatchRecordValue( + final JobBatchRecordValue value) { + final List jobs = new ArrayList<>(); + + for (final JobRecordValue job : value.getJobs()) { + final ImmutableJobRecordValue immutableJob; + if (job instanceof ImmutableJobRecordValue) { + immutableJob = (ImmutableJobRecordValue) job; + } else { + immutableJob = ImmutableJobRecordValue.builder().from(job).build(); + } + + jobs.add(immutableJob); + } + + return ImmutableJobBatchRecordValue.builder().from(value).jobs(jobs).build(); + } +} diff --git a/src/main/java/io/zeebe/protocol/immutables/record/ZeebeStyle.java b/src/main/java/io/zeebe/protocol/immutables/record/ZeebeStyle.java index c829938..5df2cb6 100644 --- a/src/main/java/io/zeebe/protocol/immutables/record/ZeebeStyle.java +++ b/src/main/java/io/zeebe/protocol/immutables/record/ZeebeStyle.java @@ -34,6 +34,7 @@ validationMethod = ValidationMethod.NONE, defaultAsDefault = true, headerComments = true, - clearBuilder = true) + clearBuilder = true, + deepImmutablesDetection = true) @JsonSerialize public @interface ZeebeStyle {} diff --git a/src/test/java/io/zeebe/protocol/immutables/ImmutableRecordCopierTest.java b/src/test/java/io/zeebe/protocol/immutables/ImmutableRecordCopierTest.java new file mode 100644 index 0000000..02c6b5f --- /dev/null +++ b/src/test/java/io/zeebe/protocol/immutables/ImmutableRecordCopierTest.java @@ -0,0 +1,41 @@ +/* + * Copyright © 2020 camunda services GmbH (info@camunda.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.zeebe.protocol.immutables; + +import static org.assertj.core.api.Assertions.assertThatCode; + +import io.zeebe.protocol.record.ValueType; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.EnumSource.Mode; + +final class ImmutableRecordCopierTest { + + /** + * This test checks that every known record value type is handled by the {@link + * ImmutableRecordCopier}, and should fail if it isn't. This is a smoke test when updating Zeebe + * versions to detect new {@link io.zeebe.protocol.record.ValueType} instances. + */ + @EnumSource( + value = ValueType.class, + names = {"NULL_VAL", "SBE_UNKNOWN"}, + mode = Mode.EXCLUDE) + @ParameterizedTest + void shouldHandleEveryKnownValueType(final ValueType type) { + assertThatCode(() -> ImmutableRecordCopier.deepCopyOfRecordValue(type, null)) + .isNotInstanceOf(IllegalArgumentException.class); + } +} diff --git a/src/test/java/io/zeebe/protocol/immutables/record/ImmutableRecordSerializationTest.java b/src/test/java/io/zeebe/protocol/immutables/record/ImmutableRecordSerializationTest.java index 6551168..924d336 100644 --- a/src/test/java/io/zeebe/protocol/immutables/record/ImmutableRecordSerializationTest.java +++ b/src/test/java/io/zeebe/protocol/immutables/record/ImmutableRecordSerializationTest.java @@ -20,7 +20,7 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import io.zeebe.broker.exporter.debug.DebugHttpExporter; -import io.zeebe.protocol.immutables.record.assertj.ImmutableAssertions; +import io.zeebe.protocol.immutables.ImmutableRecordCopier; import io.zeebe.protocol.record.Record; import io.zeebe.protocol.record.RecordValue; import io.zeebe.test.exporter.ExporterIntegrationRule; @@ -31,20 +31,32 @@ import java.util.Map; import java.util.stream.Collectors; import org.awaitility.Awaitility; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; final class ImmutableRecordSerializationTest { private static final ObjectMapper MAPPER = new ObjectMapper(); + private final ExporterIntegrationRule testHarness = new ExporterIntegrationRule(); + + @BeforeEach + void beforeEach() { + testHarness.configure("debug", DebugHttpExporter.class, Map.of("port", 9000, "limit", 3000)); + } + + @AfterEach + void afterEach() { + testHarness.stop(); + } + @Test void shouldSerializeRecords() throws IOException { // given - final ExporterIntegrationRule testHarness = new ExporterIntegrationRule(); - testHarness.configure("debug", DebugHttpExporter.class, Map.of("port", 9000, "limit", 3000)); + testHarness.start(); // when - testHarness.start(); testHarness.performSampleWorkload(); // then @@ -63,7 +75,8 @@ void shouldSerializeRecords() throws IOException { final ImmutableRecord deserializedRecord = deserializedRecords.get((exportedCount - 1) - i); final Record exportedRecord = exportedRecords.get(i); - ImmutableAssertions.assertThat(deserializedRecord).isEqualTo(exportedRecord); + assertThat(deserializedRecord) + .isEqualTo(ImmutableRecordCopier.deepCopyOfRecord(exportedRecord)); } } diff --git a/src/test/java/io/zeebe/protocol/immutables/record/IntentTypeIdResolverTest.java b/src/test/java/io/zeebe/protocol/immutables/record/IntentTypeIdResolverTest.java new file mode 100644 index 0000000..8d473f2 --- /dev/null +++ b/src/test/java/io/zeebe/protocol/immutables/record/IntentTypeIdResolverTest.java @@ -0,0 +1,57 @@ +/* + * Copyright © 2020 camunda services GmbH (info@camunda.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.zeebe.protocol.immutables.record; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.deser.BeanDeserializerFactory; +import com.fasterxml.jackson.databind.deser.DefaultDeserializationContext; +import io.zeebe.protocol.record.ValueType; +import io.zeebe.protocol.record.intent.Intent; +import java.io.IOException; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.EnumSource.Mode; + +final class IntentTypeIdResolverTest { + + /** + * This test checks that every known intent type is handled. It doesn't validate the correctness + * of the result - its goal is to be a smoke test to make sure no intents are forgotten + */ + @EnumSource( + value = ValueType.class, + names = {"NULL_VAL", "SBE_UNKNOWN"}, + mode = Mode.EXCLUDE) + @ParameterizedTest + void shouldHandleEveryKnownValueType(final ValueType type) throws IOException { + // given + final ObjectMapper mapper = new ObjectMapper(); + final DefaultDeserializationContext.Impl baseContext = + new DefaultDeserializationContext.Impl(BeanDeserializerFactory.instance); + final DefaultDeserializationContext context = + baseContext.createInstance( + mapper.getDeserializationConfig(), + mapper.createParser("{}"), + mapper.getInjectableValues()); + final IntentTypeIdResolver resolver = new IntentTypeIdResolver(); + final JavaType resolvedType = resolver.typeFromId(context, resolver.idFromValue(type)); + + assertThat(Intent.class).isAssignableFrom(resolvedType.getRawClass()); + } +} diff --git a/src/test/java/io/zeebe/protocol/immutables/record/ValueTypeIdResolverTest.java b/src/test/java/io/zeebe/protocol/immutables/record/ValueTypeIdResolverTest.java new file mode 100644 index 0000000..b832b5a --- /dev/null +++ b/src/test/java/io/zeebe/protocol/immutables/record/ValueTypeIdResolverTest.java @@ -0,0 +1,58 @@ +/* + * Copyright © 2020 camunda services GmbH (info@camunda.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.zeebe.protocol.immutables.record; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.deser.BeanDeserializerFactory; +import com.fasterxml.jackson.databind.deser.DefaultDeserializationContext; +import io.zeebe.protocol.record.RecordValue; +import io.zeebe.protocol.record.ValueType; +import java.io.IOException; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.EnumSource.Mode; + +final class ValueTypeIdResolverTest { + + /** + * This test checks that every known record value type is handled. It doesn't validate the + * correctness of the result - its goal is to be a smoke test to make sure no value types are + * forgotten + */ + @EnumSource( + value = ValueType.class, + names = {"NULL_VAL", "SBE_UNKNOWN"}, + mode = Mode.EXCLUDE) + @ParameterizedTest + void shouldHandleEveryKnownValueType(final ValueType type) throws IOException { + // given + final ObjectMapper mapper = new ObjectMapper(); + final DefaultDeserializationContext.Impl baseContext = + new DefaultDeserializationContext.Impl(BeanDeserializerFactory.instance); + final DefaultDeserializationContext context = + baseContext.createInstance( + mapper.getDeserializationConfig(), + mapper.createParser("{}"), + mapper.getInjectableValues()); + final ValueTypeIdResolver resolver = new ValueTypeIdResolver(); + final JavaType resolvedType = resolver.typeFromId(context, resolver.idFromValue(type)); + + assertThat(RecordValue.class).isAssignableFrom(resolvedType.getRawClass()); + } +} diff --git a/src/test/java/io/zeebe/protocol/immutables/record/assertj/ImmutableAssertions.java b/src/test/java/io/zeebe/protocol/immutables/record/assertj/ImmutableAssertions.java deleted file mode 100644 index 3519f0a..0000000 --- a/src/test/java/io/zeebe/protocol/immutables/record/assertj/ImmutableAssertions.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright © 2020 camunda services GmbH (info@camunda.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.zeebe.protocol.immutables.record.assertj; - -import io.zeebe.protocol.immutables.record.ImmutableRecord; -import io.zeebe.protocol.record.RecordValue; - -public final class ImmutableAssertions { - public static ImmutableRecordAssert assertThat( - final ImmutableRecord record) { - return new ImmutableRecordAssert<>(record); - } -} diff --git a/src/test/java/io/zeebe/protocol/immutables/record/assertj/ImmutableRecordAssert.java b/src/test/java/io/zeebe/protocol/immutables/record/assertj/ImmutableRecordAssert.java deleted file mode 100644 index 206cf89..0000000 --- a/src/test/java/io/zeebe/protocol/immutables/record/assertj/ImmutableRecordAssert.java +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Copyright © 2020 camunda services GmbH (info@camunda.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.zeebe.protocol.immutables.record.assertj; - -import io.zeebe.protocol.immutables.record.ImmutableDeployedWorkflow; -import io.zeebe.protocol.immutables.record.ImmutableDeploymentRecordValue; -import io.zeebe.protocol.immutables.record.ImmutableDeploymentResource; -import io.zeebe.protocol.immutables.record.ImmutableErrorRecordValue; -import io.zeebe.protocol.immutables.record.ImmutableIncidentRecordValue; -import io.zeebe.protocol.immutables.record.ImmutableJobBatchRecordValue; -import io.zeebe.protocol.immutables.record.ImmutableJobRecordValue; -import io.zeebe.protocol.immutables.record.ImmutableMessageRecordValue; -import io.zeebe.protocol.immutables.record.ImmutableMessageStartEventSubscriptionRecordValue; -import io.zeebe.protocol.immutables.record.ImmutableMessageSubscriptionRecordValue; -import io.zeebe.protocol.immutables.record.ImmutableRecord; -import io.zeebe.protocol.immutables.record.ImmutableTimerRecordValue; -import io.zeebe.protocol.immutables.record.ImmutableVariableDocumentRecordValue; -import io.zeebe.protocol.immutables.record.ImmutableVariableRecordValue; -import io.zeebe.protocol.immutables.record.ImmutableWorkflowInstanceCreationRecordValue; -import io.zeebe.protocol.immutables.record.ImmutableWorkflowInstanceRecordValue; -import io.zeebe.protocol.immutables.record.ImmutableWorkflowInstanceResultRecordValue; -import io.zeebe.protocol.immutables.record.ImmutableWorkflowInstanceSubscriptionRecordValue; -import io.zeebe.protocol.record.Record; -import io.zeebe.protocol.record.RecordValue; -import io.zeebe.protocol.record.ValueType; -import io.zeebe.protocol.record.value.DeploymentRecordValue; -import io.zeebe.protocol.record.value.ErrorRecordValue; -import io.zeebe.protocol.record.value.IncidentRecordValue; -import io.zeebe.protocol.record.value.JobBatchRecordValue; -import io.zeebe.protocol.record.value.JobRecordValue; -import io.zeebe.protocol.record.value.MessageRecordValue; -import io.zeebe.protocol.record.value.MessageStartEventSubscriptionRecordValue; -import io.zeebe.protocol.record.value.MessageSubscriptionRecordValue; -import io.zeebe.protocol.record.value.TimerRecordValue; -import io.zeebe.protocol.record.value.VariableDocumentRecordValue; -import io.zeebe.protocol.record.value.VariableRecordValue; -import io.zeebe.protocol.record.value.WorkflowInstanceCreationRecordValue; -import io.zeebe.protocol.record.value.WorkflowInstanceRecordValue; -import io.zeebe.protocol.record.value.WorkflowInstanceResultRecordValue; -import io.zeebe.protocol.record.value.WorkflowInstanceSubscriptionRecordValue; -import io.zeebe.protocol.record.value.deployment.DeployedWorkflow; -import io.zeebe.protocol.record.value.deployment.DeploymentResource; -import java.util.Collections; -import org.assertj.core.api.AbstractObjectAssert; - -public final class ImmutableRecordAssert - extends AbstractObjectAssert, ImmutableRecord> { - - public ImmutableRecordAssert(final ImmutableRecord record) { - super(record, ImmutableRecordAssert.class); - } - - /** - * Equality between two records with possible different implementations is performed by first - * copying the expected value into an equivalent {@code Immutable*} representation. This is done - * because we can then easily compare equality between both objects as they now have the same - * implementation. - * - *

NOTE: it may seem tautological to assert like this, but the generated builder is typically - * quite good, so building, say, a {@link ImmutableDeploymentRecordValue} out of a {@link - * DeploymentRecordValue} should produce a valid copy, which we can use to compare against the - * deserialized object. - * - *

NOTE: there is one caveat, which is with records/values with nested {@code Immutable*} - * classes. Since the builder will not convert those but rather just use the interface type, they - * will not be directly comparable, so they must be converted manually. - * - * @param expected the record to compare against - * @param the type of the record's value - * @return this assert for chaining - */ - public ImmutableRecordAssert isEqualTo(final Record expected) { - final ImmutableRecord immutableExpected = copyRecordForComparison(expected); - - objects.assertEqual(myself.info, actual, immutableExpected); - return myself; - } - - @SuppressWarnings("unchecked") - private ImmutableRecord copyRecordForComparison( - final Record record) { - final U value = (U) copyRecordValueForComparison(record.getValueType(), record.getValue()); - return ImmutableRecord.builder().from(record).value(value).build(); - } - - private RecordValue copyRecordValueForComparison(final ValueType type, final RecordValue value) { - switch (type) { - case JOB: - return ImmutableJobRecordValue.builder().from((JobRecordValue) value).build(); - case DEPLOYMENT: - return copyDeploymentRecordForComparison((DeploymentRecordValue) value); - case WORKFLOW_INSTANCE: - return ImmutableWorkflowInstanceRecordValue.builder() - .from((WorkflowInstanceRecordValue) value) - .build(); - case INCIDENT: - return ImmutableIncidentRecordValue.builder().from((IncidentRecordValue) value).build(); - case MESSAGE: - return ImmutableMessageRecordValue.builder().from((MessageRecordValue) value).build(); - case MESSAGE_SUBSCRIPTION: - return ImmutableMessageSubscriptionRecordValue.builder() - .from((MessageSubscriptionRecordValue) value) - .build(); - case WORKFLOW_INSTANCE_SUBSCRIPTION: - return ImmutableWorkflowInstanceSubscriptionRecordValue.builder() - .from((WorkflowInstanceSubscriptionRecordValue) value) - .build(); - case JOB_BATCH: - return copyJobBatchRecordForComparison((JobBatchRecordValue) value); - case TIMER: - return ImmutableTimerRecordValue.builder().from((TimerRecordValue) value).build(); - case MESSAGE_START_EVENT_SUBSCRIPTION: - return ImmutableMessageStartEventSubscriptionRecordValue.builder() - .from((MessageStartEventSubscriptionRecordValue) value) - .build(); - case VARIABLE: - return ImmutableVariableRecordValue.builder().from((VariableRecordValue) value).build(); - case VARIABLE_DOCUMENT: - return ImmutableVariableDocumentRecordValue.builder() - .from((VariableDocumentRecordValue) value) - .build(); - case WORKFLOW_INSTANCE_CREATION: - return ImmutableWorkflowInstanceCreationRecordValue.builder() - .from((WorkflowInstanceCreationRecordValue) value) - .build(); - case ERROR: - return ImmutableErrorRecordValue.builder().from((ErrorRecordValue) value).build(); - case WORKFLOW_INSTANCE_RESULT: - return ImmutableWorkflowInstanceResultRecordValue.builder() - .from((WorkflowInstanceResultRecordValue) value) - .build(); - case SBE_UNKNOWN: - case NULL_VAL: - default: - throw new IllegalArgumentException("Unknown value type " + type); - } - } - - private ImmutableDeploymentRecordValue copyDeploymentRecordForComparison( - final DeploymentRecordValue value) { - final ImmutableDeploymentRecordValue.Builder valueBuilder = - ImmutableDeploymentRecordValue.builder().from(value); - - valueBuilder.deployedWorkflows(Collections.emptyList()); - for (final DeployedWorkflow workflow : value.getDeployedWorkflows()) { - valueBuilder.addDeployedWorkflows(ImmutableDeployedWorkflow.builder().from(workflow).build()); - } - - valueBuilder.resources(Collections.emptyList()); - for (final DeploymentResource resource : value.getResources()) { - valueBuilder.addResources(ImmutableDeploymentResource.builder().from(resource).build()); - } - - return valueBuilder.build(); - } - - private ImmutableJobBatchRecordValue copyJobBatchRecordForComparison( - final JobBatchRecordValue value) { - final ImmutableJobBatchRecordValue.Builder valueBuilder = - ImmutableJobBatchRecordValue.builder().from(value); - - valueBuilder.jobs(Collections.emptyList()); - for (final JobRecordValue job : value.getJobs()) { - valueBuilder.addJobs(ImmutableJobRecordValue.builder().from(job).build()); - } - - return valueBuilder.build(); - } -}