Skip to content

Commit b2a8d1a

Browse files
BasLeehayco
andauthored
Integration test (#76)
* Remove nonexistend config file of already failing test * WIP. Call get resource in test without exceptions * Add about resource test * Fix test-compile source path in pom to run tests with mvn test * Clean up about resource test * Revert debug pom changes as copied from textrepo * Remove failing and obsolete anno repo tests * Test project list * Mock es search response with mock server * WIP. Show mockserver expectation logging * Remove unused test config * WIP. Mockserver checks request body (search test fails) * Add test json resources to repo * Use fully configured app extension to fix faulty es query mapper that fails to use @JsonProperty annotations in integration test * Configure body type and update response.json to verify resource output * Test response json instead of string * Fix jackson deps by depending on their BOM * Add textTokenCount to expected search response --------- Co-authored-by: HDJ <[email protected]>
1 parent e977d47 commit b2a8d1a

File tree

18 files changed

+412
-117
lines changed

18 files changed

+412
-117
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@ target/
1414
/.classpath
1515
/.project
1616
/.make
17+
1718
*.json
19+
!src/test/resources/**/*.json
20+
1821
*.lst
1922
*.log
2023
globalise.http

pom.xml

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,11 @@
3434
<dropwizard-swagger-ui.version>4.6.2</dropwizard-swagger-ui.version>
3535
<dropwizard-swagger.version>4.0.5-1</dropwizard-swagger.version>
3636
<dropwizard.version>4.0.1</dropwizard.version>
37-
<jackson-module-kotlin.version>2.19.0</jackson-module-kotlin.version>
37+
<jackson-bom.version>2.19.0</jackson-bom.version>
3838
<json-path.version>2.9.0</json-path.version>
3939
<mockito-kotlin.version>5.1.0</mockito-kotlin.version>
4040
<mockito.version>5.5.0</mockito.version>
41+
<mockserver.version>5.15.0</mockserver.version>
4142

4243
<maven-compiler-plugin.version>3.11.0</maven-compiler-plugin.version>
4344
<maven-jar-plugin.version>3.3.0</maven-jar-plugin.version>
@@ -57,6 +58,14 @@
5758
<type>pom</type>
5859
<scope>import</scope>
5960
</dependency>
61+
62+
<!-- https://github.com/FasterXML/jackson-annotations/issues/284#issuecomment-2837647751
63+
https://github.com/FasterXML/jackson-annotations/issues/284#issuecomment-2839601763 -->
64+
<dependency>
65+
<groupId>com.fasterxml.jackson.module</groupId>
66+
<artifactId>jackson-bom</artifactId>
67+
<version>${jackson-bom.version}</version>
68+
</dependency>
6069
</dependencies>
6170
</dependencyManagement>
6271

@@ -89,7 +98,7 @@
8998
</goals>
9099
<configuration>
91100
<sourceDirs>
92-
<sourceDir>${project.basedir}/src/main/kotlin</sourceDir>
101+
<sourceDir>${project.basedir}/src/test/kotlin</sourceDir>
93102
</sourceDirs>
94103
</configuration>
95104
</execution>
@@ -245,10 +254,16 @@
245254
<groupId>io.dropwizard</groupId>
246255
<artifactId>dropwizard-assets</artifactId>
247256
</dependency>
257+
248258
<dependency>
249259
<groupId>com.fasterxml.jackson.core</groupId>
250260
<artifactId>jackson-annotations</artifactId>
251261
</dependency>
262+
<!-- https://github.com/FasterXML/jackson/discussions/170 -->
263+
<dependency>
264+
<groupId>com.fasterxml.jackson.module</groupId>
265+
<artifactId>jackson-module-parameter-names</artifactId>
266+
</dependency>
252267

253268
<dependency>
254269
<groupId>io.dropwizard</groupId>
@@ -292,7 +307,6 @@
292307
<dependency>
293308
<groupId>com.fasterxml.jackson.module</groupId>
294309
<artifactId>jackson-module-kotlin</artifactId>
295-
<version>${jackson-module-kotlin.version}</version>
296310
</dependency>
297311

298312
<!-- test dependencies -->
@@ -341,6 +355,32 @@
341355
<version>${mockito-kotlin.version}</version>
342356
<scope>test</scope>
343357
</dependency>
358+
359+
<!-- testing: mock server -->
360+
<dependency>
361+
<groupId>org.mock-server</groupId>
362+
<artifactId>mockserver-netty</artifactId>
363+
<version>${mockserver.version}</version>
364+
<scope>test</scope>
365+
<exclusions>
366+
<exclusion>
367+
<groupId>junit</groupId>
368+
<artifactId>junit</artifactId>
369+
</exclusion>
370+
</exclusions>
371+
</dependency>
372+
<dependency>
373+
<groupId>org.mock-server</groupId>
374+
<artifactId>mockserver-client-java</artifactId>
375+
<version>${mockserver.version}</version>
376+
<exclusions>
377+
<exclusion>
378+
<groupId>junit</groupId>
379+
<artifactId>junit</artifactId>
380+
</exclusion>
381+
</exclusions>
382+
<scope>test</scope>
383+
</dependency>
344384
</dependencies>
345385

346386
</project>

src/main/kotlin/nl/knaw/huc/broccoli/BroccoliApplication.kt

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ package nl.knaw.huc.broccoli
22

33
import com.fasterxml.jackson.databind.ObjectMapper
44
import com.fasterxml.jackson.module.kotlin.registerKotlinModule
5+
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule
56
import com.jayway.jsonpath.Configuration
67
import com.jayway.jsonpath.JsonPath
78
import com.jayway.jsonpath.Option
9+
import com.jayway.jsonpath.ParseContext
810
import io.dropwizard.assets.AssetsBundle
911
import io.dropwizard.client.JerseyClientBuilder
1012
import io.dropwizard.client.JerseyClientConfiguration
@@ -73,10 +75,9 @@ class BroccoliApplication : Application<BroccoliConfiguration>() {
7375

7476
val client = createClient(configuration.jerseyClient, environment)
7577

76-
val jsonParser =
77-
JsonPath.using(Configuration.defaultConfiguration().addOptions(Option.DEFAULT_PATH_LEAF_TO_NULL))
78+
val jsonParser = createJsonParser()
7879

79-
val objectMapper = ObjectMapper().registerKotlinModule()
80+
val objectMapper = createJsonMapper()
8081

8182
with(environment.jersey()) {
8283
register(AboutResource(configuration, name, appVersion))
@@ -94,6 +95,7 @@ class BroccoliApplication : Application<BroccoliConfiguration>() {
9495
)
9596
}
9697

98+
9799
private fun createClient(jerseyClient: JerseyClientConfiguration, environment: Environment): Client {
98100
return JerseyClientBuilder(environment)
99101
.using(jerseyClient)
@@ -120,7 +122,7 @@ class BroccoliApplication : Application<BroccoliConfiguration>() {
120122
TextRepo(uri, apiKey)
121123
}
122124

123-
private fun createAnnoRepo(annoRepoConfig: AnnoRepoConfiguration, textType: String) =
125+
fun createAnnoRepo(annoRepoConfig: AnnoRepoConfiguration, textType: String) =
124126
with(annoRepoConfig) {
125127
val serverURI = URI.create(uri)
126128
val userAgent = "$name (${this@BroccoliApplication.javaClass.name}/$appVersion)"
@@ -149,5 +151,14 @@ class BroccoliApplication : Application<BroccoliConfiguration>() {
149151
fun main(args: Array<String>) {
150152
BroccoliApplication().run(*args)
151153
}
154+
155+
fun createJsonParser(): ParseContext =
156+
JsonPath.using(
157+
Configuration.defaultConfiguration()
158+
.addOptions(Option.DEFAULT_PATH_LEAF_TO_NULL)
159+
)
160+
161+
fun createJsonMapper() = ObjectMapper()
162+
.registerKotlinModule()
152163
}
153164
}

src/main/kotlin/nl/knaw/huc/broccoli/api/IndexQuery.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package nl.knaw.huc.broccoli.api
22

3+
import com.fasterxml.jackson.annotation.JsonCreator
34
import com.fasterxml.jackson.annotation.JsonProperty
45

5-
data class IndexQuery(
6+
data class IndexQuery
7+
@JsonCreator
8+
constructor(
69
val date: IndexRange?,
710
val terms: IndexTerms?,
811
val text: String?,

src/main/kotlin/nl/knaw/huc/broccoli/resources/AboutResource.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,6 @@ class AboutResource(
3535
private fun findHucLogLevel(): String {
3636
val loggerContext = LoggerFactory.getILoggerFactory()
3737
val logger: Logger = (loggerContext as LoggerContext).getLogger("nl.knaw.huc")
38-
return logger.level.toString()
38+
return logger.level?.toString() ?: ""
3939
}
4040
}

src/main/kotlin/nl/knaw/huc/broccoli/resources/projects/ProjectsResource.kt

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,10 @@ class ProjectsResource(
6767

6868
@POST
6969
@Path("{projectId}/search")
70+
@Consumes(MediaType.APPLICATION_JSON)
7071
fun searchIndex(
71-
@PathParam("projectId") projectId: String,
7272
queryString: IndexQuery,
73+
@PathParam("projectId") projectId: String,
7374
@QueryParam("indexName") indexParam: String?,
7475
@QueryParam("from") @Min(0) @DefaultValue("0") from: Int,
7576
@QueryParam("size") @Min(0) @DefaultValue("10") size: Int,
@@ -143,8 +144,12 @@ class ProjectsResource(
143144
auxQueries.forEachIndexed { auxIndex, auxQuery ->
144145
logger.atTrace().addKeyValue("query[$auxIndex]", jsonWriter.writeValueAsString(auxQuery)).log("aux")
145146

146-
val auxResult = client.target(project.brinta.uri).path(index.name).path("_search")
147-
.request().post(Entity.json(auxQuery))
147+
val auxResult = client
148+
.target(project.brinta.uri)
149+
.path(index.name)
150+
.path("_search")
151+
.request()
152+
.post(Entity.json(auxQuery))
148153
validateElasticResult(auxResult, queryString)
149154
val auxJson = auxResult.readEntityAsJsonString()
150155
logger.atTrace().addKeyValue("json[$auxIndex]", auxJson).log("aux")

src/test/kotlin/TestUtils.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import org.apache.commons.io.IOUtils;
2+
3+
import java.io.IOException;
4+
import java.io.InputStream;
5+
6+
import static java.lang.String.format;
7+
import static java.nio.charset.StandardCharsets.UTF_8;
8+
9+
public class TestUtils {
10+
public static String resourceAsString(String resourcePath) throws IOException {
11+
var stream = getInputStream(resourcePath);
12+
return IOUtils.toString(stream, UTF_8);
13+
}
14+
15+
private static InputStream getInputStream(String resourcePath) {
16+
var stream = TestUtils.class.getClassLoader().getResourceAsStream(resourcePath);
17+
if (stream == null) {
18+
throw new RuntimeException(format("Could not find resource [%s]", resourcePath));
19+
}
20+
return stream;
21+
}
22+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import com.jayway.jsonpath.JsonPath.parse
2+
import io.dropwizard.testing.junit5.DropwizardExtensionsSupport
3+
import io.dropwizard.testing.junit5.ResourceExtension
4+
import jakarta.ws.rs.core.Response
5+
import nl.knaw.huc.broccoli.api.ResourcePaths.ABOUT
6+
import nl.knaw.huc.broccoli.config.BroccoliConfiguration
7+
import nl.knaw.huc.broccoli.resources.AboutResource
8+
import org.assertj.core.api.Assertions.assertThat
9+
import org.junit.jupiter.api.Test
10+
import org.junit.jupiter.api.extension.ExtendWith
11+
12+
13+
@ExtendWith(DropwizardExtensionsSupport::class)
14+
class AboutResourceTest {
15+
16+
val resource: ResourceExtension
17+
val appName = "testApp"
18+
19+
init {
20+
val aboutResource = AboutResource(
21+
BroccoliConfiguration(),
22+
appName,
23+
"version"
24+
)
25+
resource = ResourceExtension
26+
.builder()
27+
.addResource(aboutResource)
28+
.build()
29+
}
30+
31+
@Test
32+
fun `AboutResource should provide app name`() {
33+
val response: Response =
34+
resource.client()
35+
.target("/$ABOUT")
36+
.request()
37+
.get()
38+
39+
assertThat(response.status).isEqualTo(200)
40+
val body = response.readEntity(String::class.java)
41+
val json = parse(body)
42+
assertThat(json.read<String>("$.appName"))
43+
.isEqualTo(appName)
44+
}
45+
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package nl.knaw.huc.broccoli.resources.projects;
2+
3+
import TestUtils.resourceAsString
4+
import com.google.gson.Gson
5+
import com.google.gson.JsonParser
6+
import io.dropwizard.testing.ResourceHelpers.resourceFilePath
7+
import io.dropwizard.testing.junit5.DropwizardAppExtension
8+
import io.dropwizard.testing.junit5.DropwizardExtensionsSupport
9+
import jakarta.ws.rs.client.Entity
10+
import jakarta.ws.rs.core.MediaType.APPLICATION_JSON_TYPE
11+
import jakarta.ws.rs.core.Response
12+
import nl.knaw.huc.broccoli.BroccoliApplication
13+
import nl.knaw.huc.broccoli.api.IndexQuery
14+
import nl.knaw.huc.broccoli.api.ResourcePaths.PROJECTS
15+
import nl.knaw.huc.broccoli.config.BroccoliConfiguration
16+
import nl.knaw.huc.broccoli.service.readEntityAsJsonString
17+
import org.assertj.core.api.Assertions.assertThat
18+
import org.junit.jupiter.api.BeforeAll
19+
import org.junit.jupiter.api.Test
20+
import org.junit.jupiter.api.TestInstance
21+
import org.junit.jupiter.api.extension.ExtendWith
22+
import org.mockserver.integration.ClientAndServer
23+
import org.mockserver.model.HttpRequest.request
24+
import org.mockserver.model.HttpResponse.response
25+
import org.mockserver.model.JsonBody.json
26+
27+
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
28+
@ExtendWith(DropwizardExtensionsSupport::class)
29+
class ProjectsResourceTest {
30+
31+
lateinit var mockServer: ClientAndServer
32+
var projectId = "dummy"
33+
lateinit var application: DropwizardAppExtension<BroccoliConfiguration>
34+
35+
36+
@BeforeAll
37+
fun setup() {
38+
mockServer =
39+
ClientAndServer.startClientAndServer(9200)
40+
41+
mockServer
42+
.`when`(request().withPath("/anno-repo.*"))
43+
.respond(response().withStatusCode(200).withBody(json(
44+
resourceAsString("./arAboutResponse.json")
45+
)))
46+
47+
application = DropwizardAppExtension(
48+
BroccoliApplication::class.java,
49+
resourceFilePath("./broccoliConfig.json")
50+
)
51+
52+
}
53+
54+
@Test
55+
fun `ProjectsResource should list projects`() {
56+
val response: Response =
57+
application.client().target(toUrl("/$PROJECTS"))
58+
.request()
59+
.get()
60+
assertThat(response.status)
61+
.isEqualTo(Response.Status.OK.statusCode)
62+
assertThat(response.readEntityAsJsonString()).isEqualTo("[\"${projectId}\"]")
63+
}
64+
65+
@Test
66+
fun `ProjectsResource should search`() {
67+
val request =
68+
resourceAsString("./projects/search/request.json")
69+
70+
val esRequest =
71+
resourceAsString("./projects/search/esRequest.json")
72+
val esResponse =
73+
resourceAsString("./projects/search/esResponse.json")
74+
val exp = mockServer.`when`(
75+
request()
76+
.withBody(json(esRequest))
77+
.withPath("/dummy-index/_search")
78+
).respond(
79+
response()
80+
.withStatusCode(200)
81+
.withBody(esResponse)
82+
.withHeader("Content-Type", "application/json")
83+
)
84+
85+
val query: IndexQuery = Gson().fromJson(request, IndexQuery::class.java)
86+
val target = toUrl("/$PROJECTS/${projectId}/search")
87+
val response: Response =
88+
application.client()
89+
.target(target)
90+
.request()
91+
.post(Entity.entity(query, APPLICATION_JSON_TYPE))
92+
assertThat(response.status)
93+
.isEqualTo(Response.Status.OK.statusCode)
94+
mockServer.verify(exp[0].id)
95+
val expectedJson =
96+
JsonParser.parseString(resourceAsString("./projects/search/response.json"))
97+
val receivedJson =
98+
JsonParser.parseString(response.readEntityAsJsonString())
99+
assertThat(receivedJson)
100+
.isEqualTo(expectedJson)
101+
}
102+
103+
/**
104+
* Application context needs host
105+
*/
106+
fun toUrl(path: String): String {
107+
return String.format("http://localhost:%d$path", application.localPort)
108+
}
109+
}

0 commit comments

Comments
 (0)