Skip to content

Commit

Permalink
Merge pull request #58 from allegro/simple-wiremock-test
Browse files Browse the repository at this point in the history
Add /emails/{id} resource and corresponding test to part2.2-rest
  • Loading branch information
3750 authored Apr 26, 2024
2 parents f326716 + ea6a6ff commit c22ca41
Show file tree
Hide file tree
Showing 22 changed files with 248 additions and 68 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package pl.allegro.tech.workshops.testsparallelexecution


import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.web.client.TestRestTemplate
Expand Down
11 changes: 11 additions & 0 deletions part2.2-rest/.readme/sequence.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,15 @@ sequenceDiagram
REST API ->>+ external service: POST /external-api-service/emails <br/>{"subject": "New ...", "sender": "...", "recipient": "..."}
external service -->>- REST API: response
REST API -->>- User: response
Note over User, external service: read e-mail
User ->>+ REST API: GET /emails/{id}
REST API ->>+ external service: GET /external-api-service/emails/{id}
external service -->>- REST API: response <br/>{"subject": "New ...", "sender": "...", "recipient": "..."}
REST API -->>- User: response <br/>{"subject": "New ...", "sender": "...", "recipient": "..."}
```

Convert to svg format

```shell
npx @mermaid-js/mermaid-cli mmdc -i part2.2-rest/.readme/sequence.md -o part2.2-rest/.readme/sequence.svg -t dark -b "" --cssFile .readme/diagrams.css
```
2 changes: 1 addition & 1 deletion part2.2-rest/.readme/sequence.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion part2.2-rest/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ Check [`tests`](src/test/groovy).
4. Run tests `./gradlew --rerun-tasks :part2.2-rest:test :part2.2-rest:createTestsExecutionReport --continue`
5. Temporarily disable test `retry email sending after error response ...` - add `@Ignore` annotation to method
containing this test.
6. Determine and remove shared state.
6. Temporarily disable tests in `SendEmailResourceTest` class - add `@Ignore` annotation this class.
7. Determine and remove shared state.

#### Shared state

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package pl.allegro.tech.workshops.testsparallelexecution.email.rest;


import jakarta.validation.constraints.NotBlank;

public record Email(String subject, @NotBlank String sender, String recipient) {

static public Email of(String subject, String sender, String recipient) {
return new Email(subject, sender, recipient);
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package pl.allegro.tech.workshops.testsparallelexecution.email.rest;

public interface EmailClient {
void send(EmailRequest email);
void send(Email email);

Email read(String id);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package pl.allegro.tech.workshops.testsparallelexecution.email.rest;

import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
Expand All @@ -15,8 +17,13 @@ public EmailController(EmailService emailService) {
this.emailService = emailService;
}

@GetMapping("/{id}")
public Email readEmail(@PathVariable String id) {
return emailService.readEmail(id);
}

@PostMapping()
public void createEmail(@Valid @RequestBody EmailRequest email) {
public void createEmail(@Valid @RequestBody Email email) {
emailService.sendEmail(email);
}

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public EmailService(EmailClient emailClient) {
this.emailClient = emailClient;
}

public void sendEmail(EmailRequest email) {
public void sendEmail(Email email) {
try {
emailClient.send(email);
} catch (Exception e) {
Expand All @@ -26,4 +26,16 @@ public void sendEmail(EmailRequest email) {
e);
}
}

public Email readEmail(String id) {
try {
return emailClient.read(id);
} catch (Exception e) {
throw new ErrorResponseException(
HttpStatus.INTERNAL_SERVER_ERROR,
ProblemDetail.forStatusAndDetail(HttpStatus.INTERNAL_SERVER_ERROR, "Email service communication error. " + e.getMessage()),
e);
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,13 @@ public ExternalEmailClient(RestTemplateBuilder builder, @Value("${application.se
}

@Override
public void send(EmailRequest email) {
public void send(Email email) {
retryTemplate.execute(context -> restTemplate.postForEntity("/external-api-service/emails", email, Void.class));
}

@Override
public Email read(String id) {
return retryTemplate.execute(context -> restTemplate.getForEntity("/external-api-service/emails/" + id, Email.class).getBody());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package pl.allegro.tech.workshops.testsparallelexecution.email.rest

import com.github.tomakehurst.wiremock.WireMockServer
import com.github.tomakehurst.wiremock.stubbing.Scenario
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Import
import org.springframework.http.ProblemDetail
import pl.allegro.tech.workshops.testsparallelexecution.BaseTestWithRest

import static com.github.tomakehurst.wiremock.client.WireMock.aResponse
import static com.github.tomakehurst.wiremock.client.WireMock.get
import static com.github.tomakehurst.wiremock.client.WireMock.urlPathMatching
import static com.github.tomakehurst.wiremock.http.Fault.CONNECTION_RESET_BY_PEER
import static com.github.tomakehurst.wiremock.http.Fault.EMPTY_RESPONSE
import static org.springframework.http.HttpHeaders.CONTENT_TYPE
import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR
import static org.springframework.http.HttpStatus.OK
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE

@Import(WiremockConfig)
class GetEmailResourceTest extends BaseTestWithRest implements WiremockPortSupport {

@Autowired
private WireMockServer wiremockServer

private static final String VALID_BODY = """{"subject": "test subject", "sender": "[email protected]", "recipient": "[email protected]"}"""

private String emailId = "2"

def cleanup() {
wiremockServer.resetAll()
wiremockServer.resetScenarios()
}

def "get e-mail"() {
given:
wiremockServer.stubFor(get(urlPathMatching("/external-api-service/emails/.*"))
.willReturn(aResponse()
.withHeader(CONTENT_TYPE, APPLICATION_JSON_VALUE)
.withStatus(200)
.withBody("""{"subject": "test subject", "sender": "[email protected]", "recipient": "[email protected]"}""")
)
)

when:
def result = restClient.get("/emails/$emailId", Email)

then:
result.statusCode == OK
result.body == Email.of("test subject", "[email protected]", "[email protected]")
}

def "handle email service errors (status=#errorResponse.status, fault=#errorResponse.fault, delay=#errorResponse.fixedDelayMilliseconds)"() {
given:
wiremockServer.stubFor(get(urlPathMatching("/external-api-service/emails/.*"))
.willReturn(errorResponse)
)
when:
def result = restClient.get("/emails/$emailId", ProblemDetail)
then:
result.statusCode == INTERNAL_SERVER_ERROR
result.body.detail.contains expectedDetail
where:
errorResponse || expectedDetail
aResponse().withStatus(400) || "400 Bad Request"
aResponse().withStatus(500) || "500 Server Error"
aResponse().withFault(EMPTY_RESPONSE) || "Unexpected end of file from server"
aResponse().withFault(CONNECTION_RESET_BY_PEER) || "Connection reset"
aResponse().withFixedDelay(1000)
.withHeader(CONTENT_TYPE, APPLICATION_JSON_VALUE)
.withStatus(200)
.withBody(VALID_BODY) || "Read timed out"
}
def "retry email fetching after error response (status=#errorResponse.status, fault=#errorResponse.fault, delay=#errorResponse.fixedDelayMilliseconds)"() {
given:
wiremockServer.stubFor(get(urlPathMatching("/external-api-service/emails/.*"))
.willReturn(errorResponse)
.inScenario("retry scenario")
.whenScenarioStateIs(Scenario.STARTED)
.willSetStateTo('after error')
)
wiremockServer.stubFor(get(urlPathMatching("/external-api-service/emails/.*"))
.willReturn(aResponse()
.withHeader(CONTENT_TYPE, APPLICATION_JSON_VALUE)
.withStatus(200)
.withBody("""{"subject": "test subject", "sender": "[email protected]", "recipient": "[email protected]"}""")
)
.inScenario("retry scenario")
.whenScenarioStateIs('after error')
.willSetStateTo('after ok')
)
when:
def result = restClient.get("/emails/$emailId", Email)
then:
result.statusCode == OK
result.body == Email.of("test subject", "[email protected]", "[email protected]")
where:
errorResponse << [
aResponse().withStatus(400),
aResponse().withStatus(500),
aResponse().withFault(EMPTY_RESPONSE),
aResponse().withFault(CONNECTION_RESET_BY_PEER),
aResponse().withFixedDelay(1000)
.withHeader(CONTENT_TYPE, APPLICATION_JSON_VALUE)
.withStatus(200)
.withBody(VALID_BODY)
]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@ package pl.allegro.tech.workshops.testsparallelexecution.email.rest

import com.github.tomakehurst.wiremock.WireMockServer
import com.github.tomakehurst.wiremock.stubbing.Scenario
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Import
import org.springframework.http.ProblemDetail
import pl.allegro.tech.workshops.testsparallelexecution.BaseTestWithRest
import spock.lang.Shared

import static com.github.tomakehurst.wiremock.client.WireMock.aResponse
import static com.github.tomakehurst.wiremock.client.WireMock.equalTo
import static com.github.tomakehurst.wiremock.client.WireMock.matchingJsonPath
import static com.github.tomakehurst.wiremock.client.WireMock.post
import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo
import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo
import static com.github.tomakehurst.wiremock.http.Fault.CONNECTION_RESET_BY_PEER
import static com.github.tomakehurst.wiremock.http.Fault.EMPTY_RESPONSE
import static org.springframework.http.HttpHeaders.ACCEPT
Expand Down Expand Up @@ -39,31 +39,23 @@ import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE
}
</pre>
*/
class EmailsByRestResourceTest extends BaseTestWithRest {
@Import(WiremockConfig)
class SendEmailResourceTest extends BaseTestWithRest implements WiremockPortSupport {

@Shared
WireMockServer wiremockServer
@Autowired
private WireMockServer wiremockServer

private String subject = "New workshops!"

def setupSpec() {
wiremockServer = new WireMockServer(8099)
wiremockServer.start()
}

def cleanupSpec() {
wiremockServer.stop()
}

def cleanup() {
wiremockServer.resetAll()
wiremockServer.resetScenarios()
}

def "send e-mail"() {
given:
def email = EmailRequest.of(subject, "[email protected]", "[email protected]")
wiremockServer.stubFor(post(urlEqualTo("/external-api-service/emails"))
def email = Email.of(subject, "[email protected]", "[email protected]")
wiremockServer.stubFor(post(urlPathEqualTo("/external-api-service/emails"))
.willReturn(aResponse()
.withHeader(CONTENT_TYPE, APPLICATION_JSON_VALUE)
.withStatus(200)
Expand All @@ -75,15 +67,15 @@ class EmailsByRestResourceTest extends BaseTestWithRest {

then:
result.statusCode == OK
wiremockServer.verify(1, postRequestedFor(urlEqualTo("/external-api-service/emails"))
wiremockServer.verify(1, postRequestedFor(urlPathEqualTo("/external-api-service/emails"))
.withHeader(ACCEPT, equalTo("application/json, application/*+json"))
)
}

def "do not sent email without sender"() {
given:
def email = EmailRequest.of(subject, sender, "[email protected]")
wiremockServer.stubFor(post(urlEqualTo("/external-api-service/emails"))
def email = Email.of(subject, sender, "[email protected]")
wiremockServer.stubFor(post(urlPathEqualTo("/external-api-service/emails"))
.willReturn(aResponse()
.withHeader(CONTENT_TYPE, APPLICATION_JSON_VALUE)
.withStatus(200)
Expand All @@ -97,7 +89,7 @@ class EmailsByRestResourceTest extends BaseTestWithRest {

then:
result.statusCode == BAD_REQUEST
wiremockServer.verify(0, postRequestedFor(urlEqualTo("/external-api-service/emails"))
wiremockServer.verify(0, postRequestedFor(urlPathEqualTo("/external-api-service/emails"))
.withHeader(ACCEPT, equalTo("application/json, application/*+json"))
)

Expand All @@ -106,8 +98,9 @@ class EmailsByRestResourceTest extends BaseTestWithRest {
}

def "handle email service errors (status=#errorResponse.status, fault=#errorResponse.fault, delay=#errorResponse.fixedDelayMilliseconds)"() {
def email = EmailRequest.of(subject, "from@example.com", "[email protected]")
wiremockServer.stubFor(post(urlEqualTo("/external-api-service/emails"))
given:
def email = Email.of(subject, "from@example.com", "[email protected]")
wiremockServer.stubFor(post(urlPathEqualTo("/external-api-service/emails"))
.willReturn(errorResponse)
)

Expand All @@ -130,14 +123,15 @@ class EmailsByRestResourceTest extends BaseTestWithRest {
}

def "retry email sending after error response (status=#errorResponse.status, fault=#errorResponse.fault, delay=#errorResponse.fixedDelayMilliseconds)"() {
def email = EmailRequest.of(subject, "from@example.com", "[email protected]")
wiremockServer.stubFor(post(urlEqualTo("/external-api-service/emails"))
given:
def email = Email.of(subject, "from@example.com", "[email protected]")
wiremockServer.stubFor(post(urlPathEqualTo("/external-api-service/emails"))
.willReturn(errorResponse)
.inScenario("retry scenario")
.whenScenarioStateIs(Scenario.STARTED)
.willSetStateTo('after error')
)
wiremockServer.stubFor(post(urlEqualTo("/external-api-service/emails"))
wiremockServer.stubFor(post(urlPathEqualTo("/external-api-service/emails"))
.willReturn(aResponse()
.withHeader(CONTENT_TYPE, APPLICATION_JSON_VALUE)
.withStatus(200)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package pl.allegro.tech.workshops.testsparallelexecution.email.rest

import com.github.tomakehurst.wiremock.WireMockServer
import org.springframework.beans.factory.annotation.Value
import org.springframework.boot.test.context.TestConfiguration
import org.springframework.context.annotation.Bean

@TestConfiguration
class WiremockConfig {

@Bean
WireMockServer getWiremockServer(@Value('${wiremock.port}') int wiremockPort) {
return new WireMockServer(wiremockPort).tap {
it.start()
}
}
}
Loading

0 comments on commit c22ca41

Please sign in to comment.