Skip to content

Commit

Permalink
fixed uniqueness check on amendment and added new unique transform in…
Browse files Browse the repository at this point in the history
… field
  • Loading branch information
eviltester committed Apr 27, 2024
1 parent fc4c788 commit 6239d04
Show file tree
Hide file tree
Showing 8 changed files with 291 additions and 51 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ public SimpleApiRoutes(DefaultGUIHTML guiTemplates){
withValidation(new MatchesRegexValidationRule("[0-9]{3}[-]?[0-9]{1}[-]?[0-9]{2}[-]?[0-9]{6}[-]?[0-9]{1}")).
withValidation(new MaximumLengthValidationRule(17)).
setMustBeUnique(true).
setUniqueAfterTransform((s) -> s.replace("-","")).
withExample("123-4-56-789012-3"),
Field.is("price",FieldType.FLOAT).
makeMandatory().
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,12 @@
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;
import uk.co.compendiumdev.challenger.http.httpclient.HttpMessageSender;
import uk.co.compendiumdev.challenger.http.httpclient.HttpResponseDetails;
import uk.co.compendiumdev.sparkstart.Environment;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.stream.Stream;

/*
Expand Down Expand Up @@ -117,6 +115,132 @@ public void canPostItemAsJsonAndAcceptJson() {
Assertions.assertEquals("book", item.type);
}


@ParameterizedTest
@ValueSource(strings = {
"",
"-"
})
public void canNotPostCreateItemsWithDuplicateISBN(String dupeSynonymReplace) {

Map<String, String> headers = new HashMap<>();
headers.put("Content-Type", "application/json");
headers.put("Accept", "application/json");

Random random = new Random();
String aRandomIsbn = randomIsbn(random);

// full valid payload
final HttpResponseDetails response =
http.send("/simpleapi/items", "POST", headers,
"""
{
"price":2.00,
"numberinstock":2,
"isbn13": "%s",
"type":book
}
""".formatted(aRandomIsbn).stripIndent());

final HttpResponseDetails duplicateIsbnResponse =
http.send("/simpleapi/items", "POST", headers,
"""
{
"price":2.00,
"numberinstock":2,
"isbn13": "%s",
"type":book
}
""".formatted(aRandomIsbn.replace("-",dupeSynonymReplace)).stripIndent());

Assertions.assertEquals(201, response.statusCode);
Assertions.assertEquals("application/json", response.getHeader("content-type"));

Assertions.assertEquals(400, duplicateIsbnResponse.statusCode);
Assertions.assertTrue(duplicateIsbnResponse.body.contains("Field isbn13 Value is not unique"), "did not expect " + duplicateIsbnResponse.body);
}

@ParameterizedTest
@ValueSource(strings = {"POST", "PUT"})
public void canNotAmendItemsToHaveDuplicateISBN(String verbPostPut) {

Random random = new Random();

Map<String, String> headers = new HashMap<>();
headers.put("Content-Type", "application/json");
headers.put("Accept", "application/json");

Item anItem = new Item();
anItem.type="book";
anItem.price=2.00F;
anItem.isbn13 = randomIsbn(random);

Item anItemToAmend = new Item();
anItemToAmend.type="book";
anItemToAmend.price=3.00F;
anItemToAmend.isbn13 = randomIsbn(random);

apiCreateItem(anItem);
Item itemToAmend = apiCreateItem(anItemToAmend);

final HttpResponseDetails amendResponse =
http.send("/simpleapi/items/"+itemToAmend.id, verbPostPut, headers,
"""
{
"price":2.00,
"numberinstock":2,
"isbn13": "%s",
"type":book
}
""".formatted(anItem.isbn13).stripIndent());


Assertions.assertEquals(400, amendResponse.statusCode, "should not be able to amend item to " + amendResponse.body);
Assertions.assertTrue(amendResponse.body.contains("Field isbn13 Value is not unique"), "did not expect " + amendResponse.body);
}

@ParameterizedTest
@ValueSource(strings = {
"",
"-"
})
public void canNotAmendItemsToHaveDuplicateISBNBasedOnUniqueComparison(String dupeSynonymReplace) {

Random random = new Random();

Map<String, String> headers = new HashMap<>();
headers.put("Content-Type", "application/json");
headers.put("Accept", "application/json");

Item anItem = new Item();
anItem.type="book";
anItem.price=2.00F;
anItem.isbn13 = randomIsbn(random);

Item anItemToAmend = new Item();
anItemToAmend.type="book";
anItemToAmend.price=3.00F;
anItemToAmend.isbn13 = randomIsbn(random);

apiCreateItem(anItem);
Item itemToAmend = apiCreateItem(anItemToAmend);

final HttpResponseDetails amendResponse =
http.send("/simpleapi/items/"+itemToAmend.id, "POST", headers,
"""
{
"price":2.00,
"numberinstock":2,
"isbn13": "%s",
"type":book
}
""".formatted(anItem.isbn13.replace("-",dupeSynonymReplace)).stripIndent());


Assertions.assertEquals(400, amendResponse.statusCode, "should not be able to amend item to " + amendResponse.body);
Assertions.assertTrue(amendResponse.body.contains("Field isbn13 Value is not unique"), "did not expect " + amendResponse.body);
}

@Test
public void cannotPostItemWithId() {

Expand Down Expand Up @@ -150,7 +274,7 @@ public void cannotPostItemWithId() {
public void canGetItemsAsJson() {

Items items = apiGetItems();
Assertions.assertTrue(!items.items.isEmpty());
Assertions.assertFalse(items.items.isEmpty());
}

@Test
Expand Down Expand Up @@ -323,7 +447,7 @@ private Items apiGetItems(){
return new Gson().fromJson(response.body, Items.class);
}

class Item{
static class Item{

Integer id;
Float price;
Expand All @@ -332,11 +456,22 @@ class Item{
String type;
}

class Items{
static class Items{
List<Item> items;
}

class ErrorMessagesResponse{
static class ErrorMessagesResponse{
List<String> errorMessages;
}

private String randomIsbn(Random random){

String isbn13 = "xxx-x-xx-xxxxxx-x";

while(isbn13.contains("x")){
isbn13 = isbn13.replaceFirst("x", String.valueOf(random.nextInt(9)));
}

return isbn13;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.Function;

// todo: beginning to think that we should have an XField for each field type
// e.g. IdField, StringField, etc. - possibly with an interface or abstract
Expand All @@ -24,12 +25,12 @@ public final class Field {

// default value for the field
private String defaultValue;
private List<ValidationRule> validationRules;
private final List<ValidationRule> validationRules;
private boolean truncateStringIfTooLong;

private int maximumIntegerValue;
private int minimumIntegerValue;
private boolean allowedNullable;
private final boolean allowedNullable;
// todo: use BigDecimal for the internal float representations
private float maximumFloatValue;
private float minimumFloatValue;
Expand All @@ -38,6 +39,7 @@ public final class Field {
private boolean mustBeUnique;

private int truncatedStringLength;
private Function<String, String> transformToMakeUnique;

// todo: rather than all these fields, consider moving to more validation rules
// to help keep the class to a more manageable size or create a FieldValidator class
Expand All @@ -61,6 +63,8 @@ private Field(final String name, final FieldType type) {
minimumFloatValue = Float.MIN_VALUE;
mustBeUnique = false;
allowedNullable=false;

transformToMakeUnique = (s) -> s;
}

public static Field is(String name, FieldType type) {
Expand Down Expand Up @@ -191,10 +195,9 @@ public ValidationReport validate(FieldValue value, boolean allowedToSetIds) {
}

private void validateObjectValue(final FieldValue value, final ValidationReport report) {
FieldValue object = value;
if(object!= null && object.asObject()!=null){
if(value!= null && value.asObject()!=null){
final ValidationReport objectValidity =
object.asObject().
value.asObject().
validateFields(new ArrayList<>(), true);
report.combine(objectValidity);
}
Expand Down Expand Up @@ -277,7 +280,7 @@ private void reportThisValueDoesNotMatchType(final ValidationReport report,
private void validateBooleanValue(final FieldValue value,
final ValidationReport report) {
try{
boolean bool = value.asBoolean();
value.asBoolean();
}catch(IllegalArgumentException e){
report.setValid(false);
report.addErrorMessage(
Expand Down Expand Up @@ -481,4 +484,18 @@ public String getActualValueToAdd(final FieldValue value) {
public FieldValue valueFor(String value) {
return FieldValue.is(this, value);
}

public Field setUniqueAfterTransform(Function<String, String> transform) {
setMustBeUnique(true);
transformToMakeUnique = transform;
return this;
}

public String uniqueAfterTransform(String string) {
try {
return transformToMakeUnique.apply(string);
}catch (Exception e){
return "ERROR: " + string + " " + e.getMessage();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,22 @@ public final class FieldValue {
private final String fieldName; // should this be name or should it be a Field reference?
private final String valueOfField;
private final Field forField; // the related field
private final String valueForUniqueComparison;
private InstanceFields objectValue;
// todo: list of strings for an array
// todo: list of InstanceFields for an array of objects

@Deprecated
private FieldValue(String fieldName, String fieldValue) {
this.forField = null;
this.fieldName = fieldName;
this.valueOfField = fieldValue;
this.objectValue = null;
}

public FieldValue(Field forField, String fieldValue) {
this.forField = forField;
this.fieldName = forField.getName();
this.valueOfField = fieldValue;
this.objectValue = null;

if(forField.mustBeUnique()){
this.valueForUniqueComparison = forField.uniqueAfterTransform(fieldValue);
}else {
this.valueForUniqueComparison = fieldValue;
}
}

@Override
Expand All @@ -35,7 +34,7 @@ public String toString() {
"fieldName='" + fieldName + "'" +
", fieldValue='" + valueOfField + "'";
if(objectValue!=null){
string = string + ",{ " + objectValue.toString() + " }";
string = string + ",{ " + objectValue + " }";
}
string = string + "}";

Expand Down Expand Up @@ -82,7 +81,7 @@ public InstanceFields asObject() {
}

public float asFloat() {
return Float.valueOf(valueOfField);
return Float.parseFloat(valueOfField);
}

public boolean asBoolean() {
Expand All @@ -97,7 +96,7 @@ public boolean asBoolean() {
}

public int asInteger() {
return Integer.valueOf(valueOfField);
return Integer.parseInt(valueOfField);
}

public String asJsonValue() {
Expand All @@ -120,4 +119,8 @@ public String asJsonValue() {
private String quoted(String aString){
return "\"" + aString.replaceAll("\"", "\\\\\"") + "\"";
}

public String asUniqueComparisonString() {
return valueForUniqueComparison;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@
final public class EntityInstanceCollection {

private final EntityDefinition definition;
private Map<String, EntityInstance> instances = new ConcurrentHashMap<>();
private final Map<String, EntityInstance> instances = new ConcurrentHashMap<>();

// id's should be auto incremented at an instance collection level, not on the field definitions
private Map<String, AutoIncrement> counters = new ConcurrentHashMap<>();
private final Map<String, AutoIncrement> counters = new ConcurrentHashMap<>();

public EntityInstanceCollection(EntityDefinition thingDefinition) {
this.definition = thingDefinition;
Expand Down Expand Up @@ -192,9 +192,6 @@ public Collection<EntityInstance> getInstances() {
/**
* This deletes the instance but does not delete any mandatorily related items, these need to be handled by
* another class using the returned list of alsoDelete, otherwise the model will be invalid
*
* @param guid
* @return
*/
public List<EntityInstance> deleteInstance(String guid) {

Expand Down Expand Up @@ -283,15 +280,15 @@ public ValidationReport checkFieldsForUniqueNess(EntityInstance instance, boolea
for(String fieldName : instance.getEntity().getFieldNames()){
Field field = instance.getEntity().getField(fieldName);
if(field.mustBeUnique()){
String valueThatMustBeUnique = instance.getFieldValue(fieldName).asString();
String valueThatMustBeUnique = instance.getFieldValue(fieldName).asUniqueComparisonString();
// check all instances to see if it is
for(EntityInstance instanceToCheck : instances.values()){
FieldValue existingValue = instanceToCheck.getFieldValue(fieldName);
if(valueThatMustBeUnique.equals(existingValue.asString())){
if(valueThatMustBeUnique.equals(existingValue.asUniqueComparisonString())){
// it is not
boolean dupeFound=true;
if(isAmendment){
if(instanceToCheck.getPrimaryKeyValue().equals(instanceToCheck.getPrimaryKeyValue())){
if(instanceToCheck.getPrimaryKeyValue().equals(instance.getPrimaryKeyValue())){
// same item so ignore this one
dupeFound=false;
}
Expand Down
Loading

0 comments on commit 6239d04

Please sign in to comment.