diff --git a/examples/device-detection/console/src/main/java/fiftyone/devicedetection/examples/console/GettingStartedCloud.java b/examples/device-detection/console/src/main/java/fiftyone/devicedetection/examples/console/GettingStartedCloud.java
index 846e1fcb4..533763807 100644
--- a/examples/device-detection/console/src/main/java/fiftyone/devicedetection/examples/console/GettingStartedCloud.java
+++ b/examples/device-detection/console/src/main/java/fiftyone/devicedetection/examples/console/GettingStartedCloud.java
@@ -22,6 +22,7 @@
package fiftyone.devicedetection.examples.console;
+import fiftyone.devicedetection.examples.shared.FindOutMore;
import fiftyone.devicedetection.DeviceDetectionPipelineBuilder;
import fiftyone.devicedetection.examples.shared.KeyHelper;
import fiftyone.devicedetection.examples.shared.EvidenceHelper;
@@ -53,6 +54,12 @@
*/
@SuppressWarnings("DuplicatedCode")
public class GettingStartedCloud {
+ /**
+ * This example's tag in the links of its "Find out more" section.
+ */
+ private static final String EXAMPLE_TAG =
+ "console-src-main-java-fiftyone-devicedetection-examples-console-gettingstartedcloud.java";
+
private static final Logger logger = LoggerFactory.getLogger(GettingStartedCloud.class);
/**
@@ -66,6 +73,8 @@ public static void main(String[] args) throws Exception {
// prepare 'evidence' for use in pipeline (see below)
List
*/
public class ShareUsageExample {
+ /**
+ * This example's tag in the links of its "Find out more" section.
+ */
+ private static final String EXAMPLE_TAG =
+ "console-src-main-java-fiftyone-devicedetection-examples-console-shareusageexample.java";
+
static final Logger logger = LoggerFactory.getLogger(ShareUsageExample.class);
// Evidence file with 20,000 examples, from the data repository in
@@ -104,6 +111,8 @@ public static void main(String[] args) throws Exception {
File evidenceFile = DataFileHelper.findDataFile(HEADER_EVIDENCE_YML);
run(Files.newInputStream(evidenceFile.toPath()));
+ FindOutMore.write(System.out, EXAMPLE_TAG,
+ FindOutMore.CLOUD_REPOSITORIES);
}
/**
diff --git a/examples/device-detection/console/src/main/java/fiftyone/devicedetection/examples/console/TacCloud.java b/examples/device-detection/console/src/main/java/fiftyone/devicedetection/examples/console/TacCloud.java
index ee5ee1e0b..c5a87bc5a 100644
--- a/examples/device-detection/console/src/main/java/fiftyone/devicedetection/examples/console/TacCloud.java
+++ b/examples/device-detection/console/src/main/java/fiftyone/devicedetection/examples/console/TacCloud.java
@@ -22,6 +22,7 @@
package fiftyone.devicedetection.examples.console;
+import fiftyone.devicedetection.examples.shared.FindOutMore;
import fiftyone.devicedetection.cloud.data.MultiDeviceDataCloud;
import fiftyone.devicedetection.shared.Constants;
import fiftyone.devicedetection.shared.DeviceData;
@@ -56,6 +57,12 @@
* file tacCloud.xml.
*/
public class TacCloud {
+ /**
+ * This example's tag in the links of its "Find out more" section.
+ */
+ private static final String EXAMPLE_TAG =
+ "console-src-main-java-fiftyone-devicedetection-examples-console-taccloud.java";
+
// Example values to use when looking up device details from TACs.
static final String TAC_1 = "35925406";
static final String TAC_2 = "86386802";
@@ -100,6 +107,10 @@ public static void run(String resourceKey, OutputStream os) throws Exception {
analyseTac(TAC_1, pipeline, writer);
analyseTac(TAC_2, pipeline, writer);
}
+ // The writer closes the stream it was given, so the section is
+ // written before it.
+ FindOutMore.write(writer, EXAMPLE_TAG,
+ FindOutMore.CLOUD_REPOSITORIES);
}
}
diff --git a/examples/device-detection/console/src/main/java/fiftyone/devicedetection/examples/console/UpdateDataFile.java b/examples/device-detection/console/src/main/java/fiftyone/devicedetection/examples/console/UpdateDataFile.java
index ebffdef86..eae7ca9fd 100644
--- a/examples/device-detection/console/src/main/java/fiftyone/devicedetection/examples/console/UpdateDataFile.java
+++ b/examples/device-detection/console/src/main/java/fiftyone/devicedetection/examples/console/UpdateDataFile.java
@@ -22,6 +22,7 @@
package fiftyone.devicedetection.examples.console;
+import fiftyone.devicedetection.examples.shared.FindOutMore;
import fiftyone.devicedetection.DeviceDetectionPipelineBuilder;
import fiftyone.devicedetection.examples.shared.DataFileHelper;
import fiftyone.devicedetection.shared.testhelpers.KeyUtils;
@@ -189,6 +190,12 @@
*/
public class UpdateDataFile {
+ /**
+ * This example's tag in the links of its "Find out more" section.
+ */
+ private static final String EXAMPLE_TAG =
+ "console-src-main-java-fiftyone-devicedetection-examples-console-updatedatafile.java";
+
public static final String UPDATE_EXAMPLE_LICENSE_KEY_NAME = "LicenseKey";
public static final String DEFAULT_DATA_FILENAME =
System.getProperty("user.dir") + File.separator + ENTERPRISE_HASH_DATA_FILE_NAME;
@@ -209,6 +216,8 @@ public static void main(String[] args) throws Exception {
String licenseKey = args.length > 0 ? args[0] : null;
String dataFilename = args.length > 1 ? args[1] : null;
run(dataFilename, licenseKey, true);
+ FindOutMore.write(System.out, EXAMPLE_TAG,
+ FindOutMore.ON_PREMISE_REPOSITORIES);
}
/**
diff --git a/examples/device-detection/console/src/main/java/fiftyone/devicedetection/examples/console/comparison/Comparer.java b/examples/device-detection/console/src/main/java/fiftyone/devicedetection/examples/console/comparison/Comparer.java
index ba231800a..19b60ceba 100644
--- a/examples/device-detection/console/src/main/java/fiftyone/devicedetection/examples/console/comparison/Comparer.java
+++ b/examples/device-detection/console/src/main/java/fiftyone/devicedetection/examples/console/comparison/Comparer.java
@@ -22,6 +22,7 @@
package fiftyone.devicedetection.examples.console.comparison;
+import fiftyone.devicedetection.examples.shared.FindOutMore;
import fiftyone.common.testhelpers.LogbackHelper;
import fiftyone.devicedetection.examples.console.comparison.Detection.BenchmarkResult;
import fiftyone.devicedetection.examples.console.comparison.Detection.Request;
@@ -50,6 +51,12 @@
* code has not been tested and it is left to those interested to uncomment and get working.
*/
public class Comparer {
+ /**
+ * This example's tag in the links of its "Find out more" section.
+ */
+ private static final String EXAMPLE_TAG =
+ "console-src-main-java-fiftyone-devicedetection-examples-console-comparison-comparer.java";
+
public static final int DEFAULT_NUMBER_OF_THREADS = 4;
public static final int DEFAULT_NUMBER_OF_RESULTS = 2500;
@SuppressWarnings("resource")
@@ -69,6 +76,8 @@ public static void main(String[] args) throws Exception {
DEFAULT_NUMBER_OF_RESULTS,
new Reporting.Minimal(),
new PrintWriter(System.out, true));
+ FindOutMore.write(System.out, EXAMPLE_TAG,
+ FindOutMore.ON_PREMISE_REPOSITORIES);
}
/**
diff --git a/examples/device-detection/console/src/test/java/fiftyone/devicedetection/examples/console/ExamplesWithoutSettingsTest.java b/examples/device-detection/console/src/test/java/fiftyone/devicedetection/examples/console/ExamplesWithoutSettingsTest.java
index aea911082..00f21fca2 100644
--- a/examples/device-detection/console/src/test/java/fiftyone/devicedetection/examples/console/ExamplesWithoutSettingsTest.java
+++ b/examples/device-detection/console/src/test/java/fiftyone/devicedetection/examples/console/ExamplesWithoutSettingsTest.java
@@ -23,6 +23,7 @@
package fiftyone.devicedetection.examples.console;
import fiftyone.devicedetection.examples.shared.DataFileHelper;
+import fiftyone.devicedetection.examples.shared.FindOutMore;
import org.junit.Test;
import java.io.ByteArrayOutputStream;
@@ -75,6 +76,7 @@ private static String runWithoutSettings(Main main) throws Exception {
}
String output = buffer.toString("UTF-8");
out.println(output);
+ assertFindOutMore(output);
return output;
}
@@ -83,6 +85,21 @@ private static void assertContains(String output, String expected) {
output.contains(expected));
}
+ /**
+ * Checks that the output has the "Find out more" section after the last
+ * link to the contact page, and that the section closes with its rule.
+ * Log lines may still follow the section.
+ */
+ private static void assertFindOutMore(String output) {
+ int heading = output.lastIndexOf(FindOutMore.HEADING);
+ assertTrue("The output should have the '" + FindOutMore.HEADING +
+ "' section after the link to the contact page",
+ heading > output.lastIndexOf(CONTACT_US));
+ assertTrue("The '" + FindOutMore.HEADING +
+ "' section should end with its rule",
+ output.indexOf(FindOutMore.RULE, heading) > heading);
+ }
+
private static void assertOffer(String output) {
assertContains(output, "This example used the free Lite data file.");
assertContains(output, CONTACT_US);
diff --git a/examples/device-detection/console/src/test/java/fiftyone/devicedetection/examples/console/FindOutMoreCoverageTest.java b/examples/device-detection/console/src/test/java/fiftyone/devicedetection/examples/console/FindOutMoreCoverageTest.java
new file mode 100644
index 000000000..f090ac8fe
--- /dev/null
+++ b/examples/device-detection/console/src/test/java/fiftyone/devicedetection/examples/console/FindOutMoreCoverageTest.java
@@ -0,0 +1,74 @@
+/* *********************************************************************
+ * This Original Work is copyright of 51 Degrees Mobile Experts Limited.
+ * Copyright 2026 51 Degrees Mobile Experts Limited, Davidson House,
+ * Forbury Square, Reading, Berkshire, United Kingdom RG1 3EU.
+ *
+ * This Original Work is licensed under the European Union Public Licence
+ * (EUPL) v.1.2 and is subject to its terms as set out below.
+ *
+ * If a copy of the EUPL was not distributed with this file, You can obtain
+ * one at https://opensource.org/licenses/EUPL-1.2.
+ *
+ * The 'Compatible Licences' set out in the Appendix to the EUPL (as may be
+ * amended by the European Commission) shall be deemed incompatible for
+ * the purposes of the Work and the provisions of the compatibility
+ * clause in Article 5 of the EUPL shall not apply.
+ *
+ * If using the Work as, or as part of, a network application, by
+ * including the attribution notice(s) required under Article 5 of the EUPL
+ * in the end user terms of the application under an appropriate heading,
+ * such notice(s) shall fulfill the requirements of that article.
+ * ********************************************************************* */
+
+package fiftyone.devicedetection.examples.console;
+
+import org.junit.Test;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Checks that every console example with a main method writes the "Find out
+ * more" section, including those that need a key or a paid data file and so
+ * do not run in ExamplesWithoutSettingsTest.
+ */
+public class FindOutMoreCoverageTest {
+
+ @Test
+ public void everyExample_WritesFindOutMore() throws IOException {
+ Path sources = Paths.get(System.getProperty("basedir", "."),
+ "src", "main", "java");
+ assertTrue(sources + " should hold the examples",
+ Files.isDirectory(sources));
+ List files;
+ try (Stream walk = Files.walk(sources)) {
+ files = walk.filter(f -> f.toString().endsWith(".java"))
+ .collect(Collectors.toList());
+ }
+ List examples = new ArrayList<>();
+ List missing = new ArrayList<>();
+ for (Path file : files) {
+ String text = new String(Files.readAllBytes(file),
+ StandardCharsets.UTF_8);
+ if (text.contains("static void main(")) {
+ examples.add(file.getFileName().toString());
+ if (text.contains("FindOutMore.write") == false) {
+ missing.add(file.getFileName().toString());
+ }
+ }
+ }
+ assertTrue("No examples were found in " + sources,
+ examples.isEmpty() == false);
+ assertTrue("These examples do not write the 'Find out more' " +
+ "section: " + missing, missing.isEmpty());
+ }
+}
diff --git a/examples/device-detection/shared/src/main/java/fiftyone/devicedetection/examples/shared/FindOutMore.java b/examples/device-detection/shared/src/main/java/fiftyone/devicedetection/examples/shared/FindOutMore.java
new file mode 100644
index 000000000..2b8f918f3
--- /dev/null
+++ b/examples/device-detection/shared/src/main/java/fiftyone/devicedetection/examples/shared/FindOutMore.java
@@ -0,0 +1,185 @@
+/* *********************************************************************
+ * This Original Work is copyright of 51 Degrees Mobile Experts Limited.
+ * Copyright 2026 51 Degrees Mobile Experts Limited, Davidson House,
+ * Forbury Square, Reading, Berkshire, United Kingdom RG1 3EU.
+ *
+ * This Original Work is licensed under the European Union Public Licence
+ * (EUPL) v.1.2 and is subject to its terms as set out below.
+ *
+ * If a copy of the EUPL was not distributed with this file, You can obtain
+ * one at https://opensource.org/licenses/EUPL-1.2.
+ *
+ * The 'Compatible Licences' set out in the Appendix to the EUPL (as may be
+ * amended by the European Commission) shall be deemed incompatible for
+ * the purposes of the Work and the provisions of the compatibility
+ * clause in Article 5 of the EUPL shall not apply.
+ *
+ * If using the Work as, or as part of, a network application, by
+ * including the attribution notice(s) required under Article 5 of the EUPL
+ * in the end user terms of the application under an appropriate heading,
+ * such notice(s) shall fulfill the requirements of that article.
+ * ********************************************************************* */
+
+package fiftyone.devicedetection.examples.shared;
+
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.util.regex.Pattern;
+
+/**
+ * Writes the "Find out more" section that ends every example's output, set
+ * apart from the example's own output by a rule above and below. It gives
+ * pages on 51degrees.com, each tagged with the example that wrote it, then
+ * the repositories behind the example.
+ */
+public final class FindOutMore {
+
+ /**
+ * The heading the section starts with.
+ */
+ public static final String HEADING = "Find out more";
+
+ /**
+ * The rule above and below the section.
+ */
+ public static final String RULE =
+ "----------------------------------------------------------------";
+
+ /**
+ * The repositories an on-premise example lists, being this SDK, the C
+ * engines it builds on and the free data files.
+ */
+ public static final String[] ON_PREMISE_REPOSITORIES = {
+ "https://github.com/51Degrees/51degrees-java",
+ "https://github.com/51Degrees/51degrees-cxx",
+ "https://github.com/51Degrees/data"
+ };
+
+ /**
+ * The repositories a cloud example lists.
+ */
+ public static final String[] CLOUD_REPOSITORIES = {
+ "https://github.com/51Degrees/51degrees-java"
+ };
+
+ /**
+ * Each page's label and its path on 51degrees.com.
+ */
+ private static final String[][] PAGES = {
+ {"Device Detection", "device-detection"},
+ {"Pricing", "pricing"},
+ {"Documentation", "documentation/index.html"}
+ };
+
+ /**
+ * What a campaign tag value may hold.
+ */
+ private static final Pattern SLUG =
+ Pattern.compile("[a-z0-9][a-z0-9._-]*");
+
+ private FindOutMore() {
+ }
+
+ /**
+ * Writes the section.
+ * @param output where to write, flushed afterwards
+ * @param example the example's tag, a lower case slug such as
+ * "console-gettingstartedonprem.java"
+ * @param repositories the repositories to list, such as
+ * {@link #ON_PREMISE_REPOSITORIES}
+ */
+ public static void write(
+ PrintWriter output,
+ String example,
+ String... repositories) {
+ output.println();
+ writeLines(output, "", example, repositories);
+ }
+
+ /**
+ * Writes the section with each line a comment, for output that has to
+ * stay valid in its own format, such as a YAML stream.
+ * @param output where to write, flushed afterwards
+ * @param comment what starts a comment line, such as "# "
+ * @param example the example's tag
+ * @param repositories the repositories to list
+ */
+ public static void writeCommented(
+ PrintWriter output,
+ String comment,
+ String example,
+ String... repositories) {
+ writeLines(output, comment, example, repositories);
+ }
+
+ private static void writeLines(
+ PrintWriter output,
+ String start,
+ String example,
+ String... repositories) {
+ output.println(start + RULE);
+ output.println(start + HEADING);
+ for (String[] page : PAGES) {
+ output.println(start + "\t" + page[0] + ": " +
+ link(page[1], example));
+ }
+ for (String repository : repositories) {
+ output.println(start + "\t" + repository);
+ }
+ output.println(start + RULE);
+ output.flush();
+ }
+
+ /**
+ * Writes the section to a stream. See
+ * {@link #write(PrintWriter, String, String...)}.
+ */
+ public static void write(
+ OutputStream output,
+ String example,
+ String... repositories) {
+ write(new PrintWriter(output), example, repositories);
+ }
+
+ /**
+ * The section as HTML, for the end of a web example's page.
+ * @param example the example's tag
+ * @param repositories the repositories to list
+ * @return the markup
+ */
+ public static String html(String example, String... repositories) {
+ StringBuilder html = new StringBuilder();
+ html.append("\n");
+ html.append("
").append(HEADING).append("
\n");
+ html.append("
\n
\n");
+ return html.toString();
+ }
+
+ /**
+ * The address of a page on 51degrees.com, tagged with the example.
+ * @param page the path of the page
+ * @param example the example's tag
+ * @return the address
+ * @throws IllegalArgumentException if the tag is not a lower case slug
+ */
+ public static String link(String page, String example) {
+ if (example == null || SLUG.matcher(example).matches() == false) {
+ throw new IllegalArgumentException(
+ "'" + example + "' is not a lower case slug.");
+ }
+ return "https://51degrees.com/" +
+ page + "?utm_source=code&utm_medium=example" +
+ "&utm_campaign=51degrees-java&utm_content=" + example +
+ "&utm_term=find-out-more";
+ }
+}
diff --git a/examples/device-detection/shared/src/test/java/fiftyone/devicedetection/examples/shared/FindOutMoreTest.java b/examples/device-detection/shared/src/test/java/fiftyone/devicedetection/examples/shared/FindOutMoreTest.java
new file mode 100644
index 000000000..8ff97350a
--- /dev/null
+++ b/examples/device-detection/shared/src/test/java/fiftyone/devicedetection/examples/shared/FindOutMoreTest.java
@@ -0,0 +1,149 @@
+/* *********************************************************************
+ * This Original Work is copyright of 51 Degrees Mobile Experts Limited.
+ * Copyright 2026 51 Degrees Mobile Experts Limited, Davidson House,
+ * Forbury Square, Reading, Berkshire, United Kingdom RG1 3EU.
+ *
+ * This Original Work is licensed under the European Union Public Licence
+ * (EUPL) v.1.2 and is subject to its terms as set out below.
+ *
+ * If a copy of the EUPL was not distributed with this file, You can obtain
+ * one at https://opensource.org/licenses/EUPL-1.2.
+ *
+ * The 'Compatible Licences' set out in the Appendix to the EUPL (as may be
+ * amended by the European Commission) shall be deemed incompatible for
+ * the purposes of the Work and the provisions of the compatibility
+ * clause in Article 5 of the EUPL shall not apply.
+ *
+ * If using the Work as, or as part of, a network application, by
+ * including the attribution notice(s) required under Article 5 of the EUPL
+ * in the end user terms of the application under an appropriate heading,
+ * such notice(s) shall fulfill the requirements of that article.
+ * ********************************************************************* */
+
+package fiftyone.devicedetection.examples.shared;
+
+import org.junit.Test;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.regex.Pattern;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+public class FindOutMoreTest {
+
+ /**
+ * The campaign tags common-ci's scripts/utm-lint.ps1 requires of a link
+ * printed by an example.
+ */
+ private static final Pattern TAGGED = Pattern.compile(
+ "https://51degrees\\.com/[^?\\s]+\\?utm_source=code" +
+ "&utm_medium=example&utm_campaign=51degrees-java" +
+ "&utm_content=[a-z0-9][a-z0-9._-]*" +
+ "&utm_term=[a-z0-9][a-z0-9._-]*");
+
+ private static String[] write(String example, String... repositories) {
+ StringWriter text = new StringWriter();
+ FindOutMore.write(new PrintWriter(text), example, repositories);
+ return text.toString().split("\r?\n");
+ }
+
+ /**
+ * The section is a blank line, a rule, the heading, the three tagged
+ * pages, the repositories and a closing rule.
+ */
+ @Test
+ public void write_LinksThenRepositories() {
+ String[] lines = write("console-example.java",
+ FindOutMore.ON_PREMISE_REPOSITORIES);
+ assertEquals(10, lines.length);
+ assertEquals("", lines[0]);
+ assertEquals(FindOutMore.RULE, lines[1]);
+ assertEquals(FindOutMore.HEADING, lines[2]);
+ assertTrue(lines[3].startsWith("\tDevice Detection: https://51degrees.com/device-detection?"));
+ assertTrue(lines[4].startsWith("\tPricing: https://51degrees.com/pricing?"));
+ assertTrue(lines[5].startsWith("\tDocumentation: https://51degrees.com/documentation/index.html?"));
+ for (int i = 3; i <= 5; i++) {
+ String link = lines[i].substring(lines[i].indexOf("https://"));
+ assertTrue(link, TAGGED.matcher(link).matches());
+ assertTrue(link, link.contains("&utm_content=console-example.java&"));
+ }
+ assertEquals("\thttps://github.com/51Degrees/51degrees-java", lines[6]);
+ assertEquals("\thttps://github.com/51Degrees/51degrees-cxx", lines[7]);
+ assertEquals("\thttps://github.com/51Degrees/data", lines[8]);
+ assertEquals(FindOutMore.RULE, lines[9]);
+ }
+
+ /**
+ * A cloud example lists this repository alone.
+ */
+ @Test
+ public void write_Cloud_ThisRepository() {
+ String[] lines = write("console-example.java",
+ FindOutMore.CLOUD_REPOSITORIES);
+ assertEquals(8, lines.length);
+ assertEquals("\thttps://github.com/51Degrees/51degrees-java", lines[6]);
+ }
+
+ /**
+ * Each line of the commented form starts with the comment marker, and
+ * there is no blank line, so a YAML stream stays valid.
+ */
+ @Test
+ public void writeCommented_EveryLineAComment() {
+ StringWriter text = new StringWriter();
+ FindOutMore.writeCommented(new PrintWriter(text), "# ",
+ "console-example.java", FindOutMore.CLOUD_REPOSITORIES);
+ String[] lines = text.toString().split("\\r?\\n");
+ assertEquals(7, lines.length);
+ for (String line : lines) {
+ assertTrue(line, line.startsWith("# "));
+ }
+ assertEquals("# " + FindOutMore.HEADING, lines[1]);
+ }
+
+ /**
+ * The HTML form has the heading, each tagged page as a link with its
+ * ampersands escaped, and each repository as a link.
+ */
+ @Test
+ public void html_LinksThenRepositories() {
+ String html = FindOutMore.html("web-example.java",
+ FindOutMore.ON_PREMISE_REPOSITORIES);
+ assertTrue(html, html.contains("" + FindOutMore.HEADING + "
"));
+ java.util.regex.Matcher href =
+ Pattern.compile("href=\"([^\"]+)\"").matcher(html);
+ int pages = 0;
+ int repositories = 0;
+ while (href.find()) {
+ String link = href.group(1);
+ if (link.startsWith("https://51degrees.com/")) {
+ assertTrue(link, link.contains("&"));
+ assertTrue(link,
+ TAGGED.matcher(link.replace("&", "&")).matches());
+ pages++;
+ } else {
+ assertTrue(link, link.startsWith("https://github.com/51Degrees/"));
+ repositories++;
+ }
+ }
+ assertEquals(3, pages);
+ assertEquals(3, repositories);
+ }
+
+ /**
+ * A tag that is not a lower case slug would fail the campaign tag
+ * convention, so it is refused.
+ */
+ @Test
+ public void link_NotASlug_Refused() {
+ assertThrows(IllegalArgumentException.class,
+ () -> FindOutMore.link("pricing", "Console-Example.java"));
+ assertThrows(IllegalArgumentException.class,
+ () -> FindOutMore.link("pricing", "console example"));
+ assertThrows(IllegalArgumentException.class,
+ () -> FindOutMore.link("pricing", null));
+ }
+}
diff --git a/examples/device-detection/web/getting-started.cloud/src/main/webapp/WEB-INF/51Degrees-Cloud.xml b/examples/device-detection/web/getting-started.cloud/src/main/webapp/WEB-INF/51Degrees-Cloud.xml
index ced491606..a7abb5715 100644
--- a/examples/device-detection/web/getting-started.cloud/src/main/webapp/WEB-INF/51Degrees-Cloud.xml
+++ b/examples/device-detection/web/getting-started.cloud/src/main/webapp/WEB-INF/51Degrees-Cloud.xml
@@ -25,7 +25,7 @@
- ${TestCloudEndpoint:-http://cloud.51degrees.com/api/v4}
+ ${TestCloudEndpoint:-https://cloud.51degrees.com/api/v4}
- ${TestDataFile:-cxx/data/ip-intelligence/51Degrees-EnterpriseIpiV41.ipi}
+ ${TestDataFile:-cxx/data/ip-intelligence/51Degrees-LiteV41.ipi}
LowMemory
IPIntelligenceOnPremiseEngine
diff --git a/examples/ip-intelligence/web/getting-started.onprem/src/main/webapp/WEB-INF/html/index.html b/examples/ip-intelligence/web/getting-started.onprem/src/main/webapp/WEB-INF/html/index.html
index 2abdface2..afb91cab8 100644
--- a/examples/ip-intelligence/web/getting-started.onprem/src/main/webapp/WEB-INF/html/index.html
+++ b/examples/ip-intelligence/web/getting-started.onprem/src/main/webapp/WEB-INF/html/index.html
@@ -92,6 +92,7 @@ Location map
${CONTACT_MESSAGE}
+ ${FIND_OUT_MORE}
diff --git a/examples/ip-intelligence/web/getting-started.onprem/src/test/java/fiftyone/ipintelligence/examples/web/GettingStartedWebOnPremTest.java b/examples/ip-intelligence/web/getting-started.onprem/src/test/java/fiftyone/ipintelligence/examples/web/GettingStartedWebOnPremTest.java
index e1831b39b..f995a4ea6 100644
--- a/examples/ip-intelligence/web/getting-started.onprem/src/test/java/fiftyone/ipintelligence/examples/web/GettingStartedWebOnPremTest.java
+++ b/examples/ip-intelligence/web/getting-started.onprem/src/test/java/fiftyone/ipintelligence/examples/web/GettingStartedWebOnPremTest.java
@@ -22,6 +22,7 @@
package fiftyone.ipintelligence.examples.web;
+import fiftyone.ipintelligence.examples.shared.FindOutMore;
import fiftyone.ipintelligence.examples.shared.DataFileHelper;
import org.eclipse.jetty.server.Server;
import org.junit.AfterClass;
@@ -96,6 +97,9 @@ public void testWebOnPrem() throws Exception {
// Assert we got a successful response
assertEquals("Expected HTTP 200 OK response", 200, code);
assertTrue("Response should not be empty", responseBody.length() > 0);
+ assertTrue("The page should end with the '" + FindOutMore.HEADING +
+ "' section", responseBody.contains(
+ "" + FindOutMore.HEADING + "
"));
}
@AfterClass
diff --git a/examples/ip-intelligence/web/getting-started.onprem/src/test/java/fiftyone/ipintelligence/examples/web/OptionsFileTest.java b/examples/ip-intelligence/web/getting-started.onprem/src/test/java/fiftyone/ipintelligence/examples/web/OptionsFileTest.java
new file mode 100644
index 000000000..047f34bdc
--- /dev/null
+++ b/examples/ip-intelligence/web/getting-started.onprem/src/test/java/fiftyone/ipintelligence/examples/web/OptionsFileTest.java
@@ -0,0 +1,61 @@
+/* *********************************************************************
+ * This Original Work is copyright of 51 Degrees Mobile Experts Limited.
+ * Copyright 2026 51 Degrees Mobile Experts Limited, Davidson House,
+ * Forbury Square, Reading, Berkshire, United Kingdom RG1 3EU.
+ *
+ * This Original Work is licensed under the European Union Public Licence
+ * (EUPL) v.1.2 and is subject to its terms as set out below.
+ *
+ * If a copy of the EUPL was not distributed with this file, You can obtain
+ * one at https://opensource.org/licenses/EUPL-1.2.
+ *
+ * The 'Compatible Licences' set out in the Appendix to the EUPL (as may be
+ * amended by the European Commission) shall be deemed incompatible for
+ * the purposes of the Work and the provisions of the compatibility
+ * clause in Article 5 of the EUPL shall not apply.
+ *
+ * If using the Work as, or as part of, a network application, by
+ * including the attribution notice(s) required under Article 5 of the EUPL
+ * in the end user terms of the application under an appropriate heading,
+ * such notice(s) shall fulfill the requirements of that article.
+ * ********************************************************************* */
+
+package fiftyone.ipintelligence.examples.web;
+
+import fiftyone.ipintelligence.examples.shared.DataFileHelper;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Checks the options file this web example runs with.
+ */
+public class OptionsFileTest {
+
+ /**
+ * With no data file set, the example falls back to the free Lite file,
+ * which a fresh clone downloads, and not to the Enterprise file, which a
+ * fresh clone never has.
+ */
+ @Test
+ public void defaultDataFile_FreeLiteFile() throws IOException {
+ String xml = new String(Files.readAllBytes(Paths.get(
+ System.getProperty("basedir", "."), "src", "main", "webapp",
+ "WEB-INF", "51Degrees-OnPrem.xml")), StandardCharsets.UTF_8);
+ Matcher dataFile = Pattern.compile(
+ "\\$\\{TestDataFile:-([^}]*)\\}")
+ .matcher(xml);
+ assertTrue("51Degrees-OnPrem.xml should give a default IP Intelligence " +
+ "data file", dataFile.find());
+ assertEquals("cxx/data/" + DataFileHelper.LITE_DATA_FILE_REL_PATH,
+ dataFile.group(1));
+ }
+}