fix(purge): say when purges are recorded only, not sent (no live CDN) #20

Merged
KofTwentyTwo merged 1 commits from fix/no-cdn-provider-honest into develop 2026-10-14 15:50:22 +00:00
8 changed files with 333 additions and 20 deletions

No files matched your search

+1
View File
@@ -10,6 +10,7 @@ All notable changes to this project are documented here. The format follows Keep
### Fixed
- Purge actions warn when providers are NONE or hostnames are OFF, and report requests as recorded but not sent. The home explains recorded-only purges and that this version does not run origin header checks.
- Tag rule `{prefix.field}` placeholders now resolve the joined table from the `<prefix>Id` field's possible-value source, so prefixed tables (`cms_blog`) work; a missing joined table is logged instead of silently skipped.
## [0.1.0] - Unreleased
+15
View File
@@ -97,3 +97,18 @@ KoF22 receiver and exact changes required.
## License
Apache License 2.0. See [LICENSE](LICENSE) and [NOTICE](NOTICE).
### Before a CDN is live
With no enabled CDN provider (including providers of type `NONE`), purges are
recorded only, not sent. A real provider also needs a `LIVE` or `LOCKED` hostname
with purging enabled. Purge From CDN, Purge Everything, and Retry Purge show a
Review warning and a recorded-only Result when sending is unavailable. Requests
for enabled providers remain visible with `SKIPPED_NONE_PROVIDER` or
`SKIPPED_STAGE_OFF`; with no enabled provider, no new request row is created.
The home Connection and Health cards explain the unconfigured state, and purge
activity labels recorded-only requests when disconnected.
This version does not run origin header checks. The Origin headers card says
nothing has been checked until the host application writes results to the
standard `cdn_origin_check` table; those results continue to appear normally.
@@ -61,14 +61,14 @@ public record CdnHomeData(List<ProviderView> providers, List<HostnameView> hostn
** A provider row as shown.
*******************************************************************************/
public record ProviderView(Integer id, String name, String type, String distributionId, String dnsTarget, String originHost,
String lastHealthStatus, String lastHealthMessage, Instant lastSuccessfulTest, Long credentialAgeDays)
String lastHealthStatus, String lastHealthMessage, Instant lastSuccessfulTest, Long credentialAgeDays, Boolean enabled)
{
/*******************************************************************************
** Whether this is a real CDN (not the NONE placeholder).
*******************************************************************************/
public boolean isConnected()
{
return (!CdnProviderType.NONE.name().equals(type));
return (Boolean.TRUE.equals(enabled) && !CdnProviderType.NONE.name().equals(type));
}
}
@@ -119,7 +119,7 @@ public record CdnHomeData(List<ProviderView> providers, List<HostnameView> hostn
LocalDate rotated = ValueUtils.getValueAsLocalDate(row.getValue("credentialRotatedDate"));
providers.add(new ProviderView(id, row.getValueString("name"), row.getValueString("providerType"), row.getValueString("zoneOrDistributionId"),
row.getValueString("dnsTarget"), row.getValueString("originHost"), row.getValueString("lastHealthStatus"), row.getValueString("lastHealthMessage"),
ValueUtils.getValueAsInstant(row.getValue("lastSuccessfulTestDate")), rotated == null ? null : ChronoUnit.DAYS.between(rotated, today)));
ValueUtils.getValueAsInstant(row.getValue("lastSuccessfulTestDate")), rotated == null ? null : ChronoUnit.DAYS.between(rotated, today), row.getValueBoolean("enabled")));
if(CdnProviderType.CLOUDFRONT.name().equals(row.getValueString("providerType")))
{
PathPlannerSettings settings = PathPlannerSettings.from(CdnPurgeDispatcher.configOf(row));
@@ -80,7 +80,7 @@ public final class CdnHomeView
String text = "All CDN checks are OK.";
if("NOT_CONNECTED".equals(state))
{
text = "No CDN connected yet.";
text = "No CDN provider configured: purges are recorded only.";
}
else if("ATTENTION".equals(state))
{
@@ -99,7 +99,7 @@ public final class CdnHomeView
open(html, "Connection");
if(!data.isConnected())
{
empty(html, "No CDN connected yet. The CloudFront distribution is created in the cutover window.");
empty(html, "No CDN provider configured: purges are recorded only. The CloudFront distribution is created in the cutover window.");
}
for(CdnHomeData.ProviderView provider : data.providers())
{
@@ -182,6 +182,7 @@ public final class CdnHomeView
Instant created = ValueUtils.getValueAsInstant(row.getValue("createDate"));
html.append("<li><a href=\"").append(esc(appPath + "/" + CdnTables.PURGE_REQUEST + "/" + row.getValue("id"))).append("\">#")
.append(esc(String.valueOf(row.getValue("id")))).append("</a> ").append(esc(row.getValueString("status")))
.append(data.isConnected() ? "" : " - recorded only (not sent)")
.append(created == null ? "" : ", " + WHEN.format(created)).append("</li>");
}
html.append("</ul>");
@@ -224,7 +225,7 @@ public final class CdnHomeView
open(html, "Origin headers");
if(data.originChecks().isEmpty())
{
empty(html, "No origin header check has run yet.");
empty(html, "Origin header checks are not run by this version; nothing has been checked.");
}
for(QRecord check : data.originChecks())
{
@@ -167,6 +167,56 @@ public final class CdnProcesses
/*******************************************************************************
** Summary counts for requests that cannot be sent, shared by all actions.
*******************************************************************************/
private static class UnsentSummary
{
private final ProcessSummaryLine absent = line(Status.WARNING,
"record has no enabled CDN provider", "records have no enabled CDN provider",
"record had no enabled CDN provider (no purge request recorded)", "records had no enabled CDN provider (no purge requests recorded)");
private final ProcessSummaryLine none = line(Status.WARNING,
"No CDN provider configured: purges are recorded only, not sent.", "No CDN provider configured: purges are recorded only, not sent.",
"purge request was recorded (not sent: no CDN provider)", "purge requests were recorded (not sent: no CDN provider)");
private final ProcessSummaryLine off = line(Status.WARNING,
"CDN hostnames are not live: purges are recorded only, not sent.", "CDN hostnames are not live: purges are recorded only, not sent.",
"purge request was recorded (not sent: CDN hostnames are not live)", "purge requests were recorded (not sent: CDN hostnames are not live)");
/*******************************************************************************
** Count a skipped request; returns whether it cannot be sent.
*******************************************************************************/
boolean add(CdnPurgeStatus status)
{
if(status == CdnPurgeStatus.SKIPPED_NONE_PROVIDER)
{
none.incrementCount(1);
return (true);
}
if(status == CdnPurgeStatus.SKIPPED_STAGE_OFF)
{
off.incrementCount(1);
return (true);
}
return (false);
}
/*******************************************************************************
** Include only reasons encountered by this action.
*******************************************************************************/
void append(ArrayList<ProcessSummaryLineInterface> lines)
{
absent.addSelfToListIfAnyCount(lines);
none.addSelfToListIfAnyCount(lines);
off.addSelfToListIfAnyCount(lines);
}
}
/*******************************************************************************
** Scheduled: send due purges and poll in-progress ones.
*******************************************************************************/
@@ -189,6 +239,7 @@ public final class CdnProcesses
*******************************************************************************/
public static class PurgeRecordsTransform extends AbstractTransformStep implements ProcessSummaryProviderInterface
{
private final UnsentSummary unsent = new UnsentSummary();
private final ProcessSummaryLine records = line(Status.OK, "record will be purged", "records will be purged",
"record was purged", "records were purged");
private final ProcessSummaryLine noRule = line(Status.INFO, "record has no CDN tag rule (no change)", "records have no CDN tag rule (no change)",
@@ -207,6 +258,7 @@ public final class CdnProcesses
String table = input.getValueString(StreamedETLWithFrontendProcess.FIELD_SOURCE_TABLE);
List<QRecord> rules = rules(table);
TagRuleEngine engine = new TagRuleEngine();
CdnPurgeStatus sending = CdnPurgeService.sendingStatus(null);
for(QRecord record : input.getRecords())
{
List<String> tags = new ArrayList<>();
@@ -226,7 +278,10 @@ public final class CdnProcesses
recordPaths.forEach(path -> paths.add(path.path()));
record.setValue("cdnTags", String.join("\n", tags));
record.setValue("cdnPaths", com.kingsrook.qbits.cdn.purge.PurgeText.paths(recordPaths));
records.incrementCountAndAddPrimaryKey(record.getValue("id"));
if(!unsent.add(sending))
{
records.incrementCountAndAddPrimaryKey(record.getValue("id"));
}
output.getRecords().add(record);
}
}
@@ -249,6 +304,7 @@ public final class CdnProcesses
lines.add(pathLine);
}
noRule.addSelfToListIfAnyCount(lines);
unsent.append(lines);
return (lines);
}
}
@@ -256,10 +312,11 @@ public final class CdnProcesses
/*******************************************************************************
** Purge From CDN, execute: one purge request (sent at once, hubs included).
** Purge From CDN, execute: record requests with hubs included.
*******************************************************************************/
public static class PurgeRecordsLoad extends AbstractLoadStep implements ProcessSummaryProviderInterface
{
private final UnsentSummary unsent = new UnsentSummary();
private final ProcessSummaryLine requested = line(Status.OK, "purge request will be recorded", "purge requests will be recorded",
"purge request was recorded (sent within seconds)", "purge requests were recorded (sent within seconds)");
@@ -277,7 +334,17 @@ public final class CdnProcesses
List<QRecord> rows = CdnPurgeService.enqueue(new CdnPurgeService.Request(CdnPurgeKind.TAG.name(),
com.kingsrook.qbits.cdn.purge.PurgeText.tags(record.getValueString("cdnTags")),
com.kingsrook.qbits.cdn.purge.PurgeText.paths(record.getValueString("cdnPaths")), true, CdnPurgeSource.RECORD_ACTION, table, record.getValue("id")));
requested.incrementCount(rows.size());
if(rows.isEmpty())
{
unsent.absent.incrementCount(1);
}
for(QRecord row : rows)
{
if(!unsent.add(CdnPurgeStatus.valueOf(row.getValueString("status"))))
{
requested.incrementCount(1);
}
}
output.getRecords().add(record);
}
}
@@ -296,6 +363,7 @@ public final class CdnProcesses
{
transform.noRule.addSelfToListIfAnyCount(lines);
}
unsent.append(lines);
return (lines);
}
}
@@ -307,6 +375,7 @@ public final class CdnProcesses
*******************************************************************************/
public static class RetryTransform extends AbstractTransformStep implements ProcessSummaryProviderInterface
{
private final UnsentSummary unsent = new UnsentSummary();
private final ProcessSummaryLine retry = line(Status.OK, "purge request will be sent again", "purge requests will be sent again",
"purge request was queued again", "purge requests were queued again");
private final ProcessSummaryLine skip = line(Status.INFO, "purge request has not failed (no change)", "purge requests have not failed (no change)",
@@ -325,7 +394,10 @@ public final class CdnProcesses
String status = record.getValueString("status");
if(CdnPurgeStatus.FAILED.name().equals(status) || CdnPurgeStatus.DEAD.name().equals(status) || CdnPurgeStatus.DEFERRED_BUDGET.name().equals(status))
{
retry.incrementCountAndAddPrimaryKey(record.getValue("id"));
if(!unsent.add(CdnPurgeService.sendingStatus(record.getValue("cdnProviderId"))))
{
retry.incrementCountAndAddPrimaryKey(record.getValue("id"));
}
output.getRecords().add(record);
}
else
@@ -346,6 +418,7 @@ public final class CdnProcesses
ArrayList<ProcessSummaryLineInterface> lines = new ArrayList<>();
retry.addSelfToListIfAnyCount(lines);
skip.addSelfToListIfAnyCount(lines);
unsent.append(lines);
return (lines);
}
}
@@ -353,10 +426,16 @@ public final class CdnProcesses
/*******************************************************************************
** Retry Purge, execute: back to PENDING (sent on the next dispatcher pass).
** Retry Purge, execute: queue live providers or record why sending is skipped.
*******************************************************************************/
public static class RetryLoad extends AbstractLoadStep
public static class RetryLoad extends AbstractLoadStep implements ProcessSummaryProviderInterface
{
private final UnsentSummary unsent = new UnsentSummary();
private final ProcessSummaryLine requested = line(Status.OK, "purge request will be recorded", "purge requests will be recorded",
"purge request was queued again", "purge requests were queued again");
/*******************************************************************************
** Reset the rows.
*******************************************************************************/
@@ -366,7 +445,12 @@ public final class CdnProcesses
List<QRecord> updates = new ArrayList<>();
for(QRecord record : input.getRecords())
{
updates.add(new QRecord().withValue("id", record.getValue("id")).withValue("status", CdnPurgeStatus.PENDING.name())
CdnPurgeStatus status = CdnPurgeService.sendingStatus(record.getValue("cdnProviderId"));
if(!unsent.add(status))
{
requested.incrementCount(1);
}
updates.add(new QRecord().withValue("id", record.getValue("id")).withValue("status", status.name())
.withValue("source", CdnPurgeSource.RETRY.name()).withValue("attempts", 0).withValue("nextAttemptDate", null)
.withValue("errorMessage", null).withValue("completedDate", null).withValue("modifyDate", CdnRuntime.clock().instant()));
}
@@ -375,6 +459,24 @@ public final class CdnProcesses
output.getRecords().addAll(new UpdateAction().execute(new UpdateInput(CdnTables.PURGE_REQUEST).withRecords(updates)).getRecords());
}
}
/*******************************************************************************
** Result lines from the requests recorded during execution.
*******************************************************************************/
@Override
public ArrayList<ProcessSummaryLineInterface> getProcessSummary(RunBackendStepOutput output, boolean isForResultScreen)
{
ArrayList<ProcessSummaryLineInterface> lines = new ArrayList<>();
requested.addSelfToListIfAnyCount(lines);
if(getTransformStep() instanceof RetryTransform transform)
{
transform.skip.addSelfToListIfAnyCount(lines);
}
unsent.append(lines);
return (lines);
}
}
@@ -384,6 +486,7 @@ public final class CdnProcesses
*******************************************************************************/
public static class PurgeHostnameTransform extends AbstractTransformStep implements ProcessSummaryProviderInterface
{
private final UnsentSummary unsent = new UnsentSummary();
private final ProcessSummaryLine providers = line(Status.WARNING,
"CDN distribution will be purged completely (every hostname on it)", "CDN distributions will be purged completely (every hostname on them)",
"CDN distribution purge was recorded", "CDN distribution purges were recorded");
@@ -402,7 +505,10 @@ public final class CdnProcesses
Serializable providerId = record.getValue("cdnProviderId");
if(providerId != null && seen.add(providerId))
{
providers.incrementCountAndAddPrimaryKey(record.getValue("id"));
if(!unsent.add(CdnPurgeService.sendingStatus(providerId)))
{
providers.incrementCountAndAddPrimaryKey(record.getValue("id"));
}
output.getRecords().add(record);
}
}
@@ -418,6 +524,7 @@ public final class CdnProcesses
{
ArrayList<ProcessSummaryLineInterface> lines = new ArrayList<>();
providers.addSelfToListIfAnyCount(lines);
unsent.append(lines);
return (lines);
}
}
@@ -427,8 +534,14 @@ public final class CdnProcesses
/*******************************************************************************
** Purge Everything, execute.
*******************************************************************************/
public static class PurgeHostnameLoad extends AbstractLoadStep
public static class PurgeHostnameLoad extends AbstractLoadStep implements ProcessSummaryProviderInterface
{
private final UnsentSummary unsent = new UnsentSummary();
private final ProcessSummaryLine requested = line(Status.OK, "purge request will be recorded", "purge requests will be recorded",
"CDN distribution purge was recorded", "CDN distribution purges were recorded");
/*******************************************************************************
** Record an ALL purge for each provider.
*******************************************************************************/
@@ -437,11 +550,36 @@ public final class CdnProcesses
{
for(QRecord record : input.getRecords())
{
CdnPurgeService.enqueueFor(record.getValue("cdnProviderId"), new CdnPurgeService.Request(CdnPurgeKind.ALL.name(), List.of(), List.of(), true,
List<QRecord> rows = CdnPurgeService.enqueueFor(record.getValue("cdnProviderId"), new CdnPurgeService.Request(CdnPurgeKind.ALL.name(), List.of(), List.of(), true,
CdnPurgeSource.MANUAL, CdnTables.HOSTNAME, record.getValue("id")));
if(rows.isEmpty())
{
unsent.absent.incrementCount(1);
}
for(QRecord row : rows)
{
if(!unsent.add(CdnPurgeStatus.valueOf(row.getValueString("status"))))
{
requested.incrementCount(1);
}
}
output.getRecords().add(record);
}
}
/*******************************************************************************
** Result lines from the requests recorded during execution.
*******************************************************************************/
@Override
public ArrayList<ProcessSummaryLineInterface> getProcessSummary(RunBackendStepOutput output, boolean isForResultScreen)
{
ArrayList<ProcessSummaryLineInterface> lines = new ArrayList<>();
requested.addSelfToListIfAnyCount(lines);
unsent.append(lines);
return (lines);
}
}
@@ -120,6 +120,36 @@ public final class CdnPurgeService
/*******************************************************************************
** Sending eligibility for one provider, or all enabled providers when null.
** A pending status means at least one provider can send; otherwise the
** status gives the same reason used when recording a new request.
*******************************************************************************/
public static CdnPurgeStatus sendingStatus(Serializable providerId) throws QException
{
QQueryFilter filter = new QQueryFilter(new QFilterCriteria("enabled", QCriteriaOperator.EQUALS, true));
if(providerId != null)
{
filter.addCriteria(new QFilterCriteria("id", QCriteriaOperator.EQUALS, providerId));
}
CdnPurgeStatus status = CdnPurgeStatus.SKIPPED_NONE_PROVIDER;
for(QRecord provider : new QueryAction().execute(new QueryInput(CdnTables.PROVIDER).withFilter(filter)).getRecords())
{
CdnPurgeStatus candidate = initialStatus(provider);
if(candidate == CdnPurgeStatus.PENDING)
{
return (candidate);
}
if(candidate == CdnPurgeStatus.SKIPPED_STAGE_OFF)
{
status = candidate;
}
}
return (status);
}
/*******************************************************************************
** Status of a new request for a provider.
*******************************************************************************/
@@ -58,10 +58,10 @@ class CdnHomeTest
{
CdnTestSupport.setUp();
String html = render(CdnWidgets.HOME_WIDGET);
assertThat(html).contains("No CDN connected yet.").contains("No edge statistics yet.").contains("No purges recorded yet.")
.contains("No path-billed provider connected yet.").contains("No origin header check has run yet.").contains("Nothing to compare yet.")
assertThat(html).contains("No CDN provider configured: purges are recorded only.").contains("No edge statistics yet.").contains("No purges recorded yet.")
.contains("No path-billed provider connected yet.").contains("Origin header checks are not run by this version; nothing has been checked.").contains("Nothing to compare yet.")
.contains("minmax(min(100%,22rem),1fr)");
assertThat(render(CdnWidgets.HEALTH_CARD_WIDGET)).contains("No CDN connected yet.").contains("href=\"/cdnAdmin\"");
assertThat(render(CdnWidgets.HEALTH_CARD_WIDGET)).contains("No CDN provider configured: purges are recorded only.").contains("href=\"/cdnAdmin\"");
}
@@ -99,7 +99,7 @@ class CdnHomeTest
String html = CdnHomeView.render(data, "/cdnAdmin");
assertThat(html).contains("d1.cloudfront.net").contains(age + " days (rotate every 90) - rotation due").contains("1,500").contains("91.5%")
.contains("<polyline").contains("396 of 396").contains("BEHAVIOR").contains("1 open").contains("/cdnAdmin/cdn_purge_request/")
.contains("www.example.com");
.contains("www.example.com").doesNotContain("recorded only (not sent)", "nothing has been checked");
assertThat(CdnHomeView.renderHealthCard(data, "/cdnAdmin")).contains("Needs attention");
}
@@ -119,4 +119,31 @@ class CdnHomeTest
assertEquals("50.0%", CdnHomeView.percent(new BigDecimal("0.5")));
assertEquals("91.5%", CdnHomeView.percent(new BigDecimal("91.5")));
}
/*******************************************************************************
** NONE and disabled providers do not imply a connection or a sent purge.
*******************************************************************************/
@Test
void recordedOnlyHome() throws Exception
{
for(boolean none : List.of(true, false))
{
CdnTestSupport.setUp();
Integer provider = CdnTestSupport.liveProvider(none ? CdnProviderType.NONE : CdnProviderType.CLOUDFRONT, null);
if(!none)
{
new com.kingsrook.qqq.backend.core.actions.tables.UpdateAction().execute(
new com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput(CdnTables.PROVIDER)
.withRecord(new QRecord().withValue("id", provider).withValue("enabled", false)));
}
CdnTestSupport.insert(CdnTables.PURGE_REQUEST, new QRecord().withValue("cdnProviderId", provider).withValue("status", "SKIPPED_NONE_PROVIDER"));
assertThat(render(CdnWidgets.HOME_WIDGET)).contains("No CDN provider configured: purges are recorded only.")
.contains("The CloudFront distribution is created in the cutover window.").contains("recorded only (not sent)")
.contains("Origin header checks are not run by this version; nothing has been checked.");
assertThat(render(CdnWidgets.HEALTH_CARD_WIDGET)).contains("No CDN provider configured: purges are recorded only.");
}
}
}
@@ -70,6 +70,16 @@ class CdnProcessesTest
RunProcessOutput review = new RunProcessAction().execute(input);
List<ProcessSummaryLineInterface> reviewLines = (List<ProcessSummaryLineInterface>) review.getValues().get(StreamedETLWithFrontendProcess.FIELD_VALIDATION_SUMMARY);
for(ProcessSummaryLineInterface reviewLine : reviewLines)
{
ProcessSummaryLine summary = (ProcessSummaryLine) reviewLine;
summary.pickMessage(false);
if(summary.getMessage().contains("purges are recorded only"))
{
assertEquals(com.kingsrook.qqq.backend.core.model.actions.processes.Status.WARNING, summary.getStatus());
}
}
RunProcessInput go = new RunProcessInput();
go.setProcessName(process);
go.setProcessUUID(review.getProcessUUID());
@@ -137,6 +147,7 @@ class CdnProcessesTest
new QRecord().withValue("cdnProviderId", 1).withValue("status", "SUCCEEDED"));
List<List<String>> lines = run(CdnProcesses.RETRY_PURGE, rows.get(0).getValue("id") + "," + rows.get(1).getValue("id"));
assertThat(lines.get(0)).containsExactly("1 purge request will be sent again", "1 purge request has not failed (no change)");
assertThat(lines.get(1)).contains("1 purge request was queued again");
QRecord retried = purges().get(0);
assertEquals("PENDING", retried.getValueString("status"));
assertEquals(0, retried.getValueInteger("attempts"));
@@ -155,9 +166,99 @@ class CdnProcessesTest
CdnTestSupport.insert(CdnTables.PROVIDER, new QRecord().withValue("name", "other").withValue("providerType", "CLOUDFRONT").withValue("enabled", true));
List<List<String>> lines = run(CdnProcesses.PURGE_HOSTNAME, "1");
assertThat(lines.get(0)).containsExactly("1 CDN distribution will be purged completely (every hostname on it)");
assertThat(lines.get(1)).containsExactly("1 CDN distribution purge was recorded");
List<QRecord> rows = purges();
assertThat(rows).hasSize(1);
assertEquals("ALL", rows.get(0).getValueString("kind"));
assertEquals(1, rows.get(0).getValueInteger("cdnProviderId"));
}
/*******************************************************************************
** Every action reports NONE and OFF honestly, including plural results.
*******************************************************************************/
@Test
void recordedOnlySummaries() throws Exception
{
for(boolean none : List.of(true, false))
{
for(String process : List.of(CdnProcesses.PURGE_PREFIX + "post", CdnProcesses.PURGE_HOSTNAME, CdnProcesses.RETRY_PURGE))
{
setUp();
String table = none ? CdnTables.PROVIDER : CdnTables.HOSTNAME;
new com.kingsrook.qqq.backend.core.actions.tables.UpdateAction().execute(
new com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput(table)
.withRecord(new QRecord().withValue("id", 1).withValue(none ? "providerType" : "stage", none ? "NONE" : "OFF")));
String ids = "1";
if(process.equals(CdnProcesses.PURGE_PREFIX + "post"))
{
CdnTestSupport.insert("post", new QRecord().withValue("slug", "one"), new QRecord().withValue("slug", "two"));
ids = "1,2";
}
else if(process.equals(CdnProcesses.RETRY_PURGE))
{
CdnTestSupport.insert(CdnTables.PURGE_REQUEST,
new QRecord().withValue("cdnProviderId", 1).withValue("status", "FAILED"),
new QRecord().withValue("cdnProviderId", 1).withValue("status", "DEAD"));
ids = "1,2";
}
List<List<String>> lines = run(process, ids);
String warning = none ? "No CDN provider configured" : "CDN hostnames are not live";
assertThat(lines.get(0)).anyMatch(line -> line.contains(warning + ": purges are recorded only, not sent."));
assertThat(lines.get(0)).noneMatch(line -> line.contains("will be purged") || line.contains("will be sent again"));
String reason = none ? "no CDN provider" : "CDN hostnames are not live";
assertThat(lines.get(1)).containsExactly(process.equals(CdnProcesses.PURGE_HOSTNAME)
? "1 purge request was recorded (not sent: " + reason + ")"
: "2 purge requests were recorded (not sent: " + reason + ")");
assertThat(purges()).allMatch(row -> (none ? "SKIPPED_NONE_PROVIDER" : "SKIPPED_STAGE_OFF").equals(row.getValueString("status")));
}
}
}
/*******************************************************************************
** Eligibility considers enabled providers and their purge-enabled hosts.
*******************************************************************************/
@Test
void sendingEligibility() throws Exception
{
CdnTestSupport.setUp();
assertEquals(com.kingsrook.qbits.cdn.model.CdnPurgeStatus.SKIPPED_NONE_PROVIDER,
com.kingsrook.qbits.cdn.purge.CdnPurgeService.sendingStatus(null));
CdnTestSupport.liveProvider(CdnProviderType.NONE, null);
Integer live = CdnTestSupport.liveProvider(CdnProviderType.CLOUDFRONT, null);
assertEquals(com.kingsrook.qbits.cdn.model.CdnPurgeStatus.PENDING,
com.kingsrook.qbits.cdn.purge.CdnPurgeService.sendingStatus(null));
new com.kingsrook.qqq.backend.core.actions.tables.UpdateAction().execute(
new com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput(CdnTables.HOSTNAME)
.withRecord(new QRecord().withValue("id", 2).withValue("purgeEnabled", false)));
assertEquals(com.kingsrook.qbits.cdn.model.CdnPurgeStatus.SKIPPED_STAGE_OFF,
com.kingsrook.qbits.cdn.purge.CdnPurgeService.sendingStatus(live));
new com.kingsrook.qqq.backend.core.actions.tables.UpdateAction().execute(
new com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput(CdnTables.PROVIDER)
.withRecord(new QRecord().withValue("id", live).withValue("enabled", false)));
assertEquals(com.kingsrook.qbits.cdn.model.CdnPurgeStatus.SKIPPED_NONE_PROVIDER,
com.kingsrook.qbits.cdn.purge.CdnPurgeService.sendingStatus(null));
}
/*******************************************************************************
** Without an enabled provider, do not claim a request row was recorded.
*******************************************************************************/
@Test
void absentProvider() throws Exception
{
CdnTestSupport.setUp();
CdnTestSupport.postRule();
CdnTestSupport.insert("post", new QRecord().withValue("slug", "one"));
List<List<String>> lines = run(CdnProcesses.PURGE_PREFIX + "post", "1");
assertThat(lines.get(0)).contains("1 No CDN provider configured: purges are recorded only, not sent.");
assertThat(lines.get(1)).containsExactly("1 record had no enabled CDN provider (no purge request recorded)");
assertThat(purges()).isEmpty();
}
}