diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/savedreports/SavedReportsTableFullVerifier.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/savedreports/SavedReportsTableFullVerifier.java
new file mode 100644
index 00000000..d6e7e687
--- /dev/null
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/savedreports/SavedReportsTableFullVerifier.java
@@ -0,0 +1,151 @@
+/*
+ * 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 .
+ */
+
+package com.kingsrook.qqq.backend.core.processes.implementations.savedreports;
+
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import com.kingsrook.qqq.backend.core.actions.tables.DeleteAction;
+import com.kingsrook.qqq.backend.core.exceptions.QException;
+import com.kingsrook.qqq.backend.core.logging.QLogger;
+import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
+import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
+import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportFormat;
+import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
+import com.kingsrook.qqq.backend.core.model.data.QRecord;
+import com.kingsrook.qqq.backend.core.model.savedreports.RenderedReport;
+import com.kingsrook.qqq.backend.core.utils.StringUtils;
+import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
+
+
+/*******************************************************************************
+ ** Utility for verifying that the RenderReports process works for all report
+ ** records stored in the saved reports table.
+ **
+ ** Meant for use within a unit test, or maybe as part of an instance's boot-up/
+ ** validation.
+ *******************************************************************************/
+public class SavedReportsTableFullVerifier
+{
+ private static final QLogger LOG = QLogger.getLogger(SavedReportsTableFullVerifier.class);
+
+ private boolean removeRenderedReports = true;
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public void verify(List savedReportRecordList, String storageTableName) throws QException
+ {
+ Map caughtExceptions = new LinkedHashMap<>();
+ for(QRecord report : savedReportRecordList)
+ {
+ runReport(report, caughtExceptions, storageTableName);
+ }
+
+ //////////////////////////////////
+ // log out an exceptions caught //
+ //////////////////////////////////
+ if(!caughtExceptions.isEmpty())
+ {
+ for(Map.Entry entry : caughtExceptions.entrySet())
+ {
+ LOG.info("Caught an exception verifying saved reports", entry.getValue(), logPair("savdReportId", entry.getKey()));
+ }
+ throw (new QException("Saved Reports Verification failed with " + caughtExceptions.size() + " exception" + StringUtils.plural(caughtExceptions.size())));
+ }
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ private void runReport(QRecord savedReport, Map caughtExceptions, String storageTableName)
+ {
+ try
+ {
+ ///////////////////////
+ // render the report //
+ ///////////////////////
+ RunBackendStepInput input = new RunBackendStepInput();
+ RunBackendStepOutput output = new RunBackendStepOutput();
+
+ input.addValue(RenderSavedReportMetaDataProducer.FIELD_NAME_REPORT_FORMAT, ReportFormat.XLSX.name());
+ input.addValue(RenderSavedReportMetaDataProducer.FIELD_NAME_STORAGE_TABLE_NAME, storageTableName);
+ input.setRecords(List.of(savedReport));
+
+ new RenderSavedReportExecuteStep().run(input, output);
+ Exception exception = output.getException();
+ if(exception != null)
+ {
+ throw (exception);
+ }
+
+ //////////////////////////////////////////
+ // clean up the report, if so requested //
+ //////////////////////////////////////////
+ if(removeRenderedReports)
+ {
+ new DeleteAction().execute(new DeleteInput(RenderedReport.TABLE_NAME).withPrimaryKey(output.getValue("renderedReportId")));
+ }
+ }
+ catch(Exception e)
+ {
+ caughtExceptions.put(savedReport.getValueInteger("id"), e);
+ }
+ }
+
+
+
+ /*******************************************************************************
+ ** Getter for removeRenderedReports
+ *******************************************************************************/
+ public boolean getRemoveRenderedReports()
+ {
+ return (this.removeRenderedReports);
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for removeRenderedReports
+ *******************************************************************************/
+ public void setRemoveRenderedReports(boolean removeRenderedReports)
+ {
+ this.removeRenderedReports = removeRenderedReports;
+ }
+
+
+
+ /*******************************************************************************
+ ** Fluent setter for removeRenderedReports
+ *******************************************************************************/
+ public SavedReportsTableFullVerifier withRemoveRenderedReports(boolean removeRenderedReports)
+ {
+ this.removeRenderedReports = removeRenderedReports;
+ return (this);
+ }
+
+}
diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/processes/implementations/savedreports/SavedReportsTableFullVerifierTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/processes/implementations/savedreports/SavedReportsTableFullVerifierTest.java
new file mode 100644
index 00000000..431bf0a6
--- /dev/null
+++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/processes/implementations/savedreports/SavedReportsTableFullVerifierTest.java
@@ -0,0 +1,84 @@
+/*
+ * 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 .
+ */
+
+package com.kingsrook.qqq.backend.core.processes.implementations.savedreports;
+
+
+import java.util.List;
+import com.kingsrook.qqq.backend.core.BaseTest;
+import com.kingsrook.qqq.backend.core.actions.reporting.GenerateReportActionTest;
+import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
+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.insert.InsertInput;
+import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
+import com.kingsrook.qqq.backend.core.model.data.QRecord;
+import com.kingsrook.qqq.backend.core.model.savedreports.ReportColumns;
+import com.kingsrook.qqq.backend.core.model.savedreports.SavedReport;
+import com.kingsrook.qqq.backend.core.model.savedreports.SavedReportsMetaDataProvider;
+import com.kingsrook.qqq.backend.core.utils.JsonUtils;
+import com.kingsrook.qqq.backend.core.utils.TestUtils;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+
+/*******************************************************************************
+ ** Unit test for SavedReportsTableFullVerifier
+ *******************************************************************************/
+class SavedReportsTableFullVerifierTest extends BaseTest
+{
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @BeforeEach
+ void beforeEach() throws Exception
+ {
+ new SavedReportsMetaDataProvider().defineAll(QContext.getQInstance(), TestUtils.MEMORY_BACKEND_NAME, TestUtils.MEMORY_BACKEND_NAME, null);
+ GenerateReportActionTest.insertPersonRecords(QContext.getQInstance());
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ void test() throws QException
+ {
+ ReportColumns reportColumns = new ReportColumns();
+ reportColumns.withColumn("id");
+
+ //////////////////////////////////
+ // insert a saved report record //
+ //////////////////////////////////
+ SavedReport savedReport = new SavedReport();
+ savedReport.setTableName(TestUtils.TABLE_NAME_PERSON_MEMORY);
+ savedReport.setLabel("Test");
+ savedReport.setColumnsJson(JsonUtils.toJson(reportColumns));
+ savedReport.setQueryFilterJson(JsonUtils.toJson(new QQueryFilter()));
+ List reportRecordList = new InsertAction().execute(new InsertInput(SavedReport.TABLE_NAME).withRecordEntity(savedReport)).getRecords();
+
+ SavedReportsTableFullVerifier savedReportsTableFullVerifier = new SavedReportsTableFullVerifier();
+ savedReportsTableFullVerifier.verify(reportRecordList, SavedReportsMetaDataProvider.REPORT_STORAGE_TABLE_NAME);
+ }
+
+}
\ No newline at end of file