mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-18 05:01:07 +00:00
Checkpoint; working insert, update, delete
This commit is contained in:
@ -41,6 +41,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.modules.backend.implementations.utils.BackendQueryFilterUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ListingHash;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -255,7 +256,7 @@ public class MemoryRecordStore
|
||||
QFieldMetaData primaryKeyField = table.getField(table.getPrimaryKeyField());
|
||||
for(QRecord record : input.getRecords())
|
||||
{
|
||||
Serializable primaryKeyValue = record.getValue(primaryKeyField.getName());
|
||||
Serializable primaryKeyValue = ValueUtils.getValueAsFieldType(primaryKeyField.getType(), record.getValue(primaryKeyField.getName()));
|
||||
if(tableData.containsKey(primaryKeyValue))
|
||||
{
|
||||
QRecord recordToUpdate = tableData.get(primaryKeyValue);
|
||||
@ -286,11 +287,13 @@ public class MemoryRecordStore
|
||||
return (0);
|
||||
}
|
||||
|
||||
QTableMetaData table = input.getTable();
|
||||
Map<Serializable, QRecord> tableData = getTableData(table);
|
||||
int rowsDeleted = 0;
|
||||
QTableMetaData table = input.getTable();
|
||||
QFieldMetaData primaryKeyField = table.getField(table.getPrimaryKeyField());
|
||||
Map<Serializable, QRecord> tableData = getTableData(table);
|
||||
int rowsDeleted = 0;
|
||||
for(Serializable primaryKeyValue : input.getPrimaryKeys())
|
||||
{
|
||||
primaryKeyValue = ValueUtils.getValueAsFieldType(primaryKeyField.getType(), primaryKeyValue);
|
||||
if(tableData.containsKey(primaryKeyValue))
|
||||
{
|
||||
tableData.remove(primaryKeyValue);
|
||||
|
@ -62,6 +62,7 @@ import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.YamlUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.collections.ListBuilder;
|
||||
import com.kingsrook.qqq.backend.core.utils.collections.MapBuilder;
|
||||
import io.javalin.http.HttpStatus;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -272,7 +273,7 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
|
||||
.withSchema(new Schema().withType("string").withEnumValues(ListBuilder.of("AND", "OR")))
|
||||
))
|
||||
.withResponses(buildStandardErrorResponses())
|
||||
.withResponse(200, new Response()
|
||||
.withResponse(HttpStatus.OK.getCode(), new Response()
|
||||
.withDescription("Successfully searched the " + tableLabel + " table (though may have found 0 records).")
|
||||
.withContent(MapBuilder.of("application/json", new Content()
|
||||
.withSchema(new Schema().withRef("#/components/schemas/" + tableName + "SearchResult"))
|
||||
@ -321,8 +322,8 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
|
||||
.withSchema(new Schema().withType(getFieldType(primaryKeyField)))
|
||||
))
|
||||
.withResponses(buildStandardErrorResponses())
|
||||
.withResponse(404, buildStandardErrorResponse("The requested " + tableLabel + " record was not found.", "Could not find " + tableLabel + " with " + primaryKeyLabel + " of 47."))
|
||||
.withResponse(200, new Response()
|
||||
.withResponse(HttpStatus.NOT_FOUND.getCode(), buildStandardErrorResponse("The requested " + tableLabel + " record was not found.", "Could not find " + tableLabel + " with " + primaryKeyLabel + " of 47."))
|
||||
.withResponse(HttpStatus.OK.getCode(), new Response()
|
||||
.withDescription("Successfully got the requested " + tableLabel)
|
||||
.withContent(MapBuilder.of("application/json", new Content()
|
||||
.withSchema(new Schema().withRef("#/components/schemas/" + tableName))
|
||||
@ -348,8 +349,8 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
|
||||
.withSchema(new Schema().withRef("#/components/schemas/" + tableName))
|
||||
)))
|
||||
.withResponses(buildStandardErrorResponses())
|
||||
.withResponse(404, buildStandardErrorResponse("The requested " + tableLabel + " record was not found.", "Could not find " + tableLabel + " with " + primaryKeyLabel + " of 47.")) // todo - 404 on update?
|
||||
.withResponse(200, new Response().withDescription("Successfully updated the requested " + tableLabel))
|
||||
.withResponse(HttpStatus.NOT_FOUND.getCode(), buildStandardErrorResponse("The requested " + tableLabel + " record was not found.", "Could not find " + tableLabel + " with " + primaryKeyLabel + " of 47."))
|
||||
.withResponse(HttpStatus.NO_CONTENT.getCode(), new Response().withDescription("Successfully updated the requested " + tableLabel))
|
||||
.withSecurity(ListBuilder.of(MapBuilder.of("OAuth2", List.of(tableUpdatePermissionName))));
|
||||
|
||||
Method idDelete = new Method()
|
||||
@ -365,8 +366,8 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
|
||||
.withSchema(new Schema().withType(getFieldType(primaryKeyField)))
|
||||
))
|
||||
.withResponses(buildStandardErrorResponses())
|
||||
.withResponse(404, buildStandardErrorResponse("The requested " + tableLabel + " record was not found.", "Could not find " + tableLabel + " with " + primaryKeyLabel + " of 47.")) // todo - 404 on update?
|
||||
.withResponse(200, new Response().withDescription("Successfully deleted the requested " + tableLabel))
|
||||
.withResponse(HttpStatus.NOT_FOUND.getCode(), buildStandardErrorResponse("The requested " + tableLabel + " record was not found.", "Could not find " + tableLabel + " with " + primaryKeyLabel + " of 47."))
|
||||
.withResponse(HttpStatus.NO_CONTENT.getCode(), new Response().withDescription("Successfully deleted the requested " + tableLabel))
|
||||
.withSecurity(ListBuilder.of(MapBuilder.of("OAuth2", List.of(tableDeletePermissionName))));
|
||||
|
||||
openAPI.getPaths().put("/" + tableName + "/{" + primaryKeyName + "}", new Path()
|
||||
@ -384,7 +385,7 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
|
||||
.withSchema(new Schema().withRef("#/components/schemas/" + tableName + "WithoutPrimaryKey"))
|
||||
)))
|
||||
.withResponses(buildStandardErrorResponses())
|
||||
.withResponse(201, new Response()
|
||||
.withResponse(HttpStatus.CREATED.getCode(), new Response()
|
||||
.withDescription("Successfully created the requested " + tableLabel)
|
||||
.withContent(MapBuilder.of("application/json", new Content()
|
||||
.withSchema(new Schema()
|
||||
@ -423,10 +424,10 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
|
||||
*/
|
||||
}
|
||||
|
||||
componentResponses.put(400, buildStandardErrorResponse("Bad Request. Some portion of the request's content was not acceptable to the server. See error message in body for details.", "Parameter id should be given an integer value, but received string: \"Foo\""));
|
||||
componentResponses.put(401, buildStandardErrorResponse("Unauthorized. The required authentication credentials were missing or invalid.", "The required authentication credentials were missing or invalid."));
|
||||
componentResponses.put(403, buildStandardErrorResponse("Forbidden. You do not have permission to access the requested resource.", "You do not have permission to access the requested resource."));
|
||||
componentResponses.put(500, buildStandardErrorResponse("Internal Server Error. An error occurred in the server processing the request.", "Database connection error. Try again later."));
|
||||
componentResponses.put(HttpStatus.BAD_REQUEST.getCode(), buildStandardErrorResponse("Bad Request. Some portion of the request's content was not acceptable to the server. See error message in body for details.", "Parameter id should be given an integer value, but received string: \"Foo\""));
|
||||
componentResponses.put(HttpStatus.UNAUTHORIZED.getCode(), buildStandardErrorResponse("Unauthorized. The required authentication credentials were missing or invalid.", "The required authentication credentials were missing or invalid."));
|
||||
componentResponses.put(HttpStatus.FORBIDDEN.getCode(), buildStandardErrorResponse("Forbidden. You do not have permission to access the requested resource.", "You do not have permission to access the requested resource."));
|
||||
componentResponses.put(HttpStatus.INTERNAL_SERVER_ERROR.getCode(), buildStandardErrorResponse("Internal Server Error. An error occurred in the server processing the request.", "Database connection error. Try again later."));
|
||||
|
||||
GenerateOpenApiSpecOutput output = new GenerateOpenApiSpecOutput();
|
||||
output.setOpenAPI(openAPI);
|
||||
@ -571,10 +572,10 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
|
||||
private static Map<Integer, Response> buildStandardErrorResponses()
|
||||
{
|
||||
return MapBuilder.of(
|
||||
400, new Response().withRef("#/components/responses/400"),
|
||||
401, new Response().withRef("#/components/responses/401"),
|
||||
403, new Response().withRef("#/components/responses/403"),
|
||||
500, new Response().withRef("#/components/responses/500")
|
||||
HttpStatus.BAD_REQUEST.getCode(), new Response().withRef("#/components/responses/" + HttpStatus.BAD_REQUEST.getCode()),
|
||||
HttpStatus.UNAUTHORIZED.getCode(), new Response().withRef("#/components/responses/" + HttpStatus.UNAUTHORIZED.getCode()),
|
||||
HttpStatus.FORBIDDEN.getCode(), new Response().withRef("#/components/responses/" + HttpStatus.FORBIDDEN.getCode()),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR.getCode(), new Response().withRef("#/components/responses/" + HttpStatus.INTERNAL_SERVER_ERROR.getCode())
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. 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.javalin;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Runtime exception
|
||||
*******************************************************************************/
|
||||
public class ApiPathNotFoundException extends RuntimeException
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public ApiPathNotFoundException(String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
@ -40,4 +40,15 @@ public class QBadRequestException extends QException
|
||||
super(message);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QBadRequestException(String message, Throwable root)
|
||||
{
|
||||
super(message, root);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import com.kingsrook.qqq.api.ApiMiddlewareType;
|
||||
import com.kingsrook.qqq.api.actions.GenerateOpenApiSpecAction;
|
||||
import com.kingsrook.qqq.api.actions.GetTableApiFieldsAction;
|
||||
@ -43,10 +44,14 @@ import com.kingsrook.qqq.api.model.metadata.tables.ApiTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.actions.permissions.PermissionsHelper;
|
||||
import com.kingsrook.qqq.backend.core.actions.permissions.TablePermissionSubType;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.CountAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.DeleteAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QAuthenticationException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QModuleDispatchException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QNotFoundException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QPermissionDeniedException;
|
||||
@ -55,18 +60,25 @@ import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput;
|
||||
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.QFilterOrderBy;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
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.actions.tables.update.UpdateInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ExceptionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
@ -79,6 +91,7 @@ import io.javalin.apibuilder.EndpointGroup;
|
||||
import io.javalin.http.ContentType;
|
||||
import io.javalin.http.Context;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.json.JSONObject;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
import static com.kingsrook.qqq.backend.javalin.QJavalinImplementation.SLOW_LOG_THRESHOLD_MS;
|
||||
|
||||
@ -132,11 +145,29 @@ public class QJavalinApiHandler
|
||||
// delete("/bulk", QJavalinApiHandler::bulkDelete);
|
||||
});
|
||||
});
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// default all other /api/ requests (for the methods we support) to a standard 404 response //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
ApiBuilder.get("/api/*", QJavalinApiHandler::doPathNotFound);
|
||||
ApiBuilder.delete("/api/*", QJavalinApiHandler::doPathNotFound);
|
||||
ApiBuilder.patch("/api/*", QJavalinApiHandler::doPathNotFound);
|
||||
ApiBuilder.post("/api/*", QJavalinApiHandler::doPathNotFound);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void doPathNotFound(Context context)
|
||||
{
|
||||
handleException(context, new QNotFoundException("Could not find any resources at path " + context.path()));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -152,7 +183,7 @@ public class QJavalinApiHandler
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
QJavalinImplementation.handleException(context, e);
|
||||
handleException(context, e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -195,9 +226,6 @@ public class QJavalinApiHandler
|
||||
|
||||
try
|
||||
{
|
||||
// todo - make sure version is known in this instance
|
||||
// todo - make sure table is supported in this version
|
||||
|
||||
QTableMetaData table = qInstance.getTable(tableName);
|
||||
validateTableAndVersion(context, version, table);
|
||||
|
||||
@ -230,8 +258,7 @@ public class QJavalinApiHandler
|
||||
+ table.getFields().get(table.getPrimaryKeyField()).getLabel() + " of " + primaryKey));
|
||||
}
|
||||
|
||||
List<? extends QFieldMetaData> tableApiFields = new GetTableApiFieldsAction().execute(new GetTableApiFieldsInput().withTableName(tableName).withVersion(version)).getFields();
|
||||
LinkedHashMap<String, Serializable> outputRecord = toApiRecord(record, tableApiFields);
|
||||
LinkedHashMap<String, Serializable> outputRecord = toApiRecord(record, tableName, version);
|
||||
|
||||
QJavalinAccessLogger.logEndSuccess();
|
||||
context.result(JsonUtils.toJson(outputRecord));
|
||||
@ -239,7 +266,7 @@ public class QJavalinApiHandler
|
||||
catch(Exception e)
|
||||
{
|
||||
QJavalinAccessLogger.logEndFail(e);
|
||||
QJavalinImplementation.handleException(context, e);
|
||||
handleException(context, e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -258,9 +285,6 @@ public class QJavalinApiHandler
|
||||
{
|
||||
List<String> badRequestMessages = new ArrayList<>();
|
||||
|
||||
// todo - make sure version is known in this instance
|
||||
// todo - make sure table is supported in this version
|
||||
|
||||
QTableMetaData table = qInstance.getTable(tableName);
|
||||
validateTableAndVersion(context, version, table);
|
||||
|
||||
@ -449,11 +473,10 @@ public class QJavalinApiHandler
|
||||
///////////////////////////////
|
||||
// map record fields for api //
|
||||
///////////////////////////////
|
||||
List<? extends QFieldMetaData> tableApiFields = new GetTableApiFieldsAction().execute(new GetTableApiFieldsInput().withTableName(tableName).withVersion(version)).getFields();
|
||||
ArrayList<Map<String, Serializable>> records = new ArrayList<>();
|
||||
ArrayList<Map<String, Serializable>> records = new ArrayList<>();
|
||||
for(QRecord record : queryOutput.getRecords())
|
||||
{
|
||||
records.add(toApiRecord(record, tableApiFields));
|
||||
records.add(toApiRecord(record, tableName, version));
|
||||
}
|
||||
output.put("records", records);
|
||||
|
||||
@ -520,7 +543,7 @@ public class QJavalinApiHandler
|
||||
GT(">", QCriteriaOperator.GREATER_THAN, QCriteriaOperator.LESS_THAN_OR_EQUALS, false, 1),
|
||||
EMPTY("EMPTY", QCriteriaOperator.IS_BLANK, QCriteriaOperator.IS_NOT_BLANK, true, 0),
|
||||
BETWEEN("BETWEEN ", QCriteriaOperator.BETWEEN, QCriteriaOperator.NOT_BETWEEN, true, 2),
|
||||
IN("IN ", QCriteriaOperator.IN, QCriteriaOperator.NOT_IN, true, 2),
|
||||
IN("IN ", QCriteriaOperator.IN, QCriteriaOperator.NOT_IN, true, null),
|
||||
// todo MATCHES
|
||||
;
|
||||
|
||||
@ -529,14 +552,14 @@ public class QJavalinApiHandler
|
||||
private final QCriteriaOperator positiveOperator;
|
||||
private final QCriteriaOperator negativeOperator;
|
||||
private final boolean supportsNot;
|
||||
private final int noOfValues; // 0 & 1 mean 0 & 1 ... 2 means 1 or more...
|
||||
private final Integer noOfValues; // null means many (IN)
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
Operator(String prefix, QCriteriaOperator positiveOperator, QCriteriaOperator negativeOperator, boolean supportsNot, int noOfValues)
|
||||
Operator(String prefix, QCriteriaOperator positiveOperator, QCriteriaOperator negativeOperator, boolean supportsNot, Integer noOfValues)
|
||||
{
|
||||
this.prefix = prefix;
|
||||
this.positiveOperator = positiveOperator;
|
||||
@ -551,7 +574,7 @@ public class QJavalinApiHandler
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static QFilterCriteria parseQueryParamToCriteria(String name, String value) throws QBadRequestException
|
||||
private static QFilterCriteria parseQueryParamToCriteria(String name, String value) throws QException
|
||||
{
|
||||
///////////////////////////////////
|
||||
// process & discard a leading ! //
|
||||
@ -600,7 +623,11 @@ public class QJavalinApiHandler
|
||||
// todo - quotes? //
|
||||
////////////////////////////////////
|
||||
List<Serializable> criteriaValues;
|
||||
if(selectedOperator.noOfValues == 1)
|
||||
if(selectedOperator.noOfValues == null)
|
||||
{
|
||||
criteriaValues = Arrays.asList(value.split(","));
|
||||
}
|
||||
else if(selectedOperator.noOfValues == 1)
|
||||
{
|
||||
criteriaValues = ListBuilder.of(value);
|
||||
}
|
||||
@ -612,9 +639,17 @@ public class QJavalinApiHandler
|
||||
}
|
||||
criteriaValues = null;
|
||||
}
|
||||
else
|
||||
else if(selectedOperator.noOfValues == 2)
|
||||
{
|
||||
criteriaValues = Arrays.asList(value.split(","));
|
||||
if(criteriaValues.size() != 2)
|
||||
{
|
||||
throw (new QBadRequestException("Operator " + selectedOperator.prefix + " for field " + name + " requires 2 values (received " + criteriaValues.size() + ")"));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw (new QException("Unexpected noOfValues [" + selectedOperator.noOfValues + "] in operator [" + selectedOperator + "]"));
|
||||
}
|
||||
|
||||
return (new QFilterCriteria(name, isNot ? selectedOperator.negativeOperator : selectedOperator.positiveOperator, criteriaValues));
|
||||
@ -627,7 +662,57 @@ public class QJavalinApiHandler
|
||||
*******************************************************************************/
|
||||
private static void doInsert(Context context)
|
||||
{
|
||||
String version = context.pathParam("version");
|
||||
String tableName = context.pathParam("tableName");
|
||||
|
||||
try
|
||||
{
|
||||
QTableMetaData table = qInstance.getTable(tableName);
|
||||
validateTableAndVersion(context, version, table);
|
||||
|
||||
InsertInput insertInput = new InsertInput();
|
||||
|
||||
setupSession(context, insertInput);
|
||||
QJavalinAccessLogger.logStart("insert", logPair("table", tableName));
|
||||
|
||||
insertInput.setTableName(tableName);
|
||||
|
||||
PermissionsHelper.checkTablePermissionThrowing(insertInput, TablePermissionSubType.INSERT);
|
||||
|
||||
try
|
||||
{
|
||||
if(!StringUtils.hasContent(context.body()))
|
||||
{
|
||||
throw (new QBadRequestException("Missing required POST body"));
|
||||
}
|
||||
|
||||
JSONObject jsonObject = new JSONObject(context.body());
|
||||
insertInput.setRecords(List.of(toQRecord(jsonObject, tableName, version)));
|
||||
}
|
||||
catch(QBadRequestException qbre)
|
||||
{
|
||||
throw (qbre);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw (new QBadRequestException("Body could not be parsed as a JSON object: " + e.getMessage(), e));
|
||||
}
|
||||
|
||||
InsertAction insertAction = new InsertAction();
|
||||
InsertOutput insertOutput = insertAction.execute(insertInput);
|
||||
|
||||
LinkedHashMap<String, Serializable> outputRecord = new LinkedHashMap<>();
|
||||
outputRecord.put(table.getPrimaryKeyField(), insertOutput.getRecords().get(0).getValue(table.getPrimaryKeyField()));
|
||||
|
||||
QJavalinAccessLogger.logEndSuccess();
|
||||
context.status(HttpStatus.Code.CREATED.getCode());
|
||||
context.result(JsonUtils.toJson(outputRecord));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
QJavalinAccessLogger.logEndFail(e);
|
||||
handleException(context, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -637,7 +722,76 @@ public class QJavalinApiHandler
|
||||
*******************************************************************************/
|
||||
private static void doUpdate(Context context)
|
||||
{
|
||||
String version = context.pathParam("version");
|
||||
String tableName = context.pathParam("tableName");
|
||||
String primaryKey = context.pathParam("primaryKey");
|
||||
|
||||
try
|
||||
{
|
||||
QTableMetaData table = qInstance.getTable(tableName);
|
||||
validateTableAndVersion(context, version, table);
|
||||
|
||||
UpdateInput updateInput = new UpdateInput();
|
||||
|
||||
setupSession(context, updateInput);
|
||||
QJavalinAccessLogger.logStart("update", logPair("table", tableName));
|
||||
|
||||
updateInput.setTableName(tableName);
|
||||
|
||||
PermissionsHelper.checkTablePermissionThrowing(updateInput, TablePermissionSubType.EDIT);
|
||||
|
||||
///////////////////////////////////////////////////////
|
||||
// throw a not found error if the record isn't found //
|
||||
///////////////////////////////////////////////////////
|
||||
GetInput getInput = new GetInput();
|
||||
getInput.setTableName(tableName);
|
||||
getInput.setPrimaryKey(primaryKey);
|
||||
GetAction getAction = new GetAction();
|
||||
GetOutput getOutput = getAction.execute(getInput);
|
||||
if(getOutput.getRecord() == null)
|
||||
{
|
||||
throw (new QNotFoundException("Could not find " + table.getLabel() + " with "
|
||||
+ table.getFields().get(table.getPrimaryKeyField()).getLabel() + " of " + primaryKey));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if(!StringUtils.hasContent(context.body()))
|
||||
{
|
||||
throw (new QBadRequestException("Missing required POST body"));
|
||||
}
|
||||
|
||||
JSONObject jsonObject = new JSONObject(context.body());
|
||||
QRecord qRecord = toQRecord(jsonObject, tableName, version);
|
||||
qRecord.setValue(table.getPrimaryKeyField(), primaryKey);
|
||||
updateInput.setRecords(List.of(qRecord));
|
||||
}
|
||||
catch(QBadRequestException qbre)
|
||||
{
|
||||
throw (qbre);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw (new QBadRequestException("Body could not be parsed as a JSON object: " + e.getMessage(), e));
|
||||
}
|
||||
|
||||
UpdateAction updateAction = new UpdateAction();
|
||||
UpdateOutput updateOutput = updateAction.execute(updateInput);
|
||||
|
||||
List<String> errors = updateOutput.getRecords().get(0).getErrors();
|
||||
if(CollectionUtils.nullSafeHasContents(errors))
|
||||
{
|
||||
throw (new QException("Error updating " + table.getLabel() + ": " + StringUtils.joinWithCommasAndAnd(errors)));
|
||||
}
|
||||
|
||||
QJavalinAccessLogger.logEndSuccess();
|
||||
context.status(HttpStatus.Code.NO_CONTENT.getCode());
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
QJavalinAccessLogger.logEndFail(e);
|
||||
handleException(context, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -647,7 +801,57 @@ public class QJavalinApiHandler
|
||||
*******************************************************************************/
|
||||
private static void doDelete(Context context)
|
||||
{
|
||||
String version = context.pathParam("version");
|
||||
String tableName = context.pathParam("tableName");
|
||||
String primaryKey = context.pathParam("primaryKey");
|
||||
|
||||
try
|
||||
{
|
||||
QTableMetaData table = qInstance.getTable(tableName);
|
||||
validateTableAndVersion(context, version, table);
|
||||
|
||||
DeleteInput deleteInput = new DeleteInput();
|
||||
|
||||
setupSession(context, deleteInput);
|
||||
QJavalinAccessLogger.logStart("delete", logPair("table", tableName));
|
||||
|
||||
deleteInput.setTableName(tableName);
|
||||
deleteInput.setPrimaryKeys(List.of(primaryKey));
|
||||
|
||||
PermissionsHelper.checkTablePermissionThrowing(deleteInput, TablePermissionSubType.DELETE);
|
||||
|
||||
///////////////////////////////////////////////////////
|
||||
// throw a not found error if the record isn't found //
|
||||
///////////////////////////////////////////////////////
|
||||
GetInput getInput = new GetInput();
|
||||
getInput.setTableName(tableName);
|
||||
getInput.setPrimaryKey(primaryKey);
|
||||
GetAction getAction = new GetAction();
|
||||
GetOutput getOutput = getAction.execute(getInput);
|
||||
if(getOutput.getRecord() == null)
|
||||
{
|
||||
throw (new QNotFoundException("Could not find " + table.getLabel() + " with "
|
||||
+ table.getFields().get(table.getPrimaryKeyField()).getLabel() + " of " + primaryKey));
|
||||
}
|
||||
|
||||
///////////////////
|
||||
// do the delete //
|
||||
///////////////////
|
||||
DeleteAction deleteAction = new DeleteAction();
|
||||
DeleteOutput deleteOutput = deleteAction.execute(deleteInput);
|
||||
if(CollectionUtils.nullSafeHasContents(deleteOutput.getRecordsWithErrors()))
|
||||
{
|
||||
throw (new QException("Error deleting " + table.getLabel() + ": " + StringUtils.joinWithCommasAndAnd(deleteOutput.getRecordsWithErrors().get(0).getErrors())));
|
||||
}
|
||||
|
||||
QJavalinAccessLogger.logEndSuccess();
|
||||
context.status(HttpStatus.Code.NO_CONTENT.getCode());
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
QJavalinAccessLogger.logEndFail(e);
|
||||
handleException(context, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -655,16 +859,52 @@ public class QJavalinApiHandler
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static LinkedHashMap<String, Serializable> toApiRecord(QRecord record, List<? extends QFieldMetaData> tableApiFields)
|
||||
private static LinkedHashMap<String, Serializable> toApiRecord(QRecord record, String tableName, String apiVersion) throws QException
|
||||
{
|
||||
LinkedHashMap<String, Serializable> outputRecord = new LinkedHashMap<>();
|
||||
List<? extends QFieldMetaData> tableApiFields = new GetTableApiFieldsAction().execute(new GetTableApiFieldsInput().withTableName(tableName).withVersion(apiVersion)).getFields();
|
||||
LinkedHashMap<String, Serializable> outputRecord = new LinkedHashMap<>();
|
||||
for(QFieldMetaData tableApiField : tableApiFields)
|
||||
{
|
||||
// todo - what about display values / possible values
|
||||
// todo - handle removed-from-this-version fields!!
|
||||
outputRecord.put(tableApiField.getName(), record.getValue(tableApiField.getName()));
|
||||
}
|
||||
return outputRecord;
|
||||
return (outputRecord);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static QRecord toQRecord(JSONObject jsonObject, String tableName, String apiVersion) throws QException
|
||||
{
|
||||
List<String> unrecognizedFieldNames = new ArrayList<>();
|
||||
|
||||
List<? extends QFieldMetaData> tableApiFields = new GetTableApiFieldsAction().execute(new GetTableApiFieldsInput().withTableName(tableName).withVersion(apiVersion)).getFields();
|
||||
Map<String, ? extends QFieldMetaData> apiFieldsMap = tableApiFields.stream().collect(Collectors.toMap(f -> f.getName(), f -> f));
|
||||
|
||||
QRecord qRecord = new QRecord();
|
||||
|
||||
for(String jsonKey : jsonObject.keySet())
|
||||
{
|
||||
if(apiFieldsMap.containsKey(jsonKey))
|
||||
{
|
||||
QFieldMetaData field = apiFieldsMap.get(jsonKey);
|
||||
qRecord.setValue(field.getName(), jsonObject.get(jsonKey));
|
||||
}
|
||||
else
|
||||
{
|
||||
unrecognizedFieldNames.add(jsonKey);
|
||||
}
|
||||
}
|
||||
|
||||
if(!unrecognizedFieldNames.isEmpty())
|
||||
{
|
||||
throw (new QBadRequestException("Request body contained " + unrecognizedFieldNames.size() + " unrecognized field name" + StringUtils.plural(unrecognizedFieldNames) + ": " + StringUtils.joinWithCommasAndAnd(unrecognizedFieldNames)));
|
||||
}
|
||||
|
||||
return (qRecord);
|
||||
}
|
||||
|
||||
|
||||
@ -709,6 +949,12 @@ public class QJavalinApiHandler
|
||||
}
|
||||
else
|
||||
{
|
||||
if(e instanceof ApiPathNotFoundException)
|
||||
{
|
||||
respondWithError(context, HttpStatus.Code.NOT_FOUND, e.getMessage()); // 404
|
||||
return;
|
||||
}
|
||||
|
||||
if(e instanceof QAuthenticationException)
|
||||
{
|
||||
respondWithError(context, HttpStatus.Code.UNAUTHORIZED, e.getMessage()); // 401
|
||||
|
@ -26,15 +26,20 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.api.BaseTest;
|
||||
import com.kingsrook.qqq.api.TestUtils;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QInstanceValidationException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.javalin.QJavalinImplementation;
|
||||
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.AfterAll;
|
||||
@ -42,6 +47,8 @@ import org.junit.jupiter.api.BeforeAll;
|
||||
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.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -77,7 +84,7 @@ class QJavalinApiHandlerTest extends BaseTest
|
||||
**
|
||||
*******************************************************************************/
|
||||
@AfterAll
|
||||
public static void afterAll()
|
||||
static void afterAll()
|
||||
{
|
||||
qJavalinImplementation.stopJavalinServer();
|
||||
}
|
||||
@ -104,6 +111,24 @@ class QJavalinApiHandlerTest extends BaseTest
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testRandom404s()
|
||||
{
|
||||
for(String method : new String[] { "get", "post", "patch", "delete" })
|
||||
{
|
||||
assertErrorResponse(HttpStatus.NOT_FOUND_404, "Could not find any resources at path", Unirest.request(method, BASE_URL + "/api/" + VERSION + "/notATable/").asString());
|
||||
assertErrorResponse(HttpStatus.NOT_FOUND_404, "Could not find any resources at path", Unirest.request(method, BASE_URL + "/api/" + VERSION + "/notATable/notAnId").asString());
|
||||
assertErrorResponse(HttpStatus.NOT_FOUND_404, "Could not find any resources at path", Unirest.request(method, BASE_URL + "/api/").asString());
|
||||
assertErrorResponse(HttpStatus.NOT_FOUND_404, "Could not find any resources at path", Unirest.request(method, BASE_URL + "/api/foo").asString());
|
||||
assertErrorResponse(HttpStatus.NOT_FOUND_404, "Could not find any resources at path", Unirest.request(method, BASE_URL + "/api/" + VERSION + "/person/1/2").asString());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -111,7 +136,7 @@ class QJavalinApiHandlerTest extends BaseTest
|
||||
void testGet404()
|
||||
{
|
||||
HttpResponse<String> response = Unirest.get(BASE_URL + "/api/" + VERSION + "/person/1").asString();
|
||||
assertEquals(404, response.getStatus());
|
||||
assertEquals(HttpStatus.NOT_FOUND_404, response.getStatus());
|
||||
JSONObject jsonObject = new JSONObject(response.getBody());
|
||||
assertEquals("Could not find Person with Id of 1", jsonObject.getString("error"));
|
||||
}
|
||||
@ -124,10 +149,10 @@ class QJavalinApiHandlerTest extends BaseTest
|
||||
@Test
|
||||
void testGet200() throws QException
|
||||
{
|
||||
insertTestRecord(1, "Homer", "Simpson");
|
||||
insertPersonRecord(1, "Homer", "Simpson");
|
||||
|
||||
HttpResponse<String> response = Unirest.get(BASE_URL + "/api/" + VERSION + "/person/1").asString();
|
||||
assertEquals(200, response.getStatus());
|
||||
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||
JSONObject jsonObject = new JSONObject(response.getBody());
|
||||
assertEquals(1, jsonObject.getInt("id"));
|
||||
assertEquals("Homer", jsonObject.getString("firstName"));
|
||||
@ -136,27 +161,14 @@ class QJavalinApiHandlerTest extends BaseTest
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void insertTestRecord(Integer id, String firstName, String lastName) throws QException
|
||||
{
|
||||
InsertInput insertInput = new InsertInput();
|
||||
insertInput.setTableName(TestUtils.TABLE_NAME_PERSON);
|
||||
insertInput.setRecords(List.of(new QRecord().withValue("id", id).withValue("firstName", firstName).withValue("lastName", lastName)));
|
||||
new InsertAction().execute(insertInput);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testQuery404()
|
||||
{
|
||||
assertError(404, BASE_URL + "/api/" + VERSION + "/notATable/query?");
|
||||
assertError(404, BASE_URL + "/api/notAVersion/person/query?");
|
||||
assertError(HttpStatus.NOT_FOUND_404, BASE_URL + "/api/" + VERSION + "/notATable/query?");
|
||||
assertError(HttpStatus.NOT_FOUND_404, BASE_URL + "/api/notAVersion/person/query?");
|
||||
}
|
||||
|
||||
|
||||
@ -196,7 +208,7 @@ class QJavalinApiHandlerTest extends BaseTest
|
||||
void testQuery200NoParams()
|
||||
{
|
||||
HttpResponse<String> response = Unirest.get(BASE_URL + "/api/" + VERSION + "/person/query").asString();
|
||||
assertEquals(200, response.getStatus());
|
||||
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||
JSONObject jsonObject = new JSONObject(response.getBody());
|
||||
assertEquals(0, jsonObject.getInt("count"));
|
||||
assertEquals(1, jsonObject.getInt("pageNo"));
|
||||
@ -211,10 +223,10 @@ class QJavalinApiHandlerTest extends BaseTest
|
||||
@Test
|
||||
void testQuery200SomethingFound() throws QException
|
||||
{
|
||||
insertTestRecord(1, "Homer", "Simpson");
|
||||
insertPersonRecord(1, "Homer", "Simpson");
|
||||
|
||||
HttpResponse<String> response = Unirest.get(BASE_URL + "/api/" + VERSION + "/person/query").asString();
|
||||
assertEquals(200, response.getStatus());
|
||||
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||
JSONObject jsonObject = new JSONObject(response.getBody());
|
||||
assertEquals(1, jsonObject.getInt("count"));
|
||||
assertEquals(1, jsonObject.getInt("pageNo"));
|
||||
@ -327,16 +339,300 @@ class QJavalinApiHandlerTest extends BaseTest
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testQuery200ManyParams()
|
||||
{
|
||||
HttpResponse<String> response = Unirest.get(BASE_URL + "/api/" + VERSION + "/person/query?pageSize=49&pageNo=2&includeCount=true&booleanOperator=AND&firstName=Homer&orderBy=firstName desc").asString();
|
||||
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||
JSONObject jsonObject = new JSONObject(response.getBody());
|
||||
assertEquals(0, jsonObject.getInt("count"));
|
||||
assertEquals(2, jsonObject.getInt("pageNo"));
|
||||
assertEquals(49, jsonObject.getInt("pageSize"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testInsert201() throws QException
|
||||
{
|
||||
HttpResponse<String> response = Unirest.post(BASE_URL + "/api/" + VERSION + "/person/")
|
||||
.body("""
|
||||
{"firstName": "Moe"}
|
||||
""")
|
||||
.asString();
|
||||
assertEquals(HttpStatus.CREATED_201, response.getStatus());
|
||||
JSONObject jsonObject = new JSONObject(response.getBody());
|
||||
assertEquals(1, jsonObject.getInt("id"));
|
||||
|
||||
QRecord record = getPersonRecord(1);
|
||||
assertEquals("Moe", record.getValueString("firstName"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testInsert400s() throws QException
|
||||
{
|
||||
HttpResponse<String> response = Unirest.post(BASE_URL + "/api/" + VERSION + "/person/")
|
||||
.body("""
|
||||
[{"firstName": "Moe"}]
|
||||
""")
|
||||
.asString();
|
||||
assertErrorResponse(HttpStatus.BAD_REQUEST_400, "Body could not be parsed as a JSON object: A JSONObject text must begin with '{'", response);
|
||||
|
||||
response = Unirest.post(BASE_URL + "/api/" + VERSION + "/person/")
|
||||
.body("""
|
||||
"firstName"="Moe"
|
||||
""")
|
||||
.asString();
|
||||
assertErrorResponse(HttpStatus.BAD_REQUEST_400, "Body could not be parsed as a JSON object: A JSONObject text must begin with '{'", response);
|
||||
|
||||
response = Unirest.post(BASE_URL + "/api/" + VERSION + "/person/")
|
||||
// no body
|
||||
.asString();
|
||||
assertErrorResponse(HttpStatus.BAD_REQUEST_400, "Missing required POST body", response);
|
||||
|
||||
response = Unirest.post(BASE_URL + "/api/" + VERSION + "/person/")
|
||||
.body("""
|
||||
{"firstName": "Moe", "firstName": "Barney"}
|
||||
""")
|
||||
.asString();
|
||||
assertErrorResponse(HttpStatus.BAD_REQUEST_400, "Body could not be parsed as a JSON object: Duplicate key \"firstName\"", response);
|
||||
|
||||
response = Unirest.post(BASE_URL + "/api/" + VERSION + "/person/")
|
||||
.body("""
|
||||
{"firstName": "Moe", "foo": "bar"}
|
||||
""")
|
||||
.asString();
|
||||
assertErrorResponse(HttpStatus.BAD_REQUEST_400, "Request body contained 1 unrecognized field name: foo", response);
|
||||
|
||||
response = Unirest.post(BASE_URL + "/api/" + VERSION + "/person/")
|
||||
.body("""
|
||||
{"firstName": "Moe", "foo": "bar", "bar": true, "baz": 1}
|
||||
""")
|
||||
.asString();
|
||||
assertErrorResponse(HttpStatus.BAD_REQUEST_400, "Request body contained 3 unrecognized field names: ", response);
|
||||
|
||||
///////////////////////////////////
|
||||
// assert it didn't get inserted //
|
||||
///////////////////////////////////
|
||||
QRecord personRecord = getPersonRecord(1);
|
||||
assertNull(personRecord);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// apparently, as long as the body *starts with* json, the JSONObject constructor builds //
|
||||
// a json object out of it?? so... this in this case we expected 400, but get 201... //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
response = Unirest.post(BASE_URL + "/api/" + VERSION + "/person/")
|
||||
.body("""
|
||||
{"firstName": "Moe"}
|
||||
Not json
|
||||
""")
|
||||
.asString();
|
||||
assertErrorResponse(HttpStatus.CREATED_201, null, response);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testUpdate204() throws QException
|
||||
{
|
||||
insertPersonRecord(1, "CM", "Burns");
|
||||
|
||||
HttpResponse<String> response = Unirest.patch(BASE_URL + "/api/" + VERSION + "/person/1")
|
||||
.body("""
|
||||
{"firstName": "Charles"}
|
||||
""")
|
||||
.asString();
|
||||
assertEquals(HttpStatus.NO_CONTENT_204, response.getStatus());
|
||||
assertFalse(StringUtils.hasContent(response.getBody()));
|
||||
|
||||
QRecord record = getPersonRecord(1);
|
||||
assertEquals("Charles", record.getValueString("firstName"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testUpdate404()
|
||||
{
|
||||
HttpResponse<String> response = Unirest.patch(BASE_URL + "/api/" + VERSION + "/person/1")
|
||||
.body("""
|
||||
{"firstName": "Charles"}
|
||||
""")
|
||||
.asString();
|
||||
assertErrorResponse(HttpStatus.NOT_FOUND_404, "Could not find Person with Id of 1", response);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testUpdate400s() throws QException
|
||||
{
|
||||
insertPersonRecord(1, "Mo", "Szyslak");
|
||||
|
||||
HttpResponse<String> response = Unirest.patch(BASE_URL + "/api/" + VERSION + "/person/1")
|
||||
.body("""
|
||||
[{"firstName": "Moe"}]
|
||||
""")
|
||||
.asString();
|
||||
assertErrorResponse(HttpStatus.BAD_REQUEST_400, "Body could not be parsed as a JSON object: A JSONObject text must begin with '{'", response);
|
||||
|
||||
response = Unirest.patch(BASE_URL + "/api/" + VERSION + "/person/1")
|
||||
.body("""
|
||||
"firstName"="Moe"
|
||||
""")
|
||||
.asString();
|
||||
assertErrorResponse(HttpStatus.BAD_REQUEST_400, "Body could not be parsed as a JSON object: A JSONObject text must begin with '{'", response);
|
||||
|
||||
response = Unirest.patch(BASE_URL + "/api/" + VERSION + "/person/1")
|
||||
// no body
|
||||
.asString();
|
||||
assertErrorResponse(HttpStatus.BAD_REQUEST_400, "Missing required POST body", response);
|
||||
|
||||
response = Unirest.patch(BASE_URL + "/api/" + VERSION + "/person/1")
|
||||
.body("""
|
||||
{"firstName": "Moe", "firstName": "Barney"}
|
||||
""")
|
||||
.asString();
|
||||
assertErrorResponse(HttpStatus.BAD_REQUEST_400, "Body could not be parsed as a JSON object: Duplicate key \"firstName\"", response);
|
||||
|
||||
response = Unirest.patch(BASE_URL + "/api/" + VERSION + "/person/1")
|
||||
.body("""
|
||||
{"firstName": "Moe", "foo": "bar"}
|
||||
""")
|
||||
.asString();
|
||||
assertErrorResponse(HttpStatus.BAD_REQUEST_400, "Request body contained 1 unrecognized field name: foo", response);
|
||||
|
||||
response = Unirest.patch(BASE_URL + "/api/" + VERSION + "/person/1")
|
||||
.body("""
|
||||
{"firstName": "Moe", "foo": "bar", "bar": true, "baz": 1}
|
||||
""")
|
||||
.asString();
|
||||
assertErrorResponse(HttpStatus.BAD_REQUEST_400, "Request body contained 3 unrecognized field names: ", response);
|
||||
|
||||
///////////////////////////////////
|
||||
// assert it didn't get updated. //
|
||||
///////////////////////////////////
|
||||
QRecord personRecord = getPersonRecord(1);
|
||||
assertEquals("Mo", personRecord.getValueString("firstName"));
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// apparently, as long as the body *starts with* json, the JSONObject constructor builds //
|
||||
// a json object out of it?? so... this in this case we expected 400, but get 204... //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
response = Unirest.patch(BASE_URL + "/api/" + VERSION + "/person/1")
|
||||
.body("""
|
||||
{"firstName": "Moe"}
|
||||
Not json
|
||||
""")
|
||||
.asString();
|
||||
assertErrorResponse(HttpStatus.NO_CONTENT_204, null, response);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testDeleteWithoutPkey() throws QException
|
||||
{
|
||||
insertPersonRecord(1, "CM", "Burns");
|
||||
|
||||
HttpResponse<String> response = Unirest.delete(BASE_URL + "/api/" + VERSION + "/person/").asString();
|
||||
assertErrorResponse(HttpStatus.NOT_FOUND_404, "Could not find any resources at path", response);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testDelete204() throws QException
|
||||
{
|
||||
insertPersonRecord(1, "CM", "Burns");
|
||||
|
||||
HttpResponse<String> response = Unirest.delete(BASE_URL + "/api/" + VERSION + "/person/1").asString();
|
||||
assertEquals(HttpStatus.NO_CONTENT_204, response.getStatus());
|
||||
assertFalse(StringUtils.hasContent(response.getBody()));
|
||||
|
||||
QRecord record = getPersonRecord(1);
|
||||
assertNull(record);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testDelete404()
|
||||
{
|
||||
HttpResponse<String> response = Unirest.delete(BASE_URL + "/api/" + VERSION + "/person/1")
|
||||
.asString();
|
||||
assertErrorResponse(HttpStatus.NOT_FOUND_404, "Could not find Person with Id of 1", response);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static QRecord getPersonRecord(Integer id) throws QException
|
||||
{
|
||||
GetInput getInput = new GetInput();
|
||||
getInput.setTableName(TestUtils.TABLE_NAME_PERSON);
|
||||
getInput.setPrimaryKey(id);
|
||||
GetOutput getOutput = new GetAction().execute(getInput);
|
||||
QRecord record = getOutput.getRecord();
|
||||
return record;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void insertPersonRecord(Integer id, String firstName, String lastName) throws QException
|
||||
{
|
||||
InsertInput insertInput = new InsertInput();
|
||||
insertInput.setTableName(TestUtils.TABLE_NAME_PERSON);
|
||||
insertInput.setRecords(List.of(new QRecord().withValue("id", id).withValue("firstName", firstName).withValue("lastName", lastName)));
|
||||
new InsertAction().execute(insertInput);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void insertSimpsons() throws QException
|
||||
{
|
||||
insertTestRecord(1, "Homer", "Simpson");
|
||||
insertTestRecord(2, "Marge", "Simpson");
|
||||
insertTestRecord(3, "Bart", "Simpson");
|
||||
insertTestRecord(4, "Lisa", "Simpson");
|
||||
insertTestRecord(5, "Maggie", "Simpson");
|
||||
insertPersonRecord(1, "Homer", "Simpson");
|
||||
insertPersonRecord(2, "Marge", "Simpson");
|
||||
insertPersonRecord(3, "Bart", "Simpson");
|
||||
insertPersonRecord(4, "Lisa", "Simpson");
|
||||
insertPersonRecord(5, "Maggie", "Simpson");
|
||||
}
|
||||
|
||||
|
||||
@ -347,7 +643,7 @@ class QJavalinApiHandlerTest extends BaseTest
|
||||
private void assertPersonQueryFindsFirstNames(List<String> expectedFirstNames, String queryString)
|
||||
{
|
||||
HttpResponse<String> response = Unirest.get(BASE_URL + "/api/" + VERSION + "/person/query?" + queryString).asString();
|
||||
assertEquals(200, response.getStatus());
|
||||
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||
JSONObject jsonObject = new JSONObject(response.getBody());
|
||||
assertEquals(expectedFirstNames.size(), jsonObject.getInt("count"));
|
||||
|
||||
@ -368,22 +664,6 @@ class QJavalinApiHandlerTest extends BaseTest
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testQuery200ManyParams()
|
||||
{
|
||||
HttpResponse<String> response = Unirest.get(BASE_URL + "/api/" + VERSION + "/person/query?pageSize=49&pageNo=2&includeCount=true&booleanOperator=AND&firstName=Homer&orderBy=firstName desc").asString();
|
||||
assertEquals(200, response.getStatus());
|
||||
JSONObject jsonObject = new JSONObject(response.getBody());
|
||||
assertEquals(0, jsonObject.getInt("count"));
|
||||
assertEquals(2, jsonObject.getInt("pageNo"));
|
||||
assertEquals(49, jsonObject.getInt("pageSize"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -401,10 +681,30 @@ class QJavalinApiHandlerTest extends BaseTest
|
||||
private void assertError(String expectedErrorMessage, String url)
|
||||
{
|
||||
HttpResponse<String> response = Unirest.get(url).asString();
|
||||
assertEquals(400, response.getStatus());
|
||||
assertErrorResponse(HttpStatus.BAD_REQUEST_400, expectedErrorMessage, response);
|
||||
JSONObject jsonObject = new JSONObject(response.getBody());
|
||||
String error = jsonObject.getString("error");
|
||||
assertThat(error).contains(expectedErrorMessage);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void assertErrorResponse(Integer expectedStatusCode, String expectedErrorMessage, HttpResponse<String> response)
|
||||
{
|
||||
if(expectedStatusCode != null)
|
||||
{
|
||||
assertEquals(expectedStatusCode, response.getStatus());
|
||||
}
|
||||
|
||||
if(expectedErrorMessage != null)
|
||||
{
|
||||
JSONObject jsonObject = new JSONObject(response.getBody());
|
||||
String error = jsonObject.getString("error");
|
||||
assertThat(error).contains(expectedErrorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user