Files
qbit-cdn/src/test/java/com/kingsrook/qbits/cdn/processes/CdnProviderProcessesTest.java
T
james.maesandClaude Opus 5.5 e6685a7c80 feat: provider actions (test connection, edge stats, drift), checkstyle in verify
- spi.CdnOperations (connection test, stats import, drift) implemented by the CloudFront adapter.
- Test Connection / Import Edge Stats / Check Config Drift as standard ETL actions on cdn_provider, plus daily scheduled stats import and drift/connection checks; findings are opened, kept and resolved.
- Checkstyle now runs in verify with the repo's config and an Apache header file; violations fixed.
- WireMock removed (Jetty conflict with the QQQ BOM); tests use a JDK HttpServer stub.
- README describes the complete qbit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 14:31:17 -05:00

208 lines
9.5 KiB
Java

/*
* Copyright 2026 Kingsrook, LLC
*
* 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
*
* http://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.cdn.processes;
import java.util.List;
import com.kingsrook.qbits.cdn.CdnRuntime;
import com.kingsrook.qbits.cdn.CdnTestSupport;
import com.kingsrook.qbits.cdn.CdnTestSupport.FakePathProvider;
import com.kingsrook.qbits.cdn.model.CdnTables;
import com.kingsrook.qbits.cdn.spi.CdnOperations;
import com.kingsrook.qbits.cdn.spi.CdnProviderType;
import com.kingsrook.qqq.backend.core.actions.processes.RunProcessAction;
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
import com.kingsrook.qqq.backend.core.model.actions.processes.ProcessSummaryLine;
import com.kingsrook.qqq.backend.core.model.actions.processes.ProcessSummaryLineInterface;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessInput;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.StreamedETLWithFrontendProcess;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
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.assertNull;
/*******************************************************************************
** Test Connection, Import Edge Stats and Check Config Drift through
** RunProcessAction, plus the scheduled variants.
*******************************************************************************/
class CdnProviderProcessesTest
{
private Integer provider;
/*******************************************************************************
** One operable provider and one without operations (NONE).
*******************************************************************************/
@BeforeEach
void setUp() throws Exception
{
CdnTestSupport.setUp();
provider = CdnTestSupport.liveProvider(CdnProviderType.CLOUDFRONT, null);
CdnTestSupport.insert(CdnTables.PROVIDER, new QRecord().withValue("name", "none").withValue("providerType", "NONE").withValue("enabled", true));
CdnRuntime.setEnvironment(name -> "CDN_EXPECTED_CONFIG".equals(name) ? "{}" : null);
}
/*******************************************************************************
** Restore the environment.
*******************************************************************************/
@AfterEach
void tearDown()
{
CdnRuntime.setEnvironment(null);
}
/*******************************************************************************
** Review then execute on every provider; returns [review, result] lines.
*******************************************************************************/
@SuppressWarnings("unchecked")
private static List<List<String>> run(String process) throws Exception
{
RunProcessInput input = new RunProcessInput();
input.setProcessName(process);
input.addValue("recordsParam", "recordIds");
input.addValue("recordIds", "1,2");
input.setFrontendStepBehavior(RunProcessInput.FrontendStepBehavior.BREAK);
RunProcessOutput review = new RunProcessAction().execute(input);
RunProcessInput go = new RunProcessInput();
go.setProcessName(process);
go.setProcessUUID(review.getProcessUUID());
go.setStartAfterStep(StreamedETLWithFrontendProcess.STEP_NAME_REVIEW);
go.setFrontendStepBehavior(RunProcessInput.FrontendStepBehavior.SKIP);
RunProcessOutput result = new RunProcessAction().execute(go);
return (List.of(
text((List<ProcessSummaryLineInterface>) review.getValues().get(StreamedETLWithFrontendProcess.FIELD_VALIDATION_SUMMARY), false),
text((List<ProcessSummaryLineInterface>) result.getValues().get(StreamedETLWithFrontendProcess.FIELD_PROCESS_SUMMARY), true)));
}
/*******************************************************************************
** Lines as shown.
*******************************************************************************/
private static List<String> text(List<ProcessSummaryLineInterface> lines, boolean past)
{
return (lines == null ? List.of() : lines.stream().map(line ->
{
ProcessSummaryLine summary = (ProcessSummaryLine) line;
summary.pickMessage(past);
return (summary.getCount() + " " + summary.getMessage());
}).toList());
}
/*******************************************************************************
** The provider row.
*******************************************************************************/
private QRecord row() throws Exception
{
return (new GetAction().executeForRecord(new GetInput(CdnTables.PROVIDER).withPrimaryKey(provider)));
}
/*******************************************************************************
** A good test records the DNS target and origin; a failed one an error line.
*******************************************************************************/
@Test
void testConnection() throws Exception
{
List<List<String>> lines = run(CdnProviderProcesses.TEST_CONNECTION);
assertThat(lines.get(0)).containsExactly("1 provider will be checked", "1 provider does not support this (no change)");
assertThat(lines.get(1)).containsExactly("1 connection is OK");
assertEquals("OK", row().getValueString("lastHealthStatus"));
assertEquals("d1.cloudfront.net", row().getValueString("dnsTarget"));
assertEquals("origin.example.com:8443", row().getValueString("originHost"));
assertNotNull(row().getValue("lastSuccessfulTestDate"));
FakePathProvider.connectionOk = false;
assertThat(run(CdnProviderProcesses.TEST_CONNECTION).get(1)).containsExactly("1 connection test failed");
assertEquals("FAILED", row().getValueString("lastHealthStatus"));
assertEquals("AccessDenied", row().getValueString("lastHealthMessage"));
}
/*******************************************************************************
** Stats are imported and re-imports update the same rows.
*******************************************************************************/
@Test
void importEdgeStats() throws Exception
{
assertThat(run(CdnProviderProcesses.IMPORT_EDGE_STATS).get(1)).containsExactly("2 days of statistics were imported");
run(CdnProviderProcesses.IMPORT_EDGE_STATS);
List<QRecord> stats = new QueryAction().execute(new QueryInput(CdnTables.EDGE_STAT_DAILY)).getRecords();
assertThat(stats).hasSize(2);
assertThat(stats).anyMatch(stat -> stat.getValueBigDecimal("cacheHitRate") != null && stat.getValueBigDecimal("cacheHitRate").doubleValue() == 90.0);
assertThat(stats).anyMatch(stat -> stat.getValue("cacheHitRate") == null);
}
/*******************************************************************************
** Findings are recorded, kept, and resolved when gone; no expected config
** is a failed line.
*******************************************************************************/
@Test
void checkDrift() throws Exception
{
assertThat(run(CdnProviderProcesses.CHECK_DRIFT).get(1)).containsExactly("1 provider has no drift");
FakePathProvider.drift = List.of(new CdnOperations.DriftItem("origin.header:X-Origin-Verify", "present", "missing"));
assertThat(run(CdnProviderProcesses.CHECK_DRIFT).get(1)).containsExactly("1 drift finding is open");
run(CdnProviderProcesses.CHECK_DRIFT);
List<QRecord> findings = new QueryAction().execute(new QueryInput(CdnTables.DRIFT_FINDING)).getRecords();
assertThat(findings).hasSize(1);
assertNull(findings.get(0).getValue("resolvedDate"));
FakePathProvider.drift = List.of();
run(CdnProviderProcesses.CHECK_DRIFT);
assertNotNull(new QueryAction().execute(new QueryInput(CdnTables.DRIFT_FINDING)).getRecords().get(0).getValue("resolvedDate"));
CdnRuntime.setEnvironment(name -> null);
assertThat(run(CdnProviderProcesses.CHECK_DRIFT).get(1).get(0)).startsWith("1 drift check failed: No expected config");
}
/*******************************************************************************
** The scheduled steps run for every operable provider.
*******************************************************************************/
@Test
void scheduledSteps() throws Exception
{
new CdnProviderProcesses.ScheduledStatsStep().run(null, null);
new CdnProviderProcesses.ScheduledDriftStep().run(null, null);
assertThat(new QueryAction().execute(new QueryInput(CdnTables.EDGE_STAT_DAILY)).getRecords()).hasSize(2);
assertEquals("OK", row().getValueString("lastHealthStatus"));
}
}