Permission base webPerformance -> web_performance (read, write, test, admin, view). Process and widget names stay webPerformance*. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
167 lines
8.1 KiB
Java
167 lines
8.1 KiB
Java
/*
|
|
* QQQ - Low-code Application Framework for Engineers.
|
|
* Copyright (C) 2021-2025. Kingsrook, LLC
|
|
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
|
* contact@kingsrook.com
|
|
* https://github.com/Kingsrook/
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* https://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package com.kingsrook.qbits.webperf;
|
|
|
|
|
|
import java.math.BigDecimal;
|
|
import java.sql.Connection;
|
|
import java.sql.DatabaseMetaData;
|
|
import java.sql.ResultSet;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
import java.util.Locale;
|
|
import java.util.Set;
|
|
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
|
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
|
import com.kingsrook.qqq.backend.core.context.QContext;
|
|
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.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.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.module.rdbms.jdbc.ConnectionManager;
|
|
import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSBackendMetaData;
|
|
import org.junit.jupiter.api.AfterEach;
|
|
import org.junit.jupiter.api.Test;
|
|
import static org.assertj.core.api.Assertions.assertThat;
|
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
|
|
|
|
/*******************************************************************************
|
|
** The changelog and the table meta-data agree on a real database (H2): every
|
|
** field of every table has its column, every table can be queried through
|
|
** QQQ, people-edited tables accept inserts with the webperf permissions,
|
|
** and the seeded budgets are inserted once.
|
|
*******************************************************************************/
|
|
class WebPerfSchemaTest
|
|
{
|
|
/*******************************************************************************
|
|
** Clear the context.
|
|
*******************************************************************************/
|
|
@AfterEach
|
|
void tearDown()
|
|
{
|
|
QContext.clear();
|
|
}
|
|
|
|
|
|
|
|
/*******************************************************************************
|
|
** An instance with the QBit under a prefix and its changelog applied.
|
|
*******************************************************************************/
|
|
private static QInstance installed(String prefix) throws Exception
|
|
{
|
|
QInstance qInstance = WebPerfTestSupport.newInstance();
|
|
new WebPerfQBitProducer().withQBitConfig(WebPerfTestSupport.config().withTableNamePrefix(prefix)).produce(qInstance);
|
|
WebPerfTestSupport.applyChangelog(qInstance, prefix);
|
|
WebPerfTestSupport.login(qInstance, "web_performance.read", "web_performance.write");
|
|
return (qInstance);
|
|
}
|
|
|
|
|
|
|
|
/*******************************************************************************
|
|
** Every field has its column; every table is queryable through QQQ.
|
|
*******************************************************************************/
|
|
@Test
|
|
void testEveryFieldHasItsColumn() throws Exception
|
|
{
|
|
QInstance qInstance = installed("web_performance_");
|
|
try(Connection connection = ConnectionManager.getConnection((RDBMSBackendMetaData) qInstance.getBackend(WebPerfTestSupport.BACKEND)))
|
|
{
|
|
DatabaseMetaData metaData = connection.getMetaData();
|
|
for(String base : WebPerfSchema.TABLES)
|
|
{
|
|
QTableMetaData table = qInstance.getTable("web_performance_" + base);
|
|
Set<String> columns = new HashSet<>();
|
|
try(ResultSet resultSet = metaData.getColumns(null, null, table.getName(), null))
|
|
{
|
|
while(resultSet.next())
|
|
{
|
|
columns.add(resultSet.getString("COLUMN_NAME").toLowerCase(Locale.ROOT));
|
|
}
|
|
}
|
|
assertThat(columns).as(table.getName() + " exists").isNotEmpty();
|
|
for(QFieldMetaData field : table.getFields().values())
|
|
{
|
|
assertThat(columns).as(table.getName() + "." + field.getName()).contains(field.getBackendName());
|
|
}
|
|
assertThat(columns).as(table.getName() + " has no column without a field").hasSize(table.getFields().size());
|
|
|
|
QueryOutput output = new QueryAction().execute(new QueryInput(table.getName()).withFilter(new QQueryFilter()));
|
|
assertThat(output.getRecords()).as(table.getName()).isNotNull();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*******************************************************************************
|
|
** The seeded budgets: Google's "good" thresholds and the category bands,
|
|
** inserted once (a re-run of the changeset adds nothing).
|
|
*******************************************************************************/
|
|
@Test
|
|
void testSeededBudgets() throws Exception
|
|
{
|
|
QInstance qInstance = installed("web_performance_");
|
|
List<QRecord> budgets = new QueryAction().execute(new QueryInput("web_performance_budget").withFilter(new QQueryFilter())).getRecords();
|
|
assertEquals(13, budgets.size());
|
|
assertThat(budgets).allMatch(r -> Boolean.TRUE.equals(r.getValueBoolean("seeded")) && Boolean.TRUE.equals(r.getValueBoolean("enabled")));
|
|
|
|
QRecord inp = budgets.stream().filter(r -> "INP good (real users, p75)".equals(r.getValueString("name"))).findFirst().orElseThrow();
|
|
assertEquals("RUM", inp.getValueString("source"));
|
|
assertEquals(0, new BigDecimal("200").compareTo(inp.getValueBigDecimal("threshold")));
|
|
QRecord critical = budgets.stream().filter(r -> "CRITICAL".equals(r.getValueString("severity"))).findFirst().orElseThrow();
|
|
assertEquals("KEY", critical.getValueString("scopeValue"));
|
|
|
|
WebPerfTestSupport.applyChangelog(qInstance, "web_performance_");
|
|
assertEquals(13, new QueryAction().execute(new QueryInput("web_performance_budget").withFilter(new QQueryFilter())).getRecords().size());
|
|
}
|
|
|
|
|
|
|
|
/*******************************************************************************
|
|
** A tested page and a budget can be added by a user with web_performance.write,
|
|
** under any prefix; the unique key keeps one row per site and URL.
|
|
*******************************************************************************/
|
|
@Test
|
|
void testPeopleEditedTablesAcceptInserts() throws Exception
|
|
{
|
|
installed("perf_");
|
|
InsertOutput output = new InsertAction().execute(new InsertInput("perf_target").withRecords(List.of(
|
|
new QRecord().withValue("siteKey", "site:1").withValue("url", "https://www.example.com/about").withValue("path", "/about")
|
|
.withValue("source", "MANUAL").withValue("tier", "KEY").withValue("enabled", true))));
|
|
assertThat(output.getRecords().get(0).getErrors()).isEmpty();
|
|
|
|
QRecord stored = new QueryAction().execute(new QueryInput("perf_target").withFilter(new QQueryFilter())).getRecords().get(0);
|
|
assertEquals("https://www.example.com/about", stored.getValueString("url"));
|
|
assertEquals("KEY", stored.getValueString("tier"));
|
|
|
|
InsertOutput duplicate = new InsertAction().execute(new InsertInput("perf_target").withRecords(List.of(
|
|
new QRecord().withValue("siteKey", "site:1").withValue("url", "https://www.example.com/about"))));
|
|
assertThat(duplicate.getRecords().get(0).getErrors()).isNotEmpty();
|
|
}
|
|
}
|