diff --git a/docs/repository-sqlite-migration-audit.md b/docs/ai/repository-sqlite-migration-audit.md
similarity index 100%
rename from docs/repository-sqlite-migration-audit.md
rename to docs/ai/repository-sqlite-migration-audit.md
diff --git a/examplemodels/src/main/java/uk/co/compendiumdev/thingifier/application/examples/ConfiguredWriteMethodsThingifier.java b/examplemodels/src/main/java/uk/co/compendiumdev/thingifier/application/examples/ConfiguredWriteMethodsThingifier.java
new file mode 100644
index 00000000..a2f279bb
--- /dev/null
+++ b/examplemodels/src/main/java/uk/co/compendiumdev/thingifier/application/examples/ConfiguredWriteMethodsThingifier.java
@@ -0,0 +1,39 @@
+package uk.co.compendiumdev.thingifier.application.examples;
+
+import static uk.co.compendiumdev.thingifier.apiconfig.EntityPatchUpdateStyle.JSON_MERGE_PATCH_RFC7396;
+import static uk.co.compendiumdev.thingifier.apiconfig.EntityPatchUpdateStyle.JSON_PATCH_RFC6902;
+import static uk.co.compendiumdev.thingifier.apiconfig.EntityPatchUpdateStyle.PARTIAL_JSON_UPDATE;
+import static uk.co.compendiumdev.thingifier.apiconfig.EntityWriteOperation.CREATE;
+import static uk.co.compendiumdev.thingifier.apiconfig.EntityWriteOperation.UPDATE;
+import static uk.co.compendiumdev.thingifier.core.domain.definitions.field.definition.FieldType.STRING;
+
+import uk.co.compendiumdev.thingifier.Thingifier;
+import uk.co.compendiumdev.thingifier.core.domain.definitions.EntityDefinition;
+import uk.co.compendiumdev.thingifier.core.domain.definitions.field.definition.Field;
+import uk.co.compendiumdev.thingifier.core.domain.definitions.field.definition.FieldType;
+import uk.co.compendiumdev.thingifier.core.domain.definitions.validation.VRule;
+
+public class ConfiguredWriteMethodsThingifier {
+
+ public Thingifier get() {
+ Thingifier notes = new Thingifier();
+ notes.setDocumentation(
+ "Configured Write Methods",
+ "A sample API configured so POST creates, PATCH updates, and PUT updates.");
+
+ EntityDefinition note = notes.defineThing("note", "notes");
+ note.addAsPrimaryKeyField(Field.is("id", FieldType.AUTO_INCREMENT));
+ note.addFields(
+ Field.is("title", STRING).makeMandatory().withValidation(VRule.notEmpty()),
+ Field.is("description", STRING));
+
+ notes.apiDefaults().writeMethods().entities().postCan(CREATE);
+ notes.apiDefaults()
+ .writeMethods()
+ .entities()
+ .patchCan(PARTIAL_JSON_UPDATE, JSON_MERGE_PATCH_RFC7396, JSON_PATCH_RFC6902);
+ notes.apiDefaults().writeMethods().entities().putCan(UPDATE);
+
+ return notes;
+ }
+}
diff --git a/pom.xml b/pom.xml
index 606a48cc..c7a63cd1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -16,6 +16,8 @@
1.5.6-SNAPSHOT
2.2.52
2.8.6
+ 2.21.1
+ 0.6.3
3.53.2.0
2.6
5.6.2
@@ -140,6 +142,7 @@
thingifier-crud-ui
todoManagerRestAuto
standAloneTodoListRestApi
+ standAloneConfiguredWriteMethodsRestApi
standAloneTodoListManagerRestApi
standAloneSqlLiteTodoListManagerRestApi
standAloneTodoListManagerRestApiAuto
diff --git a/standAloneConfiguredWriteMethodsRestApi/pom.xml b/standAloneConfiguredWriteMethodsRestApi/pom.xml
new file mode 100644
index 00000000..59c63a83
--- /dev/null
+++ b/standAloneConfiguredWriteMethodsRestApi/pom.xml
@@ -0,0 +1,81 @@
+
+
+ 4.0.0
+
+
+ uk.co.compendiumdev.thingifier
+ thingifier-root
+ 1.5.6-SNAPSHOT
+
+
+ standAloneConfiguredWriteMethodsRestApi
+
+
+
+ uk.co.compendiumdev
+ thingifier
+ ${thingifier.version}
+
+
+ uk.co.compendiumdev.thingifier
+ examplemodels
+ ${thingifier.version}
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ ${junit.jupiter.version}
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ ${junit.jupiter.version}
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.3
+
+ ${java.version}
+ ${java.version}
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 3.0.0-M4
+
+
+ maven-assembly-plugin
+
+
+ package
+
+ single
+
+
+
+
+
+ jar-with-dependencies
+
+ runConfiguredWriteMethodsRestAPI-${project.version}
+ false
+
+
+ uk.co.compendiumdev.configuredwritemethods.application.Main
+
+
+
+
+
+
+
diff --git a/standAloneConfiguredWriteMethodsRestApi/src/main/java/uk/co/compendiumdev/configuredwritemethods/application/Main.java b/standAloneConfiguredWriteMethodsRestApi/src/main/java/uk/co/compendiumdev/configuredwritemethods/application/Main.java
new file mode 100644
index 00000000..cf83a8bd
--- /dev/null
+++ b/standAloneConfiguredWriteMethodsRestApi/src/main/java/uk/co/compendiumdev/configuredwritemethods/application/Main.java
@@ -0,0 +1,34 @@
+package uk.co.compendiumdev.configuredwritemethods.application;
+
+import static uk.co.compendiumdev.thingifier.adapter.httpserver.ServerRoutes.get;
+
+import uk.co.compendiumdev.thingifier.adapter.httpserver.MainImplementation;
+import uk.co.compendiumdev.thingifier.application.examples.ConfiguredWriteMethodsThingifier;
+
+public class Main {
+
+ public static void main(String[] args) {
+ MainImplementation app = new MainImplementation();
+ app.registerModel("configuredWriteMethods", new ConfiguredWriteMethodsThingifier().get());
+
+ app.setDefaultsFromArgs(args);
+
+ app.configurePortAndDefaultRoutes();
+ app.setupBuiltInConfigurableRoutes();
+
+ app.chooseThingifier();
+ app.configureThingifierWithProfile();
+
+ app.setupDefaultGui();
+
+ get(
+ "/",
+ (request, response) -> {
+ response.redirect("/gui/entities");
+ return "";
+ });
+
+ app.startRestServer();
+ app.addBuiltInArgConfiguredHooks();
+ }
+}
diff --git a/standAloneConfiguredWriteMethodsRestApi/src/test/java/uk/co/compendiumdev/configuredwritemethods/application/ConfiguredWriteMethodsDirectApiTest.java b/standAloneConfiguredWriteMethodsRestApi/src/test/java/uk/co/compendiumdev/configuredwritemethods/application/ConfiguredWriteMethodsDirectApiTest.java
new file mode 100644
index 00000000..3dda418e
--- /dev/null
+++ b/standAloneConfiguredWriteMethodsRestApi/src/test/java/uk/co/compendiumdev/configuredwritemethods/application/ConfiguredWriteMethodsDirectApiTest.java
@@ -0,0 +1,118 @@
+package uk.co.compendiumdev.configuredwritemethods.application;
+
+import static uk.co.compendiumdev.configuredwritemethods.application.ConfiguredWriteMethodsSampleApiTestSupport.createStoredNote;
+import static uk.co.compendiumdev.configuredwritemethods.application.ConfiguredWriteMethodsSampleApiTestSupport.currentNote;
+import static uk.co.compendiumdev.configuredwritemethods.application.ConfiguredWriteMethodsSampleApiTestSupport.noteJson;
+import static uk.co.compendiumdev.configuredwritemethods.application.ConfiguredWriteMethodsSampleApiTestSupport.patch;
+import static uk.co.compendiumdev.configuredwritemethods.application.ConfiguredWriteMethodsSampleApiTestSupport.post;
+import static uk.co.compendiumdev.configuredwritemethods.application.ConfiguredWriteMethodsSampleApiTestSupport.put;
+import static uk.co.compendiumdev.configuredwritemethods.application.ConfiguredWriteMethodsSampleApiTestSupport.sample;
+import static uk.co.compendiumdev.thingifier.apiconfig.EntityPatchUpdateStyle.JSON_MERGE_PATCH_RFC7396;
+import static uk.co.compendiumdev.thingifier.apiconfig.EntityPatchUpdateStyle.JSON_PATCH_RFC6902;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import uk.co.compendiumdev.thingifier.Thingifier;
+import uk.co.compendiumdev.thingifier.api.response.ApiResponse;
+
+class ConfiguredWriteMethodsDirectApiTest {
+
+ @Test
+ void postNotesCreatesANote() {
+ final Thingifier thingifier = sample();
+
+ final ApiResponse response = post(thingifier, "notes", noteJson("One"));
+
+ Assertions.assertEquals(201, response.getStatusCode());
+ Assertions.assertEquals(
+ "One", response.getReturnedInstance().getFieldValue("title").asString());
+ Assertions.assertEquals(
+ "sample", response.getReturnedInstance().getFieldValue("description").asString());
+ }
+
+ @Test
+ void postExistingNoteIsBlocked() {
+ final Thingifier thingifier = sample();
+ final String id = createStoredNote(thingifier).getPrimaryKeyValue();
+
+ final ApiResponse response = post(thingifier, "notes/" + id, "{\"title\":\"Blocked\"}");
+
+ Assertions.assertEquals(405, response.getStatusCode());
+ Assertions.assertEquals(
+ "Existing", currentNote(thingifier, id).getFieldValue("title").asString());
+ }
+
+ @Test
+ void partialJsonPatchUpdatesTheExistingNote() {
+ final Thingifier thingifier = sample();
+ final String id = createStoredNote(thingifier).getPrimaryKeyValue();
+
+ final ApiResponse response =
+ patch(thingifier, "notes/" + id, "{\"title\":\"Patched Direct\"}");
+
+ Assertions.assertEquals(200, response.getStatusCode());
+ Assertions.assertEquals(
+ "Patched Direct", response.getReturnedInstance().getFieldValue("title").asString());
+ Assertions.assertEquals(
+ "sample", response.getReturnedInstance().getFieldValue("description").asString());
+ }
+
+ @Test
+ void mergePatchUpdatesTheExistingNote() {
+ final Thingifier thingifier = sample();
+ final String id = createStoredNote(thingifier).getPrimaryKeyValue();
+
+ final ApiResponse response =
+ patch(
+ thingifier,
+ "notes/" + id,
+ "{\"description\":\"Merged Direct\"}",
+ JSON_MERGE_PATCH_RFC7396.mediaType());
+
+ Assertions.assertEquals(200, response.getStatusCode());
+ Assertions.assertEquals(
+ "Existing", response.getReturnedInstance().getFieldValue("title").asString());
+ Assertions.assertEquals(
+ "Merged Direct",
+ response.getReturnedInstance().getFieldValue("description").asString());
+ }
+
+ @Test
+ void jsonPatchUpdatesTheExistingNote() {
+ final Thingifier thingifier = sample();
+ final String id = createStoredNote(thingifier).getPrimaryKeyValue();
+
+ final ApiResponse response =
+ patch(
+ thingifier,
+ "notes/" + id,
+ "[{\"op\":\"replace\",\"path\":\"/title\",\"value\":\"Json Patch Direct\"}]",
+ JSON_PATCH_RFC6902.mediaType());
+
+ Assertions.assertEquals(200, response.getStatusCode());
+ Assertions.assertEquals(
+ "Json Patch Direct",
+ response.getReturnedInstance().getFieldValue("title").asString());
+ Assertions.assertEquals(
+ "sample", response.getReturnedInstance().getFieldValue("description").asString());
+ }
+
+ @Test
+ void putExistingNoteUpdatesTheNote() {
+ final Thingifier thingifier = sample();
+ final String id = createStoredNote(thingifier).getPrimaryKeyValue();
+
+ final ApiResponse response = put(thingifier, "notes/" + id, "{\"title\":\"Put Direct\"}");
+
+ Assertions.assertEquals(200, response.getStatusCode());
+ Assertions.assertEquals(
+ "Put Direct", response.getReturnedInstance().getFieldValue("title").asString());
+ }
+
+ @Test
+ void putMissingNoteIsBlockedBecausePutCannotCreate() {
+ final ApiResponse response = put(sample(), "notes/999", "{\"title\":\"Missing\"}");
+
+ Assertions.assertEquals(405, response.getStatusCode());
+ }
+}
diff --git a/standAloneConfiguredWriteMethodsRestApi/src/test/java/uk/co/compendiumdev/configuredwritemethods/application/ConfiguredWriteMethodsDocumentationTest.java b/standAloneConfiguredWriteMethodsRestApi/src/test/java/uk/co/compendiumdev/configuredwritemethods/application/ConfiguredWriteMethodsDocumentationTest.java
new file mode 100644
index 00000000..4564b9a2
--- /dev/null
+++ b/standAloneConfiguredWriteMethodsRestApi/src/test/java/uk/co/compendiumdev/configuredwritemethods/application/ConfiguredWriteMethodsDocumentationTest.java
@@ -0,0 +1,76 @@
+package uk.co.compendiumdev.configuredwritemethods.application;
+
+import static uk.co.compendiumdev.configuredwritemethods.application.ConfiguredWriteMethodsSampleApiTestSupport.documentation;
+import static uk.co.compendiumdev.configuredwritemethods.application.ConfiguredWriteMethodsSampleApiTestSupport.patchFormatsHeader;
+import static uk.co.compendiumdev.configuredwritemethods.application.ConfiguredWriteMethodsSampleApiTestSupport.route;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import uk.co.compendiumdev.thingifier.api.docgen.ApiRoutingDefinition;
+import uk.co.compendiumdev.thingifier.api.docgen.RoutingVerb;
+
+class ConfiguredWriteMethodsDocumentationTest {
+
+ @Test
+ void exposesPostCreateOnTheCollectionRoute() {
+ final ApiRoutingDefinition definition = documentation();
+
+ Assertions.assertTrue(
+ route(definition, RoutingVerb.POST, "notes").status().isReturnedFromCall());
+ }
+
+ @Test
+ void blocksPostUpdateOnTheInstanceRoute() {
+ final ApiRoutingDefinition definition = documentation();
+
+ Assertions.assertEquals(
+ 405, route(definition, RoutingVerb.POST, "notes/:id").status().value());
+ }
+
+ @Test
+ void blocksPutAndPatchOnTheCollectionRoute() {
+ final ApiRoutingDefinition definition = documentation();
+
+ Assertions.assertEquals(405, route(definition, RoutingVerb.PUT, "notes").status().value());
+ Assertions.assertEquals(
+ 405, route(definition, RoutingVerb.PATCH, "notes").status().value());
+ }
+
+ @Test
+ void exposesPatchAndPutUpdateOnTheInstanceRoute() {
+ final ApiRoutingDefinition definition = documentation();
+
+ Assertions.assertTrue(
+ route(definition, RoutingVerb.PATCH, "notes/:id").status().isReturnedFromCall());
+ Assertions.assertTrue(
+ route(definition, RoutingVerb.PUT, "notes/:id").status().isReturnedFromCall());
+ }
+
+ @Test
+ void collectionOptionsAllowOnlyTheCollectionMethods() {
+ final ApiRoutingDefinition definition = documentation();
+
+ Assertions.assertEquals(
+ "OPTIONS, GET, HEAD, POST, QUERY",
+ route(definition, RoutingVerb.OPTIONS, "notes").headerValue());
+ }
+
+ @Test
+ void instanceOptionsAllowOnlyTheInstanceMethods() {
+ final ApiRoutingDefinition definition = documentation();
+
+ Assertions.assertEquals(
+ "OPTIONS, GET, HEAD, PUT, PATCH, DELETE",
+ route(definition, RoutingVerb.OPTIONS, "notes/:id").headerValue());
+ }
+
+ @Test
+ void instanceOptionsExposeConfiguredPatchMediaTypes() {
+ final ApiRoutingDefinition definition = documentation();
+
+ Assertions.assertEquals(
+ patchFormatsHeader(),
+ route(definition, RoutingVerb.OPTIONS, "notes/:id")
+ .getResponseHeaderValue("Accept-Patch"));
+ }
+}
diff --git a/standAloneConfiguredWriteMethodsRestApi/src/test/java/uk/co/compendiumdev/configuredwritemethods/application/ConfiguredWriteMethodsHttpApiTest.java b/standAloneConfiguredWriteMethodsRestApi/src/test/java/uk/co/compendiumdev/configuredwritemethods/application/ConfiguredWriteMethodsHttpApiTest.java
new file mode 100644
index 00000000..bf9b7814
--- /dev/null
+++ b/standAloneConfiguredWriteMethodsRestApi/src/test/java/uk/co/compendiumdev/configuredwritemethods/application/ConfiguredWriteMethodsHttpApiTest.java
@@ -0,0 +1,146 @@
+package uk.co.compendiumdev.configuredwritemethods.application;
+
+import static uk.co.compendiumdev.configuredwritemethods.application.ConfiguredWriteMethodsSampleApiTestSupport.createStoredNote;
+import static uk.co.compendiumdev.configuredwritemethods.application.ConfiguredWriteMethodsSampleApiTestSupport.currentNote;
+import static uk.co.compendiumdev.configuredwritemethods.application.ConfiguredWriteMethodsSampleApiTestSupport.httpApi;
+import static uk.co.compendiumdev.configuredwritemethods.application.ConfiguredWriteMethodsSampleApiTestSupport.httpApiFor;
+import static uk.co.compendiumdev.configuredwritemethods.application.ConfiguredWriteMethodsSampleApiTestSupport.jsonRequest;
+import static uk.co.compendiumdev.configuredwritemethods.application.ConfiguredWriteMethodsSampleApiTestSupport.noteJson;
+import static uk.co.compendiumdev.configuredwritemethods.application.ConfiguredWriteMethodsSampleApiTestSupport.sample;
+import static uk.co.compendiumdev.thingifier.apiconfig.EntityPatchUpdateStyle.JSON_MERGE_PATCH_RFC7396;
+import static uk.co.compendiumdev.thingifier.apiconfig.EntityPatchUpdateStyle.JSON_PATCH_RFC6902;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import uk.co.compendiumdev.thingifier.Thingifier;
+import uk.co.compendiumdev.thingifier.api.http.HttpApiResponse;
+
+class ConfiguredWriteMethodsHttpApiTest {
+
+ @Test
+ void postNotesCreatesANote() {
+ final HttpApiResponse response =
+ httpApi().post(jsonRequest("notes", "POST", noteJson("One")));
+
+ Assertions.assertEquals(201, response.getStatusCode());
+ Assertions.assertEquals(
+ "One",
+ response.apiResponse().getReturnedInstance().getFieldValue("title").asString());
+ Assertions.assertEquals(
+ "sample",
+ response.apiResponse()
+ .getReturnedInstance()
+ .getFieldValue("description")
+ .asString());
+ }
+
+ @Test
+ void postExistingNoteIsBlocked() {
+ final Thingifier thingifier = sample();
+ final String id = createStoredNote(thingifier).getPrimaryKeyValue();
+
+ final HttpApiResponse response =
+ httpApiFor(thingifier)
+ .post(jsonRequest("notes/" + id, "POST", "{\"title\":\"Blocked\"}"));
+
+ Assertions.assertEquals(405, response.getStatusCode());
+ Assertions.assertEquals(
+ "Existing", currentNote(thingifier, id).getFieldValue("title").asString());
+ }
+
+ @Test
+ void partialJsonPatchUpdatesTheExistingNote() {
+ final Thingifier thingifier = sample();
+ final String id = createStoredNote(thingifier).getPrimaryKeyValue();
+
+ final HttpApiResponse response =
+ httpApiFor(thingifier)
+ .patch(jsonRequest("notes/" + id, "PATCH", "{\"title\":\"Patched\"}"));
+
+ Assertions.assertEquals(200, response.getStatusCode());
+ Assertions.assertEquals(
+ "Patched",
+ response.apiResponse().getReturnedInstance().getFieldValue("title").asString());
+ Assertions.assertEquals(
+ "sample",
+ response.apiResponse()
+ .getReturnedInstance()
+ .getFieldValue("description")
+ .asString());
+ }
+
+ @Test
+ void mergePatchUpdatesTheExistingNote() {
+ final Thingifier thingifier = sample();
+ final String id = createStoredNote(thingifier).getPrimaryKeyValue();
+
+ final HttpApiResponse response =
+ httpApiFor(thingifier)
+ .patch(
+ jsonRequest(
+ "notes/" + id,
+ "PATCH",
+ "{\"description\":\"Merged\"}",
+ JSON_MERGE_PATCH_RFC7396.mediaType()));
+
+ Assertions.assertEquals(200, response.getStatusCode());
+ Assertions.assertEquals(
+ "Existing",
+ response.apiResponse().getReturnedInstance().getFieldValue("title").asString());
+ Assertions.assertEquals(
+ "Merged",
+ response.apiResponse()
+ .getReturnedInstance()
+ .getFieldValue("description")
+ .asString());
+ }
+
+ @Test
+ void jsonPatchUpdatesTheExistingNote() {
+ final Thingifier thingifier = sample();
+ final String id = createStoredNote(thingifier).getPrimaryKeyValue();
+
+ final HttpApiResponse response =
+ httpApiFor(thingifier)
+ .patch(
+ jsonRequest(
+ "notes/" + id,
+ "PATCH",
+ "[{\"op\":\"replace\",\"path\":\"/title\",\"value\":\"Json Patch\"}]",
+ JSON_PATCH_RFC6902.mediaType()));
+
+ Assertions.assertEquals(200, response.getStatusCode());
+ Assertions.assertEquals(
+ "Json Patch",
+ response.apiResponse().getReturnedInstance().getFieldValue("title").asString());
+ Assertions.assertEquals(
+ "sample",
+ response.apiResponse()
+ .getReturnedInstance()
+ .getFieldValue("description")
+ .asString());
+ }
+
+ @Test
+ void putExistingNoteUpdatesTheNote() {
+ final Thingifier thingifier = sample();
+ final String id = createStoredNote(thingifier).getPrimaryKeyValue();
+
+ final HttpApiResponse response =
+ httpApiFor(thingifier)
+ .put(jsonRequest("notes/" + id, "PUT", "{\"title\":\"Put\"}"));
+
+ Assertions.assertEquals(200, response.getStatusCode());
+ Assertions.assertEquals(
+ "Put",
+ response.apiResponse().getReturnedInstance().getFieldValue("title").asString());
+ }
+
+ @Test
+ void putMissingNoteIsBlockedBecausePutCannotCreate() {
+ final HttpApiResponse response =
+ httpApi().put(jsonRequest("notes/999", "PUT", "{\"title\":\"Missing\"}"));
+
+ Assertions.assertEquals(405, response.getStatusCode());
+ }
+}
diff --git a/standAloneConfiguredWriteMethodsRestApi/src/test/java/uk/co/compendiumdev/configuredwritemethods/application/ConfiguredWriteMethodsSampleApiTestSupport.java b/standAloneConfiguredWriteMethodsRestApi/src/test/java/uk/co/compendiumdev/configuredwritemethods/application/ConfiguredWriteMethodsSampleApiTestSupport.java
new file mode 100644
index 00000000..778c3d6e
--- /dev/null
+++ b/standAloneConfiguredWriteMethodsRestApi/src/test/java/uk/co/compendiumdev/configuredwritemethods/application/ConfiguredWriteMethodsSampleApiTestSupport.java
@@ -0,0 +1,129 @@
+package uk.co.compendiumdev.configuredwritemethods.application;
+
+import static uk.co.compendiumdev.thingifier.apiconfig.EntityPatchUpdateStyle.JSON_MERGE_PATCH_RFC7396;
+import static uk.co.compendiumdev.thingifier.apiconfig.EntityPatchUpdateStyle.JSON_PATCH_RFC6902;
+import static uk.co.compendiumdev.thingifier.apiconfig.EntityPatchUpdateStyle.PARTIAL_JSON_UPDATE;
+
+import io.swagger.v3.oas.models.OpenAPI;
+import uk.co.compendiumdev.thingifier.Thingifier;
+import uk.co.compendiumdev.thingifier.api.docgen.ApiRoutingDefinition;
+import uk.co.compendiumdev.thingifier.api.docgen.ApiRoutingDefinitionDocGenerator;
+import uk.co.compendiumdev.thingifier.api.docgen.RoutingDefinition;
+import uk.co.compendiumdev.thingifier.api.docgen.RoutingVerb;
+import uk.co.compendiumdev.thingifier.api.docgen.ThingifierApiDocumentationDefn;
+import uk.co.compendiumdev.thingifier.api.http.HttpApiRequest;
+import uk.co.compendiumdev.thingifier.api.http.ThingifierHttpApi;
+import uk.co.compendiumdev.thingifier.api.http.bodyparser.BodyParser;
+import uk.co.compendiumdev.thingifier.api.http.headers.HttpHeadersBlock;
+import uk.co.compendiumdev.thingifier.api.response.ApiResponse;
+import uk.co.compendiumdev.thingifier.application.examples.ConfiguredWriteMethodsThingifier;
+import uk.co.compendiumdev.thingifier.core.EntityRelModel;
+import uk.co.compendiumdev.thingifier.core.domain.definitions.EntityDefinition;
+import uk.co.compendiumdev.thingifier.core.domain.instances.EntityInstance;
+import uk.co.compendiumdev.thingifier.core.domain.instances.EntityInstanceDraft;
+import uk.co.compendiumdev.thingifier.swaggerizer.Swaggerizer;
+
+final class ConfiguredWriteMethodsSampleApiTestSupport {
+
+ private ConfiguredWriteMethodsSampleApiTestSupport() {}
+
+ static Thingifier sample() {
+ return new ConfiguredWriteMethodsThingifier().get();
+ }
+
+ static ThingifierHttpApi httpApi() {
+ return httpApiFor(sample());
+ }
+
+ static ThingifierHttpApi httpApiFor(final Thingifier thingifier) {
+ return new ThingifierHttpApi(thingifier);
+ }
+
+ static ApiRoutingDefinition documentation() {
+ return new ApiRoutingDefinitionDocGenerator(sample()).generate("");
+ }
+
+ static OpenAPI swagger() {
+ return new Swaggerizer(new ThingifierApiDocumentationDefn().setThingifier(sample()))
+ .swagger();
+ }
+
+ static EntityInstance createStoredNote(final Thingifier thingifier) {
+ final EntityDefinition note = thingifier.getDefinitionNamed("note");
+ return thingifier
+ .getStore(EntityRelModel.DEFAULT_DATABASE_NAME)
+ .entities()
+ .create(
+ EntityInstanceDraft.forEntity(note)
+ .withField("title", "Existing")
+ .withField("description", "sample"));
+ }
+
+ static EntityInstance currentNote(final Thingifier thingifier, final String id) {
+ return thingifier
+ .getStore(EntityRelModel.DEFAULT_DATABASE_NAME)
+ .entityQueries()
+ .findByPrimaryKey(thingifier.getDefinitionNamed("note"), id);
+ }
+
+ static ApiResponse post(final Thingifier thingifier, final String path, final String body) {
+ return thingifier.api().post(path, parser(thingifier, body), new HttpHeadersBlock());
+ }
+
+ static ApiResponse put(final Thingifier thingifier, final String path, final String body) {
+ return thingifier.api().put(path, parser(thingifier, body), new HttpHeadersBlock());
+ }
+
+ static ApiResponse patch(final Thingifier thingifier, final String path, final String body) {
+ return patch(thingifier, path, body, PARTIAL_JSON_UPDATE.mediaType());
+ }
+
+ static ApiResponse patch(
+ final Thingifier thingifier,
+ final String path,
+ final String body,
+ final String contentType) {
+ HttpHeadersBlock headers = new HttpHeadersBlock();
+ headers.put("Content-Type", contentType);
+ return thingifier.api().patch(path, body, headers);
+ }
+
+ static HttpApiRequest jsonRequest(final String path, final String verb, final String body) {
+ return jsonRequest(path, verb, body, PARTIAL_JSON_UPDATE.mediaType());
+ }
+
+ static HttpApiRequest jsonRequest(
+ final String path, final String verb, final String body, final String contentType) {
+ return new HttpApiRequest(path)
+ .setVerb(verb)
+ .addHeader("Content-Type", contentType)
+ .addHeader("Accept", "application/json")
+ .setBody(body);
+ }
+
+ static String noteJson(final String title) {
+ return "{\"title\":\"" + title + "\",\"description\":\"sample\"}";
+ }
+
+ static String patchFormatsHeader() {
+ return String.join(
+ ", ",
+ PARTIAL_JSON_UPDATE.mediaType(),
+ JSON_MERGE_PATCH_RFC7396.mediaType(),
+ JSON_PATCH_RFC6902.mediaType());
+ }
+
+ static RoutingDefinition route(
+ final ApiRoutingDefinition definition, final RoutingVerb verb, final String url) {
+ return definition.definitions().stream()
+ .filter(route -> route.verb() == verb)
+ .filter(route -> route.url().equals(url))
+ .findFirst()
+ .orElseThrow();
+ }
+
+ private static BodyParser parser(final Thingifier thingifier, final String body) {
+ return new BodyParser(
+ new HttpApiRequest("/request").setBody(body), thingifier.getThingNames());
+ }
+}
diff --git a/standAloneConfiguredWriteMethodsRestApi/src/test/java/uk/co/compendiumdev/configuredwritemethods/application/ConfiguredWriteMethodsSwaggerTest.java b/standAloneConfiguredWriteMethodsRestApi/src/test/java/uk/co/compendiumdev/configuredwritemethods/application/ConfiguredWriteMethodsSwaggerTest.java
new file mode 100644
index 00000000..2cd70c34
--- /dev/null
+++ b/standAloneConfiguredWriteMethodsRestApi/src/test/java/uk/co/compendiumdev/configuredwritemethods/application/ConfiguredWriteMethodsSwaggerTest.java
@@ -0,0 +1,56 @@
+package uk.co.compendiumdev.configuredwritemethods.application;
+
+import static uk.co.compendiumdev.configuredwritemethods.application.ConfiguredWriteMethodsSampleApiTestSupport.swagger;
+import static uk.co.compendiumdev.thingifier.apiconfig.EntityPatchUpdateStyle.JSON_MERGE_PATCH_RFC7396;
+import static uk.co.compendiumdev.thingifier.apiconfig.EntityPatchUpdateStyle.JSON_PATCH_RFC6902;
+import static uk.co.compendiumdev.thingifier.apiconfig.EntityPatchUpdateStyle.PARTIAL_JSON_UPDATE;
+
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.PathItem;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+class ConfiguredWriteMethodsSwaggerTest {
+
+ @Test
+ void exposesPostCreateOnlyOnTheCollectionRoute() {
+ final OpenAPI openApi = swagger();
+ final PathItem collection = openApi.getPaths().get("/notes");
+
+ Assertions.assertNotNull(collection.getPost());
+ Assertions.assertNull(collection.getPut());
+ Assertions.assertNull(collection.getPatch());
+ }
+
+ @Test
+ void exposesPatchAndPutUpdateOnlyOnTheInstanceRoute() {
+ final OpenAPI openApi = swagger();
+ final PathItem instance = openApi.getPaths().get("/notes/{id}");
+
+ Assertions.assertNull(instance.getPost());
+ Assertions.assertNotNull(instance.getPatch());
+ Assertions.assertNotNull(instance.getPut());
+ }
+
+ @Test
+ void patchRequestBodyExposesAllConfiguredPatchMediaTypes() {
+ final OpenAPI openApi = swagger();
+ final PathItem instance = openApi.getPaths().get("/notes/{id}");
+
+ Assertions.assertTrue(
+ instance.getPatch()
+ .getRequestBody()
+ .getContent()
+ .containsKey(PARTIAL_JSON_UPDATE.mediaType()));
+ Assertions.assertTrue(
+ instance.getPatch()
+ .getRequestBody()
+ .getContent()
+ .containsKey(JSON_MERGE_PATCH_RFC7396.mediaType()));
+ Assertions.assertTrue(
+ instance.getPatch()
+ .getRequestBody()
+ .getContent()
+ .containsKey(JSON_PATCH_RFC6902.mediaType()));
+ }
+}
diff --git a/thingifier-crud-ui/src/test/java/uk/co/compendiumdev/thingifier/crudui/CrudUiControllerTest.java b/thingifier-crud-ui/src/test/java/uk/co/compendiumdev/thingifier/crudui/CrudUiControllerTest.java
index a818667f..63ae59e9 100644
--- a/thingifier-crud-ui/src/test/java/uk/co/compendiumdev/thingifier/crudui/CrudUiControllerTest.java
+++ b/thingifier-crud-ui/src/test/java/uk/co/compendiumdev/thingifier/crudui/CrudUiControllerTest.java
@@ -4,7 +4,9 @@
import com.google.gson.JsonParser;
import java.nio.file.Path;
import java.util.Map;
+import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import uk.co.compendiumdev.thingifier.swaggerizer.OpenApiSpecificationVersion;
@@ -13,188 +15,161 @@ public class CrudUiControllerTest {
@TempDir Path temp;
+ private ActiveThingifierWorkspace workspace;
+ private CrudUiController controller;
+
+ @BeforeEach
+ public void createWorkspace() {
+ workspace = ActiveThingifierWorkspace.defaultTodoManagerWorkspace();
+ controller = new CrudUiController(workspace);
+ }
+
+ @AfterEach
+ public void closeWorkspace() {
+ workspace.close();
+ }
+
@Test
public void workspaceRouteReturnsSchemaMetadata() {
- try (ActiveThingifierWorkspace workspace =
- ActiveThingifierWorkspace.defaultTodoManagerWorkspace()) {
- CrudUiController controller = new CrudUiController(workspace);
-
- UiHttpResponse response = controller.workspace();
-
- Assertions.assertEquals(200, response.statusCode(), response.body());
- Assertions.assertTrue(response.body().contains("\"entities\""));
- Assertions.assertTrue(response.body().contains("\"relationships\""));
- Assertions.assertTrue(response.body().contains("\"schemaYaml\""));
- Assertions.assertTrue(response.body().contains("\"project\""));
- Assertions.assertTrue(response.body().contains("\"storage\""));
- }
+ UiHttpResponse response = controller.workspace();
+
+ Assertions.assertEquals(200, response.statusCode(), response.body());
+ Assertions.assertTrue(response.body().contains("\"entities\""));
+ Assertions.assertTrue(response.body().contains("\"relationships\""));
+ Assertions.assertTrue(response.body().contains("\"schemaYaml\""));
+ Assertions.assertTrue(response.body().contains("\"project\""));
+ Assertions.assertTrue(response.body().contains("\"storage\""));
}
@Test
public void storageSwitchEndpointChangesWorkspaceStorageMode() {
- try (ActiveThingifierWorkspace workspace =
- ActiveThingifierWorkspace.defaultTodoManagerWorkspace()) {
- CrudUiController controller = new CrudUiController(workspace);
- Path databaseFile = temp.resolve("controller.sqlite");
-
- UiHttpResponse response =
- controller.switchStorage(
- JsonSupport.toJson(
- Map.of(
- "mode",
- "sqlite-file",
- "sqliteFile",
- databaseFile.toString())));
-
- Assertions.assertEquals(200, response.statusCode(), response.body());
- Assertions.assertEquals("sqlite-file", workspace.snapshot().storage().mode());
- Assertions.assertTrue(response.body().contains("\"storageStatus\": \"switched\""));
- }
+ Path databaseFile = temp.resolve("controller.sqlite");
+
+ UiHttpResponse response =
+ controller.switchStorage(
+ JsonSupport.toJson(
+ Map.of(
+ "mode",
+ "sqlite-file",
+ "sqliteFile",
+ databaseFile.toString())));
+
+ Assertions.assertEquals(200, response.statusCode(), response.body());
+ Assertions.assertEquals("sqlite-file", workspace.snapshot().storage().mode());
+ Assertions.assertTrue(response.body().contains("\"storageStatus\": \"switched\""));
}
@Test
public void projectBrowseEndpointReturnsSelectedPathFromChooser() {
- try (ActiveThingifierWorkspace workspace =
- ActiveThingifierWorkspace.defaultTodoManagerWorkspace()) {
- Path chosen = temp.resolve("chosen-project");
- CrudUiController controller =
- new CrudUiController(
- workspace, request -> ProjectPathSelection.selected(chosen.toString()));
-
- UiHttpResponse response =
- controller.browseProject(
- JsonSupport.toJson(Map.of("action", "save", "path", "")));
- JsonObject body = JsonParser.parseString(response.body()).getAsJsonObject();
-
- Assertions.assertEquals(200, response.statusCode());
- Assertions.assertTrue(body.get("selected").getAsBoolean());
- Assertions.assertEquals(chosen.toString(), body.get("path").getAsString());
- }
+ Path chosen = temp.resolve("chosen-project");
+ CrudUiController browsingController =
+ new CrudUiController(
+ workspace, request -> ProjectPathSelection.selected(chosen.toString()));
+
+ UiHttpResponse response =
+ browsingController.browseProject(
+ JsonSupport.toJson(Map.of("action", "save", "path", "")));
+ JsonObject body = JsonParser.parseString(response.body()).getAsJsonObject();
+
+ Assertions.assertEquals(200, response.statusCode());
+ Assertions.assertTrue(body.get("selected").getAsBoolean());
+ Assertions.assertEquals(chosen.toString(), body.get("path").getAsString());
}
@Test
public void projectBrowseEndpointReturnsCancelledSelection() {
- try (ActiveThingifierWorkspace workspace =
- ActiveThingifierWorkspace.defaultTodoManagerWorkspace()) {
- CrudUiController controller =
- new CrudUiController(workspace, request -> ProjectPathSelection.cancelled());
-
- UiHttpResponse response =
- controller.browseProject(
- JsonSupport.toJson(Map.of("action", "load", "path", "")));
- JsonObject body = JsonParser.parseString(response.body()).getAsJsonObject();
-
- Assertions.assertEquals(200, response.statusCode());
- Assertions.assertFalse(body.get("selected").getAsBoolean());
- Assertions.assertEquals(
- "Project browsing cancelled.", body.get("message").getAsString());
- }
+ CrudUiController browsingController =
+ new CrudUiController(workspace, request -> ProjectPathSelection.cancelled());
+
+ UiHttpResponse response =
+ browsingController.browseProject(
+ JsonSupport.toJson(Map.of("action", "load", "path", "")));
+ JsonObject body = JsonParser.parseString(response.body()).getAsJsonObject();
+
+ Assertions.assertEquals(200, response.statusCode());
+ Assertions.assertFalse(body.get("selected").getAsBoolean());
+ Assertions.assertEquals("Project browsing cancelled.", body.get("message").getAsString());
}
@Test
public void projectBrowseEndpointReportsUnavailableChooserAsBadRequest() {
- try (ActiveThingifierWorkspace workspace =
- ActiveThingifierWorkspace.defaultTodoManagerWorkspace()) {
- CrudUiController controller =
- new CrudUiController(
- workspace,
- request -> ProjectPathSelection.unavailable("Browse unavailable"));
-
- UiHttpResponse response =
- controller.browseProject(
- JsonSupport.toJson(Map.of("action", "save", "path", "")));
-
- Assertions.assertEquals(400, response.statusCode());
- Assertions.assertTrue(response.body().contains("Browse unavailable"));
- }
+ CrudUiController browsingController =
+ new CrudUiController(
+ workspace,
+ request -> ProjectPathSelection.unavailable("Browse unavailable"));
+
+ UiHttpResponse response =
+ browsingController.browseProject(
+ JsonSupport.toJson(Map.of("action", "save", "path", "")));
+
+ Assertions.assertEquals(400, response.statusCode());
+ Assertions.assertTrue(response.body().contains("Browse unavailable"));
}
@Test
public void sqliteFileStorageSwitchRequiresAFilePathAndDoesNotMutateWorkspace() {
- try (ActiveThingifierWorkspace workspace =
- ActiveThingifierWorkspace.defaultTodoManagerWorkspace()) {
- CrudUiController controller = new CrudUiController(workspace);
- long version = workspace.snapshot().version();
-
- UiHttpResponse response =
- controller.switchStorage(JsonSupport.toJson(Map.of("mode", "sqlite-file")));
-
- Assertions.assertEquals(400, response.statusCode());
- Assertions.assertEquals(version, workspace.snapshot().version());
- Assertions.assertEquals("memory", workspace.snapshot().storage().mode());
- }
+ long version = workspace.snapshot().version();
+
+ UiHttpResponse response =
+ controller.switchStorage(JsonSupport.toJson(Map.of("mode", "sqlite-file")));
+
+ Assertions.assertEquals(400, response.statusCode());
+ Assertions.assertEquals(version, workspace.snapshot().version());
+ Assertions.assertEquals("memory", workspace.snapshot().storage().mode());
}
@Test
public void invalidYamlDoesNotReplaceWorkspace() {
- try (ActiveThingifierWorkspace workspace =
- ActiveThingifierWorkspace.defaultTodoManagerWorkspace()) {
- CrudUiController controller = new CrudUiController(workspace);
-
- UiHttpResponse response = controller.loadYaml("formatVersion: 1\nentities: [");
+ UiHttpResponse response = controller.loadYaml("formatVersion: 1\nentities: [");
- Assertions.assertEquals(400, response.statusCode());
- Assertions.assertEquals("Todo Manager", workspace.snapshot().definition().title());
- }
+ Assertions.assertEquals(400, response.statusCode());
+ Assertions.assertEquals("Todo Manager", workspace.snapshot().definition().title());
}
@Test
public void schemaYamlPreviewDoesNotReplaceWorkspace() {
- try (ActiveThingifierWorkspace workspace =
- ActiveThingifierWorkspace.defaultTodoManagerWorkspace()) {
- CrudUiController controller = new CrudUiController(workspace);
- long version = workspace.snapshot().version();
- String title = workspace.snapshot().definition().title();
-
- UiHttpResponse response =
- controller.schemaFromYaml(TestResources.text("/models/minimal-todo.yaml"));
- JsonObject body = JsonParser.parseString(response.body()).getAsJsonObject();
-
- Assertions.assertEquals(200, response.statusCode());
- Assertions.assertTrue(body.get("valid").getAsBoolean());
- Assertions.assertEquals(version, workspace.snapshot().version());
- Assertions.assertEquals(title, workspace.snapshot().definition().title());
- }
+ long version = workspace.snapshot().version();
+ String title = workspace.snapshot().definition().title();
+
+ UiHttpResponse response =
+ controller.schemaFromYaml(TestResources.text("/models/minimal-todo.yaml"));
+ JsonObject body = JsonParser.parseString(response.body()).getAsJsonObject();
+
+ Assertions.assertEquals(200, response.statusCode());
+ Assertions.assertTrue(body.get("valid").getAsBoolean());
+ Assertions.assertEquals(version, workspace.snapshot().version());
+ Assertions.assertEquals(title, workspace.snapshot().definition().title());
}
@Test
public void schemaDraftPreviewDoesNotReplaceWorkspace() {
- try (ActiveThingifierWorkspace workspace =
- ActiveThingifierWorkspace.defaultTodoManagerWorkspace()) {
- CrudUiController controller = new CrudUiController(workspace);
- JsonObject parsed =
- JsonParser.parseString(
- controller
- .schemaFromYaml(
- TestResources.text("/models/minimal-todo.yaml"))
- .body())
- .getAsJsonObject();
- long version = workspace.snapshot().version();
-
- UiHttpResponse response = controller.previewSchema(parsed.get("draft").toString());
-
- Assertions.assertEquals(200, response.statusCode());
- Assertions.assertTrue(
- JsonParser.parseString(response.body())
- .getAsJsonObject()
- .get("valid")
- .getAsBoolean());
- Assertions.assertEquals(version, workspace.snapshot().version());
- Assertions.assertEquals("Todo Manager", workspace.snapshot().definition().title());
- }
+ JsonObject parsed =
+ JsonParser.parseString(
+ controller
+ .schemaFromYaml(
+ TestResources.text("/models/minimal-todo.yaml"))
+ .body())
+ .getAsJsonObject();
+ long version = workspace.snapshot().version();
+
+ UiHttpResponse response = controller.previewSchema(parsed.get("draft").toString());
+
+ Assertions.assertEquals(200, response.statusCode());
+ Assertions.assertTrue(
+ JsonParser.parseString(response.body())
+ .getAsJsonObject()
+ .get("valid")
+ .getAsBoolean());
+ Assertions.assertEquals(version, workspace.snapshot().version());
+ Assertions.assertEquals("Todo Manager", workspace.snapshot().definition().title());
}
@Test
public void malformedSchemaYamlPreviewReturnsBadRequest() {
- try (ActiveThingifierWorkspace workspace =
- ActiveThingifierWorkspace.defaultTodoManagerWorkspace()) {
- CrudUiController controller = new CrudUiController(workspace);
+ UiHttpResponse response = controller.schemaFromYaml("formatVersion: 1\nentities: [");
- UiHttpResponse response = controller.schemaFromYaml("formatVersion: 1\nentities: [");
-
- Assertions.assertEquals(400, response.statusCode());
- Assertions.assertEquals("Todo Manager", workspace.snapshot().definition().title());
- }
+ Assertions.assertEquals(400, response.statusCode());
+ Assertions.assertEquals("Todo Manager", workspace.snapshot().definition().title());
}
@Test
@@ -287,124 +262,96 @@ public void staticSchemaResourceIsAvailable() {
@Test
public void openApiJsonUsesActiveWorkspaceApiServer() {
- try (ActiveThingifierWorkspace workspace =
- ActiveThingifierWorkspace.defaultTodoManagerWorkspace()) {
- workspace.replaceWithYaml(TestResources.text("/models/project-tasks.yaml"));
- CrudUiController controller = new CrudUiController(workspace);
-
- UiHttpResponse response = controller.openApiJson();
-
- Assertions.assertEquals(200, response.statusCode());
- Assertions.assertEquals("application/json", response.contentType());
- Assertions.assertTrue(response.body().contains("\"openapi\""));
- Assertions.assertTrue(openApiVersion(response.body()).startsWith("3.1."));
- Assertions.assertTrue(response.body().contains("\"/api\""));
- Assertions.assertTrue(response.body().contains("\"/projects\""));
- Assertions.assertTrue(response.body().contains("\"/projects/{id}/tasks\""));
- }
+ workspace.replaceWithYaml(TestResources.text("/models/project-tasks.yaml"));
+
+ UiHttpResponse response = controller.openApiJson();
+
+ Assertions.assertEquals(200, response.statusCode());
+ Assertions.assertEquals("application/json", response.contentType());
+ Assertions.assertTrue(response.body().contains("\"openapi\""));
+ Assertions.assertTrue(openApiVersion(response.body()).startsWith("3.1."));
+ Assertions.assertTrue(response.body().contains("\"/api\""));
+ Assertions.assertTrue(response.body().contains("\"/projects\""));
+ Assertions.assertTrue(response.body().contains("\"/projects/{id}/tasks\""));
}
@Test
public void openApiJsonCanBeGeneratedForOpenApi30() {
- try (ActiveThingifierWorkspace workspace =
- ActiveThingifierWorkspace.defaultTodoManagerWorkspace()) {
- workspace.replaceWithYaml(TestResources.text("/models/project-tasks.yaml"));
- CrudUiController controller = new CrudUiController(workspace);
-
- UiHttpResponse response =
- controller.openApiJson(OpenApiSpecificationVersion.OPENAPI_3_0);
-
- Assertions.assertEquals(200, response.statusCode());
- Assertions.assertEquals("application/json", response.contentType());
- Assertions.assertTrue(openApiVersion(response.body()).startsWith("3.0."));
- Assertions.assertTrue(response.body().contains("\"/api\""));
- Assertions.assertTrue(response.body().contains("\"/projects/{id}/tasks\""));
- }
+ workspace.replaceWithYaml(TestResources.text("/models/project-tasks.yaml"));
+
+ UiHttpResponse response = controller.openApiJson(OpenApiSpecificationVersion.OPENAPI_3_0);
+
+ Assertions.assertEquals(200, response.statusCode());
+ Assertions.assertEquals("application/json", response.contentType());
+ Assertions.assertTrue(openApiVersion(response.body()).startsWith("3.0."));
+ Assertions.assertTrue(response.body().contains("\"/api\""));
+ Assertions.assertTrue(response.body().contains("\"/projects/{id}/tasks\""));
}
@Test
public void openApiJsonCanBeGeneratedForOpenApi32() {
- try (ActiveThingifierWorkspace workspace =
- ActiveThingifierWorkspace.defaultTodoManagerWorkspace()) {
- workspace.replaceWithYaml(TestResources.text("/models/project-tasks.yaml"));
- CrudUiController controller = new CrudUiController(workspace);
-
- UiHttpResponse response =
- controller.openApiJson(OpenApiSpecificationVersion.OPENAPI_3_2);
-
- Assertions.assertEquals(200, response.statusCode());
- Assertions.assertEquals("application/json", response.contentType());
- Assertions.assertEquals("3.2.0", openApiVersion(response.body()));
- Assertions.assertTrue(response.body().contains("\"/api\""));
- Assertions.assertTrue(response.body().contains("\"/projects/{id}/tasks\""));
- Assertions.assertTrue(response.body().contains("\"query\""));
- Assertions.assertFalse(response.body().contains("\"x-query-operation\""));
- }
+ workspace.replaceWithYaml(TestResources.text("/models/project-tasks.yaml"));
+
+ UiHttpResponse response = controller.openApiJson(OpenApiSpecificationVersion.OPENAPI_3_2);
+
+ Assertions.assertEquals(200, response.statusCode());
+ Assertions.assertEquals("application/json", response.contentType());
+ Assertions.assertEquals("3.2.0", openApiVersion(response.body()));
+ Assertions.assertTrue(response.body().contains("\"/api\""));
+ Assertions.assertTrue(response.body().contains("\"/projects/{id}/tasks\""));
+ Assertions.assertTrue(response.body().contains("\"query\""));
+ Assertions.assertFalse(response.body().contains("\"x-query-operation\""));
}
@Test
public void openApiDownloadAddsAttachmentHeader() {
- try (ActiveThingifierWorkspace workspace =
- ActiveThingifierWorkspace.defaultTodoManagerWorkspace()) {
- workspace.replaceWithYaml(TestResources.text("/models/project-tasks.yaml"));
- CrudUiController controller = new CrudUiController(workspace);
-
- UiHttpResponse response = controller.downloadOpenApi(false);
-
- Assertions.assertEquals(200, response.statusCode());
- Assertions.assertEquals("application/json", response.contentType());
- Assertions.assertEquals(
- "attachment; filename=\"project-tasks-openapi.json\"",
- response.headers().get("Content-Disposition"));
- Assertions.assertTrue(response.body().contains("\"Project Tasks\""));
- }
+ workspace.replaceWithYaml(TestResources.text("/models/project-tasks.yaml"));
+
+ UiHttpResponse response = controller.downloadOpenApi(false);
+
+ Assertions.assertEquals(200, response.statusCode());
+ Assertions.assertEquals("application/json", response.contentType());
+ Assertions.assertEquals(
+ "attachment; filename=\"project-tasks-openapi.json\"",
+ response.headers().get("Content-Disposition"));
+ Assertions.assertTrue(response.body().contains("\"Project Tasks\""));
}
@Test
public void apiDocumentationPageListsSchemaAndGeneratedRoutes() {
- try (ActiveThingifierWorkspace workspace =
- ActiveThingifierWorkspace.defaultTodoManagerWorkspace()) {
- workspace.replaceWithYaml(TestResources.text("/models/project-tasks.yaml"));
- CrudUiController controller = new CrudUiController(workspace);
-
- UiHttpResponse response = controller.apiDocumentationPage();
-
- Assertions.assertEquals(200, response.statusCode());
- Assertions.assertEquals("text/html", response.contentType());
- Assertions.assertTrue(response.body().contains("Project Tasks API Documentation"));
- Assertions.assertTrue(response.body().contains("href=\"/favicon.svg\""));
- Assertions.assertTrue(response.body().contains("/api/projects"));
- Assertions.assertTrue(response.body().contains("/api/projects/{id}/tasks"));
- Assertions.assertTrue(response.body().contains("Download OpenAPI"));
- }
+ workspace.replaceWithYaml(TestResources.text("/models/project-tasks.yaml"));
+
+ UiHttpResponse response = controller.apiDocumentationPage();
+
+ Assertions.assertEquals(200, response.statusCode());
+ Assertions.assertEquals("text/html", response.contentType());
+ Assertions.assertTrue(response.body().contains("Project Tasks API Documentation"));
+ Assertions.assertTrue(response.body().contains("href=\"/favicon.svg\""));
+ Assertions.assertTrue(response.body().contains("/api/projects"));
+ Assertions.assertTrue(response.body().contains("/api/projects/{id}/tasks"));
+ Assertions.assertTrue(response.body().contains("Download OpenAPI"));
}
@Test
public void swaggerUiPageEmbedsUnpkgSwaggerUiForCurrentOpenApiJson() {
- try (ActiveThingifierWorkspace workspace =
- ActiveThingifierWorkspace.defaultTodoManagerWorkspace()) {
- CrudUiController controller = new CrudUiController(workspace);
-
- UiHttpResponse response = controller.swaggerUi();
-
- Assertions.assertEquals(200, response.statusCode());
- Assertions.assertTrue(
- response.body().contains("https://unpkg.com/swagger-ui-dist/swagger-ui.css"));
- Assertions.assertTrue(
- response.body()
- .contains("https://unpkg.com/swagger-ui-dist/swagger-ui-bundle.js"));
- Assertions.assertTrue(response.body().contains("href=\"/favicon.svg\""));
- Assertions.assertTrue(response.body().contains("SwaggerUIBundle"));
- Assertions.assertTrue(response.body().contains("/openapi.json"));
- Assertions.assertTrue(response.body().contains("/openapi-3.1.json"));
- Assertions.assertTrue(response.body().contains("/openapi-3.2.json"));
- Assertions.assertTrue(response.body().contains("/openapi-3.0.json"));
- Assertions.assertTrue(response.body().contains("/css/swagger-copy-for-ai.css"));
- Assertions.assertTrue(response.body().contains("/js/swagger-copy-for-ai.js"));
- Assertions.assertTrue(response.body().contains("window.thingifierSwaggerCopyForAi"));
- Assertions.assertTrue(response.body().contains("openApiUrl: \"/openapi.json\""));
- Assertions.assertTrue(response.body().contains("\"urls.primaryName\""));
- }
+ UiHttpResponse response = controller.swaggerUi();
+
+ Assertions.assertEquals(200, response.statusCode());
+ Assertions.assertTrue(
+ response.body().contains("https://unpkg.com/swagger-ui-dist/swagger-ui.css"));
+ Assertions.assertTrue(
+ response.body().contains("https://unpkg.com/swagger-ui-dist/swagger-ui-bundle.js"));
+ Assertions.assertTrue(response.body().contains("href=\"/favicon.svg\""));
+ Assertions.assertTrue(response.body().contains("SwaggerUIBundle"));
+ Assertions.assertTrue(response.body().contains("/openapi.json"));
+ Assertions.assertTrue(response.body().contains("/openapi-3.1.json"));
+ Assertions.assertTrue(response.body().contains("/openapi-3.2.json"));
+ Assertions.assertTrue(response.body().contains("/openapi-3.0.json"));
+ Assertions.assertTrue(response.body().contains("/css/swagger-copy-for-ai.css"));
+ Assertions.assertTrue(response.body().contains("/js/swagger-copy-for-ai.js"));
+ Assertions.assertTrue(response.body().contains("window.thingifierSwaggerCopyForAi"));
+ Assertions.assertTrue(response.body().contains("openApiUrl: \"/openapi.json\""));
+ Assertions.assertTrue(response.body().contains("\"urls.primaryName\""));
}
@Test
diff --git a/thingifier-crud-ui/src/test/java/uk/co/compendiumdev/thingifier/crudui/WorkspaceProjectServiceTest.java b/thingifier-crud-ui/src/test/java/uk/co/compendiumdev/thingifier/crudui/WorkspaceProjectServiceTest.java
index b37a72cb..0f391823 100644
--- a/thingifier-crud-ui/src/test/java/uk/co/compendiumdev/thingifier/crudui/WorkspaceProjectServiceTest.java
+++ b/thingifier-crud-ui/src/test/java/uk/co/compendiumdev/thingifier/crudui/WorkspaceProjectServiceTest.java
@@ -19,175 +19,173 @@ public class WorkspaceProjectServiceTest {
@Test
public void saveCreatesProjectManifestSchemaAndDataFiles() throws Exception {
- try (ActiveThingifierWorkspace workspace =
- ActiveThingifierWorkspace.defaultTodoManagerWorkspace()) {
- workspace.replaceWithYaml(TestResources.text("/models/project-tasks.yaml"));
- createRelatedProjectAndTodo(new DynamicThingifierApiProxy(workspace));
- CrudUiController controller = new CrudUiController(workspace);
- Path projectFolder = temp.resolve("project-bundle");
-
- UiHttpResponse response = controller.saveProject(request(projectFolder));
-
- Assertions.assertEquals(200, response.statusCode());
- Assertions.assertTrue(Files.exists(projectFolder.resolve("projectfile.erproj")));
- Assertions.assertTrue(Files.exists(projectFolder.resolve("schema.yaml")));
- Assertions.assertTrue(Files.exists(projectFolder.resolve("data.json")));
- Assertions.assertFalse(
- Files.readString(projectFolder.resolve("data.json")).contains("schemaYaml"));
- Assertions.assertTrue(response.body().contains("\"projectStatus\": \"saved\""));
- Assertions.assertTrue(response.body().contains("\"active\": true"));
- }
+ withDefaultWorkspace(
+ (workspace, controller, proxy) -> {
+ workspace.replaceWithYaml(TestResources.text("/models/project-tasks.yaml"));
+ createRelatedProjectAndTodo(proxy);
+ Path projectFolder = temp.resolve("project-bundle");
+
+ UiHttpResponse response = controller.saveProject(request(projectFolder));
+
+ Assertions.assertEquals(200, response.statusCode());
+ Assertions.assertTrue(
+ Files.exists(projectFolder.resolve("projectfile.erproj")));
+ Assertions.assertTrue(Files.exists(projectFolder.resolve("schema.yaml")));
+ Assertions.assertTrue(Files.exists(projectFolder.resolve("data.json")));
+ Assertions.assertFalse(
+ Files.readString(projectFolder.resolve("data.json"))
+ .contains("schemaYaml"));
+ Assertions.assertTrue(response.body().contains("\"projectStatus\": \"saved\""));
+ Assertions.assertTrue(response.body().contains("\"active\": true"));
+ });
}
@Test
- public void savedProjectReloadsSchemaDataAndRelationshipEdges() {
+ public void savedProjectReloadsSchemaDataAndRelationshipEdges() throws Exception {
Path projectFolder = temp.resolve("project-bundle");
- try (ActiveThingifierWorkspace source =
- ActiveThingifierWorkspace.defaultTodoManagerWorkspace()) {
- source.replaceWithYaml(TestResources.text("/models/project-tasks.yaml"));
- createRelatedProjectAndTodo(new DynamicThingifierApiProxy(source));
- new CrudUiController(source).saveProject(request(projectFolder));
- }
-
- try (ActiveThingifierWorkspace target =
- ActiveThingifierWorkspace.defaultTodoManagerWorkspace()) {
- CrudUiController controller = new CrudUiController(target);
-
- UiHttpResponse response = controller.loadProject(request(projectFolder));
- DynamicThingifierApiProxy targetProxy = new DynamicThingifierApiProxy(target);
-
- Assertions.assertEquals(200, response.statusCode());
- Assertions.assertTrue(response.body().contains("\"projectStatus\": \"loaded\""));
- Assertions.assertEquals("Project Tasks", target.snapshot().definition().title());
- Assertions.assertTrue(targetProxy.getJson("projects").body().contains("Project A"));
- Assertions.assertTrue(targetProxy.getJson("todos").body().contains("Task A"));
- Assertions.assertEquals(
- 1,
- root(targetProxy.getJson("projects/1/tasks").body())
- .getAsJsonArray("todos")
- .size());
- }
+ withDefaultWorkspace(
+ (source, controller, proxy) -> {
+ source.replaceWithYaml(TestResources.text("/models/project-tasks.yaml"));
+ createRelatedProjectAndTodo(proxy);
+ controller.saveProject(request(projectFolder));
+ });
+
+ withDefaultWorkspace(
+ (target, controller, proxy) -> {
+ UiHttpResponse response = controller.loadProject(request(projectFolder));
+
+ Assertions.assertEquals(200, response.statusCode());
+ Assertions.assertTrue(
+ response.body().contains("\"projectStatus\": \"loaded\""));
+ Assertions.assertEquals(
+ "Project Tasks", target.snapshot().definition().title());
+ Assertions.assertTrue(proxy.getJson("projects").body().contains("Project A"));
+ Assertions.assertTrue(proxy.getJson("todos").body().contains("Task A"));
+ Assertions.assertEquals(
+ 1,
+ root(proxy.getJson("projects/1/tasks").body())
+ .getAsJsonArray("todos")
+ .size());
+ });
}
@Test
public void sqliteBackedSaveCreatesManifestSchemaAndDatabaseFile() throws Exception {
Path projectFolder = temp.resolve("sqlite-project-bundle");
- try (ActiveThingifierWorkspace workspace =
- ActiveThingifierWorkspace.defaultTodoManagerWorkspace()) {
- workspace.replaceWithYaml(TestResources.text("/models/project-tasks.yaml"));
- createRelatedProjectAndTodo(new DynamicThingifierApiProxy(workspace));
- workspace.switchStorage(WorkspaceStorage.sqliteFile(temp.resolve("source.sqlite")));
-
- UiHttpResponse response =
- new CrudUiController(workspace).saveProject(request(projectFolder));
-
- Assertions.assertEquals(200, response.statusCode());
- Assertions.assertTrue(Files.exists(projectFolder.resolve("projectfile.erproj")));
- Assertions.assertTrue(Files.exists(projectFolder.resolve("schema.yaml")));
- Assertions.assertTrue(Files.exists(projectFolder.resolve("data.sqlite")));
- Assertions.assertFalse(Files.exists(projectFolder.resolve("data.json")));
- Assertions.assertTrue(
- Files.readString(projectFolder.resolve("projectfile.erproj"))
- .contains("dataFile: data.sqlite"));
- Assertions.assertFalse(
- Files.readString(projectFolder.resolve("projectfile.erproj"))
- .contains("storage:"));
- }
+ withDefaultWorkspace(
+ (workspace, controller, proxy) -> {
+ workspace.replaceWithYaml(TestResources.text("/models/project-tasks.yaml"));
+ createRelatedProjectAndTodo(proxy);
+ workspace.switchStorage(
+ WorkspaceStorage.sqliteFile(temp.resolve("source.sqlite")));
+
+ UiHttpResponse response = controller.saveProject(request(projectFolder));
+
+ Assertions.assertEquals(200, response.statusCode());
+ Assertions.assertTrue(
+ Files.exists(projectFolder.resolve("projectfile.erproj")));
+ Assertions.assertTrue(Files.exists(projectFolder.resolve("schema.yaml")));
+ Assertions.assertTrue(Files.exists(projectFolder.resolve("data.sqlite")));
+ Assertions.assertFalse(Files.exists(projectFolder.resolve("data.json")));
+ Assertions.assertTrue(
+ Files.readString(projectFolder.resolve("projectfile.erproj"))
+ .contains("dataFile: data.sqlite"));
+ Assertions.assertFalse(
+ Files.readString(projectFolder.resolve("projectfile.erproj"))
+ .contains("storage:"));
+ });
}
@Test
public void saveAsSqliteFromMemorySwitchesWorkspaceToProjectDatabaseFile() throws Exception {
Path projectFolder = temp.resolve("sqlite-save-as-project");
- try (ActiveThingifierWorkspace workspace =
- ActiveThingifierWorkspace.defaultTodoManagerWorkspace()) {
- workspace.replaceWithYaml(TestResources.text("/models/project-tasks.yaml"));
- createRelatedProjectAndTodo(new DynamicThingifierApiProxy(workspace));
-
- UiHttpResponse response =
- new CrudUiController(workspace).saveProject(request(projectFolder, "sqlite"));
-
- Assertions.assertEquals(200, response.statusCode(), response.body());
- Assertions.assertTrue(Files.exists(projectFolder.resolve("data.sqlite")));
- Assertions.assertFalse(Files.exists(projectFolder.resolve("data.json")));
- Assertions.assertEquals("sqlite-file", workspace.snapshot().storage().mode());
- Assertions.assertEquals(
- projectFolder.resolve("data.sqlite").toAbsolutePath().normalize().toString(),
- workspace.snapshot().storage().sqliteFilePath());
- Assertions.assertTrue(
- new DynamicThingifierApiProxy(workspace)
- .getJson("projects")
- .body()
- .contains("Project A"));
- }
+ withDefaultWorkspace(
+ (workspace, controller, proxy) -> {
+ workspace.replaceWithYaml(TestResources.text("/models/project-tasks.yaml"));
+ createRelatedProjectAndTodo(proxy);
+
+ UiHttpResponse response =
+ controller.saveProject(request(projectFolder, "sqlite"));
+
+ Assertions.assertEquals(200, response.statusCode(), response.body());
+ Assertions.assertTrue(Files.exists(projectFolder.resolve("data.sqlite")));
+ Assertions.assertFalse(Files.exists(projectFolder.resolve("data.json")));
+ Assertions.assertEquals("sqlite-file", workspace.snapshot().storage().mode());
+ Assertions.assertEquals(
+ projectFolder
+ .resolve("data.sqlite")
+ .toAbsolutePath()
+ .normalize()
+ .toString(),
+ workspace.snapshot().storage().sqliteFilePath());
+ Assertions.assertTrue(proxy.getJson("projects").body().contains("Project A"));
+ });
}
@Test
public void saveAsJsonFromSqliteSwitchesWorkspaceToInMemoryStorage() throws Exception {
Path projectFolder = temp.resolve("json-save-as-project");
- try (ActiveThingifierWorkspace workspace =
- ActiveThingifierWorkspace.defaultTodoManagerWorkspace()) {
- workspace.replaceWithYaml(TestResources.text("/models/project-tasks.yaml"));
- createRelatedProjectAndTodo(new DynamicThingifierApiProxy(workspace));
- workspace.switchStorage(WorkspaceStorage.sqliteFile(temp.resolve("source.sqlite")));
-
- UiHttpResponse response =
- new CrudUiController(workspace).saveProject(request(projectFolder, "json"));
-
- Assertions.assertEquals(200, response.statusCode(), response.body());
- Assertions.assertTrue(Files.exists(projectFolder.resolve("data.json")));
- Assertions.assertFalse(
- Files.readString(projectFolder.resolve("projectfile.erproj"))
- .contains("sqlite-file"));
- Assertions.assertEquals("memory", workspace.snapshot().storage().mode());
- Assertions.assertTrue(
- new DynamicThingifierApiProxy(workspace)
- .getJson("projects")
- .body()
- .contains("Project A"));
- }
+ withDefaultWorkspace(
+ (workspace, controller, proxy) -> {
+ workspace.replaceWithYaml(TestResources.text("/models/project-tasks.yaml"));
+ createRelatedProjectAndTodo(proxy);
+ workspace.switchStorage(
+ WorkspaceStorage.sqliteFile(temp.resolve("source.sqlite")));
+
+ UiHttpResponse response =
+ controller.saveProject(request(projectFolder, "json"));
+
+ Assertions.assertEquals(200, response.statusCode(), response.body());
+ Assertions.assertTrue(Files.exists(projectFolder.resolve("data.json")));
+ Assertions.assertFalse(
+ Files.readString(projectFolder.resolve("projectfile.erproj"))
+ .contains("sqlite-file"));
+ Assertions.assertEquals("memory", workspace.snapshot().storage().mode());
+ Assertions.assertTrue(proxy.getJson("projects").body().contains("Project A"));
+ });
}
@Test
- public void sqliteBackedProjectReloadsDataAndSwitchesStorageMode() {
+ public void sqliteBackedProjectReloadsDataAndSwitchesStorageMode() throws Exception {
Path projectFolder = temp.resolve("sqlite-project-bundle");
- try (ActiveThingifierWorkspace source =
- ActiveThingifierWorkspace.defaultTodoManagerWorkspace()) {
- source.replaceWithYaml(TestResources.text("/models/project-tasks.yaml"));
- createRelatedProjectAndTodo(new DynamicThingifierApiProxy(source));
- source.switchStorage(WorkspaceStorage.sqliteFile(temp.resolve("source.sqlite")));
- new CrudUiController(source).saveProject(request(projectFolder));
- }
-
- try (ActiveThingifierWorkspace target =
- ActiveThingifierWorkspace.defaultTodoManagerWorkspace()) {
- UiHttpResponse response =
- new CrudUiController(target).loadProject(request(projectFolder));
- DynamicThingifierApiProxy targetProxy = new DynamicThingifierApiProxy(target);
-
- Assertions.assertEquals(200, response.statusCode());
- Assertions.assertEquals("sqlite-file", target.snapshot().storage().mode());
- Assertions.assertTrue(
- target.snapshot().storage().sqliteFilePath().endsWith("data.sqlite"));
- Assertions.assertTrue(targetProxy.getJson("projects").body().contains("Project A"));
- Assertions.assertEquals(
- 1,
- root(targetProxy.getJson("projects/1/tasks").body())
- .getAsJsonArray("todos")
- .size());
- }
+ withDefaultWorkspace(
+ (source, controller, proxy) -> {
+ source.replaceWithYaml(TestResources.text("/models/project-tasks.yaml"));
+ createRelatedProjectAndTodo(proxy);
+ source.switchStorage(
+ WorkspaceStorage.sqliteFile(temp.resolve("source.sqlite")));
+ controller.saveProject(request(projectFolder));
+ });
+
+ withDefaultWorkspace(
+ (target, controller, proxy) -> {
+ UiHttpResponse response = controller.loadProject(request(projectFolder));
+
+ Assertions.assertEquals(200, response.statusCode());
+ Assertions.assertEquals("sqlite-file", target.snapshot().storage().mode());
+ Assertions.assertTrue(
+ target.snapshot().storage().sqliteFilePath().endsWith("data.sqlite"));
+ Assertions.assertTrue(proxy.getJson("projects").body().contains("Project A"));
+ Assertions.assertEquals(
+ 1,
+ root(proxy.getJson("projects/1/tasks").body())
+ .getAsJsonArray("todos")
+ .size());
+ });
}
@Test
public void loadProjectTreatsSqliteDataFileAsSqliteStorageWithoutStorageBlock()
throws Exception {
Path projectFolder = temp.resolve("edited-sqlite-project-bundle");
- try (ActiveThingifierWorkspace source =
- ActiveThingifierWorkspace.defaultTodoManagerWorkspace()) {
- source.replaceWithYaml(TestResources.text("/models/project-tasks.yaml"));
- createRelatedProjectAndTodo(new DynamicThingifierApiProxy(source));
- source.switchStorage(WorkspaceStorage.sqliteFile(temp.resolve("source.sqlite")));
- new CrudUiController(source).saveProject(request(projectFolder));
- }
+ withDefaultWorkspace(
+ (source, controller, proxy) -> {
+ source.replaceWithYaml(TestResources.text("/models/project-tasks.yaml"));
+ createRelatedProjectAndTodo(proxy);
+ source.switchStorage(
+ WorkspaceStorage.sqliteFile(temp.resolve("source.sqlite")));
+ controller.saveProject(request(projectFolder));
+ });
Files.move(
projectFolder.resolve("data.sqlite"), projectFolder.resolve("todomanager.sqlite"));
Files.writeString(
@@ -198,258 +196,268 @@ public void loadProjectTreatsSqliteDataFileAsSqliteStorageWithoutStorageBlock()
+ "schemaFile: schema.yaml\n"
+ "dataFile: todomanager.sqlite\n");
- try (ActiveThingifierWorkspace target =
- ActiveThingifierWorkspace.defaultTodoManagerWorkspace()) {
- UiHttpResponse response =
- new CrudUiController(target).loadProject(request(projectFolder));
- DynamicThingifierApiProxy targetProxy = new DynamicThingifierApiProxy(target);
-
- Assertions.assertEquals(200, response.statusCode(), response.body());
- Assertions.assertEquals("sqlite-file", target.snapshot().storage().mode());
- Assertions.assertTrue(
- target.snapshot().storage().sqliteFilePath().endsWith("todomanager.sqlite"));
- Assertions.assertTrue(targetProxy.getJson("projects").body().contains("Project A"));
- }
+ withDefaultWorkspace(
+ (target, controller, proxy) -> {
+ UiHttpResponse response = controller.loadProject(request(projectFolder));
+
+ Assertions.assertEquals(200, response.statusCode(), response.body());
+ Assertions.assertEquals("sqlite-file", target.snapshot().storage().mode());
+ Assertions.assertTrue(
+ target.snapshot()
+ .storage()
+ .sqliteFilePath()
+ .endsWith("todomanager.sqlite"));
+ Assertions.assertTrue(proxy.getJson("projects").body().contains("Project A"));
+ });
}
@Test
public void loadProjectSniffsSqliteDataFileWhenFilenameHasNoSqliteExtension() throws Exception {
Path projectFolder = temp.resolve("sniffed-sqlite-project-bundle");
- try (ActiveThingifierWorkspace source =
- ActiveThingifierWorkspace.defaultTodoManagerWorkspace()) {
- source.replaceWithYaml(TestResources.text("/models/project-tasks.yaml"));
- createRelatedProjectAndTodo(new DynamicThingifierApiProxy(source));
- source.switchStorage(WorkspaceStorage.sqliteFile(temp.resolve("source.sqlite")));
- new CrudUiController(source).saveProject(request(projectFolder));
- }
+ withDefaultWorkspace(
+ (source, controller, proxy) -> {
+ source.replaceWithYaml(TestResources.text("/models/project-tasks.yaml"));
+ createRelatedProjectAndTodo(proxy);
+ source.switchStorage(
+ WorkspaceStorage.sqliteFile(temp.resolve("source.sqlite")));
+ controller.saveProject(request(projectFolder));
+ });
Files.move(projectFolder.resolve("data.sqlite"), projectFolder.resolve("data.bin"));
Files.writeString(
projectFolder.resolve("projectfile.erproj"),
"formatVersion: 1\n" + "schemaFile: schema.yaml\n" + "dataFile: data.bin\n");
- try (ActiveThingifierWorkspace target =
- ActiveThingifierWorkspace.defaultTodoManagerWorkspace()) {
- UiHttpResponse response =
- new CrudUiController(target).loadProject(request(projectFolder));
+ withDefaultWorkspace(
+ (target, controller, proxy) -> {
+ UiHttpResponse response = controller.loadProject(request(projectFolder));
- Assertions.assertEquals(200, response.statusCode(), response.body());
- Assertions.assertEquals("sqlite-file", target.snapshot().storage().mode());
- Assertions.assertTrue(
- target.snapshot().storage().sqliteFilePath().endsWith("data.bin"));
- }
+ Assertions.assertEquals(200, response.statusCode(), response.body());
+ Assertions.assertEquals("sqlite-file", target.snapshot().storage().mode());
+ Assertions.assertTrue(
+ target.snapshot().storage().sqliteFilePath().endsWith("data.bin"));
+ });
}
@Test
- public void loadAcceptsDirectProjectFilePath() {
+ public void loadAcceptsDirectProjectFilePath() throws Exception {
Path projectFolder = temp.resolve("project-bundle");
- try (ActiveThingifierWorkspace source =
- ActiveThingifierWorkspace.defaultTodoManagerWorkspace()) {
- source.replaceWithYaml(TestResources.text("/models/project-tasks.yaml"));
- new CrudUiController(source).saveProject(request(projectFolder));
- }
-
- try (ActiveThingifierWorkspace target =
- ActiveThingifierWorkspace.defaultTodoManagerWorkspace()) {
- UiHttpResponse response =
- new CrudUiController(target)
- .loadProject(request(projectFolder.resolve("projectfile.erproj")));
-
- Assertions.assertEquals(200, response.statusCode());
- Assertions.assertEquals("Project Tasks", target.snapshot().definition().title());
- }
+ withDefaultWorkspace(
+ (source, controller, proxy) -> {
+ source.replaceWithYaml(TestResources.text("/models/project-tasks.yaml"));
+ controller.saveProject(request(projectFolder));
+ });
+
+ withDefaultWorkspace(
+ (target, controller, proxy) -> {
+ UiHttpResponse response =
+ controller.loadProject(
+ request(projectFolder.resolve("projectfile.erproj")));
+
+ Assertions.assertEquals(200, response.statusCode());
+ Assertions.assertEquals(
+ "Project Tasks", target.snapshot().definition().title());
+ });
}
@Test
public void checkSavePathReportsCreatableFolderAndManagedFiles() throws Exception {
Path creatableFolder = temp.resolve("new-project");
- try (ActiveThingifierWorkspace workspace =
- ActiveThingifierWorkspace.defaultTodoManagerWorkspace()) {
- CrudUiController controller = new CrudUiController(workspace);
-
- JsonObject creatable =
- root(controller.checkProject(actionRequest("save", creatableFolder)).body());
-
- Assertions.assertTrue(creatable.get("canProceed").getAsBoolean());
- Assertions.assertEquals("creatable-folder", creatable.get("kind").getAsString());
-
- Path existingFolder = temp.resolve("existing-project");
- Files.createDirectories(existingFolder);
- Files.writeString(existingFolder.resolve("projectfile.erproj"), "managed");
- Files.writeString(existingFolder.resolve("schema.yaml"), "managed");
-
- JsonObject existing =
- root(controller.checkProject(actionRequest("save", existingFolder)).body());
-
- Assertions.assertTrue(existing.get("canProceed").getAsBoolean());
- Assertions.assertTrue(
- existing.get("warning").getAsString().contains("projectfile.erproj"));
- Assertions.assertTrue(existing.get("warning").getAsString().contains("schema.yaml"));
- }
+ withDefaultWorkspace(
+ (workspace, controller, proxy) -> {
+ JsonObject creatable =
+ root(
+ controller
+ .checkProject(actionRequest("save", creatableFolder))
+ .body());
+
+ Assertions.assertTrue(creatable.get("canProceed").getAsBoolean());
+ Assertions.assertEquals(
+ "creatable-folder", creatable.get("kind").getAsString());
+
+ Path existingFolder = temp.resolve("existing-project");
+ Files.createDirectories(existingFolder);
+ Files.writeString(existingFolder.resolve("projectfile.erproj"), "managed");
+ Files.writeString(existingFolder.resolve("schema.yaml"), "managed");
+
+ JsonObject existing =
+ root(
+ controller
+ .checkProject(actionRequest("save", existingFolder))
+ .body());
+
+ Assertions.assertTrue(existing.get("canProceed").getAsBoolean());
+ Assertions.assertTrue(
+ existing.get("warning").getAsString().contains("projectfile.erproj"));
+ Assertions.assertTrue(
+ existing.get("warning").getAsString().contains("schema.yaml"));
+ });
}
@Test
- public void checkLoadPathAcceptsFolderAndDirectProjectFile() {
+ public void checkLoadPathAcceptsFolderAndDirectProjectFile() throws Exception {
Path projectFolder = temp.resolve("project-bundle");
- try (ActiveThingifierWorkspace source =
- ActiveThingifierWorkspace.defaultTodoManagerWorkspace()) {
- new CrudUiController(source).saveProject(request(projectFolder));
- }
- try (ActiveThingifierWorkspace workspace =
- ActiveThingifierWorkspace.defaultTodoManagerWorkspace()) {
- CrudUiController controller = new CrudUiController(workspace);
-
- JsonObject folderCheck =
- root(controller.checkProject(actionRequest("load", projectFolder)).body());
- JsonObject fileCheck =
- root(
- controller
- .checkProject(
- actionRequest(
- "load",
- projectFolder.resolve("projectfile.erproj")))
- .body());
-
- Assertions.assertTrue(folderCheck.get("canProceed").getAsBoolean());
- Assertions.assertEquals("folder", folderCheck.get("kind").getAsString());
- Assertions.assertTrue(fileCheck.get("canProceed").getAsBoolean());
- Assertions.assertEquals("project-file", fileCheck.get("kind").getAsString());
- }
+ withDefaultWorkspace(
+ (source, controller, proxy) -> controller.saveProject(request(projectFolder)));
+
+ withDefaultWorkspace(
+ (workspace, controller, proxy) -> {
+ JsonObject folderCheck =
+ root(
+ controller
+ .checkProject(actionRequest("load", projectFolder))
+ .body());
+ JsonObject fileCheck =
+ root(
+ controller
+ .checkProject(
+ actionRequest(
+ "load",
+ projectFolder.resolve(
+ "projectfile.erproj")))
+ .body());
+
+ Assertions.assertTrue(folderCheck.get("canProceed").getAsBoolean());
+ Assertions.assertEquals("folder", folderCheck.get("kind").getAsString());
+ Assertions.assertTrue(fileCheck.get("canProceed").getAsBoolean());
+ Assertions.assertEquals("project-file", fileCheck.get("kind").getAsString());
+ });
}
@Test
- public void browserExportFilesEmitsJsonBackedProjectFiles() {
- try (ActiveThingifierWorkspace workspace =
- ActiveThingifierWorkspace.defaultTodoManagerWorkspace()) {
- UiHttpResponse response = new CrudUiController(workspace).exportProjectFiles();
- JsonObject body = root(response.body());
-
- Assertions.assertEquals(200, response.statusCode());
- Assertions.assertTrue(response.body().contains("\"projectStatus\": \"exported\""));
- Assertions.assertTrue(response.body().contains("\"projectfile.erproj\""));
- Assertions.assertTrue(response.body().contains("\"schema.yaml\""));
- Assertions.assertTrue(response.body().contains("\"data.json\""));
- Assertions.assertFalse(response.body().contains("\"data.sqlite\""));
- Assertions.assertEquals("memory", body.get("storageMode").getAsString());
- Assertions.assertEquals("json", body.get("projectStorageMode").getAsString());
- }
+ public void browserExportFilesEmitsJsonBackedProjectFiles() throws Exception {
+ withDefaultWorkspace(
+ (workspace, controller, proxy) -> {
+ UiHttpResponse response = controller.exportProjectFiles();
+ JsonObject body = root(response.body());
+
+ Assertions.assertEquals(200, response.statusCode());
+ Assertions.assertTrue(
+ response.body().contains("\"projectStatus\": \"exported\""));
+ Assertions.assertTrue(response.body().contains("\"projectfile.erproj\""));
+ Assertions.assertTrue(response.body().contains("\"schema.yaml\""));
+ Assertions.assertTrue(response.body().contains("\"data.json\""));
+ Assertions.assertFalse(response.body().contains("\"data.sqlite\""));
+ Assertions.assertEquals("memory", body.get("storageMode").getAsString());
+ Assertions.assertEquals("json", body.get("projectStorageMode").getAsString());
+ });
}
@Test
- public void browserExportFilesEmitsSqliteBackedProjectFiles() {
- try (ActiveThingifierWorkspace workspace =
- ActiveThingifierWorkspace.defaultTodoManagerWorkspace()) {
- workspace.switchStorage(WorkspaceStorage.sqliteFile(temp.resolve("source.sqlite")));
-
- UiHttpResponse response = new CrudUiController(workspace).exportProjectFiles();
- JsonObject body = root(response.body());
-
- Assertions.assertEquals(200, response.statusCode());
- Assertions.assertTrue(response.body().contains("\"projectfile.erproj\""));
- Assertions.assertTrue(response.body().contains("\"schema.yaml\""));
- Assertions.assertTrue(response.body().contains("\"data.sqlite\""));
- Assertions.assertFalse(response.body().contains("\"data.json\""));
- Assertions.assertEquals("sqlite-file", body.get("storageMode").getAsString());
- Assertions.assertEquals("sqlite", body.get("projectStorageMode").getAsString());
- }
+ public void browserExportFilesEmitsSqliteBackedProjectFiles() throws Exception {
+ withDefaultWorkspace(
+ (workspace, controller, proxy) -> {
+ workspace.switchStorage(
+ WorkspaceStorage.sqliteFile(temp.resolve("source.sqlite")));
+
+ UiHttpResponse response = controller.exportProjectFiles();
+ JsonObject body = root(response.body());
+
+ Assertions.assertEquals(200, response.statusCode());
+ Assertions.assertTrue(response.body().contains("\"projectfile.erproj\""));
+ Assertions.assertTrue(response.body().contains("\"schema.yaml\""));
+ Assertions.assertTrue(response.body().contains("\"data.sqlite\""));
+ Assertions.assertFalse(response.body().contains("\"data.json\""));
+ Assertions.assertEquals("sqlite-file", body.get("storageMode").getAsString());
+ Assertions.assertEquals("sqlite", body.get("projectStorageMode").getAsString());
+ });
}
@Test
- public void browserExportFilesCanExportSqliteProjectFromMemoryWorkspace() {
- try (ActiveThingifierWorkspace workspace =
- ActiveThingifierWorkspace.defaultTodoManagerWorkspace()) {
- UiHttpResponse response =
- new CrudUiController(workspace)
- .exportProjectFiles(
+ public void browserExportFilesCanExportSqliteProjectFromMemoryWorkspace() throws Exception {
+ withDefaultWorkspace(
+ (workspace, controller, proxy) -> {
+ UiHttpResponse response =
+ controller.exportProjectFiles(
JsonSupport.toJson(Map.of("projectStorageMode", "sqlite")));
- JsonObject body = root(response.body());
-
- Assertions.assertEquals(200, response.statusCode());
- Assertions.assertTrue(response.body().contains("\"data.sqlite\""));
- Assertions.assertFalse(response.body().contains("\"data.json\""));
- Assertions.assertEquals("sqlite-file", body.get("storageMode").getAsString());
- Assertions.assertEquals("sqlite", body.get("projectStorageMode").getAsString());
- Assertions.assertEquals("memory", workspace.snapshot().storage().mode());
- }
+ JsonObject body = root(response.body());
+
+ Assertions.assertEquals(200, response.statusCode());
+ Assertions.assertTrue(response.body().contains("\"data.sqlite\""));
+ Assertions.assertFalse(response.body().contains("\"data.json\""));
+ Assertions.assertEquals("sqlite-file", body.get("storageMode").getAsString());
+ Assertions.assertEquals("sqlite", body.get("projectStorageMode").getAsString());
+ Assertions.assertEquals("memory", workspace.snapshot().storage().mode());
+ });
}
@Test
public void browserLoadFilesLoadsJsonBackedProjectWithoutServerPath() throws Exception {
Path projectFolder = temp.resolve("browser-json-project");
- try (ActiveThingifierWorkspace source =
- ActiveThingifierWorkspace.defaultTodoManagerWorkspace()) {
- source.replaceWithYaml(TestResources.text("/models/project-tasks.yaml"));
- createRelatedProjectAndTodo(new DynamicThingifierApiProxy(source));
- new CrudUiController(source).saveProject(request(projectFolder));
- }
-
- try (ActiveThingifierWorkspace target =
- ActiveThingifierWorkspace.defaultTodoManagerWorkspace()) {
- UiHttpResponse response =
- new CrudUiController(target)
- .loadProjectFiles(browserFilePayload(projectFolder, "browser-json"));
- DynamicThingifierApiProxy targetProxy = new DynamicThingifierApiProxy(target);
-
- Assertions.assertEquals(200, response.statusCode(), response.body());
- Assertions.assertEquals("Project Tasks", target.snapshot().definition().title());
- Assertions.assertEquals(
- "Browser folder: browser-json", target.snapshot().projectPath());
- Assertions.assertTrue(targetProxy.getJson("projects").body().contains("Project A"));
- Assertions.assertEquals(
- 1,
- root(targetProxy.getJson("projects/1/tasks").body())
- .getAsJsonArray("todos")
- .size());
- }
+ withDefaultWorkspace(
+ (source, controller, proxy) -> {
+ source.replaceWithYaml(TestResources.text("/models/project-tasks.yaml"));
+ createRelatedProjectAndTodo(proxy);
+ controller.saveProject(request(projectFolder));
+ });
+
+ withDefaultWorkspace(
+ (target, controller, proxy) -> {
+ UiHttpResponse response =
+ controller.loadProjectFiles(
+ browserFilePayload(projectFolder, "browser-json"));
+
+ Assertions.assertEquals(200, response.statusCode(), response.body());
+ Assertions.assertEquals(
+ "Project Tasks", target.snapshot().definition().title());
+ Assertions.assertEquals(
+ "Browser folder: browser-json", target.snapshot().projectPath());
+ Assertions.assertTrue(proxy.getJson("projects").body().contains("Project A"));
+ Assertions.assertEquals(
+ 1,
+ root(proxy.getJson("projects/1/tasks").body())
+ .getAsJsonArray("todos")
+ .size());
+ });
}
@Test
public void browserLoadFilesLoadsSqliteBackedProject() throws Exception {
Path projectFolder = temp.resolve("browser-sqlite-project");
- try (ActiveThingifierWorkspace source =
- ActiveThingifierWorkspace.defaultTodoManagerWorkspace()) {
- source.replaceWithYaml(TestResources.text("/models/project-tasks.yaml"));
- createRelatedProjectAndTodo(new DynamicThingifierApiProxy(source));
- source.switchStorage(WorkspaceStorage.sqliteFile(temp.resolve("source.sqlite")));
- new CrudUiController(source).saveProject(request(projectFolder));
- }
-
- try (ActiveThingifierWorkspace target =
- ActiveThingifierWorkspace.defaultTodoManagerWorkspace()) {
- UiHttpResponse response =
- new CrudUiController(target)
- .loadProjectFiles(browserFilePayload(projectFolder, "browser-sqlite"));
- DynamicThingifierApiProxy targetProxy = new DynamicThingifierApiProxy(target);
-
- Assertions.assertEquals(200, response.statusCode(), response.body());
- Assertions.assertEquals("sqlite-file", target.snapshot().storage().mode());
- Assertions.assertEquals(
- "Browser folder: browser-sqlite", target.snapshot().projectPath());
- Assertions.assertTrue(targetProxy.getJson("projects").body().contains("Project A"));
- Assertions.assertEquals(
- 1,
- root(targetProxy.getJson("projects/1/tasks").body())
- .getAsJsonArray("todos")
- .size());
- }
+ withDefaultWorkspace(
+ (source, controller, proxy) -> {
+ source.replaceWithYaml(TestResources.text("/models/project-tasks.yaml"));
+ createRelatedProjectAndTodo(proxy);
+ source.switchStorage(
+ WorkspaceStorage.sqliteFile(temp.resolve("source.sqlite")));
+ controller.saveProject(request(projectFolder));
+ });
+
+ withDefaultWorkspace(
+ (target, controller, proxy) -> {
+ UiHttpResponse response =
+ controller.loadProjectFiles(
+ browserFilePayload(projectFolder, "browser-sqlite"));
+
+ Assertions.assertEquals(200, response.statusCode(), response.body());
+ Assertions.assertEquals("sqlite-file", target.snapshot().storage().mode());
+ Assertions.assertEquals(
+ "Browser folder: browser-sqlite", target.snapshot().projectPath());
+ Assertions.assertTrue(proxy.getJson("projects").body().contains("Project A"));
+ Assertions.assertEquals(
+ 1,
+ root(proxy.getJson("projects/1/tasks").body())
+ .getAsJsonArray("todos")
+ .size());
+ });
}
@Test
- public void invalidBrowserLoadFilesDoesNotMutateWorkspace() {
- try (ActiveThingifierWorkspace workspace =
- ActiveThingifierWorkspace.defaultTodoManagerWorkspace()) {
- long version = workspace.snapshot().version();
- List