CE-1887 Initial (not quite finished) version 1 middleware api spec

This commit is contained in:
2024-10-17 20:26:54 -05:00
parent 8dedc98866
commit cc55b32206
89 changed files with 11930 additions and 86 deletions

View File

@ -0,0 +1,356 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.api.wip;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;
import java.util.stream.Stream;
import io.javalin.config.Key;
import io.javalin.http.Context;
import io.javalin.http.HandlerType;
import io.javalin.http.HttpStatus;
import io.javalin.json.JsonMapper;
import io.javalin.plugin.ContextPlugin;
import io.javalin.security.RouteRole;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
/*******************************************************************************
**
*******************************************************************************/
public class TestContext implements Context
{
private Map<String, String> queryParams = new LinkedHashMap<>();
private Map<String, String> pathParams = new LinkedHashMap<>();
private Map<String, String> formParams = new LinkedHashMap<>();
private InputStream result;
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public TestContext()
{
}
/***************************************************************************
**
***************************************************************************/
public TestContext withQueryParam(String key, String value)
{
queryParams.put(key, value);
return (this);
}
/***************************************************************************
**
***************************************************************************/
public TestContext withPathParam(String key, String value)
{
pathParams.put(key, value);
return (this);
}
/***************************************************************************
**
***************************************************************************/
public TestContext withFormParam(String key, String value)
{
formParams.put(key, value);
return (this);
}
/***************************************************************************
**
***************************************************************************/
@Override
public String queryParam(String key)
{
return queryParams.get(key);
}
/***************************************************************************
**
***************************************************************************/
@Override
public String formParam(String key)
{
return formParams.get(key);
}
/***************************************************************************
**
***************************************************************************/
@Override
public boolean strictContentTypes()
{
return false;
}
/***************************************************************************
**
***************************************************************************/
@Override
public HttpServletRequest req()
{
return null;
}
/***************************************************************************
**
***************************************************************************/
@Override
public HttpServletResponse res()
{
return null;
}
/***************************************************************************
**
***************************************************************************/
@Override
public HandlerType handlerType()
{
return null;
}
/***************************************************************************
**
***************************************************************************/
@Override
public String matchedPath()
{
return "";
}
/***************************************************************************
**
***************************************************************************/
@Override
public String endpointHandlerPath()
{
return "";
}
/***************************************************************************
**
***************************************************************************/
@Override
public <T> T appData(Key<T> key)
{
return null;
}
/***************************************************************************
**
***************************************************************************/
@Override
public JsonMapper jsonMapper()
{
return null;
}
/***************************************************************************
**
***************************************************************************/
@Override
public <T> T with(Class<? extends ContextPlugin<?, T>> aClass)
{
return null;
}
/***************************************************************************
**
***************************************************************************/
@Override
public String pathParam(String key)
{
return pathParams.get(key);
}
/***************************************************************************
**
***************************************************************************/
@Override
public Map<String, String> pathParamMap()
{
return pathParams;
}
/***************************************************************************
**
***************************************************************************/
@Override
public ServletOutputStream outputStream()
{
return null;
}
/***************************************************************************
**
***************************************************************************/
@Override
public Context minSizeForCompression(int i)
{
return null;
}
/***************************************************************************
**
***************************************************************************/
@Override
public Context result(InputStream inputStream)
{
this.result = inputStream;
return (this);
}
/***************************************************************************
**
***************************************************************************/
@Override
public InputStream resultInputStream()
{
return null;
}
/***************************************************************************
**
***************************************************************************/
@Override
public void future(Supplier<? extends CompletableFuture<?>> supplier)
{
}
/***************************************************************************
**
***************************************************************************/
@Override
public void redirect(String s, HttpStatus httpStatus)
{
}
/***************************************************************************
**
***************************************************************************/
@Override
public void writeJsonStream(Stream<?> stream)
{
}
/***************************************************************************
**
***************************************************************************/
@Override
public Context skipRemainingHandlers()
{
return null;
}
/***************************************************************************
**
***************************************************************************/
@Override
public Set<RouteRole> routeRoles()
{
return Set.of();
}
/*******************************************************************************
** Getter for response
**
*******************************************************************************/
public String getResultAsString() throws IOException
{
byte[] bytes = IOUtils.readFully(result, result.available());
return new String(bytes, StandardCharsets.UTF_8);
}
}

