View Javadoc
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          // Start WireMock server with custom transformer
40          wireMockServer = new WireMockServer(WireMockConfiguration.wireMockConfig().dynamicPort()
41                  .extensions(new CaptureRequestTransformer()));
42          wireMockServer.start();
43  
44          // Mock artifact registration endpoint with debugging transformer
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          // clear captured registered artifacts
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          // Create the artifact configuration
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          // Verify content types - the main AsyncAPI artifact should be YAML, Avro schemas should be JSON
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         // Create the artifact configuration
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     // Custom transformer to capture/debug requests and generate dynamic responses
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             // Extract groupId from URL pattern: /apis/registry/v3/groups/{groupId}/artifacts
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