-
Notifications
You must be signed in to change notification settings - Fork 157
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Setup TestKit with random walker #433
RandomWalkLinkChecker introduced that allows to randomly walk a API endpoint by following links to verify that GET requests can successfully by searched.
- Loading branch information
Showing
6 changed files
with
229 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
anchor:testkit[] | ||
|
||
# TestKit | ||
|
||
With `crnk-testkit` is in the early stages of providing utilities to facilitate testing. | ||
`RandomWalkLinkChecker` is a class that performs a random walk on an API endpoint by following | ||
all the links as specified by JSON:API. It will verify that each links provides a valid | ||
`2xx` response code. The setup looks like: | ||
|
||
[source,java] | ||
---- | ||
CrnkClient client = ... | ||
HttpAdapter httpAdapter = client.getHttpAdapter(); | ||
RandomWalkLinkChecker linkChecker = new RandomWalkLinkChecker(httpAdapter); | ||
linkChecker.setWalkLength(100); | ||
linkChecker.addStartUrl(...) | ||
linkChecker.performCheck(); | ||
---- | ||
|
||
More possibilities for automation in the future are to add checks for: | ||
|
||
- paging | ||
- filtering | ||
- sorting | ||
- security | ||
- field sets | ||
- ... | ||
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
apply plugin: 'java' | ||
|
||
dependencies { | ||
compile project(':crnk-core') | ||
compile project(':crnk-client') | ||
testCompile project(':crnk-test') | ||
testCompile project(':crnk-home') | ||
|
||
compile 'org.assertj:assertj-core:3.9.1' | ||
compile 'org.apache.httpcomponents:httpclient:4.5.2' | ||
} |
143 changes: 143 additions & 0 deletions
143
crnk-testkit/src/main/java/io/crnk/testkit/RandomWalkLinkChecker.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
package io.crnk.testkit; | ||
|
||
import java.io.IOException; | ||
import java.util.ArrayList; | ||
import java.util.HashSet; | ||
import java.util.Iterator; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Random; | ||
import java.util.Set; | ||
|
||
import com.fasterxml.jackson.core.JsonProcessingException; | ||
import com.fasterxml.jackson.databind.JsonNode; | ||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
import com.fasterxml.jackson.databind.node.ArrayNode; | ||
import com.fasterxml.jackson.databind.node.ObjectNode; | ||
import io.crnk.client.http.HttpAdapter; | ||
import io.crnk.client.http.HttpAdapterRequest; | ||
import io.crnk.client.http.HttpAdapterResponse; | ||
import io.crnk.core.engine.http.HttpMethod; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
/** | ||
* Performs a random walk across an API endpoint by following JSON:API links. | ||
*/ | ||
public class RandomWalkLinkChecker { | ||
|
||
private static final Logger LOGGER = LoggerFactory.getLogger(RandomWalkLinkChecker.class); | ||
|
||
private final HttpAdapter httpAdapter; | ||
|
||
private int walkLength = 1000; | ||
|
||
private Set<String> visited = new HashSet<>(); | ||
|
||
private List<String> upcoming = new ArrayList<>(); | ||
|
||
private ObjectMapper mapper = new ObjectMapper(); | ||
|
||
private String currentUrl; | ||
|
||
public RandomWalkLinkChecker(HttpAdapter httpAdapter) { | ||
this.httpAdapter = httpAdapter; | ||
} | ||
|
||
public void addStartUrl(String url) { | ||
upcoming.add(url); | ||
} | ||
|
||
public void setWalkLength(int walkLength) { | ||
this.walkLength = walkLength; | ||
} | ||
|
||
public int getWalkLength() { | ||
return walkLength; | ||
} | ||
|
||
public Set<String> performCheck() { | ||
Random random = new Random(); | ||
|
||
int index = 0; | ||
while (index < walkLength && !upcoming.isEmpty()) { | ||
|
||
int nextIndex = random.nextInt(upcoming.size()); | ||
|
||
currentUrl = upcoming.remove(nextIndex); | ||
if (!visited.contains(currentUrl)) { | ||
visited.add(currentUrl); | ||
index++; | ||
visit(currentUrl); | ||
} | ||
} | ||
return visited; | ||
} | ||
|
||
private void visit(String url) { | ||
try { | ||
LOGGER.info("visiting {}", url); | ||
HttpAdapterRequest request = httpAdapter.newRequest(url, HttpMethod.GET, null); | ||
HttpAdapterResponse response = request.execute(); | ||
int code = response.code(); | ||
if (code >= 300) { | ||
throw new IllegalStateException("expected endpoint to return success status code, got " + code + " from " + url); | ||
} | ||
String body = response.body(); | ||
if (body == null) { | ||
throw new IllegalStateException("expected a body to be returned from " + url); | ||
} | ||
|
||
JsonNode jsonNode = mapper.reader().readTree(body); | ||
findLinks(jsonNode); | ||
} catch (IOException e) { | ||
throw new IllegalStateException("failed to visit " + url, e); | ||
} | ||
} | ||
|
||
private void findLinks(JsonNode jsonNode) { | ||
if (jsonNode instanceof ArrayNode) { | ||
ArrayNode arrayNode = (ArrayNode) jsonNode; | ||
for (int i = 0; i < arrayNode.size(); i++) { | ||
JsonNode childNode = arrayNode.get(i); | ||
findLinks(childNode); | ||
} | ||
} else if (jsonNode instanceof ObjectNode) { | ||
ObjectNode objectNode = (ObjectNode) jsonNode; | ||
Iterator<Map.Entry<String, JsonNode>> fields = objectNode.fields(); | ||
while (fields.hasNext()) { | ||
Map.Entry<String, JsonNode> entry = fields.next(); | ||
String name = entry.getKey(); | ||
if (name.equals("links")) { | ||
JsonNode linksValue = entry.getValue(); | ||
if(!(linksValue instanceof ObjectNode)){ | ||
throw new IllegalStateException("illegal use of links field in " + currentUrl + ": " + linksValue); | ||
} | ||
collectLinks((ObjectNode) linksValue); | ||
} else { | ||
findLinks(entry.getValue()); | ||
} | ||
} | ||
} | ||
} | ||
|
||
private void collectLinks(ObjectNode linksNode) { | ||
Iterator<Map.Entry<String, JsonNode>> iterator = linksNode.fields(); | ||
while (iterator.hasNext()) { | ||
Map.Entry<String, JsonNode> entry = iterator.next(); | ||
JsonNode link = entry.getValue(); | ||
String url = link.asText(); | ||
if (url == null || !url.startsWith("http")) { | ||
try { | ||
String linkName = entry.getKey(); | ||
throw new IllegalStateException( | ||
"expected link `" + linkName + "` from " + currentUrl + " to contain a valid link, got " + url + " from " + mapper.writer().writeValueAsString(linksNode)); | ||
} catch (JsonProcessingException e) { | ||
throw new IllegalStateException(e); | ||
} | ||
} else if (!visited.contains(url)) { | ||
upcoming.add(url); | ||
} | ||
} | ||
} | ||
} |
42 changes: 42 additions & 0 deletions
42
crnk-testkit/src/test/java/io/crnk/testkit/RandomWalkLinkCheckerTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
package io.crnk.testkit; | ||
|
||
|
||
import java.util.Set; | ||
|
||
import io.crnk.client.http.inmemory.InMemoryHttpAdapter; | ||
import io.crnk.core.boot.CrnkBoot; | ||
import io.crnk.core.module.SimpleModule; | ||
import io.crnk.core.repository.InMemoryResourceRepository; | ||
import io.crnk.home.HomeModule; | ||
import io.crnk.test.mock.models.BulkTask; | ||
import io.crnk.test.mock.models.HistoricTask; | ||
import org.junit.Assert; | ||
import org.junit.Test; | ||
|
||
public class RandomWalkLinkCheckerTest { | ||
|
||
@Test | ||
public void test() { | ||
SimpleModule testModule = new SimpleModule("test"); | ||
testModule.addRepository(new InMemoryResourceRepository<>(BulkTask.class)); | ||
testModule.addRepository(new InMemoryResourceRepository<>(HistoricTask.class)); | ||
|
||
CrnkBoot boot = new CrnkBoot(); | ||
boot.addModule(HomeModule.create()); | ||
boot.addModule(testModule); | ||
boot.boot(); | ||
|
||
String baseUrl = "http://localhost"; | ||
InMemoryHttpAdapter adapter = new InMemoryHttpAdapter(boot, baseUrl); | ||
|
||
RandomWalkLinkChecker checker = new RandomWalkLinkChecker(adapter); | ||
checker.addStartUrl(baseUrl + "/"); | ||
Set<String> visited = checker.performCheck(); | ||
Assert.assertTrue(visited.contains("http://localhost/tasks/history")); | ||
Assert.assertTrue(visited.contains("http://localhost/")); | ||
Assert.assertTrue(visited.contains("http://localhost/bulkTasks")); | ||
Assert.assertTrue(visited.contains("http://localhost/tasks/")); | ||
Assert.assertEquals(4, visited.size()); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters