Repository: authorjapps/zerocode
Pull request #269 (2019-07-04 11:00:27)

Implemented Fixed length random generator
**Implements #245** 

As well as few improvements : 
* Removed duplicate testCaseTokens for efficiency
* Every occurrence of a RANDOM.NUMBER will be
  replaced with a unique number

Tests are yet to be added. **_Looking for suggestion/improvements._**

jneate: @santhoshTpixler looks good mate

Cheers for picking up the PR 👍 

@authorjapps @santhoshTpixler suggest that this PR and PR #273 be merged at the same time as they both benefit from the updated Wiki pages.
authorjapps: > @authorjapps @santhoshTpixler suggest that this PR and PR #273 be merged at the same time as they both benefit from the updated Wiki pages.

Thanks. Understood.


authorjapps: PR looks good.
Added some very minor cosmetic comments, if you could take care please @santhoshTpixler .

_Sorry again for the late replies as we all were occupied a bit with other jobs._

authorjapps: > This branch has conflicts that must be resolved
Use the web editor or the  to resolve conflicts.
Conflicting files
core/src/test/java/org/jsmart/zerocode/core/utils/TokenUtilsTest.java

When you get chance please @santhoshTpixler .

Also, Can you take care of these minor comments?

Patch:
diff --git a/core/src/main/java/org/jsmart/zerocode/core/utils/FixedLengthRandomGenerator.java b/core/src/main/java/org/jsmart/zerocode/core/utils/FixedLengthRandomGenerator.java
new file mode 100644
index 00000000..724b94ab
--- /dev/null
+++ b/core/src/main/java/org/jsmart/zerocode/core/utils/FixedLengthRandomGenerator.java
@@ -0,0 +1,52 @@
+package org.jsmart.zerocode.core.utils;
+
+import java.util.HashMap;
+import java.util.concurrent.ThreadLocalRandom;
+
+/**
+ * A fixed length random generator supports up to 19 digits
+ */
+public class FixedLengthRandomGenerator {
+
+    /*
+     * Generators are preserved to avoid creating duplicate instances
+     * for the entire life cycle
+     * */
+    private static final HashMap<Integer, FixedLengthRandomGenerator> GENERATOR_MAP = new HashMap<>();
+
+    private long lowerBound;
+    private long upperBound;
+
+
+    private FixedLengthRandomGenerator(int length) {
+        this.lowerBound = (long) Math.pow(10, (length - 1));
+        this.upperBound = (long) Math.pow(10, length);
+    }
+
+    public String generateRandomNumber() {
+        return String.valueOf(ThreadLocalRandom.current().nextLong(this.lowerBound, this.upperBound));
+    }
+
+    @Override
+    public String toString() {
+        return this.generateRandomNumber();
+    }
+
+    /**
+     * @param length of the random number(0 < length < 20)
+     * @return a {@link FixedLengthRandomGenerator} number of given length
+     */
+    public static FixedLengthRandomGenerator getGenerator(int length) {
+        if (length < 1 || length > 19) {
+            throw new RuntimeException("length of the random number should be between (1-19)");
+        }
+        FixedLengthRandomGenerator buff = GENERATOR_MAP.get(length);
+        if (buff == null) {
+            buff = new FixedLengthRandomGenerator(length);
+            GENERATOR_MAP.put(length, buff);
+            return buff;
+        } else {
+            return buff;
+        }
+    }
+}
diff --git a/core/src/main/java/org/jsmart/zerocode/core/utils/TokenUtils.java b/core/src/main/java/org/jsmart/zerocode/core/utils/TokenUtils.java
index eb444110..8c3c0bf4 100644
--- a/core/src/main/java/org/jsmart/zerocode/core/utils/TokenUtils.java
+++ b/core/src/main/java/org/jsmart/zerocode/core/utils/TokenUtils.java
@@ -1,18 +1,22 @@
 package org.jsmart.zerocode.core.utils;
 
-import org.apache.commons.lang.text.StrSubstitutor;
+import static java.util.UUID.randomUUID;
+import static org.apache.commons.lang.StringEscapeUtils.escapeJava;
+import static org.jsmart.zerocode.core.engine.preprocessor.ZeroCodeTokens.*;
 
 import java.io.IOException;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-import static java.util.UUID.randomUUID;
