From 698ed7bb32e18954c371a2679693933fe3970f21 Mon Sep 17 00:00:00 2001 From: James Rosewell Date: Sat, 7 Nov 2026 16:51:09 +0000 Subject: [PATCH] Fail the example cloud tests when there is no resource key The cloud tests under examples/ skipped when no resource key was set, so a run with no key reported success having called nothing. Each now asks its KeyHelper for the key, which reads _51DEGREES_RESOURCE_KEY, then _51DEGREES_RESOURCE_KEY_BESPOKE, then the names the example reads today, each as an environment variable and then as a system property, and fails the test naming every name tried, the product the key has to carry, how to see what a key carries and where to create one. KeyHelper.getOrSetResourceKey passed a null key to System.setProperty, which throws a NullPointerException, whenever no key was found and it was asked not to throw. It now keeps only a valid key. The Device Detection web cloud test sends a known User-Agent and checks that the page shows the browser and platform the cloud engine detected, rather than accepting any 200 response. --- .../console/GettingStartedCloudTest.java | 10 +- .../examples/console/MetadataCloudTest.java | 8 +- .../examples/console/MinimalExampleTest.java | 8 +- .../console/NativeModelCloudTest.java | 11 +- .../examples/console/TacCloudTest.java | 9 +- .../examples/shared/KeyHelper.java | 124 +++++++++++++++- .../examples/shared/KeyHelperTest.java | 136 ++++++++++++++++++ .../web/GettingStartedWebCloudTest.java | 83 +++++++++-- .../console/GetAllPropertiesCloudTest.java | 7 +- .../console/GettingStartedCloudMixedTest.java | 9 +- .../console/GettingStartedCloudTest.java | 7 +- .../examples/console/MetadataCloudTest.java | 7 +- .../examples/shared/KeyHelper.java | 92 +++++++++++- .../examples/shared/KeyHelperTest.java | 129 +++++++++++++++++ .../web/GettingStartedWebCloudMixedTest.java | 9 +- .../web/GettingStartedWebCloudTest.java | 8 +- 16 files changed, 579 insertions(+), 78 deletions(-) create mode 100644 examples/device-detection/shared/src/test/java/fiftyone/devicedetection/examples/shared/KeyHelperTest.java create mode 100644 examples/ip-intelligence/shared/src/test/java/fiftyone/ipintelligence/examples/shared/KeyHelperTest.java diff --git a/examples/device-detection/console/src/test/java/fiftyone/devicedetection/examples/console/GettingStartedCloudTest.java b/examples/device-detection/console/src/test/java/fiftyone/devicedetection/examples/console/GettingStartedCloudTest.java index ee38b77f4..c8a85dd39 100644 --- a/examples/device-detection/console/src/test/java/fiftyone/devicedetection/examples/console/GettingStartedCloudTest.java +++ b/examples/device-detection/console/src/test/java/fiftyone/devicedetection/examples/console/GettingStartedCloudTest.java @@ -24,18 +24,14 @@ import fiftyone.devicedetection.examples.shared.EvidenceHelper; import fiftyone.devicedetection.examples.shared.KeyHelper; - -import fiftyone.devicedetection.shared.testhelpers.KeyUtils; import org.junit.Test; -import static org.junit.Assume.assumeFalse; - public class GettingStartedCloudTest { @Test public void gettingStartedCloudTest() throws Exception { - String resourceKey = KeyHelper.getOrSetTestResourceKey(false); - assumeFalse("Skipping test, no resource key found", - KeyUtils.isInvalidKey(resourceKey)); + String resourceKey = KeyHelper.requireTestResourceKey( + "whether a device is mobile, and its platform and browser", + "device"); GettingStartedCloud.run(resourceKey, EvidenceHelper.setUpEvidence(), System.out); diff --git a/examples/device-detection/console/src/test/java/fiftyone/devicedetection/examples/console/MetadataCloudTest.java b/examples/device-detection/console/src/test/java/fiftyone/devicedetection/examples/console/MetadataCloudTest.java index cebb3ffed..c818aae69 100644 --- a/examples/device-detection/console/src/test/java/fiftyone/devicedetection/examples/console/MetadataCloudTest.java +++ b/examples/device-detection/console/src/test/java/fiftyone/devicedetection/examples/console/MetadataCloudTest.java @@ -23,17 +23,13 @@ package fiftyone.devicedetection.examples.console; import fiftyone.devicedetection.examples.shared.KeyHelper; -import fiftyone.devicedetection.shared.testhelpers.KeyUtils; import org.junit.Test; -import static org.junit.Assume.assumeFalse; - public class MetadataCloudTest { @Test public void gettingMetaDataCloudTest() throws Exception { - String resourceKey = KeyHelper.getOrSetTestResourceKey(false); - assumeFalse("Skipping test, no resource key found", - KeyUtils.isInvalidKey(resourceKey)); + String resourceKey = KeyHelper.requireTestResourceKey( + "the device detection properties it offers", "device"); MetadataCloud.run(resourceKey, System.out); } diff --git a/examples/device-detection/console/src/test/java/fiftyone/devicedetection/examples/console/MinimalExampleTest.java b/examples/device-detection/console/src/test/java/fiftyone/devicedetection/examples/console/MinimalExampleTest.java index f1622ec37..12bd91179 100644 --- a/examples/device-detection/console/src/test/java/fiftyone/devicedetection/examples/console/MinimalExampleTest.java +++ b/examples/device-detection/console/src/test/java/fiftyone/devicedetection/examples/console/MinimalExampleTest.java @@ -23,17 +23,13 @@ package fiftyone.devicedetection.examples.console; import fiftyone.devicedetection.examples.shared.KeyHelper; -import fiftyone.devicedetection.shared.testhelpers.KeyUtils; import org.junit.Test; -import static org.junit.Assume.assumeFalse; - public class MinimalExampleTest { @Test public void minimalExampleTest() throws Exception { - String resourceKey = KeyHelper.getOrSetTestResourceKey(false); - assumeFalse("Skipping test, no resource key found", - KeyUtils.isInvalidKey(resourceKey)); + String resourceKey = KeyHelper.requireTestResourceKey( + "whether a device is mobile", "device"); MinimalExample.run(resourceKey); } } \ No newline at end of file diff --git a/examples/device-detection/console/src/test/java/fiftyone/devicedetection/examples/console/NativeModelCloudTest.java b/examples/device-detection/console/src/test/java/fiftyone/devicedetection/examples/console/NativeModelCloudTest.java index 85facd407..9b59297f3 100644 --- a/examples/device-detection/console/src/test/java/fiftyone/devicedetection/examples/console/NativeModelCloudTest.java +++ b/examples/device-detection/console/src/test/java/fiftyone/devicedetection/examples/console/NativeModelCloudTest.java @@ -23,18 +23,17 @@ package fiftyone.devicedetection.examples.console; import fiftyone.devicedetection.examples.shared.KeyHelper; -import fiftyone.devicedetection.shared.testhelpers.KeyUtils; import org.junit.Test; -import static org.junit.Assume.assumeFalse; +import java.util.Collections; public class NativeModelCloudTest { @Test public void run() throws Exception { - String resourceKey = - KeyUtils.getNamedKey(NativeModelCloud.NATIVE_MODEL_EXAMPLE_RESOURCE_KEY_NAME); - assumeFalse("Skipping test, no resource key found", - KeyUtils.isInvalidKey(resourceKey)); + String resourceKey = KeyHelper.requireResourceKey( + Collections.singletonList( + NativeModelCloud.NATIVE_MODEL_EXAMPLE_RESOURCE_KEY_NAME), + "the devices that match a native model name", "hardware"); NativeModelCloud.run(resourceKey, System.out); } diff --git a/examples/device-detection/console/src/test/java/fiftyone/devicedetection/examples/console/TacCloudTest.java b/examples/device-detection/console/src/test/java/fiftyone/devicedetection/examples/console/TacCloudTest.java index a07d2ea14..7d3a356ff 100644 --- a/examples/device-detection/console/src/test/java/fiftyone/devicedetection/examples/console/TacCloudTest.java +++ b/examples/device-detection/console/src/test/java/fiftyone/devicedetection/examples/console/TacCloudTest.java @@ -23,18 +23,17 @@ package fiftyone.devicedetection.examples.console; import fiftyone.devicedetection.examples.shared.KeyHelper; -import fiftyone.devicedetection.shared.testhelpers.KeyUtils; import org.junit.Test; -import static org.junit.Assume.assumeFalse; +import java.util.Collections; public class TacCloudTest { @Test public void run() throws Exception { - String resourceKey = KeyUtils.getNamedKey(TacCloud.TAC_EXAMPLE_RESOURCE_KEY_NAME); - assumeFalse("Skipping test, no resource key found", - KeyUtils.isInvalidKey(resourceKey)); + String resourceKey = KeyHelper.requireResourceKey( + Collections.singletonList(TacCloud.TAC_EXAMPLE_RESOURCE_KEY_NAME), + "the devices that match a TAC", "hardware"); TacCloud.run(resourceKey, System.out); } } \ No newline at end of file diff --git a/examples/device-detection/shared/src/main/java/fiftyone/devicedetection/examples/shared/KeyHelper.java b/examples/device-detection/shared/src/main/java/fiftyone/devicedetection/examples/shared/KeyHelper.java index b6fc1a25f..467a443be 100644 --- a/examples/device-detection/shared/src/main/java/fiftyone/devicedetection/examples/shared/KeyHelper.java +++ b/examples/device-detection/shared/src/main/java/fiftyone/devicedetection/examples/shared/KeyHelper.java @@ -27,12 +27,127 @@ import org.slf4j.LoggerFactory; import java.util.*; -import java.util.function.Consumer; +import java.util.function.Function; public class KeyHelper { public static final String TEST_RESOURCE_KEY = "TestResourceKey"; static Logger logger = LoggerFactory.getLogger(KeyHelper.class); + /** + * The names every cloud test reads a resource key from first, before + * the names its own example reads. + */ + public static final List SHARED_RESOURCE_KEY_NAMES = + Collections.unmodifiableList(Arrays.asList( + "_51DEGREES_RESOURCE_KEY", + "_51DEGREES_RESOURCE_KEY_BESPOKE")); + + static final String CREATE_KEY_URL = + "https://configure.51degrees.com/?utm_source=code&utm_medium=example&utm_campaign=51degrees-java&utm_content=shared-src-main-java-fiftyone-devicedetection-examples-shared-keyhelper.java&utm_term=no-resource-key"; + + /** + * The resource key for a test that calls the live cloud service, from + * {@link #SHARED_RESOURCE_KEY_NAMES} and then {@link #TEST_RESOURCE_KEY}. + * Fails the test when there is none, because a cloud test that passes + * without a key has called nothing. + * @param asksFor what the test asks the service for + * @param products the products the key has to carry + * @return the resource key + */ + public static String requireTestResourceKey(String asksFor, + String... products) { + return requireResourceKey(Collections.singletonList(TEST_RESOURCE_KEY), + asksFor, products); + } + + /** + * The resource key for a test that calls the live cloud service, from + * {@link #SHARED_RESOURCE_KEY_NAMES} and then the given names. + * @param exampleNames the names the example reads, in the order it + * reads them + * @param asksFor what the test asks the service for + * @param products the products the key has to carry + * @return the resource key + */ + public static String requireResourceKey(List exampleNames, + String asksFor, + String... products) { + return requireResourceKey(KeyHelper::readKey, exampleNames, asksFor, + products); + } + + static String requireResourceKey(Function read, + List exampleNames, + String asksFor, + String... products) { + List names = new ArrayList<>(SHARED_RESOURCE_KEY_NAMES); + names.addAll(exampleNames); + for (String name : names) { + String value = read.apply(name); + if (KeyUtils.isInvalidKey(value) == false) { + return value.trim(); + } + } + throw new AssertionError(noResourceKey(names, asksFor, products)); + } + + static String noResourceKey(List names, String asksFor, + String... products) { + return "This test calls the live 51Degrees cloud service, so it " + + "needs a resource key and there is nothing to fall back on. " + + "None of " + String.join(", ", names) + " is set, as an " + + "environment variable or as a system property, tried in that " + + "order. The key has to carry the " + productList(products) + + ", because the test asks the service for " + asksFor + ". To " + + "see the products a key carries, open " + + "https://cloud.51degrees.com/api/v4/accessibleproperties?resource=. " + + "Create a key at " + CREATE_KEY_URL + "."; + } + + /** + * The message for a value the cloud answered without. + * @param thing the property or element that has no value + * @param reason the reason the cloud gave, or null + * @param products the products a key needs for the value + * @return the message + */ + public static String answeredWithout(String thing, String reason, + String... products) { + return "The cloud answered without " + thing + " (" + + (reason == null || reason.trim().isEmpty() ? + "no reason given" : reason.trim()) + + "), which is what a key that does not carry the " + + productList(products) + " gets."; + } + + private static String productList(String... products) { + StringBuilder list = new StringBuilder(); + for (int i = 0; i < products.length; i++) { + if (i > 0) { + list.append(i == products.length - 1 ? " and " : ", "); + } + list.append(products[i]); + } + return list + (products.length == 1 ? " product" : " products"); + } + + /** + * A key read as an environment variable, as its upper case name, and + * then as a system property, skipping any that is not a key, such as + * the empty string the example poms pass for a key that is not set. + */ + private static String readKey(String name) { + for (String value : new String[]{ + System.getenv(name), + System.getenv(name.toUpperCase()), + System.getProperty(name)}) { + if (KeyUtils.isInvalidKey(value) == false) { + return value; + } + } + return null; + } + /** * Obtain a resource key either from environment variable or from a property. */ @@ -79,8 +194,11 @@ public static String getOrSetResourceKey(String value, String variableName, } } - // capture the passed parameter for next time called - System.setProperty(variableName, value); + // Keep a valid key for next time. System.setProperty throws on null, + // and an invalid key is not worth keeping. + if (KeyUtils.isInvalidKey(value) == false) { + System.setProperty(variableName, value); + } return value; } public static String getOrSetSuperResourceKey(String value, String variablename) { diff --git a/examples/device-detection/shared/src/test/java/fiftyone/devicedetection/examples/shared/KeyHelperTest.java b/examples/device-detection/shared/src/test/java/fiftyone/devicedetection/examples/shared/KeyHelperTest.java new file mode 100644 index 000000000..1a75b45a0 --- /dev/null +++ b/examples/device-detection/shared/src/test/java/fiftyone/devicedetection/examples/shared/KeyHelperTest.java @@ -0,0 +1,136 @@ +/* ********************************************************************* + * 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.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeTrue; + +public class KeyHelperTest { + /** + * Values of the form KeyUtils accepts as a key. Neither is a real key. + */ + private static final String KEY_A = "AQAAAAAAAAAAAAAAAAAAAAAA"; + private static final String KEY_B = "AgAAAAAAAAAAAAAAAAAAAAAA"; + + /** + * With no key anywhere, the lookup logs that there is none and stores + * nothing, rather than passing null to System.setProperty. + */ + @Test + public void getOrSetTestResourceKey_NoKey_StoresNothing() { + String name = KeyHelper.TEST_RESOURCE_KEY; + // A test can clear a system property but not an environment + // variable. + assumeTrue(name + " is set in the environment", + System.getenv(name) == null && + System.getenv(name.toUpperCase()) == null); + String saved = System.clearProperty(name); + try { + assertNull(KeyHelper.getOrSetTestResourceKey(false)); + assertNull(System.getProperty(name)); + } finally { + if (saved != null) { + System.setProperty(name, saved); + } + } + } + + @Test + public void requireResourceKey_NoKey_FailsNamingEveryName() { + AssertionError error = assertThrows(AssertionError.class, () -> + KeyHelper.requireResourceKey(name -> null, + Collections.singletonList(KeyHelper.TEST_RESOURCE_KEY), + "whether a device is mobile", "device")); + assertEquals("This test calls the live 51Degrees cloud service, so " + + "it needs a resource key and there is nothing to fall back " + + "on. None of _51DEGREES_RESOURCE_KEY, " + + "_51DEGREES_RESOURCE_KEY_BESPOKE, TestResourceKey is set, as " + + "an environment variable or as a system property, tried in " + + "that order. The key has to carry the device product, " + + "because the test asks the service for whether a device is " + + "mobile. To see the products a key carries, open " + + "https://cloud.51degrees.com/api/v4/accessibleproperties?resource=. " + + "Create a key at " + KeyHelper.CREATE_KEY_URL + ".", + error.getMessage()); + } + + @Test + public void requireResourceKey_TwoProducts_NamesBoth() { + AssertionError error = assertThrows(AssertionError.class, () -> + KeyHelper.requireResourceKey(name -> null, + Collections.singletonList("SuperResourceKey"), + "the devices that match a TAC", "hardware", "device")); + assertTrue(error.getMessage(), error.getMessage().contains( + "None of _51DEGREES_RESOURCE_KEY, " + + "_51DEGREES_RESOURCE_KEY_BESPOKE, SuperResourceKey is set")); + assertTrue(error.getMessage(), error.getMessage().contains( + "carry the hardware and device products, because")); + } + + /** + * A name holding something that is not a key, such as the empty + * string the example poms pass for a key that is not set, is passed + * over. + */ + @Test + public void requireResourceKey_FirstValidNameWins() { + Map values = new HashMap<>(); + values.put("_51DEGREES_RESOURCE_KEY", ""); + values.put("_51DEGREES_RESOURCE_KEY_BESPOKE", KEY_A); + values.put(KeyHelper.TEST_RESOURCE_KEY, KEY_B); + assertEquals(KEY_A, KeyHelper.requireResourceKey(values::get, + Collections.singletonList(KeyHelper.TEST_RESOURCE_KEY), + "whether a device is mobile", "device")); + } + + @Test + public void requireResourceKey_ExampleNameAfterSharedNames() { + Map values = new HashMap<>(); + values.put(KeyHelper.TEST_RESOURCE_KEY, KEY_B); + assertEquals(KEY_B, KeyHelper.requireResourceKey(values::get, + Collections.singletonList(KeyHelper.TEST_RESOURCE_KEY), + "whether a device is mobile", "device")); + } + + @Test + public void answeredWithout_NoReason_SaysSo() { + assertEquals("The cloud answered without BrowserName (no reason " + + "given), which is what a key that does not carry the device " + + "product gets.", + KeyHelper.answeredWithout("BrowserName", null, "device")); + assertEquals("The cloud answered without BrowserName (BrowserName " + + "is a paid feature), which is what a key that does not carry " + + "the device product gets.", + KeyHelper.answeredWithout("BrowserName", + "BrowserName is a paid feature", "device")); + } +} diff --git a/examples/device-detection/web/getting-started.cloud/src/test/java/fiftyone/devicedetection/web/GettingStartedWebCloudTest.java b/examples/device-detection/web/getting-started.cloud/src/test/java/fiftyone/devicedetection/web/GettingStartedWebCloudTest.java index e2890ac00..11fa1f57a 100644 --- a/examples/device-detection/web/getting-started.cloud/src/test/java/fiftyone/devicedetection/web/GettingStartedWebCloudTest.java +++ b/examples/device-detection/web/getting-started.cloud/src/test/java/fiftyone/devicedetection/web/GettingStartedWebCloudTest.java @@ -24,7 +24,6 @@ import fiftyone.devicedetection.examples.shared.KeyHelper; import fiftyone.devicedetection.examples.web.EmbedJetty; -import fiftyone.devicedetection.shared.testhelpers.KeyUtils; import org.eclipse.jetty.server.Server; import org.junit.AfterClass; import org.junit.BeforeClass; @@ -33,42 +32,100 @@ import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; +import java.nio.charset.StandardCharsets; import java.util.Scanner; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import static fiftyone.devicedetection.examples.web.GettingStartedWebCloud.resourceBase; import static fiftyone.pipeline.util.FileFinder.getFilePath; import static org.junit.Assert.assertEquals; -import static org.junit.Assume.assumeFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +/** + * Requests the example's page with a known User-Agent and checks that the + * page shows what the cloud detected for it. + */ public class GettingStartedWebCloudTest { + /** + * Chrome 98 on Windows 10. + */ + private static final String USER_AGENT = + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " + + "(KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36"; + + /** + * What the page shows in place of a value the cloud did not return, + * followed by the reason. + */ + private static final String UNKNOWN = "Unknown."; + private static Server SERVER = null; @BeforeClass public static void startJetty() throws Exception { + String resourceKey = KeyHelper.requireTestResourceKey( + "the browser and platform of a known User-Agent", "device"); + // The example's configuration file reads the key from here. + System.setProperty(KeyHelper.TEST_RESOURCE_KEY, resourceKey); SERVER = EmbedJetty.startWebApp(getFilePath(resourceBase).getAbsolutePath(), 8081); } @Test public void testWebCloud() throws Exception { - String resourceKey = KeyUtils.getNamedKey("TestResourceKey"); - assumeFalse("Skipping test, no resource key found", - KeyUtils.isInvalidKey(resourceKey)); - HttpURLConnection connection = (HttpURLConnection) new URL("http://localhost:8081/").openConnection(); + connection.setRequestProperty("User-Agent", USER_AGENT); - InputStream response = connection.getInputStream(); - try (Scanner scanner = new Scanner(response)) { - String responseBody = scanner.useDelimiter("\\A").next(); - System.out.println(responseBody); - } int code = connection.getResponseCode(); + String body = read(code < 400 ? + connection.getInputStream() : connection.getErrorStream()); connection.disconnect(); - assertEquals(200, code); + + assertEquals("The page answered " + code + " with " + body, + 200, code); + assertTrue("The page does not say the cloud engine detected the " + + "values it shows. " + body, + body.contains("cloud device detection engine")); + assertDetected(body, "Browser Name", "BrowserName", "Chrome"); + assertDetected(body, "Platform Name", "PlatformName", "Windows"); + } + + /** + * Checks the value in the page's row for a property, which is the + * reason when the cloud returned no value. + */ + private static void assertDetected(String body, String label, + String property, String expected) { + Matcher row = Pattern.compile(Pattern.quote(label + + "") + "(.*?)") + .matcher(body); + assertTrue("The page has no " + label + " row. " + body, row.find()); + String value = row.group(1).trim(); + if (value.startsWith(UNKNOWN)) { + String reason = value.substring(UNKNOWN.length()).trim(); + fail(KeyHelper.answeredWithout(property, + "null".equals(reason) ? null : reason, "device")); + } + assertEquals(label + " on the page", expected, value); + } + + private static String read(InputStream stream) { + if (stream == null) { + return ""; + } + try (Scanner scanner = new Scanner(stream, + StandardCharsets.UTF_8.name())) { + scanner.useDelimiter("\\A"); + return scanner.hasNext() ? scanner.next() : ""; + } } @AfterClass public static void stopJetty() throws Exception { - SERVER.stop(); + if (SERVER != null) { + SERVER.stop(); + } } } \ No newline at end of file diff --git a/examples/ip-intelligence/console/src/test/java/fiftyone/ipintelligence/examples/console/GetAllPropertiesCloudTest.java b/examples/ip-intelligence/console/src/test/java/fiftyone/ipintelligence/examples/console/GetAllPropertiesCloudTest.java index be4be97b1..e024e7506 100644 --- a/examples/ip-intelligence/console/src/test/java/fiftyone/ipintelligence/examples/console/GetAllPropertiesCloudTest.java +++ b/examples/ip-intelligence/console/src/test/java/fiftyone/ipintelligence/examples/console/GetAllPropertiesCloudTest.java @@ -26,15 +26,12 @@ import fiftyone.ipintelligence.shared.testhelpers.KeyUtils; import org.junit.Test; -import static org.junit.Assume.assumeFalse; - public class GetAllPropertiesCloudTest { @Test public void getAllPropertiesCloudTest() throws Exception { - String resourceKey = KeyUtils.getNamedKey(KeyHelper.TEST_RESOURCE_KEY); - assumeFalse("Skipping test, no resource key found", - KeyUtils.isInvalidKey(resourceKey)); + String resourceKey = KeyHelper.requireTestResourceKey( + "every IP Intelligence property the key carries", "ip"); GetAllPropertiesCloud.run(resourceKey, KeyUtils.getNamedKey(GettingStartedCloud.CLOUD_END_POINT_NAME), System.out); diff --git a/examples/ip-intelligence/console/src/test/java/fiftyone/ipintelligence/examples/console/GettingStartedCloudMixedTest.java b/examples/ip-intelligence/console/src/test/java/fiftyone/ipintelligence/examples/console/GettingStartedCloudMixedTest.java index fc0aab3ac..9e43a0078 100644 --- a/examples/ip-intelligence/console/src/test/java/fiftyone/ipintelligence/examples/console/GettingStartedCloudMixedTest.java +++ b/examples/ip-intelligence/console/src/test/java/fiftyone/ipintelligence/examples/console/GettingStartedCloudMixedTest.java @@ -26,15 +26,14 @@ import fiftyone.ipintelligence.shared.testhelpers.KeyUtils; import org.junit.Test; -import static org.junit.Assume.assumeFalse; - public class GettingStartedCloudMixedTest { @Test public void gettingStartedCloudMixedTest() throws Exception { - String resourceKey = KeyUtils.getNamedKey(KeyHelper.TEST_RESOURCE_KEY); - assumeFalse("Skipping test, no resource key found", - KeyUtils.isInvalidKey(resourceKey)); + String resourceKey = KeyHelper.requireTestResourceKey( + "the network and location of an IP address, and whether a " + + "device is mobile", + "ip", "device"); GettingStartedCloudMixed.run(resourceKey, KeyUtils.getNamedKey(GettingStartedCloud.CLOUD_END_POINT_NAME), GettingStartedMixed.setUpCombinedEvidence(), System.out); diff --git a/examples/ip-intelligence/console/src/test/java/fiftyone/ipintelligence/examples/console/GettingStartedCloudTest.java b/examples/ip-intelligence/console/src/test/java/fiftyone/ipintelligence/examples/console/GettingStartedCloudTest.java index 37dddd104..5030e2f2f 100644 --- a/examples/ip-intelligence/console/src/test/java/fiftyone/ipintelligence/examples/console/GettingStartedCloudTest.java +++ b/examples/ip-intelligence/console/src/test/java/fiftyone/ipintelligence/examples/console/GettingStartedCloudTest.java @@ -27,15 +27,12 @@ import fiftyone.ipintelligence.shared.testhelpers.KeyUtils; import org.junit.Test; -import static org.junit.Assume.assumeFalse; - public class GettingStartedCloudTest { @Test public void gettingStartedCloudTest() throws Exception { - String resourceKey = KeyUtils.getNamedKey(KeyHelper.TEST_RESOURCE_KEY); - assumeFalse("Skipping test, no resource key found", - KeyUtils.isInvalidKey(resourceKey)); + String resourceKey = KeyHelper.requireTestResourceKey( + "the network, location and time zone of an IP address", "ip"); GettingStartedCloud.run(resourceKey, KeyUtils.getNamedKey(GettingStartedCloud.CLOUD_END_POINT_NAME), EvidenceHelper.setUpEvidence(), System.out); diff --git a/examples/ip-intelligence/console/src/test/java/fiftyone/ipintelligence/examples/console/MetadataCloudTest.java b/examples/ip-intelligence/console/src/test/java/fiftyone/ipintelligence/examples/console/MetadataCloudTest.java index c1ac46032..a531deb55 100644 --- a/examples/ip-intelligence/console/src/test/java/fiftyone/ipintelligence/examples/console/MetadataCloudTest.java +++ b/examples/ip-intelligence/console/src/test/java/fiftyone/ipintelligence/examples/console/MetadataCloudTest.java @@ -26,15 +26,12 @@ import fiftyone.ipintelligence.shared.testhelpers.KeyUtils; import org.junit.Test; -import static org.junit.Assume.assumeFalse; - public class MetadataCloudTest { @Test public void metadataCloudTest() throws Exception { - String resourceKey = KeyUtils.getNamedKey(KeyHelper.TEST_RESOURCE_KEY); - assumeFalse("Skipping test, no resource key found", - KeyUtils.isInvalidKey(resourceKey)); + String resourceKey = KeyHelper.requireTestResourceKey( + "the IP Intelligence properties it offers", "ip"); MetadataCloud.run(resourceKey, KeyUtils.getNamedKey(GettingStartedCloud.CLOUD_END_POINT_NAME), System.out); diff --git a/examples/ip-intelligence/shared/src/main/java/fiftyone/ipintelligence/examples/shared/KeyHelper.java b/examples/ip-intelligence/shared/src/main/java/fiftyone/ipintelligence/examples/shared/KeyHelper.java index 653ecc7b9..846c714f6 100644 --- a/examples/ip-intelligence/shared/src/main/java/fiftyone/ipintelligence/examples/shared/KeyHelper.java +++ b/examples/ip-intelligence/shared/src/main/java/fiftyone/ipintelligence/examples/shared/KeyHelper.java @@ -27,6 +27,7 @@ import org.slf4j.LoggerFactory; import java.util.*; +import java.util.function.Function; public class KeyHelper { /** @@ -38,6 +39,90 @@ public class KeyHelper { public static final String TEST_RESOURCE_KEY = "TestResourceKey"; static Logger logger = LoggerFactory.getLogger(KeyHelper.class); + /** + * The names every cloud test reads a resource key from first, before + * the names its own example reads. + */ + public static final List SHARED_RESOURCE_KEY_NAMES = + Collections.unmodifiableList(Arrays.asList( + "_51DEGREES_RESOURCE_KEY", + "_51DEGREES_RESOURCE_KEY_BESPOKE")); + + static final String CREATE_KEY_URL = + "https://configure.51degrees.com/?utm_source=code&utm_medium=example&utm_campaign=51degrees-java&utm_content=shared-src-main-java-fiftyone-ipintelligence-examples-shared-keyhelper.java&utm_term=no-resource-key"; + + /** + * The resource key for a test that calls the live cloud service, from + * {@link #SHARED_RESOURCE_KEY_NAMES} and then the names + * {@link #getOrSetTestResourceKey()} reads. Fails the test when there + * is none, because a cloud test that passes without a key has called + * nothing. + * @param asksFor what the test asks the service for + * @param products the products the key has to carry + * @return the resource key + */ + public static String requireTestResourceKey(String asksFor, + String... products) { + return requireResourceKey(KeyHelper::readKey, Arrays.asList( + RESOURCE_KEY_ENV_VAR, TEST_RESOURCE_KEY), asksFor, products); + } + + static String requireResourceKey(Function read, + List exampleNames, + String asksFor, + String... products) { + List names = new ArrayList<>(SHARED_RESOURCE_KEY_NAMES); + names.addAll(exampleNames); + for (String name : names) { + String value = read.apply(name); + if (KeyUtils.isInvalidKey(value) == false) { + return value.trim(); + } + } + throw new AssertionError(noResourceKey(names, asksFor, products)); + } + + static String noResourceKey(List names, String asksFor, + String... products) { + return "This test calls the live 51Degrees cloud service, so it " + + "needs a resource key and there is nothing to fall back on. " + + "None of " + String.join(", ", names) + " is set, as an " + + "environment variable or as a system property, tried in that " + + "order. The key has to carry the " + productList(products) + + ", because the test asks the service for " + asksFor + ". To " + + "see the products a key carries, open " + + "https://cloud.51degrees.com/api/v4/accessibleproperties?resource=. " + + "Create a key at " + CREATE_KEY_URL + "."; + } + + private static String productList(String... products) { + StringBuilder list = new StringBuilder(); + for (int i = 0; i < products.length; i++) { + if (i > 0) { + list.append(i == products.length - 1 ? " and " : ", "); + } + list.append(products[i]); + } + return list + (products.length == 1 ? " product" : " products"); + } + + /** + * A key read as an environment variable, as its upper case name, and + * then as a system property, skipping any that is not a key, such as + * the empty string the example poms pass for a key that is not set. + */ + private static String readKey(String name) { + for (String value : new String[]{ + System.getenv(name), + System.getenv(name.toUpperCase()), + System.getProperty(name)}) { + if (KeyUtils.isInvalidKey(value) == false) { + return value; + } + } + return null; + } + /** * Obtain a resource key either from environment variable or from a property. */ @@ -92,8 +177,11 @@ public static String getOrSetResourceKey(String value, String variableName, } } - // capture the passed parameter for next time called - System.setProperty(variableName, value); + // Keep a valid key for next time. System.setProperty throws on null, + // and an invalid key is not worth keeping. + if (KeyUtils.isInvalidKey(value) == false) { + System.setProperty(variableName, value); + } return value; } public static String getOrSetSuperResourceKey(String value, String variablename) { diff --git a/examples/ip-intelligence/shared/src/test/java/fiftyone/ipintelligence/examples/shared/KeyHelperTest.java b/examples/ip-intelligence/shared/src/test/java/fiftyone/ipintelligence/examples/shared/KeyHelperTest.java new file mode 100644 index 000000000..67b2660cb --- /dev/null +++ b/examples/ip-intelligence/shared/src/test/java/fiftyone/ipintelligence/examples/shared/KeyHelperTest.java @@ -0,0 +1,129 @@ +/* ********************************************************************* + * 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.shared; + +import org.junit.Test; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThrows; +import static org.junit.Assume.assumeTrue; + +public class KeyHelperTest { + /** + * Values of the form KeyUtils accepts as a key. Neither is a real key. + */ + private static final String KEY_A = "AQAAAAAAAAAAAAAAAAAAAAAA"; + private static final String KEY_B = "AgAAAAAAAAAAAAAAAAAAAAAA"; + + /** + * The names getOrSetTestResourceKey reads, in its order. + */ + private static final List EXAMPLE_NAMES = Arrays.asList( + KeyHelper.RESOURCE_KEY_ENV_VAR, KeyHelper.TEST_RESOURCE_KEY); + + /** + * With no key anywhere, the lookup logs that there is none and stores + * nothing, rather than passing null to System.setProperty. + */ + @Test + public void getOrSetTestResourceKey_NoKey_StoresNothing() { + // A test can clear a system property but not an environment + // variable. + for (String name : EXAMPLE_NAMES) { + assumeTrue(name + " is set in the environment", + System.getenv(name) == null && + System.getenv(name.toUpperCase()) == null); + } + Map saved = new HashMap<>(); + for (String name : EXAMPLE_NAMES) { + saved.put(name, System.clearProperty(name)); + } + try { + assertNull(KeyHelper.getOrSetTestResourceKey(false)); + for (String name : EXAMPLE_NAMES) { + assertNull(name, System.getProperty(name)); + } + } finally { + for (Map.Entry entry : saved.entrySet()) { + if (entry.getValue() != null) { + System.setProperty(entry.getKey(), entry.getValue()); + } + } + } + } + + @Test + public void requireResourceKey_NoKey_FailsNamingEveryName() { + AssertionError error = assertThrows(AssertionError.class, () -> + KeyHelper.requireResourceKey(name -> null, EXAMPLE_NAMES, + "the network and location of an IP address, and whether a " + + "device is mobile", + "ip", "device")); + assertEquals("This test calls the live 51Degrees cloud service, so " + + "it needs a resource key and there is nothing to fall back " + + "on. None of _51DEGREES_RESOURCE_KEY, " + + "_51DEGREES_RESOURCE_KEY_BESPOKE, 51DEGREES_RESOURCE_KEY, " + + "TestResourceKey is set, as an environment variable or as a " + + "system property, tried in that order. The key has to carry " + + "the ip and device products, because the test asks the " + + "service for the network and location of an IP address, and " + + "whether a device is mobile. To see the products a key " + + "carries, open " + + "https://cloud.51degrees.com/api/v4/accessibleproperties?resource=. " + + "Create a key at " + KeyHelper.CREATE_KEY_URL + ".", + error.getMessage()); + } + + /** + * A name holding something that is not a key, such as the empty + * string the example poms pass for a key that is not set, is passed + * over. + */ + @Test + public void requireResourceKey_FirstValidNameWins() { + Map values = new HashMap<>(); + values.put("_51DEGREES_RESOURCE_KEY", ""); + values.put("_51DEGREES_RESOURCE_KEY_BESPOKE", KEY_A); + values.put(KeyHelper.TEST_RESOURCE_KEY, KEY_B); + assertEquals(KEY_A, KeyHelper.requireResourceKey(values::get, + EXAMPLE_NAMES, "every IP Intelligence property", "ip")); + } + + @Test + public void requireResourceKey_ExampleNamesInTheirOrder() { + Map values = new HashMap<>(); + values.put(KeyHelper.RESOURCE_KEY_ENV_VAR, KEY_A); + values.put(KeyHelper.TEST_RESOURCE_KEY, KEY_B); + assertEquals(KEY_A, KeyHelper.requireResourceKey(values::get, + EXAMPLE_NAMES, "every IP Intelligence property", "ip")); + values.remove(KeyHelper.RESOURCE_KEY_ENV_VAR); + assertEquals(KEY_B, KeyHelper.requireResourceKey(values::get, + EXAMPLE_NAMES, "every IP Intelligence property", "ip")); + } +} diff --git a/examples/ip-intelligence/web/getting-started.cloud.mixed/src/test/java/fiftyone/ipintelligence/examples/web/GettingStartedWebCloudMixedTest.java b/examples/ip-intelligence/web/getting-started.cloud.mixed/src/test/java/fiftyone/ipintelligence/examples/web/GettingStartedWebCloudMixedTest.java index ca1b1ecfc..ff49ca9e7 100644 --- a/examples/ip-intelligence/web/getting-started.cloud.mixed/src/test/java/fiftyone/ipintelligence/examples/web/GettingStartedWebCloudMixedTest.java +++ b/examples/ip-intelligence/web/getting-started.cloud.mixed/src/test/java/fiftyone/ipintelligence/examples/web/GettingStartedWebCloudMixedTest.java @@ -23,7 +23,6 @@ package fiftyone.ipintelligence.examples.web; import fiftyone.ipintelligence.examples.shared.KeyHelper; -import fiftyone.ipintelligence.shared.testhelpers.KeyUtils; import org.eclipse.jetty.server.Server; import org.junit.AfterClass; import org.junit.BeforeClass; @@ -36,7 +35,6 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import static org.junit.Assume.assumeFalse; public class GettingStartedWebCloudMixedTest { private static Server SERVER; @@ -44,9 +42,10 @@ public class GettingStartedWebCloudMixedTest { @BeforeClass public static void startJetty() throws Exception { - String resourceKey = KeyUtils.getNamedKey(KeyHelper.TEST_RESOURCE_KEY); - assumeFalse("Skipping test, no resource key found", - KeyUtils.isInvalidKey(resourceKey)); + String resourceKey = KeyHelper.requireTestResourceKey( + "the network and location of the address a request comes from, " + + "and the device that sent it", + "ip", "device"); // Make the resource key available to the pipeline configuration file. // The resource key must include both device detection and IP // Intelligence properties as this example uses both engines. diff --git a/examples/ip-intelligence/web/getting-started.cloud/src/test/java/fiftyone/ipintelligence/examples/web/GettingStartedWebCloudTest.java b/examples/ip-intelligence/web/getting-started.cloud/src/test/java/fiftyone/ipintelligence/examples/web/GettingStartedWebCloudTest.java index ea87c40a7..06a7b8b48 100644 --- a/examples/ip-intelligence/web/getting-started.cloud/src/test/java/fiftyone/ipintelligence/examples/web/GettingStartedWebCloudTest.java +++ b/examples/ip-intelligence/web/getting-started.cloud/src/test/java/fiftyone/ipintelligence/examples/web/GettingStartedWebCloudTest.java @@ -23,7 +23,6 @@ package fiftyone.ipintelligence.examples.web; import fiftyone.ipintelligence.examples.shared.KeyHelper; -import fiftyone.ipintelligence.shared.testhelpers.KeyUtils; import org.eclipse.jetty.server.Server; import org.junit.AfterClass; import org.junit.BeforeClass; @@ -38,7 +37,6 @@ import static fiftyone.pipeline.util.FileFinder.getFilePath; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import static org.junit.Assume.assumeFalse; public class GettingStartedWebCloudTest { private static Server SERVER; @@ -46,9 +44,9 @@ public class GettingStartedWebCloudTest { @BeforeClass public static void startJetty() throws Exception { - String resourceKey = KeyUtils.getNamedKey(KeyHelper.TEST_RESOURCE_KEY); - assumeFalse("Skipping test, no resource key found", - KeyUtils.isInvalidKey(resourceKey)); + String resourceKey = KeyHelper.requireTestResourceKey( + "the network and location of the address a request comes from", + "ip"); // Make the resource key available to the pipeline configuration file System.setProperty(KeyHelper.TEST_RESOURCE_KEY, resourceKey);