Repository: wiremock/wiremock (language: java)

Issue:
Support multiple mappings files when remotely loading stubs from the filesystem
### Proposal

While it's possible at present to push a collection of stub mappings from the file system to a remote instance via `wireMock.loadMappingsFrom("/my/path")` this only supports single stub mapping files, not the newer multiple mapping file format (with the `"mappings"` key at the root).

This would be desirable so that sets of related stubs can be combined into single files, without needing to merge the entire set into a single file for remote import.

### References

https://github.com/wiremock/wiremock/issues/1921


Discussion / hints:

Fix patch:
diff --git a/src/main/java/com/github/tomakehurst/wiremock/standalone/RemoteMappingsLoader.java b/src/main/java/com/github/tomakehurst/wiremock/standalone/RemoteMappingsLoader.java
index fcc88265ab..8bdf835181 100644
--- a/src/main/java/com/github/tomakehurst/wiremock/standalone/RemoteMappingsLoader.java
+++ b/src/main/java/com/github/tomakehurst/wiremock/standalone/RemoteMappingsLoader.java
@@ -25,10 +25,13 @@
 import com.github.tomakehurst.wiremock.common.BinaryFile;
 import com.github.tomakehurst.wiremock.common.ContentTypes;
 import com.github.tomakehurst.wiremock.common.FileSource;
+import com.github.tomakehurst.wiremock.common.Json;
+import com.github.tomakehurst.wiremock.common.JsonException;
 import com.github.tomakehurst.wiremock.common.TextFile;
 import com.github.tomakehurst.wiremock.http.ContentTypeHeader;
 import com.github.tomakehurst.wiremock.http.HttpHeaders;
 import com.github.tomakehurst.wiremock.stubbing.StubMapping;
+import com.github.tomakehurst.wiremock.stubbing.StubMappingCollection;
 import java.util.List;
 import java.util.stream.Collectors;
 
@@ -50,9 +53,16 @@ public void load() {
             .filter(byFileExtension("json"))
             .collect(Collectors.toList());
     for (TextFile mappingFile : mappingFiles) {
-      StubMapping mapping = StubMapping.buildFrom(mappingFile.readContentsAsString());
-      convertBodyFromFileIfNecessary(mapping);
-      wireMock.register(mapping);
+      try {
+        StubMappingCollection stubCollection =
+            Json.read(mappingFile.readContentsAsString(), StubMappingCollection.class);
+        for (StubMapping mapping : stubCollection.getMappingOrMappings()) {
+          convertBodyFromFileIfNecessary(mapping);
+          wireMock.register(mapping);
+        }
+      } catch (JsonException e) {
+        throw new MappingFileException(mappingFile.getPath(), e.getErrors().first().getDetail());
+      }
     }
   }
 


Test patch:
diff --git a/src/test/java/com/github/tomakehurst/wiremock/RemoteMappingsLoaderAcceptanceTest.java b/src/test/java/com/github/tomakehurst/wiremock/RemoteMappingsLoaderAcceptanceTest.java
index eeef44aec7..9c26a72573 100644
--- a/src/test/java/com/github/tomakehurst/wiremock/RemoteMappingsLoaderAcceptanceTest.java
+++ b/src/test/java/com/github/tomakehurst/wiremock/RemoteMappingsLoaderAcceptanceTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016-2022 Thomas Akehurst
+ * Copyright (C) 2016-2023 Thomas Akehurst
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -87,4 +87,23 @@ public void convertsBodyFileToStringBodyWhenAKnownImageTypeFromContentTypeHeader
 
     assertThat(stubMapping.getItem().getResponse().specifiesBinaryBodyContent(), is(true));
   }
+
+  @Test
+  public void loadMultipleMappingsFromOneFile() {
+    wmClient.loadMappingsFrom(rootDir);
+
+    assertThat(testClient.get("/todo/items").content(), is("<items><item>Buy milk</item></items>"));
+    assertThat(
+        testClient
+            .postWithBody(
+                "/todo/items",
+                "{\"subscription\": \"Cancel newspaper subscription\"}",
+                "application/json",
+                "UTF-8")
+            .statusCode(),
+        is(201));
+    assertThat(
+        testClient.get("/todo/items").content(),
+        is("<items><item>Buy milk</item><item>Cancel newspaper subscription</item></items>"));
+  }
 }
diff --git a/src/test/resources/remoteloader/__files/body.xml b/src/test/resources/remoteloader/__files/body.xml
new file mode 100644
index 0000000000..d264440900
--- /dev/null
+++ b/src/test/resources/remoteloader/__files/body.xml
@@ -0,0 +1,1 @@
+<items><item>Buy milk</item><item>Cancel newspaper subscription</item></items>
\ No newline at end of file
diff --git a/src/test/resources/remoteloader/mappings/with-several-mappings-in-one-file.json b/src/test/resources/remoteloader/mappings/with-several-mappings-in-one-file.json
new file mode 100644
index 0000000000..e669b2a304
--- /dev/null
+++ b/src/test/resources/remoteloader/mappings/with-several-mappings-in-one-file.json
@@ -0,0 +1,37 @@
+{"mappings": [
+  {
+    "request": {
+      "method": "GET",
+      "url": "/todo/items"
+    },
+    "response": {
+      "body": "<items><item>Buy milk<\/item><\/items>",
+      "status": 200
+    },
+    "requiredScenarioState": "Started",
+    "scenarioName": "To do list"
+  },
+  {
+    "request": {
+      "method": "POST",
+      "bodyPatterns": [{"contains": "Cancel newspaper subscription"}],
+      "url": "/todo/items"
+    },
+    "newScenarioState": "Cancel newspaper item added",
+    "response": {"status": 201},
+    "requiredScenarioState": "Started",
+    "scenarioName": "To do list"
+  },
+  {
+    "request": {
+      "method": "GET",
+      "url": "/todo/items"
+    },
+    "response": {
+      "bodyFileName": "body.xml",
+      "status": 200
+    },
+    "requiredScenarioState": "Cancel newspaper item added",
+    "scenarioName": "To do list"
+  }
+]}