CE-798 - Add defaultQuickFilterFieldNames as table meta-data, along with instance validation; add junit (for the validation logic)

This commit is contained in:
2024-01-23 14:03:44 -06:00
parent 78f764c4cd
commit 7fbd3ce853
4 changed files with 431 additions and 2 deletions

View File

@ -22,17 +22,23 @@
package com.kingsrook.qqq.frontend.materialdashboard.model.metadata;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.kingsrook.qqq.backend.core.instances.QInstanceValidator;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QSupplementalTableMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
/*******************************************************************************
**
** table-level meta-data for this module (handled as QSupplementalTableMetaData)
*******************************************************************************/
public class MaterialDashboardTableMetaData extends QSupplementalTableMetaData
{
private List<List<String>> gotoFieldNames;
private List<String> defaultQuickFilterFieldNames;
/*******************************************************************************
@ -86,4 +92,73 @@ public class MaterialDashboardTableMetaData extends QSupplementalTableMetaData
return (this);
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public void validate(QInstance qInstance, QTableMetaData tableMetaData, QInstanceValidator qInstanceValidator)
{
super.validate(qInstance, tableMetaData, qInstanceValidator);
String prefix = "MaterialDashboardTableMetaData supplementalTableMetaData for table [" + tableMetaData.getName() + "] ";
for(List<String> gotoFieldNameSubList : CollectionUtils.nonNullList(gotoFieldNames))
{
qInstanceValidator.assertCondition(!gotoFieldNameSubList.isEmpty(), prefix + "has an empty gotoFieldNames list");
validateListOfFieldNames(tableMetaData, gotoFieldNameSubList, qInstanceValidator, prefix + "gotoFieldNames: ");
}
validateListOfFieldNames(tableMetaData, defaultQuickFilterFieldNames, qInstanceValidator, prefix + "defaultQuickFilterFieldNames: ");
}
/*******************************************************************************
**
*******************************************************************************/
private void validateListOfFieldNames(QTableMetaData tableMetaData, List<String> fieldNames, QInstanceValidator qInstanceValidator, String prefix)
{
Set<String> usedNames = new HashSet<>();
for(String fieldName : CollectionUtils.nonNullList(fieldNames))
{
if(qInstanceValidator.assertNoException(() -> tableMetaData.getField(fieldName), prefix + " unrecognized field name: " + fieldName))
{
qInstanceValidator.assertCondition(!usedNames.contains(fieldName), prefix + " has a duplicated field name: " + fieldName);
usedNames.add(fieldName);
}
}
}
/*******************************************************************************
** Getter for defaultQuickFilterFieldNames
*******************************************************************************/
public List<String> getDefaultQuickFilterFieldNames()
{
return (this.defaultQuickFilterFieldNames);
}
/*******************************************************************************
** Setter for defaultQuickFilterFieldNames
*******************************************************************************/
public void setDefaultQuickFilterFieldNames(List<String> defaultQuickFilterFieldNames)
{
this.defaultQuickFilterFieldNames = defaultQuickFilterFieldNames;
}
/*******************************************************************************
** Fluent setter for defaultQuickFilterFieldNames
*******************************************************************************/
public MaterialDashboardTableMetaData withDefaultQuickFilterFieldNames(List<String> defaultQuickFilterFieldNames)
{
this.defaultQuickFilterFieldNames = defaultQuickFilterFieldNames;
return (this);
}
}

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.frontend.materialdashboard.junit;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.session.QSession;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
/*******************************************************************************
**
*******************************************************************************/
public class BaseTest
{
private static final QLogger LOG = QLogger.getLogger(BaseTest.class);
/*******************************************************************************
**
*******************************************************************************/
@BeforeEach
void baseBeforeEach()
{
QContext.init(TestUtils.defineInstance(), new QSession());
}
/*******************************************************************************
**
*******************************************************************************/
@AfterEach
void baseAfterEach()
{
QContext.clear();
}
/*******************************************************************************
**
*******************************************************************************/
protected static void reInitInstanceInContext(QInstance qInstance)
{
if(qInstance.equals(QContext.getQInstance()))
{
LOG.warn("Unexpected condition - the same qInstance that is already in the QContext was passed into reInit. You probably want a new QInstance object instance.");
}
QContext.init(qInstance, new QSession());
}
}

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.frontend.materialdashboard.junit;
import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationType;
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.authentication.QAuthenticationMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
/*******************************************************************************
**
*******************************************************************************/
public class TestUtils
{
public static final String DEFAULT_BACKEND_NAME = "memoryBackend";
public static final String TABLE_NAME_PERSON = "person";
/*******************************************************************************
**
*******************************************************************************/
public static QInstance defineInstance()
{
QInstance qInstance = new QInstance();
qInstance.addBackend(defineBackend());
qInstance.addTable(defineTablePerson());
qInstance.setAuthentication(defineAuthentication());
return (qInstance);
}
/*******************************************************************************
** Define the authentication used in standard tests - using 'mock' type.
**
*******************************************************************************/
public static QAuthenticationMetaData defineAuthentication()
{
return new QAuthenticationMetaData()
.withName("mock")
.withType(QAuthenticationType.MOCK);
}
/*******************************************************************************
**
*******************************************************************************/
public static QBackendMetaData defineBackend()
{
return (new QBackendMetaData()
.withName(DEFAULT_BACKEND_NAME)
.withBackendType("memory"));
}
/*******************************************************************************
**
*******************************************************************************/
private static QTableMetaData defineTablePerson()
{
return new QTableMetaData()
.withName(TABLE_NAME_PERSON)
.withLabel("Person")
.withBackendName(DEFAULT_BACKEND_NAME)
.withPrimaryKeyField("id")
.withField(new QFieldMetaData("id", QFieldType.INTEGER).withIsEditable(false))
.withField(new QFieldMetaData("createDate", QFieldType.DATE_TIME).withIsEditable(false))
.withField(new QFieldMetaData("modifyDate", QFieldType.DATE_TIME).withIsEditable(false))
.withField(new QFieldMetaData("firstName", QFieldType.STRING))
.withField(new QFieldMetaData("lastName", QFieldType.STRING))
.withField(new QFieldMetaData("birthDate", QFieldType.DATE))
.withField(new QFieldMetaData("email", QFieldType.STRING));
}
}