View File

@ -0,0 +1,92 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.api.wip;
import java.util.Map;
import com.kingsrook.qqq.openapi.model.Schema;
import com.kingsrook.qqq.openapi.model.Type;
import org.assertj.core.api.AbstractStringAssert;
import org.json.JSONArray;
import org.json.JSONObject;
import static org.assertj.core.api.Assertions.assertThat;
/*******************************************************************************
**
*******************************************************************************/
public class TestUtils
{
/***************************************************************************
**
***************************************************************************/
private static void assertStringVsSchema(String string, Schema schema, String path)
{
String description = "At path " + path;
final AbstractStringAssert<?> assertion = assertThat(string).describedAs(description);
Type type = Type.valueOf(schema.getType().toUpperCase());
switch(type)
{
case OBJECT ->
{
assertion.startsWith("{");
JSONObject object = new JSONObject(string);
for(Map.Entry<String, Schema> entry : schema.getProperties().entrySet())
{
// todo deal with optional
Object subObject = object.get(entry.getKey());
assertStringVsSchema(subObject.toString(), entry.getValue(), path + "/" + entry.getKey());
}
}
case ARRAY ->
{
assertion.startsWith("[");
JSONArray array = new JSONArray(string);
for(int i = 0; i < array.length(); i++)
{
Object subObject = array.get(i);
assertStringVsSchema(subObject.toString(), schema.getItems(), path + "[" + i + "]");
}
}
case BOOLEAN ->
{
assertion.matches("(true|false)");
}
case INTEGER ->
{
assertion.matches("-?\\d+");
}
case NUMBER ->
{
assertion.matches("-?\\d+(\\.\\d+)?");
}
case STRING ->
{
assertion.matches("\".*\"");
}
}
}
}

View File

@ -0,0 +1,75 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.api.wip.v1;
import com.kingsrook.qqq.api.wip.TestContext;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import org.junit.jupiter.api.Test;
/*******************************************************************************
** Unit test for QueryGetSpecV1
*******************************************************************************/
class QueryGetSpecV1Test
{
/***************************************************************************
**
***************************************************************************/
@Test
void testBuildInput() throws Exception
{
TestContext context = new TestContext()
.withPathParam("table", "person")
.withQueryParam("filter", "{}");
// QueryMiddlewareInput queryMiddlewareInput = new QueryGetSpecV1().buildInput(context);
// assertEquals("person", queryMiddlewareInput.getTable());
// assertNotNull(queryMiddlewareInput.getFilter());
// assertEquals(0, queryMiddlewareInput.getFilter().getCriteria().size());
// assertEquals(0, queryMiddlewareInput.getFilter().getOrderBys().size());
// assertNull(queryMiddlewareInput.getQueryJoins());
}
/***************************************************************************
**
***************************************************************************/
@Test
void testBuildOutput() throws Exception
{
TestContext context = new TestContext();
QueryOutput queryOutput = new QueryOutput(new QueryInput());
queryOutput.addRecord(new QRecord().withValue("firstName", "Darin").withDisplayValue("firstName", "Darin"));
// QueryGetSpecV1 queryGetSpecV1 = new QueryGetSpecV1();
// queryGetSpecV1.buildOutput(context, new QueryMiddlewareOutput(queryOutput));
// String resultJson = context.getResultAsString();
// TestUtils.assertResultJsonVsSpec(queryGetSpecV1.defineSimpleSuccessResponse(), resultJson);
}
}

View File