-import static org.apache.commons.lang.StringEscapeUtils.escapeJava;
-import static org.jsmart.zerocode.core.engine.preprocessor.ZeroCodeTokens.*;
+import org.apache.commons.lang.text.StrSubstitutor;
 
 public class TokenUtils {
 
@@ -20,8 +24,7 @@ public class TokenUtils {
         Map<String, Object> paramMap = new HashMap<>();
 
         final List<String> testCaseTokens = getTestCaseTokens(requestJsonOrAnyString);
-
-        testCaseTokens.forEach(runTimeToken -> {
+        testCaseTokens.stream().distinct().forEach(runTimeToken -> {
             populateParamMap(paramMap, runTimeToken);
         });
 
@@ -34,7 +37,12 @@ public class TokenUtils {
         getKnownTokens().forEach(inStoreToken -> {
                     if (runTimeToken.startsWith(inStoreToken)) {
                         if (runTimeToken.startsWith(RANDOM_NUMBER)) {
-                            paramaMap.put(runTimeToken, System.currentTimeMillis() + "");
+                            String[] slices = runTimeToken.split(":");
+                            if (slices.length == 2) {
+                                paramaMap.put(runTimeToken, FixedLengthRandomGenerator.getGenerator(Integer.parseInt(slices[1])));
+                            } else {
+                                paramaMap.put(runTimeToken, System.currentTimeMillis());
+                            }
 
                         } else if (runTimeToken.startsWith(RANDOM_STRING_PREFIX)) {
                             int length = Integer.parseInt(runTimeToken.substring(RANDOM_STRING_PREFIX.length()));
@@ -53,11 +61,11 @@ public class TokenUtils {
                             String formatPattern = runTimeToken.substring(LOCALDATETIME_NOW.length());
                             DateTimeFormatter formatter = DateTimeFormatter.ofPattern(formatPattern);
                             paramaMap.put(runTimeToken, LocalDateTime.now().format(formatter));
-                            
+
                         } else if (runTimeToken.startsWith(SYSTEM_PROPERTY)) {
 
-                        	String propertyName = runTimeToken.substring(SYSTEM_PROPERTY.length());
-                        	paramaMap.put(runTimeToken, System.getProperty(propertyName));
+                            String propertyName = runTimeToken.substring(SYSTEM_PROPERTY.length());
+                            paramaMap.put(runTimeToken, System.getProperty(propertyName));
 
                         } else if (runTimeToken.startsWith(XML_FILE)) {
                             String xmlFileResource = runTimeToken.substring(XML_FILE.length());


Test patch:
diff --git a/core/src/test/java/org/jsmart/zerocode/core/utils/TokenUtilsTest.java b/core/src/test/java/org/jsmart/zerocode/core/utils/TokenUtilsTest.java
index 49588d72..051e6abd 100644
--- a/core/src/test/java/org/jsmart/zerocode/core/utils/TokenUtilsTest.java
+++ b/core/src/test/java/org/jsmart/zerocode/core/utils/TokenUtilsTest.java
@@ -1,14 +1,22 @@
 package org.jsmart.zerocode.core.utils;
 
-import org.junit.Ignore;
-import org.junit.Test;
-
 import static org.hamcrest.CoreMatchers.is;
 import static org.jsmart.zerocode.core.utils.TokenUtils.resolveKnownTokens;
 import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import java.util.stream.IntStream;
+
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
 
 public class TokenUtilsTest {
 
+    @Rule
+    public ExpectedException exceptionRule = ExpectedException.none();
+
     @Test
     public void testResolve_knownTokens() {
         String clientId = "zerocode-clientid_${RANDOM.NUMBER}";
@@ -22,19 +30,21 @@ public class TokenUtilsTest {
     @Ignore("This might fail for who is e.g. Open Jdk or Zulu JDK")
     @Test
     public void testResolveSystemProperty_PROPERTY_FOUND_javaVendor() {
-    	
-    	String exampleInputString = "zerocode-tokentest: ${SYSTEM.PROPERTY:java.vendor}"; // java.vendor = Oracle Corporation
-    	String resolvedString = resolveKnownTokens(exampleInputString);
-    	String resolvedToken = resolvedString.substring("zerocode-tokentest: ".length());
-    	
-    	assertThat(resolvedToken.equals("Oracle Corporation"), is(true));
+
+        String exampleInputString = "zerocode-tokentest: ${SYSTEM.PROPERTY:java.vendor}"; // java.vendor = Oracle
+        // Corporation
+        String resolvedString = resolveKnownTokens(exampleInputString);
+        String resolvedToken = resolvedString.substring("zerocode-tokentest: ".length());
+
+        assertThat(resolvedToken.equals("Oracle Corporation"), is(true));
 
     }
 
     @Test
     public void testResolveSystemProperty_PROPERTY_FOUND_fileEncoing() {
 
-        String exampleInputString = "zerocode-tokentest: ${SYSTEM.PROPERTY:file.encoding}"; // java.vendor = Oracle Corporation
+        String exampleInputString = "zerocode-tokentest: ${SYSTEM.PROPERTY:file.encoding}"; // java.vendor = Oracle
+        // Corporation
         String resolvedString = resolveKnownTokens(exampleInputString);
         String resolvedToken = resolvedString.substring("zerocode-tokentest: ".length());
 
@@ -43,12 +53,49 @@ public class TokenUtilsTest {
 
     @Test
     public void testResolveSystemProperty_PROPERTY_NOT_FOUND() {
-    	
-    	String exampleInputString = "zerocode-tokentest: ${SYSTEM.PROPERTY:dummy_property_one}"; // Property does not exist
-    	String resolvedString = resolveKnownTokens(exampleInputString);
-    	String unResolvedToken = resolvedString.substring("zerocode-tokentest: ".length());
-    	// If the property is NOT FOUND then the token is not resolved and remains as a normal string
-    	assertThat(unResolvedToken.equals("${SYSTEM.PROPERTY:dummy_property_one}"), is(true));
-    	
+
+        String exampleInputString = "zerocode-tokentest: ${SYSTEM.PROPERTY:dummy_property_one}"; // Property does not
+        // exist
+        String resolvedString = resolveKnownTokens(exampleInputString);
+        String unResolvedToken = resolvedString.substring("zerocode-tokentest: ".length());
+        // If the property is NOT FOUND then the token is not resolved and remains as a
+        // normal string
+        assertThat(unResolvedToken.equals("${SYSTEM.PROPERTY:dummy_property_one}"), is(true));
+
     }
+
+    @Test
+    public void testFixedRandomGenerator_success() {
+        IntStream.rangeClosed(1, 19).forEach(i -> {
+            assertTrue(FixedLengthRandomGenerator.getGenerator(i).generateRandomNumber().length() == i);
+        });
+    }
+
+    @Test
+    public void testFixedRandomGenerator_failure_min() {
+        exceptionRule.expect(RuntimeException.class);
+        FixedLengthRandomGenerator.getGenerator(0).generateRandomNumber();
+    }
+
+    @Test
+    public void testFixedRandomGenerator_failure_max() {
+        exceptionRule.expect(RuntimeException.class);
+        FixedLengthRandomGenerator.getGenerator(20).generateRandomNumber();
+    }
+
+    @Test
+    public void testFixedRandomTokenReplace() {
+        String clientId = "zerocode-clientid_${RANDOM.NUMBER:10}";
+        String uniqueClientId = resolveKnownTokens(clientId);
+        String uniqueId = uniqueClientId.substring("zerocode-clientid_".length());
+        assertTrue(uniqueId.length() == 10);
+    }
+
+    @Test
+    public void testFixedRandomUniqueness() {
+        String result = resolveKnownTokens("${RANDOM.NUMBER:12},${RANDOM.NUMBER:12}");
+        String[] split = result.split(",");
+        assertTrue(split[0] != split[1]);
+    }
+
 }
\ No newline at end of file
diff --git a/http-testing/src/test/resources/helloworld_token_resolving/helloworld_token_resolving_ok.json b/http-testing/src/test/resources/helloworld_token_resolving/helloworld_token_resolving_ok.json
index ea5a4071..9aeebe59 100644
--- a/http-testing/src/test/resources/helloworld_token_resolving/helloworld_token_resolving_ok.json
+++ b/http-testing/src/test/resources/helloworld_token_resolving/helloworld_token_resolving_ok.json
@@ -8,6 +8,9 @@
             "request": {
             	"headers":{
             		"x-random-number": "${RANDOM.NUMBER}",
+            		"x-fixed-length2-number": "${RANDOM.NUMBER:2}",
+            		"x-fixed-length4-number1": "${RANDOM.NUMBER:4}",
+            		"x-fixed-length4-number2": "${RANDOM.NUMBER:4}",
             		"x-random-string-4": "${RANDOM.STRING:4}",
             		"x-system-property-java-vendor": "${SYSTEM.PROPERTY:java.vendor}",
             		"x-static-alphabet-6": "${STATIC.ALPHABET:6}",