View File

@ -0,0 +1,178 @@
/*
* 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.frontend.materialdashboard.model.metadata;
import java.util.List;
import java.util.function.Consumer;
import com.kingsrook.qqq.backend.core.exceptions.QInstanceValidationException;
import com.kingsrook.qqq.backend.core.instances.QInstanceValidator;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.frontend.materialdashboard.junit.BaseTest;
import com.kingsrook.qqq.frontend.materialdashboard.junit.TestUtils;
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.fail;
/*******************************************************************************
** Unit test for MaterialDashboardTableMetaData
*******************************************************************************/
class MaterialDashboardTableMetaDataTest extends BaseTest
{
/*******************************************************************************
**
*******************************************************************************/
@Test
void testValidateGoToFieldNames()
{
assertValidationFailureReasons(qInstance -> qInstance.getTable(TestUtils.TABLE_NAME_PERSON).withSupplementalMetaData(new MaterialDashboardTableMetaData().withGotoFieldNames(List.of(List.of()))),
"empty gotoFieldNames list");
assertValidationFailureReasons(qInstance -> qInstance.getTable(TestUtils.TABLE_NAME_PERSON).withSupplementalMetaData(new MaterialDashboardTableMetaData().withGotoFieldNames(List.of(List.of("foo")))),
"unrecognized field name: foo");
assertValidationFailureReasons(qInstance -> qInstance.getTable(TestUtils.TABLE_NAME_PERSON).withSupplementalMetaData(new MaterialDashboardTableMetaData().withGotoFieldNames(List.of(List.of("foo"), List.of("bar", "baz")))),
"unrecognized field name: foo",
"unrecognized field name: bar",
"unrecognized field name: baz");
assertValidationFailureReasons(qInstance -> qInstance.getTable(TestUtils.TABLE_NAME_PERSON).withSupplementalMetaData(new MaterialDashboardTableMetaData().withGotoFieldNames(List.of(List.of("firstName", "firstName")))),
"duplicated field name: firstName");
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testValidateQuickFilterFieldNames()
{
assertValidationFailureReasons(qInstance -> qInstance.getTable(TestUtils.TABLE_NAME_PERSON).withSupplementalMetaData(new MaterialDashboardTableMetaData().withDefaultQuickFilterFieldNames(List.of("foo"))),
"unrecognized field name: foo");
assertValidationFailureReasons(qInstance -> qInstance.getTable(TestUtils.TABLE_NAME_PERSON).withSupplementalMetaData(new MaterialDashboardTableMetaData().withDefaultQuickFilterFieldNames(List.of("firstName", "lastName", "firstName"))),
"duplicated field name: firstName");
}
//////////////////////////////////////////////////////////////////////////
// todo - methods below here were copied from QInstanceValidatorTest... //
// how to share those... //
//////////////////////////////////////////////////////////////////////////
/*******************************************************************************
** Run a little setup code on a qInstance; then validate it, and assert that it
** failed validation with reasons that match the supplied vararg-reasons (but allow
** more reasons - e.g., helpful when one thing we're testing causes other errors).
*******************************************************************************/
private void assertValidationFailureReasonsAllowingExtraReasons(Consumer<QInstance> setup, String... reasons)
{
assertValidationFailureReasons(setup, true, reasons);
}
/*******************************************************************************
** Run a little setup code on a qInstance; then validate it, and assert that it
** failed validation with reasons that match the supplied vararg-reasons (and
** require that exact # of reasons).
*******************************************************************************/
private void assertValidationFailureReasons(Consumer<QInstance> setup, String... reasons)
{
assertValidationFailureReasons(setup, false, reasons);
}
/*******************************************************************************
** Implementation for the overloads of this name.
*******************************************************************************/
private void assertValidationFailureReasons(Consumer<QInstance> setup, boolean allowExtraReasons, String... reasons)
{
try
{
QInstance qInstance = TestUtils.defineInstance();
setup.accept(qInstance);
new QInstanceValidator().validate(qInstance);
fail("Should have thrown validationException");
}
catch(QInstanceValidationException e)
{
if(!allowExtraReasons)
{
int noOfReasons = e.getReasons() == null ? 0 : e.getReasons().size();
assertEquals(reasons.length, noOfReasons, "Expected number of validation failure reasons.\nExpected reasons: " + String.join(",", reasons)
+ "\nActual reasons: " + (noOfReasons > 0 ? String.join("\n", e.getReasons()) : "--"));
}
for(String reason : reasons)
{
assertReason(reason, e);
}
}
}
/*******************************************************************************
** Assert that an instance is valid!
*******************************************************************************/
private void assertValidationSuccess(Consumer<QInstance> setup)
{
try
{
QInstance qInstance = TestUtils.defineInstance();
setup.accept(qInstance);
new QInstanceValidator().validate(qInstance);
}
catch(QInstanceValidationException e)
{
fail("Expected no validation errors, but received: " + e.getMessage());
}
}
/*******************************************************************************
** utility method for asserting that a specific reason string is found within
** the list of reasons in the QInstanceValidationException.
**
*******************************************************************************/
private void assertReason(String reason, QInstanceValidationException e)
{
assertNotNull(e.getReasons(), "Expected there to be a reason for the failure (but there was not)");
assertThat(e.getReasons())
.withFailMessage("Expected any of:\n%s\nTo match: [%s]", e.getReasons(), reason)
.anyMatch(s -> s.contains(reason));
}
/////////////////////////////////////////////////////////////////
// todo - end of methods copied from QInstanceValidatorTest... //
/////////////////////////////////////////////////////////////////
}