@ -57,6 +57,7 @@ class QJavalinProcessHandlerTest extends QJavalinTestBase
/*******************************************************************************
** test running a process
**
** Note: ported to v1
*******************************************************************************/
@Test
public void test_processGreetInit()
@ -73,6 +74,7 @@ class QJavalinProcessHandlerTest extends QJavalinTestBase
/*******************************************************************************
** test running a process that requires rows, but we didn't tell it how to get them.
**
** Note: ported to v1
*******************************************************************************/
@Test
public void test_processRequiresRowsButNotSpecified()
@ -90,6 +92,7 @@ class QJavalinProcessHandlerTest extends QJavalinTestBase
/*******************************************************************************
** test running a process and telling it rows to load via recordIds param
**
** Note: ported to v1
*******************************************************************************/
@Test
public void test_processRequiresRowsWithRecordIdParam()
@ -108,6 +111,7 @@ class QJavalinProcessHandlerTest extends QJavalinTestBase
/*******************************************************************************
** test running a process and telling it rows to load via filter JSON
**
** Note: ported to v1
*******************************************************************************/
@Test
public void test_processRequiresRowsWithFilterJSON()
@ -169,6 +173,7 @@ class QJavalinProcessHandlerTest extends QJavalinTestBase
/*******************************************************************************
** test running a process with field values on the query string
**
** Note: ported to v1
*******************************************************************************/
@Test
public void test_processGreetInitWithQueryValues()
@ -185,6 +190,7 @@ class QJavalinProcessHandlerTest extends QJavalinTestBase
/*******************************************************************************
** test init'ing a process that goes async
**
** Note: ported to v1, but needs todo more - the status part too in a higher-level
*******************************************************************************/
@Test
public void test_processInitGoingAsync() throws InterruptedException
@ -221,6 +227,7 @@ class QJavalinProcessHandlerTest extends QJavalinTestBase
/*******************************************************************************
** test init'ing a process that does NOT goes async
**
** Note: not ported to v1, but feels redundant, so, not going to.
*******************************************************************************/
@Test
public void test_processInitNotGoingAsync()

View File

@ -24,11 +24,17 @@ package com.kingsrook.qqq.backend.javalin;
import java.io.InputStream;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;
import java.util.stream.Stream;
import io.javalin.config.Key;
import io.javalin.http.Context;
import io.javalin.http.HandlerType;
import io.javalin.http.HttpStatus;
import io.javalin.json.JsonMapper;
import io.javalin.plugin.ContextPlugin;
import io.javalin.security.RouteRole;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@ -133,6 +139,14 @@ class QJavalinUtilsTest
@Override
public boolean strictContentTypes()
{
return false;
}
/***************************************************************************
**
***************************************************************************/
@ -157,17 +171,6 @@ class QJavalinUtilsTest
/***************************************************************************
**
***************************************************************************/
@Override
public <T> T appAttribute(@NotNull String s)
{
return null;
}
/***************************************************************************
**
***************************************************************************/
@ -204,6 +207,30 @@ class QJavalinUtilsTest
@Override
public <T> T appData(@NotNull Key<T> key)
{
return null;
}
@Override
public @NotNull JsonMapper jsonMapper()
{
return null;
}
@Override
public <T> T with(@NotNull Class<? extends ContextPlugin<?, T>> aClass)
{
return null;
}
/***************************************************************************
**
***************************************************************************/
@ -240,6 +267,14 @@ class QJavalinUtilsTest
@Override
public @NotNull Context minSizeForCompression(int i)
{
return null;
}
/***************************************************************************
**
***************************************************************************/
@ -283,5 +318,29 @@ class QJavalinUtilsTest
{
}
@Override
public void writeJsonStream(@NotNull Stream<?> stream)
{
}
@Override
public @NotNull Context skipRemainingHandlers()
{
return null;
}
@Override
public @NotNull Set<RouteRole> routeRoles()
{
return Set.of();
}
}
}

View File

