1 package io.apicurio.registry.maven;
2
3 import com.fasterxml.jackson.core.JsonProcessingException;
4 import com.fasterxml.jackson.databind.JsonNode;
5 import com.fasterxml.jackson.databind.ObjectMapper;
6 import com.github.tomakehurst.wiremock.WireMockServer;
7 import com.github.tomakehurst.wiremock.common.FileSource;
8 import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
9 import com.github.tomakehurst.wiremock.extension.Parameters;
10 import com.github.tomakehurst.wiremock.extension.ResponseTransformer;
11 import com.github.tomakehurst.wiremock.http.Request;
12 import com.github.tomakehurst.wiremock.http.Response;
13 import io.apicurio.registry.rest.v3.beans.IfArtifactExists;
14 import io.apicurio.registry.types.ArtifactType;
15 import org.junit.jupiter.api.AfterEach;
16 import org.junit.jupiter.api.BeforeEach;
17 import org.junit.jupiter.api.Test;
18
19 import java.io.File;
20 import java.nio.file.Paths;
21 import java.util.HashMap;
22 import java.util.HashSet;
23 import java.util.Map;
24 import java.util.Set;
25
26 import static com.github.tomakehurst.wiremock.client.WireMock.*;
27 import static org.junit.jupiter.api.Assertions.assertEquals;
28 import static org.junit.jupiter.api.Assertions.assertTrue;
29
30 public class RegisterAsyncApiAvroAutoRefsTest {
31
32 private RegisterRegistryMojo mojo;
33 private final File examplesRoot = Paths.get("../../examples/").toAbsolutePath().toFile();
34 private WireMockServer wireMockServer;
35 private Set<String> registeredArtifacts = new HashSet<>();
36 private Map<String, String> artifactContentTypes = new HashMap<>();
37
38 @BeforeEach public void setup() {
39
40 wireMockServer = new WireMockServer(WireMockConfiguration.wireMockConfig().dynamicPort()
41 .extensions(new CaptureRequestTransformer()));
42 wireMockServer.start();
43
44
45 wireMockServer.stubFor(post(urlPathMatching("/apis/registry/v3/groups/.*/artifacts")).willReturn(
46 aResponse().withTransformers("capture-request-transformer").withStatus(200)
47 .withHeader("Content-Type", "application/json")));
48
49 mojo = new RegisterRegistryMojo();
50 mojo.setRegistryUrl(wireMockServer.baseUrl() + "/apis/registry/v3");
51
52
53 registeredArtifacts.clear();
54 artifactContentTypes.clear();
55 }
56
57 @AfterEach public void tearDown() {
58 if (wireMockServer != null) {
59 wireMockServer.stop();
60 }
61 }
62
63 @Test public void testAsyncApiWithAvroAutoRefs() throws Exception {
64 File exampleDir = new File(examplesRoot, "asyncapi-avro-maven-with-references-auto");
65 File asyncApiFile = new File(exampleDir, "src/main/resources/schemas/asyncapi-avro-refs.yml");
66
67
68 RegisterArtifact artifact = new RegisterArtifact();
69 artifact.setGroupId("asyncapi-avro-maven-with-references-auto");
70 artifact.setArtifactId("CustomersExample");
71 artifact.setVersion("1.0.0");
72 artifact.setArtifactType(ArtifactType.ASYNCAPI);
73 artifact.setFile(asyncApiFile);
74 artifact.setIfExists(IfArtifactExists.FIND_OR_CREATE_VERSION);
75 artifact.setCanonicalize(true);
76 artifact.setAutoRefs(true);
77
78 mojo.setArtifacts(java.util.Collections.singletonList(artifact));
79 mojo.execute();
80
81 assertEquals(6, registeredArtifacts.size());
82 assertTrue(registeredArtifacts.contains("asyncapi-avro-maven-with-references-auto:CustomersExample"));
83 assertTrue(registeredArtifacts.contains(
84 "asyncapi-avro-maven-with-references-auto:io.example.api.dtos.CustomerEvent"));
85 assertTrue(registeredArtifacts.contains(
86 "asyncapi-avro-maven-with-references-auto:io.example.api.dtos.Address"));
87 assertTrue(registeredArtifacts.contains(
88 "asyncapi-avro-maven-with-references-auto:io.example.api.dtos.PaymentMethod"));
89 assertTrue(registeredArtifacts.contains(
90 "asyncapi-avro-maven-with-references-auto:io.example.api.dtos.PaymentMethodType"));
91 assertTrue(registeredArtifacts.contains(
92 "asyncapi-avro-maven-with-references-auto:io.example.api.dtos.CustomerDeletedEvent"));
93
94
95 String asyncApiContentType = artifactContentTypes
96 .get("asyncapi-avro-maven-with-references-auto:CustomersExample");
97 assertEquals("application/x-yaml", asyncApiContentType,
98 "AsyncAPI YAML file should be registered with application/x-yaml content-type but was: "
99 + asyncApiContentType);
100
101 String avroContentType = artifactContentTypes
102 .get("asyncapi-avro-maven-with-references-auto:io.example.api.dtos.CustomerEvent");
103 assertEquals("application/json", avroContentType,
104 "Avro schema file should be registered with application/json content-type but was: "
105 + avroContentType);
106
107 }
108
109 @Test public void testAvroAutoRefs() throws Exception {
110 File exampleDir = new File(examplesRoot, "avro-maven-with-references-auto");
111 File avroFile = new File(exampleDir, "src/main/resources/schemas/TradeRaw.avsc");
112
113
114 RegisterArtifact artifact = new RegisterArtifact();
115 artifact.setGroupId("com.kubetrade.schema.trade");
116 artifact.setArtifactId("TradeRaw");
117 artifact.setVersion("2.0");
118 artifact.setArtifactType(ArtifactType.AVRO);
119 artifact.setFile(avroFile);
120 artifact.setIfExists(IfArtifactExists.FIND_OR_CREATE_VERSION);
121 artifact.setCanonicalize(true);
122 artifact.setAutoRefs(true);
123
124 mojo.setArtifacts(java.util.Collections.singletonList(artifact));
125 mojo.execute();
126
127 assertEquals(4, registeredArtifacts.size());
128 assertTrue(registeredArtifacts.contains("com.kubetrade.schema.trade:TradeRaw"));
129 assertTrue(registeredArtifacts.contains("com.kubetrade.schema.trade:TradeKey"));
130 assertTrue(registeredArtifacts.contains("com.kubetrade.schema.trade:TradeValue"));
131 assertTrue(registeredArtifacts.contains("com.kubetrade.schema.common:Exchange"));
132 }
133
134
135 class CaptureRequestTransformer extends ResponseTransformer {
136
137 @Override public String getName() {
138 return "capture-request-transformer";
139 }
140
141 @Override public Response transform(Request request, Response response, FileSource fileSource,
142 Parameters parameters) {
143 try {
144 String responseBody = generateResponseBasedOnRequest(request);
145 return Response.Builder.like(response).but().body(responseBody).build();
146 } catch (JsonProcessingException e) {
147 throw new RuntimeException(e);
148 }
149
150 }
151
152 private String generateResponseBasedOnRequest(Request request) throws JsonProcessingException {
153 String url = request.getUrl();
154 String body = request.getBodyAsString();
155
156 String groupId = extractGroupIdFromUrl(url);
157 String artifactId = extractArtifactIdFromBody(body);
158 String contentType = extractContentTypeFromBody(body);
159
160 String key = groupId + ":" + artifactId;
161 registeredArtifacts.add(key);
162 artifactContentTypes.put(key, contentType);
163
164 return """
165 {
166 "version": {
167 "version": "1",
168 "globalId": 12345,
169 "contentId": 67890
170 },
171 "artifact": {
172 "groupId": "%s",
173 "artifactId": "%s"
174 }
175 }
176 """.formatted(groupId, artifactId);
177 }
178
179 private String extractGroupIdFromUrl(String url) {
180
181 String[] parts = url.split("/");
182 for (int i = 0; i < parts.length - 1; i++) {
183 if ("groups".equals(parts[i]) && i + 1 < parts.length) {
184 return parts[i + 1];
185 }
186 }
187 return "default";
188 }
189
190 private String extractArtifactIdFromBody(String body) throws JsonProcessingException {
191 ObjectMapper mapper = new ObjectMapper();
192 JsonNode jsonNode = mapper.readTree(body);
193
194 JsonNode artifactIdNode = jsonNode.get("artifactId");
195 if (artifactIdNode != null && !artifactIdNode.isNull()) {
196 return artifactIdNode.asText();
197 }
198 throw new RuntimeException("ArtifactId not found in request body");
199 }
200
201 private String extractContentTypeFromBody(String body) throws JsonProcessingException {
202 ObjectMapper mapper = new ObjectMapper();
203 JsonNode jsonNode = mapper.readTree(body);
204
205 JsonNode firstVersionNode = jsonNode.get("firstVersion");
206 if (firstVersionNode != null && !firstVersionNode.isNull()) {
207 JsonNode contentNode = firstVersionNode.get("content");
208 if (contentNode != null && !contentNode.isNull()) {
209 JsonNode contentTypeNode = contentNode.get("contentType");
210 if (contentTypeNode != null && !contentTypeNode.isNull()) {
211 return contentTypeNode.asText();
212 }
213 }
214 }
215 return "unknown";
216 }
217
218 }
219 }
220