From b5d7f194ef06a25c7f70e92c30e7f60b88d9aab4 Mon Sep 17 00:00:00 2001 From: John Niang Date: Thu, 26 May 2022 16:38:10 +0800 Subject: [PATCH] Create unstructured Extension which can store any Extensions (#2111) * Extract ExtensionOperator and MetadataOperator * Move groupVersionKind methods up to ExtensionOperator interface * Add Unstructured Extension for generic Extension * Refine mapping of GVK and Scheme * Add two compatible methods --- .../halo/app/extension/AbstractExtension.java | 26 +---- .../app/extension/DefaultExtensionClient.java | 4 +- .../run/halo/app/extension/Extension.java | 30 +----- .../halo/app/extension/ExtensionOperator.java | 76 +++++++++++++ .../app/extension/JSONExtensionConverter.java | 9 +- .../java/run/halo/app/extension/Metadata.java | 9 +- .../halo/app/extension/MetadataOperator.java | 54 ++++++++++ .../java/run/halo/app/extension/Schemes.java | 26 ++++- .../run/halo/app/extension/Unstructured.java | 100 ++++++++++++++++++ .../app/extension/AbstractExtensionTest.java | 2 +- .../extension/DefaultExtensionClientTest.java | 69 ++++++++++-- .../extension/JSONExtensionConverterTest.java | 2 +- .../run/halo/app/extension/SchemesTest.java | 12 +++ .../halo/app/extension/UnstructuredTest.java | 84 +++++++++++++++ 14 files changed, 422 insertions(+), 81 deletions(-) create mode 100644 src/main/java/run/halo/app/extension/ExtensionOperator.java create mode 100644 src/main/java/run/halo/app/extension/MetadataOperator.java create mode 100644 src/main/java/run/halo/app/extension/Unstructured.java create mode 100644 src/test/java/run/halo/app/extension/UnstructuredTest.java diff --git a/src/main/java/run/halo/app/extension/AbstractExtension.java b/src/main/java/run/halo/app/extension/AbstractExtension.java index f34d3be85..b495d4551 100644 --- a/src/main/java/run/halo/app/extension/AbstractExtension.java +++ b/src/main/java/run/halo/app/extension/AbstractExtension.java @@ -1,6 +1,5 @@ package run.halo.app.extension; -import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; /** @@ -11,33 +10,10 @@ import lombok.Data; @Data public abstract class AbstractExtension implements Extension { - @Schema(required = true) private String apiVersion; - @Schema(required = true) private String kind; - @Schema(required = true) - private Metadata metadata; + private MetadataOperator metadata; - @Override - public void groupVersionKind(GroupVersionKind gvk) { - this.apiVersion = gvk.groupVersion().toString(); - this.kind = gvk.kind(); - } - - @Override - public GroupVersionKind groupVersionKind() { - return GroupVersionKind.fromAPIVersionAndKind(this.apiVersion, this.kind); - } - - @Override - public void metadata(Metadata metadata) { - this.metadata = metadata; - } - - @Override - public Metadata metadata() { - return this.metadata; - } } diff --git a/src/main/java/run/halo/app/extension/DefaultExtensionClient.java b/src/main/java/run/halo/app/extension/DefaultExtensionClient.java index c5fbbbba1..f5f53ef2a 100644 --- a/src/main/java/run/halo/app/extension/DefaultExtensionClient.java +++ b/src/main/java/run/halo/app/extension/DefaultExtensionClient.java @@ -68,7 +68,7 @@ public class DefaultExtensionClient implements ExtensionClient { @Override public void create(E extension) { - extension.metadata().setCreationTimestamp(Instant.now()); + extension.getMetadata().setCreationTimestamp(Instant.now()); var extensionStore = converter.convertTo(extension); storeClient.create(extensionStore.getName(), extensionStore.getData()); } @@ -76,7 +76,7 @@ public class DefaultExtensionClient implements ExtensionClient { @Override public void update(E extension) { var extensionStore = converter.convertTo(extension); - Assert.notNull(extension.metadata().getVersion(), + Assert.notNull(extension.getMetadata().getVersion(), "Extension version must not be null when updating"); storeClient.update(extensionStore.getName(), extensionStore.getVersion(), extensionStore.getData()); diff --git a/src/main/java/run/halo/app/extension/Extension.java b/src/main/java/run/halo/app/extension/Extension.java index 9ce661dc1..9760d58fa 100644 --- a/src/main/java/run/halo/app/extension/Extension.java +++ b/src/main/java/run/halo/app/extension/Extension.java @@ -4,34 +4,6 @@ package run.halo.app.extension; * Extension is an interface which represents an Extension. It contains setters and getters of * GroupVersionKind and Metadata. */ -public interface Extension { - - /** - * Sets GroupVersionKind of the Extension. - * - * @param gvk is GroupVersionKind data. - */ - void groupVersionKind(GroupVersionKind gvk); - - /** - * Gets GroupVersionKind of the Extension. - * - * @return GroupVersionKind of the Extension. - */ - GroupVersionKind groupVersionKind(); - - /** - * Sets metadata of the Extension. - * - * @param metadata metadata of the Extension. - */ - void metadata(Metadata metadata); - - /** - * Gets metadata of the Extension. - * - * @return metadata of the Extension. - */ - Metadata metadata(); +public interface Extension extends ExtensionOperator { } diff --git a/src/main/java/run/halo/app/extension/ExtensionOperator.java b/src/main/java/run/halo/app/extension/ExtensionOperator.java new file mode 100644 index 000000000..ba6b9bd9c --- /dev/null +++ b/src/main/java/run/halo/app/extension/ExtensionOperator.java @@ -0,0 +1,76 @@ +package run.halo.app.extension; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * ExtensionOperator contains some getters and setters for required fields of Extension. + * + * @author johnniang + */ +public interface ExtensionOperator { + + @Schema(required = true) + @JsonProperty("apiVersion") + String getApiVersion(); + + @Schema(required = true) + @JsonProperty("kind") + String getKind(); + + @Schema(required = true, implementation = Metadata.class) + @JsonProperty("metadata") + MetadataOperator getMetadata(); + + void setApiVersion(String apiVersion); + + void setKind(String kind); + + void setMetadata(MetadataOperator metadata); + + /** + * This method is only for backward compatibility. Same as {@link #getMetadata()}. + * + * @return Extension metadata. + * @see #getMetadata() + */ + @JsonIgnore + @Deprecated(forRemoval = true) + default MetadataOperator metadata() { + return getMetadata(); + } + + /** + * This method is only for backward compatibility. Same as + * {@link #setMetadata(MetadataOperator)}. + * + * @param metadata is Extension metadata. + * @see #setMetadata(MetadataOperator) + */ + @Deprecated(forRemoval = true) + default void metadata(MetadataOperator metadata) { + setMetadata(metadata); + } + + /** + * Sets GroupVersionKind of the Extension. + * + * @param gvk is GroupVersionKind data. + */ + default void groupVersionKind(GroupVersionKind gvk) { + setApiVersion(gvk.groupVersion().toString()); + setKind(gvk.kind()); + } + + /** + * Gets GroupVersionKind of the Extension. + * + * @return GroupVersionKind of the Extension. + */ + @JsonIgnore + default GroupVersionKind groupVersionKind() { + return GroupVersionKind.fromAPIVersionAndKind(getApiVersion(), getKind()); + } + +} diff --git a/src/main/java/run/halo/app/extension/JSONExtensionConverter.java b/src/main/java/run/halo/app/extension/JSONExtensionConverter.java index 7cff1c3d7..7c1fb1a91 100644 --- a/src/main/java/run/halo/app/extension/JSONExtensionConverter.java +++ b/src/main/java/run/halo/app/extension/JSONExtensionConverter.java @@ -33,8 +33,9 @@ public class JSONExtensionConverter implements ExtensionConverter { @Override public ExtensionStore convertTo(E extension) { - var scheme = Schemes.INSTANCE.get(extension.getClass()); - var storeName = ExtensionUtil.buildStoreName(scheme, extension.metadata().getName()); + var gvk = extension.groupVersionKind(); + var scheme = Schemes.INSTANCE.get(gvk); + var storeName = ExtensionUtil.buildStoreName(scheme, extension.getMetadata().getName()); try { if (logger.isDebugEnabled()) { logger.debug("JSON schema({}): {}", scheme.type(), @@ -56,7 +57,7 @@ public class JSONExtensionConverter implements ExtensionConverter { // keep converting var data = objectMapper.writeValueAsBytes(extensionNode); - var version = extension.metadata().getVersion(); + var version = extension.getMetadata().getVersion(); return new ExtensionStore(storeName, data, version); } catch (JsonProcessingException e) { throw new ExtensionConvertException("Failed write Extension as bytes", e); @@ -67,7 +68,7 @@ public class JSONExtensionConverter implements ExtensionConverter { public E convertFrom(Class type, ExtensionStore extensionStore) { try { var extension = objectMapper.readValue(extensionStore.getData(), type); - extension.metadata().setVersion(extensionStore.getVersion()); + extension.getMetadata().setVersion(extensionStore.getVersion()); return extension; } catch (IOException e) { throw new ExtensionConvertException("Failed to read Extension " + type + " from bytes", diff --git a/src/main/java/run/halo/app/extension/Metadata.java b/src/main/java/run/halo/app/extension/Metadata.java index 42f435666..83c5e9b10 100644 --- a/src/main/java/run/halo/app/extension/Metadata.java +++ b/src/main/java/run/halo/app/extension/Metadata.java @@ -1,6 +1,5 @@ package run.halo.app.extension; -import io.swagger.v3.oas.annotations.media.Schema; import java.time.Instant; import java.util.Map; import lombok.Data; @@ -11,42 +10,36 @@ import lombok.Data; * @author johnniang */ @Data -public class Metadata { +public class Metadata implements MetadataOperator { /** * Metadata name. The name is unique globally. */ - @Schema(required = true) private String name; /** * Labels are like key-value format. */ - @Schema(nullable = true) private Map labels; /** * Annotations are like key-value format. */ - @Schema(nullable = true) private Map annotations; /** * Current version of the Extension. It will be bumped up every update. */ - @Schema(nullable = true) private Long version; /** * Creation timestamp of the Extension. */ - @Schema(nullable = true) private Instant creationTimestamp; /** * Deletion timestamp of the Extension. */ - @Schema(nullable = true) private Instant deletionTimestamp; } diff --git a/src/main/java/run/halo/app/extension/MetadataOperator.java b/src/main/java/run/halo/app/extension/MetadataOperator.java new file mode 100644 index 000000000..a712ee642 --- /dev/null +++ b/src/main/java/run/halo/app/extension/MetadataOperator.java @@ -0,0 +1,54 @@ +package run.halo.app.extension; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import io.swagger.v3.oas.annotations.media.Schema; +import java.time.Instant; +import java.util.Map; + +/** + * MetadataOperator contains some getters and setters for required fields of metadata. + * + * @author johnniang + */ +@JsonDeserialize(as = Metadata.class) +@Schema(implementation = Metadata.class) +public interface MetadataOperator { + + @Schema(required = true) + @JsonProperty("name") + String getName(); + + @Schema(nullable = true) + @JsonProperty("labels") + Map getLabels(); + + @Schema(nullable = true) + @JsonProperty("annotations") + Map getAnnotations(); + + @Schema(nullable = true) + @JsonProperty("version") + Long getVersion(); + + @Schema(nullable = true) + @JsonProperty("creationTimestamp") + Instant getCreationTimestamp(); + + @Schema(nullable = true) + @JsonProperty("deletionTimestamp") + Instant getDeletionTimestamp(); + + void setName(String name); + + void setLabels(Map labels); + + void setAnnotations(Map annotations); + + void setVersion(Long version); + + void setCreationTimestamp(Instant creationTimestamp); + + void setDeletionTimestamp(Instant deletionTimestamp); + +} diff --git a/src/main/java/run/halo/app/extension/Schemes.java b/src/main/java/run/halo/app/extension/Schemes.java index 909297c17..7af21f79b 100644 --- a/src/main/java/run/halo/app/extension/Schemes.java +++ b/src/main/java/run/halo/app/extension/Schemes.java @@ -1,5 +1,6 @@ package run.halo.app.extension; +import com.github.victools.jsonschema.generator.Option; import com.github.victools.jsonschema.generator.OptionPreset; import com.github.victools.jsonschema.generator.SchemaGenerator; import com.github.victools.jsonschema.generator.SchemaGeneratorConfigBuilder; @@ -39,23 +40,22 @@ public enum Schemes { /** * The map mapping GroupVersionKind and type of Extension. */ - private final Map> gvkToType; + private final Map gvkToScheme; Schemes() { schemes = new HashSet<>(); typeToScheme = new HashMap<>(); - gvkToType = new HashMap<>(); + gvkToScheme = new HashMap<>(); } /** * Clear registered schemes. - *

* This method is only for test. */ void clear() { schemes.clear(); typeToScheme.clear(); - gvkToType.clear(); + gvkToScheme.clear(); } /** @@ -74,10 +74,17 @@ public enum Schemes { type.getName())); } + // TODO Move the generation logic outside. // generate JSON schema var module = new Swagger2Module(); var config = new SchemaGeneratorConfigBuilder(SchemaVersion.DRAFT_2019_09, OptionPreset.PLAIN_JSON) + .with( + // See https://victools.github.io/jsonschema-generator/#generator-options + // fore more. + Option.INLINE_ALL_SCHEMAS, + Option.MAP_VALUES_AS_ADDITIONAL_PROPERTIES + ) .with(module) .build(); var generator = new SchemaGenerator(config); @@ -102,7 +109,7 @@ public enum Schemes { return; } typeToScheme.put(scheme.type(), scheme); - gvkToType.put(scheme.groupVersionKind(), scheme.type()); + gvkToScheme.put(scheme.groupVersionKind(), scheme); } /** @@ -116,6 +123,10 @@ public enum Schemes { } + public Optional fetch(GroupVersionKind gvk) { + return Optional.ofNullable(gvkToScheme.get(gvk)); + } + /** * Gets a scheme using Extension type. * @@ -128,4 +139,9 @@ public enum Schemes { "Scheme was not found for Extension " + type.getSimpleName())); } + public Scheme get(GroupVersionKind gvk) { + return fetch(gvk).orElseThrow(() -> new SchemeNotFoundException( + "Scheme was not found for GVK " + gvk)); + } + } diff --git a/src/main/java/run/halo/app/extension/Unstructured.java b/src/main/java/run/halo/app/extension/Unstructured.java new file mode 100644 index 000000000..ed4aa809c --- /dev/null +++ b/src/main/java/run/halo/app/extension/Unstructured.java @@ -0,0 +1,100 @@ +package run.halo.app.extension; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import java.io.IOException; + +/** + * Unstructured is a generic Extension, which wraps ObjectNode to maintain the Extension data, like + * apiVersion, kind, metadata and others. + * + * @author johnniang + */ +@JsonSerialize(using = Unstructured.UnstructuredSerializer.class) +@JsonDeserialize(using = Unstructured.UnstructuredDeserializer.class) +public class Unstructured implements Extension { + + public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + static { + OBJECT_MAPPER.registerModule(new JavaTimeModule()); + } + + private final ObjectNode extension; + + public Unstructured() { + this(OBJECT_MAPPER.createObjectNode()); + } + + public Unstructured(ObjectNode extension) { + this.extension = extension; + } + + @Override + public String getApiVersion() { + return extension.get("apiVersion").asText(); + } + + @Override + public String getKind() { + return extension.get("kind").asText(); + } + + @Override + public MetadataOperator getMetadata() { + var metaMap = extension.get("metadata"); + return OBJECT_MAPPER.convertValue(metaMap, Metadata.class); + } + + @Override + public void setApiVersion(String apiVersion) { + extension.put("apiVersion", apiVersion); + } + + @Override + public void setKind(String kind) { + extension.put("kind", kind); + } + + @Override + public void setMetadata(MetadataOperator metadata) { + JsonNode metaNode = OBJECT_MAPPER.valueToTree(metadata); + extension.set("metadata", metaNode); + } + + ObjectNode getExtension() { + return extension; + } + + // TODO Add other convenient methods here to set and get nested fields in the future. + + public static class UnstructuredSerializer extends JsonSerializer { + + @Override + public void serialize(Unstructured value, JsonGenerator gen, SerializerProvider serializers) + throws IOException { + gen.writeTree(value.extension); + } + + } + + public static class UnstructuredDeserializer extends JsonDeserializer { + + @Override + public Unstructured deserialize(JsonParser p, DeserializationContext ctxt) + throws IOException { + return new Unstructured(p.getCodec().readTree(p)); + } + } + +} diff --git a/src/test/java/run/halo/app/extension/AbstractExtensionTest.java b/src/test/java/run/halo/app/extension/AbstractExtensionTest.java index ececa5ee3..6d42d2e11 100644 --- a/src/test/java/run/halo/app/extension/AbstractExtensionTest.java +++ b/src/test/java/run/halo/app/extension/AbstractExtensionTest.java @@ -45,7 +45,7 @@ class AbstractExtensionTest { Metadata metadata = new Metadata(); metadata.setName("fake"); - extension.metadata(metadata); + extension.setMetadata(metadata); assertEquals(metadata, extension.getMetadata()); } diff --git a/src/test/java/run/halo/app/extension/DefaultExtensionClientTest.java b/src/test/java/run/halo/app/extension/DefaultExtensionClientTest.java index 50129c334..683a4c523 100644 --- a/src/test/java/run/halo/app/extension/DefaultExtensionClientTest.java +++ b/src/test/java/run/halo/app/extension/DefaultExtensionClientTest.java @@ -13,6 +13,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import com.fasterxml.jackson.core.JsonProcessingException; import java.util.List; import java.util.Optional; import org.junit.jupiter.api.BeforeAll; @@ -58,11 +59,35 @@ class DefaultExtensionClientTest { } ExtensionStore createExtensionStore(String name) { + return createExtensionStore(name, null); + } + + ExtensionStore createExtensionStore(String name, Long version) { var extensionStore = new ExtensionStore(); extensionStore.setName(name); + extensionStore.setVersion(version); return extensionStore; } + Unstructured createUnstructured() throws JsonProcessingException { + String extensionJson = """ + { + "apiVersion": "fake.halo.run/v1alpha1", + "kind": "Fake", + "metadata": { + "labels": { + "category": "fake", + "default": "true" + }, + "name": "fake", + "creationTimestamp": "2011-12-03T10:15:30Z", + "version": 12345 + } + } + """; + return Unstructured.OBJECT_MAPPER.readValue(extensionJson, Unstructured.class); + } + @Test void shouldThrowSchemeNotFoundExceptionWhenSchemeNotRegistered() { class UnRegisteredExtension extends AbstractExtension { @@ -205,8 +230,24 @@ class DefaultExtensionClientTest { client.create(fake); - verify(converter, times(1)).convertTo(any()); - verify(storeClient, times(1)).create(any(), any()); + verify(converter, times(1)).convertTo(eq(fake)); + verify(storeClient, times(1)).create(eq("/registry/fake.halo.run/fakes/fake"), any()); + assertNotNull(fake.getMetadata().getCreationTimestamp()); + } + + @Test + void shouldCreateUsingUnstructuredSuccessfully() throws JsonProcessingException { + var fake = createUnstructured(); + + when(converter.convertTo(any())).thenReturn( + createExtensionStore("/registry/fake.halo.run/fakes/fake")); + when(storeClient.create(any(), any())).thenReturn( + createExtensionStore("/registry/fake.halo.run/fakes/fake")); + + client.create(fake); + + verify(converter, times(1)).convertTo(eq(fake)); + verify(storeClient, times(1)).create(eq("/registry/fake.halo.run/fakes/fake"), any()); assertNotNull(fake.getMetadata().getCreationTimestamp()); } @@ -214,14 +255,30 @@ class DefaultExtensionClientTest { void shouldUpdateSuccessfully() { var fake = createFakeExtension("fake", 2L); when(converter.convertTo(any())).thenReturn( - createExtensionStore("/registry/fake.halo.run/fakes/fake")); + createExtensionStore("/registry/fake.halo.run/fakes/fake", 2L)); when(storeClient.update(any(), any(), any())).thenReturn( - createExtensionStore("/registry/fake.halo.run/fakes/fake")); + createExtensionStore("/registry/fake.halo.run/fakes/fake", 2L)); client.update(fake); - verify(converter, times(1)).convertTo(any()); - verify(storeClient, times(1)).update(any(), any(), any()); + verify(converter, times(1)).convertTo(eq(fake)); + verify(storeClient, times(1)) + .update(eq("/registry/fake.halo.run/fakes/fake"), eq(2L), any()); + } + + @Test + void shouldUpdateUnstructuredSuccessfully() throws JsonProcessingException { + var fake = createUnstructured(); + when(converter.convertTo(any())).thenReturn( + createExtensionStore("/registry/fake.halo.run/fakes/fake", 12345L)); + when(storeClient.update(any(), any(), any())).thenReturn( + createExtensionStore("/registry/fake.halo.run/fakes/fake", 12345L)); + + client.update(fake); + + verify(converter, times(1)).convertTo(eq(fake)); + verify(storeClient, times(1)) + .update(eq("/registry/fake.halo.run/fakes/fake"), eq(12345L), any()); } @Test diff --git a/src/test/java/run/halo/app/extension/JSONExtensionConverterTest.java b/src/test/java/run/halo/app/extension/JSONExtensionConverterTest.java index 6d02eec40..14d56f5db 100644 --- a/src/test/java/run/halo/app/extension/JSONExtensionConverterTest.java +++ b/src/test/java/run/halo/app/extension/JSONExtensionConverterTest.java @@ -87,7 +87,7 @@ class JSONExtensionConverterTest { Metadata metadata = new Metadata(); metadata.setName(name); metadata.setVersion(version); - fake.metadata(metadata); + fake.setMetadata(metadata); return fake; } diff --git a/src/test/java/run/halo/app/extension/SchemesTest.java b/src/test/java/run/halo/app/extension/SchemesTest.java index 684fe28f6..9d53c94ce 100644 --- a/src/test/java/run/halo/app/extension/SchemesTest.java +++ b/src/test/java/run/halo/app/extension/SchemesTest.java @@ -3,11 +3,13 @@ package run.halo.app.extension; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import static run.halo.app.extension.GroupVersionKind.fromAPIVersionAndKind; import java.util.Optional; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import run.halo.app.extension.exception.ExtensionException; +import run.halo.app.extension.exception.SchemeNotFoundException; class SchemesTest { @@ -35,6 +37,13 @@ class SchemesTest { void shouldFetchNothingWhenUnregistered() { var scheme = Schemes.INSTANCE.fetch(FakeExtension.class); assertEquals(Optional.empty(), scheme); + assertThrows(SchemeNotFoundException.class, + () -> Schemes.INSTANCE.get(FakeExtension.class)); + + var gvk = fromAPIVersionAndKind("fake.halo.run/v1alpha1", "Fake"); + scheme = Schemes.INSTANCE.fetch(gvk); + assertEquals(Optional.empty(), scheme); + assertThrows(SchemeNotFoundException.class, () -> Schemes.INSTANCE.get(gvk)); } @Test @@ -43,5 +52,8 @@ class SchemesTest { var scheme = Schemes.INSTANCE.fetch(FakeExtension.class); assertTrue(scheme.isPresent()); + + scheme = Schemes.INSTANCE.fetch(fromAPIVersionAndKind("fake.halo.run/v1alpha1", "Fake")); + assertTrue(scheme.isPresent()); } } \ No newline at end of file diff --git a/src/test/java/run/halo/app/extension/UnstructuredTest.java b/src/test/java/run/halo/app/extension/UnstructuredTest.java new file mode 100644 index 000000000..97197c74e --- /dev/null +++ b/src/test/java/run/halo/app/extension/UnstructuredTest.java @@ -0,0 +1,84 @@ +package run.halo.app.extension; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import java.time.Instant; +import java.util.Map; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +class UnstructuredTest { + + ObjectMapper objectMapper = Unstructured.OBJECT_MAPPER; + + String extensionJson = """ + { + "apiVersion": "fake.halo.run/v1alpha1", + "kind": "Fake", + "metadata": { + "labels": { + "category": "fake", + "default": "true" + }, + "name": "fake-extension", + "creationTimestamp": "2011-12-03T10:15:30Z", + "version": 12345 + } + } + """; + + @BeforeAll + static void setUpGlobally() { + Schemes.INSTANCE.register(FakeExtension.class); + } + + @Test + void shouldSerializeCorrectly() throws JsonProcessingException { + var extensionNode = (ObjectNode) objectMapper.readTree(extensionJson); + var extension = new Unstructured(extensionNode); + + var gotNode = objectMapper.valueToTree(extension); + assertEquals(extensionNode, gotNode); + } + + @Test + void shouldDeserializeCorrectly() throws JsonProcessingException { + var extension = objectMapper.readValue(extensionJson, Unstructured.class); + var wantJsonNode = objectMapper.readTree(extensionJson); + assertEquals(wantJsonNode, extension.getExtension()); + } + + @Test + void shouldGetExtensionCorrectly() throws JsonProcessingException { + var extension = objectMapper.readValue(extensionJson, Unstructured.class); + + assertEquals("fake.halo.run/v1alpha1", extension.getApiVersion()); + assertEquals("Fake", extension.getKind()); + assertEquals(createMetadata(), extension.getMetadata()); + } + + @Test + void shouldSetExtensionCorrectly() { + var extension = new Unstructured(); + extension.setApiVersion("fake.halo.run/v1alpha1"); + extension.setKind("Fake"); + extension.setMetadata(createMetadata()); + + assertEquals("fake.halo.run/v1alpha1", extension.getApiVersion()); + assertEquals("Fake", extension.getKind()); + assertEquals(createMetadata(), extension.getMetadata()); + } + + private Metadata createMetadata() { + var metadata = new Metadata(); + metadata.setName("fake-extension"); + metadata.setLabels(Map.of("category", "fake", "default", "true")); + metadata.setCreationTimestamp(Instant.parse("2011-12-03T10:15:30Z")); + metadata.setVersion(12345L); + return metadata; + } + +} \ No newline at end of file