@ -62,6 +62,10 @@ import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinOn;
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinType;
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
import com.kingsrook.qqq.backend.core.model.metadata.permissions.PermissionLevel;
import com.kingsrook.qqq.backend.core.model.metadata.permissions.QPermissionRules;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.PVSValueFormatAndFields;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSourceType;
@ -189,11 +193,35 @@ public class TestUtils
throw new IllegalStateException("Error adding script tables to instance");
}
defineApps(qInstance);
return (qInstance);
}
private static void defineApps(QInstance qInstance)
{
QAppMetaData childApp = new QAppMetaData()
.withName("childApp")
.withLabel("Child App")
.withIcon(new QIcon().withName("child_friendly"))
.withPermissionRules(new QPermissionRules().withLevel(PermissionLevel.NOT_PROTECTED))
.withChild(qInstance.getProcess(PROCESS_NAME_GREET_PEOPLE_INTERACTIVE));
qInstance.addApp(childApp);
QAppMetaData exampleApp = new QAppMetaData()
.withName("homeApp")
.withLabel("Home App")
.withIcon(new QIcon().withName("home"))
.withPermissionRules(new QPermissionRules().withLevel(PermissionLevel.NOT_PROTECTED))
.withChild(childApp)
.withChild(qInstance.getTable(TABLE_NAME_PERSON));
qInstance.addApp(exampleApp);
}
/*******************************************************************************
**
*******************************************************************************/
@ -690,7 +718,7 @@ public class TestUtils
{
return (new RenderWidgetOutput(new RawHTML("title",
QContext.getQSession().getValue(QSession.VALUE_KEY_USER_TIMEZONE_OFFSET_MINUTES)
+ "|" + QContext.getQSession().getValue(QSession.VALUE_KEY_USER_TIMEZONE)
+ "|" + QContext.getQSession().getValue(QSession.VALUE_KEY_USER_TIMEZONE)
)));
}
}

View File

