mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-19 05:30:43 +00:00
automatic audits
This commit is contained in:
@ -165,9 +165,9 @@ class AuditActionTest extends BaseTest
|
||||
Integer recordId2 = 1702;
|
||||
Integer recordId3 = 1703;
|
||||
AuditInput auditInput = new AuditInput();
|
||||
AuditAction.appendToInput(auditInput, TestUtils.TABLE_NAME_PERSON_MEMORY, recordId1, Map.of(), "Test Audit", List.of("Detail1", "Detail2"));
|
||||
AuditAction.appendToInput(auditInput, TestUtils.TABLE_NAME_PERSON_MEMORY, recordId1, Map.of(), "Test Audit", List.of(new QRecord().withValue("message", "Detail1"), new QRecord().withValue("message", "Detail2")));
|
||||
AuditAction.appendToInput(auditInput, TestUtils.TABLE_NAME_ORDER, recordId2, Map.of(TestUtils.SECURITY_KEY_TYPE_STORE, 47), "Test Another Audit", null);
|
||||
AuditAction.appendToInput(auditInput, TestUtils.TABLE_NAME_PERSON_MEMORY, recordId3, Map.of(TestUtils.SECURITY_KEY_TYPE_STORE, 42), "Audit 3", List.of("Detail3"));
|
||||
AuditAction.appendToInput(auditInput, TestUtils.TABLE_NAME_PERSON_MEMORY, recordId3, Map.of(TestUtils.SECURITY_KEY_TYPE_STORE, 42), "Audit 3", List.of(new QRecord().withValue("message", "Detail3")));
|
||||
new AuditAction().execute(auditInput);
|
||||
|
||||
/////////////////////////////////////
|
||||
|
@ -0,0 +1,178 @@
|
||||
/*
|
||||
* 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.backend.core.actions.audits;
|
||||
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.BaseTest;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.audits.DMLAuditInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
|
||||
import com.kingsrook.qqq.backend.core.model.audits.AuditsMetaDataProvider;
|
||||
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.audits.AuditLevel;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.audits.QAuditRules;
|
||||
import com.kingsrook.qqq.backend.core.modules.backend.implementations.memory.MemoryRecordStore;
|
||||
import com.kingsrook.qqq.backend.core.utils.TestUtils;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Unit test for DMLAuditAction
|
||||
*******************************************************************************/
|
||||
class DMLAuditActionTest extends BaseTest
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void test() throws QException
|
||||
{
|
||||
QInstance qInstance = QContext.getQInstance();
|
||||
new AuditsMetaDataProvider().defineAll(qInstance, TestUtils.MEMORY_BACKEND_NAME, null);
|
||||
|
||||
UpdateInput updateInput = new UpdateInput();
|
||||
updateInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY);
|
||||
|
||||
InsertInput insertInput = new InsertInput();
|
||||
insertInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY);
|
||||
|
||||
DeleteInput deleteInput = new DeleteInput();
|
||||
deleteInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY);
|
||||
|
||||
List<QRecord> recordList = List.of(new QRecord().withValue("id", 1).withValue("firstName", "Darin").withValue("noOfShoes", 5).withValue("favoriteShapeId", null).withValue("createDate", Instant.now()).withValue("modifyDate", Instant.now()));
|
||||
List<QRecord> oldRecordList = List.of(new QRecord().withValue("id", 1).withValue("firstName", "Tim").withValue("noOfShoes", null).withValue("favoriteShapeId", 1).withValue("lastName", "Simpson"));
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
// set audit rules null - confirm no audits are built //
|
||||
////////////////////////////////////////////////////////
|
||||
{
|
||||
qInstance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY).setAuditRules(null);
|
||||
new DMLAuditAction().execute(new DMLAuditInput().withTableActionInput(deleteInput).withRecordList(recordList));
|
||||
List<QRecord> auditList = TestUtils.queryTable("audit");
|
||||
assertTrue(auditList.isEmpty());
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
// set audit level NONE - confirm no audits are built //
|
||||
////////////////////////////////////////////////////////
|
||||
{
|
||||
qInstance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY).setAuditRules(new QAuditRules().withAuditLevel(AuditLevel.NONE));
|
||||
new DMLAuditAction().execute(new DMLAuditInput().withTableActionInput(deleteInput).withRecordList(recordList));
|
||||
List<QRecord> auditList = TestUtils.queryTable("audit");
|
||||
assertTrue(auditList.isEmpty());
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////
|
||||
// set audit level RECORD - confirm only header, no detail //
|
||||
/////////////////////////////////////////////////////////////
|
||||
{
|
||||
qInstance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY).setAuditRules(new QAuditRules().withAuditLevel(AuditLevel.RECORD));
|
||||
new DMLAuditAction().execute(new DMLAuditInput().withTableActionInput(insertInput).withRecordList(recordList));
|
||||
List<QRecord> auditList = TestUtils.queryTable("audit");
|
||||
assertEquals(1, auditList.size());
|
||||
assertEquals("Record was Inserted", auditList.get(0).getValueString("message"));
|
||||
List<QRecord> auditDetailList = TestUtils.queryTable("auditDetail");
|
||||
assertTrue(auditDetailList.isEmpty());
|
||||
MemoryRecordStore.getInstance().reset();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
// set audit level FIELD - confirm header, and detail //
|
||||
////////////////////////////////////////////////////////
|
||||
{
|
||||
qInstance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY).setAuditRules(new QAuditRules().withAuditLevel(AuditLevel.FIELD));
|
||||
new DMLAuditAction().execute(new DMLAuditInput().withTableActionInput(updateInput).withRecordList(recordList));
|
||||
List<QRecord> auditList = TestUtils.queryTable("audit");
|
||||
assertEquals(1, auditList.size());
|
||||
assertEquals("Record was Edited", auditList.get(0).getValueString("message"));
|
||||
List<QRecord> auditDetailList = TestUtils.queryTable("auditDetail");
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// since we didn't provide old-records, there should be a detail for every field in the updated record - OTHER THAN createDate and modifyDate //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
assertEquals(recordList.get(0).getValues().size() - 2, auditDetailList.size());
|
||||
assertTrue(auditDetailList.stream().allMatch(r -> r.getValueString("message").matches("Set.*to.*")));
|
||||
assertTrue(auditDetailList.stream().allMatch(r -> r.getValueString("fieldName") != null));
|
||||
assertTrue(auditDetailList.stream().allMatch(r -> r.getValueString("oldValue") == null));
|
||||
assertTrue(auditDetailList.stream().anyMatch(r -> r.getValueString("newValue") != null));
|
||||
MemoryRecordStore.getInstance().reset();
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////
|
||||
// this time supply old-records to the edit //
|
||||
//////////////////////////////////////////////
|
||||
{
|
||||
qInstance.getTable(TestUtils.TABLE_NAME_SHAPE).setAuditRules(new QAuditRules().withAuditLevel(AuditLevel.NONE));
|
||||
TestUtils.insertDefaultShapes(qInstance);
|
||||
|
||||
qInstance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY).setAuditRules(new QAuditRules().withAuditLevel(AuditLevel.FIELD));
|
||||
new DMLAuditAction().execute(new DMLAuditInput().withTableActionInput(updateInput).withOldRecordList(oldRecordList).withRecordList(recordList));
|
||||
List<QRecord> auditList = TestUtils.queryTable("audit");
|
||||
assertEquals(1, auditList.size());
|
||||
assertEquals("Record was Edited", auditList.get(0).getValueString("message"));
|
||||
List<QRecord> auditDetailList = TestUtils.queryTable("auditDetail");
|
||||
|
||||
assertEquals(3, auditDetailList.size());
|
||||
|
||||
assertEquals("Removed \"Triangle\" from Favorite Shape", auditDetailList.get(0).getValueString("message"));
|
||||
assertEquals("favoriteShapeId", auditDetailList.get(0).getValueString("fieldName"));
|
||||
assertEquals("Triangle", auditDetailList.get(0).getValueString("oldValue"));
|
||||
assertNull(auditDetailList.get(0).getValueString("newValue"));
|
||||
|
||||
assertEquals("Changed First Name from \"Tim\" to \"Darin\"", auditDetailList.get(1).getValueString("message"));
|
||||
assertEquals("firstName", auditDetailList.get(1).getValueString("fieldName"));
|
||||
assertEquals("Tim", auditDetailList.get(1).getValueString("oldValue"));
|
||||
assertEquals("Darin", auditDetailList.get(1).getValueString("newValue"));
|
||||
|
||||
assertEquals("Set No Of Shoes to 5", auditDetailList.get(2).getValueString("message"));
|
||||
assertEquals("noOfShoes", auditDetailList.get(2).getValueString("fieldName"));
|
||||
assertNull(auditDetailList.get(2).getValueString("oldValue"));
|
||||
assertEquals("5", auditDetailList.get(2).getValueString("newValue"));
|
||||
|
||||
MemoryRecordStore.getInstance().reset();
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////
|
||||
// confirm we don't log null fields on inserts //
|
||||
/////////////////////////////////////////////////
|
||||
{
|
||||
qInstance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY).setAuditRules(new QAuditRules().withAuditLevel(AuditLevel.FIELD));
|
||||
new DMLAuditAction().execute(new DMLAuditInput().withTableActionInput(insertInput).withRecordList(recordList));
|
||||
List<QRecord> auditDetailList = TestUtils.queryTable("auditDetail");
|
||||
assertFalse(auditDetailList.isEmpty());
|
||||
assertTrue(auditDetailList.stream().noneMatch(r -> r.getValueString("message").contains("Favorite Shape")));
|
||||
MemoryRecordStore.getInstance().reset();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -24,11 +24,21 @@ package com.kingsrook.qqq.backend.core.actions.tables;
|
||||
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.BaseTest;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
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.insert.InsertInput;
|
||||
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.model.audits.AuditsMetaDataProvider;
|
||||
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.audits.AuditLevel;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.audits.QAuditRules;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.TestUtils;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
@ -79,4 +89,64 @@ class DeleteActionTest extends BaseTest
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testAuditsByPrimaryKey() throws QException
|
||||
{
|
||||
QInstance qInstance = QContext.getQInstance();
|
||||
|
||||
InsertInput insertInput = new InsertInput();
|
||||
insertInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY);
|
||||
insertInput.setRecords(List.of(
|
||||
new QRecord().withValue("id", 1),
|
||||
new QRecord().withValue("id", 2)));
|
||||
new InsertAction().execute(insertInput);
|
||||
|
||||
new AuditsMetaDataProvider().defineAll(qInstance, TestUtils.MEMORY_BACKEND_NAME, null);
|
||||
qInstance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY).setAuditRules(new QAuditRules().withAuditLevel(AuditLevel.RECORD));
|
||||
|
||||
DeleteInput deleteInput = new DeleteInput();
|
||||
deleteInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY);
|
||||
deleteInput.setPrimaryKeys(List.of(1, 2));
|
||||
new DeleteAction().execute(deleteInput);
|
||||
|
||||
List<QRecord> audits = TestUtils.queryTable("audit");
|
||||
assertEquals(2, audits.size());
|
||||
assertTrue(audits.stream().allMatch(r -> r.getValueString("message").equals("Record was Deleted")));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testAuditsByFilter() throws QException
|
||||
{
|
||||
QInstance qInstance = QContext.getQInstance();
|
||||
|
||||
InsertInput insertInput = new InsertInput();
|
||||
insertInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY);
|
||||
insertInput.setRecords(List.of(
|
||||
new QRecord().withValue("id", 1),
|
||||
new QRecord().withValue("id", 2)));
|
||||
new InsertAction().execute(insertInput);
|
||||
|
||||
new AuditsMetaDataProvider().defineAll(qInstance, TestUtils.MEMORY_BACKEND_NAME, null);
|
||||
qInstance.getTable(TestUtils.TABLE_NAME_PERSON_MEMORY).setAuditRules(new QAuditRules().withAuditLevel(AuditLevel.RECORD));
|
||||
|
||||
DeleteInput deleteInput = new DeleteInput();
|
||||
deleteInput.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY);
|
||||
deleteInput.setQueryFilter(new QQueryFilter(new QFilterCriteria("id", QCriteriaOperator.IN, 1, 2)));
|
||||
new DeleteAction().execute(deleteInput);
|
||||
|
||||
List<QRecord> audits = TestUtils.queryTable("audit");
|
||||
assertEquals(2, audits.size());
|
||||
assertTrue(audits.stream().allMatch(r -> r.getValueString("message").equals("Record was Deleted")));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -83,8 +83,8 @@ class QValueFormatterTest extends BaseTest
|
||||
assertEquals("No", QValueFormatter.formatValue(new QFieldMetaData().withType(QFieldType.BOOLEAN), false));
|
||||
|
||||
assertNull(QValueFormatter.formatValue(new QFieldMetaData().withType(QFieldType.TIME), null));
|
||||
assertEquals("5:00 AM", QValueFormatter.formatValue(new QFieldMetaData().withType(QFieldType.TIME), LocalTime.of(5, 0)));
|
||||
assertEquals("5:00 PM", QValueFormatter.formatValue(new QFieldMetaData().withType(QFieldType.TIME), LocalTime.of(17, 0)));
|
||||
assertEquals("5:00:00 AM", QValueFormatter.formatValue(new QFieldMetaData().withType(QFieldType.TIME), LocalTime.of(5, 0)));
|
||||
assertEquals("5:00:47 PM", QValueFormatter.formatValue(new QFieldMetaData().withType(QFieldType.TIME), LocalTime.of(17, 0, 47)));
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
// this one flows through the exceptional cases //
|
||||
@ -196,8 +196,8 @@ class QValueFormatterTest extends BaseTest
|
||||
void testFormatDates()
|
||||
{
|
||||
assertEquals("2023-02-01", QValueFormatter.formatDate(LocalDate.of(2023, Month.FEBRUARY, 1)));
|
||||
assertEquals("2023-02-01 07:15 PM", QValueFormatter.formatDateTime(LocalDateTime.of(2023, Month.FEBRUARY, 1, 19, 15)));
|
||||
assertEquals("2023-02-01 07:15 PM CST", QValueFormatter.formatDateTimeWithZone(ZonedDateTime.of(LocalDateTime.of(2023, Month.FEBRUARY, 1, 19, 15), ZoneId.of("US/Central"))));
|
||||
assertEquals("2023-02-01 07:15:00 PM", QValueFormatter.formatDateTime(LocalDateTime.of(2023, Month.FEBRUARY, 1, 19, 15, 0)));
|
||||
assertEquals("2023-02-01 07:15:47 PM CST", QValueFormatter.formatDateTimeWithZone(ZonedDateTime.of(LocalDateTime.of(2023, Month.FEBRUARY, 1, 19, 15, 47), ZoneId.of("US/Central"))));
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user