@ -0,0 +1,101 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.middleware.javalin.schemabuilder;
import java.util.List;
import com.kingsrook.qqq.middleware.javalin.specs.v1.responses.AuthenticationMetaDataResponseV1;
import com.kingsrook.qqq.middleware.javalin.specs.v1.responses.MetaDataResponseV1;
import com.kingsrook.qqq.middleware.javalin.specs.v1.responses.ProcessInitOrStepOrStatusResponseV1;
import com.kingsrook.qqq.middleware.javalin.specs.v1.responses.components.AppTreeNode;
import com.kingsrook.qqq.openapi.model.Schema;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
/*******************************************************************************
** Unit test for SchemaBuilder
*******************************************************************************/
class SchemaBuilderTest
{
/*******************************************************************************
**
*******************************************************************************/
@Test
void testIncludesAOneOf()
{
Schema schema = new SchemaBuilder().classToSchema(AuthenticationMetaDataResponseV1.class);
System.out.println(schema);
Schema valuesSchema = schema.getProperties().get("values");
List<Schema> oneOf = valuesSchema.getOneOf();
assertEquals(2, oneOf.size());
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testUsesIncludeProperties()
{
Schema schema = new SchemaBuilder().classToSchema(ProcessInitOrStepOrStatusResponseV1.TypedResponse.class);
for(Schema oneOf : schema.getOneOf())
{
/////////////////////////////////////////////////////////////////////////////////////////
// all of the wrapped one-of schemas should contain these fields from the parent class //
/////////////////////////////////////////////////////////////////////////////////////////
assertTrue(oneOf.getProperties().containsKey("type"));
assertTrue(oneOf.getProperties().containsKey("processUUID"));
}
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testDescriptionOnGetters()
{
Schema schema = new SchemaBuilder().classToSchema(MetaDataResponseV1.class);
assertTrue(schema.getProperties().containsKey("apps"));
assertNotNull(schema.getProperties().get("apps").getDescription());
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testRecursive()
{
Schema schema = new SchemaBuilder().classToSchema(AppTreeNode.class);
Schema childrenSchema = schema.getProperties().get("children");
assertNotNull(childrenSchema.getItems());
System.out.println(schema);
}
}

View File

@ -0,0 +1,124 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.middleware.javalin.specs;
import com.kingsrook.qqq.backend.core.modules.backend.implementations.memory.MemoryRecordStore;
import com.kingsrook.qqq.backend.javalin.TestUtils;
import io.javalin.Javalin;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
/*******************************************************************************
**
*******************************************************************************/
public abstract class SpecTestBase
{
private static int PORT = 6263;
protected static Javalin service;
/***************************************************************************
**
***************************************************************************/
protected abstract AbstractEndpointSpec<?, ?, ?> getSpec();
/***************************************************************************
**
***************************************************************************/
protected abstract String getVersion();
/***************************************************************************
**
***************************************************************************/
protected String getBaseUrlAndPath()
{
return "http://localhost:" + PORT + "/qqq/" + getVersion();
}
/*******************************************************************************
**
*******************************************************************************/
@BeforeEach
@AfterEach
void beforeAndAfterEach()
{
MemoryRecordStore.fullReset();
}
/*******************************************************************************
**
*******************************************************************************/
@BeforeEach
void beforeEach() throws Exception
{
//////////////////////////////////////////////////////////////////////////////////////
// during initial dev here, we were having issues running multiple tests together, //
// where the second (but not first, and not any after second) would fail w/ javalin //
// not responding... so, this "works" - to constantly change our port, and stop //
// and restart aggresively... could be optimized, but it works. //
//////////////////////////////////////////////////////////////////////////////////////
PORT++;
if(service != null)
{
service.stop();
service = null;
}
if(service == null)
{
service = Javalin.create(config ->
{
config.router.apiBuilder(() -> getSpec().defineRoute(getVersion()));
}
).start(PORT);
}
TestUtils.primeTestDatabase();
}
/*******************************************************************************
**
*******************************************************************************/
@AfterAll
static void afterAll()
{
if(service != null)
{
service.stop();
service = null;
}
}
}

View File

@ -0,0 +1,80 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.middleware.javalin.specs.v1;
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
import com.kingsrook.qqq.middleware.javalin.specs.AbstractEndpointSpec;
import com.kingsrook.qqq.middleware.javalin.specs.SpecTestBase;
import kong.unirest.HttpResponse;
import kong.unirest.Unirest;
import org.json.JSONObject;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
/*******************************************************************************
** Unit test for AuthenticationMetaDataSpecV1
*******************************************************************************/
class AuthenticationMetaDataSpecV1Test extends SpecTestBase
{
/***************************************************************************
**
***************************************************************************/
@Override
protected AbstractEndpointSpec<?, ?, ?> getSpec()
{
return new AuthenticationMetaDataSpecV1();
}
/***************************************************************************
**
***************************************************************************/
@Override
protected String getVersion()
{
return "v1";
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void test()
{
HttpResponse<String> response = Unirest.get(getBaseUrlAndPath() + "/metaData/authentication").asString();
assertEquals(200, response.getStatus());
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
assertNotNull(jsonObject);
assertTrue(jsonObject.has("name"));
assertTrue(jsonObject.has("type"));
}
}

View File

@ -0,0 +1,90 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.middleware.javalin.specs.v1;
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
import com.kingsrook.qqq.middleware.javalin.specs.AbstractEndpointSpec;
import com.kingsrook.qqq.middleware.javalin.specs.SpecTestBase;
import kong.unirest.HttpResponse;
import kong.unirest.Unirest;
import org.json.JSONObject;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
/*******************************************************************************
** Unit test for ManageSessionV1
*******************************************************************************/
class ManageSessionSpecV1Test extends SpecTestBase
{
/***************************************************************************
**
***************************************************************************/
@Override
protected AbstractEndpointSpec<?, ?, ?> getSpec()
{
return new ManageSessionSpecV1();
}
/***************************************************************************
**
***************************************************************************/
@Override
protected String getVersion()
{
return "v1";
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void test()
{
String body = """
{
"accessToken": "abcdefg"
}
""";
HttpResponse<String> response = Unirest.post(getBaseUrlAndPath() + "/manageSession")
.header("Content-Type", "application/json")
.body(body)
.asString();
assertEquals(200, response.getStatus());
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
assertNotNull(jsonObject);
assertTrue(jsonObject.has("uuid"));
assertThat(response.getHeaders().get("Set-Cookie")).anyMatch(s -> s.contains("sessionUUID"));
}
}

View File

@ -0,0 +1,79 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.middleware.javalin.specs.v1;
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
import com.kingsrook.qqq.middleware.javalin.specs.AbstractEndpointSpec;
import com.kingsrook.qqq.middleware.javalin.specs.SpecTestBase;
import kong.unirest.HttpResponse;
import kong.unirest.Unirest;
import org.json.JSONObject;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
/*******************************************************************************
** Unit test for MetaDataSpecV1
*******************************************************************************/
class MetaDataSpecV1Test extends SpecTestBase
{
/***************************************************************************
**
***************************************************************************/
@Override
protected AbstractEndpointSpec<?, ?, ?> getSpec()
{
return new MetaDataSpecV1();
}
/***************************************************************************
**
***************************************************************************/
@Override
protected String getVersion()
{
return "v1";
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void test()
{
HttpResponse<String> response = Unirest.get(getBaseUrlAndPath() + "/metaData").asString();
assertEquals(200, response.getStatus());
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
assertThat(jsonObject.getJSONObject("tables").length()).isGreaterThanOrEqualTo(1);
assertThat(jsonObject.getJSONObject("processes").length()).isGreaterThanOrEqualTo(1);
assertThat(jsonObject.getJSONObject("apps").length()).isGreaterThanOrEqualTo(1);
assertThat(jsonObject.getJSONArray("appTree").length()).isGreaterThanOrEqualTo(1);
}
}

View File

@ -0,0 +1,219 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.middleware.javalin.specs.v1;
import java.util.List;
import com.kingsrook.qqq.backend.core.logging.QCollectingLogger;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.processes.implementations.mock.MockBackendStep;
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
import com.kingsrook.qqq.backend.javalin.TestUtils;
import com.kingsrook.qqq.middleware.javalin.specs.AbstractEndpointSpec;
import com.kingsrook.qqq.middleware.javalin.specs.SpecTestBase;
import kong.unirest.HttpResponse;
import kong.unirest.Unirest;
import org.json.JSONObject;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
/*******************************************************************************
** Unit test for ProcessInitSpecV1
*******************************************************************************/
class ProcessInitSpecV1Test extends SpecTestBase
{
/***************************************************************************
**
***************************************************************************/
@Override
protected AbstractEndpointSpec<?, ?, ?> getSpec()
{
return new ProcessInitSpecV1();
}
/***************************************************************************
**
***************************************************************************/
@Override
protected String getVersion()
{
return "v1";
}
/*******************************************************************************
**
*******************************************************************************/
@AfterEach
void afterEach()
{
QLogger.deactivateCollectingLoggerForClass(MockBackendStep.class);
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testGetInitialRecordsFromRecordIdsParam()
{
QCollectingLogger collectingLogger = QLogger.activateCollectingLoggerForClass(MockBackendStep.class);
HttpResponse<String> response = Unirest.post(getBaseUrlAndPath() + "/processes/greet/init")
.multiPartContent()
.field("recordsParam", "recordIds")
.field("recordIds", "2,3")
.asString();
assertEquals(200, response.getStatus());
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
assertNotNull(jsonObject);
assertEquals("COMPLETE", jsonObject.getString("type"));
assertEquals("null X null", jsonObject.getJSONObject("values").getString("outputMessage")); // these nulls are because we didn't pass values for some fields.
assertThat(collectingLogger.getCollectedMessages())
.filteredOn(clm -> clm.getMessage().contains("We are mocking"))
.hasSize(2);
// todo - also request records
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testGetInitialRecordsFromFilterParam()
{
QCollectingLogger collectingLogger = QLogger.activateCollectingLoggerForClass(MockBackendStep.class);
QQueryFilter queryFilter = new QQueryFilter()
.withCriteria(new QFilterCriteria()
.withFieldName("id")
.withOperator(QCriteriaOperator.IN)
.withValues(List.of(3, 4, 5)));
String filterJSON = JsonUtils.toJson(queryFilter);
HttpResponse<String> response = Unirest.post(getBaseUrlAndPath() + "/processes/greet/init")
.multiPartContent()
.field("recordsParam", "filterJSON")
.field("filterJSON", filterJSON)
.asString();
assertEquals(200, response.getStatus());
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
assertNotNull(jsonObject);
assertEquals("COMPLETE", jsonObject.getString("type"));
assertEquals("null X null", jsonObject.getJSONObject("values").getString("outputMessage"));
assertThat(collectingLogger.getCollectedMessages())
.filteredOn(clm -> clm.getMessage().contains("We are mocking"))
.hasSize(3);
// todo - also request records
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testRequiresRowsButNotSpecified()
{
HttpResponse<String> response = Unirest.post(getBaseUrlAndPath() + "/processes/greet/init")
.multiPartContent()
.asString();
assertEquals(200, response.getStatus());
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
assertNotNull(jsonObject);
assertEquals("ERROR", jsonObject.getString("type"));
assertTrue(jsonObject.has("error"));
assertTrue(jsonObject.getString("error").contains("Missing input records"));
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testFieldValues()
{
HttpResponse<String> response = Unirest.post(getBaseUrlAndPath() + "/processes/greet/init")
.multiPartContent()
.field("recordsParam", "recordIds")
.field("recordIds", "2,3")
.field("values", new JSONObject()
.put("greetingPrefix", "Hey")
.put("greetingSuffix", "Jude")
.toString()
)
.asString();
assertEquals(200, response.getStatus());
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
assertNotNull(jsonObject);
assertEquals("COMPLETE", jsonObject.getString("type"));
assertEquals("Hey X Jude", jsonObject.getJSONObject("values").getString("outputMessage"));
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testInitGoingAsync()
{
HttpResponse<String> response = Unirest.post(getBaseUrlAndPath() + "/processes/" + TestUtils.PROCESS_NAME_SIMPLE_SLEEP + "/init")
.multiPartContent()
.field("stepTimeoutMillis", "50")
.field("values", new JSONObject()
.put(TestUtils.SleeperStep.FIELD_SLEEP_MILLIS, 500)
.toString()
)
.asString();
assertEquals(200, response.getStatus());
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
String processUUID = jsonObject.getString("processUUID");
String jobUUID = jsonObject.getString("jobUUID");
assertNotNull(processUUID, "Process UUID should not be null.");
assertNotNull(jobUUID, "Job UUID should not be null");
// todo - in a higher-level test, resume test_processInitGoingAsync at the // request job status before sleep is done // line
}
}

View File

@ -0,0 +1,108 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.middleware.javalin.specs.v1;
import java.util.Map;
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
import com.kingsrook.qqq.middleware.javalin.specs.AbstractEndpointSpec;
import com.kingsrook.qqq.middleware.javalin.specs.SpecTestBase;
import kong.unirest.HttpResponse;
import kong.unirest.Unirest;
import org.eclipse.jetty.http.HttpStatus;
import org.json.JSONArray;
import org.json.JSONObject;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
/*******************************************************************************
** Unit test for ProcessMetaDataSpecV1
*******************************************************************************/
class ProcessMetaDataSpecV1Test extends SpecTestBase
{
/***************************************************************************
**
***************************************************************************/
@Override
protected AbstractEndpointSpec<?, ?, ?> getSpec()
{
return new ProcessMetaDataSpecV1();
}
/***************************************************************************
**
***************************************************************************/
@Override
protected String getVersion()
{
return "v1";
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void test()
{
HttpResponse<String> response = Unirest.get(getBaseUrlAndPath() + "/metaData/process/greetInteractive").asString();
assertEquals(200, response.getStatus());
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
assertEquals("greetInteractive", jsonObject.getString("name"));
assertEquals("Greet Interactive", jsonObject.getString("label"));
assertEquals("person", jsonObject.getString("tableName"));
JSONArray frontendSteps = jsonObject.getJSONArray("frontendSteps");
JSONObject setupStep = frontendSteps.getJSONObject(0);
assertEquals("Setup", setupStep.getString("label"));
JSONArray setupFields = setupStep.getJSONArray("formFields");
assertEquals(2, setupFields.length());
assertTrue(setupFields.toList().stream().anyMatch(field -> "greetingPrefix".equals(((Map<?, ?>) field).get("name"))));
}
/*******************************************************************************
** test the process-level meta-data endpoint for a non-real name
**
*******************************************************************************/
@Test
public void testNotFound()
{
HttpResponse<String> response = Unirest.get(getBaseUrlAndPath() + "/metaData/process/notAnActualProcess").asString();
assertEquals(HttpStatus.NOT_FOUND_404, response.getStatus());
JSONObject jsonObject = JsonUtils.toJSONObject(response.getBody());
assertEquals(1, jsonObject.keySet().size(), "Number of top-level keys");
String error = jsonObject.getString("error");
assertThat(error).contains("Process").contains("notAnActualProcess").contains("not found");
}
}