https://github.com/ivankatliarchuk/sc-contract-car-rental
consumer app
https://github.com/ivankatliarchuk/sc-contract-car-rental
Last synced: 8 months ago
JSON representation
consumer app
- Host: GitHub
- URL: https://github.com/ivankatliarchuk/sc-contract-car-rental
- Owner: ivankatliarchuk
- License: apache-2.0
- Created: 2017-12-08T08:40:28.000Z (over 8 years ago)
- Default Branch: master
- Last Pushed: 2017-11-08T17:34:08.000Z (over 8 years ago)
- Last Synced: 2025-05-07T22:35:08.820Z (about 1 year ago)
- Language: Shell
- Size: 117 KB
- Stars: 0
- Watchers: 1
- Forks: 24
- Open Issues: 0
-
Metadata Files:
- Readme: README.adoc
- License: LICENSE
Awesome Lists containing this project
README
= Spring Cloud Contract Car Rental
== car-rental (consumer)
Sends an HTTP request to fraud detection from a test
Gets a Stream message from fraud detection
== fraud-detection (producer)
Producer of the HTTP api and messages
== Presentation steps
=== Before
Start Rabbit & Eureka
[souce,bash]
----
$ docker-compose up -d
$ spring cloud eureka
----
=== Start.spring.io
=== car-rental
web, stubrunner, stream rabbit, wiremock, Eureka Discovery
and `stream-test-support` for Contract & Stream
```xml
org.springframework.cloud
spring-cloud-stream-test-support
test
```
=== fraud-detection
web, stream rabbit, verifier, REST Docs, wiremock, Eureka Discovery
and `stream-test-support` for Contract & Stream
```xml
org.springframework.cloud
spring-cloud-stream-test-support
test
```
and `plugin` - REMEMBER ABOUT `EXTENSIONS`
```xml
org.springframework.cloud
spring-cloud-contract-maven-plugin
${spring-cloud-contract.version}
true
com.example.frauddetection.BaseClass
```
=== Code
==== HTTP
*CONSUMER*
- Generate consumer from start.spring.io
- Add the missing dependencies
- Start with a test on the consumer side
- Write a not passing test to reach the `/fraud` endpoint of the producer
- Add the WireMock stub to make the test pass (port `6543`)
*PRODUCER*
- Generate producer from start.spring.io
- Add the missing dependencies
- Write a contract for the `/frauds` endpoint (the typo is deliberate)
- Configure the plugin with `baseClassForTests`
- Run `./mvnw clean install` - show breaking tests
- Write the controller
- Write missing base class setup
- Rerun the `./mvnw clean install` - things pass
- Run the app at port `6544`
*CONSUMER*
- Write a new test that will reach the producer application directly (port `6544`)
- The previous test with a stub passes, the new one fails (oops)
- Write a new test with `StubRunnerRule` `com.example:fraud-detection` at (port `6545`)
- The test fails cause first we'll shoot at `/fraud`
- The test passes once we shoot at `/frauds`
==== Messaging
*CONSUMER*
- Enable binding for Sink
- In `application.properties`
* set the port to `9876`
* `spring.cloud.stream.bindings.input.destination=fraud`
* on the producer side it will be `frauds`
- Add a `Fraud` pojo with a `name`
- Add a `FraudListener` `@Component`
* with a method `fraud` that has `@StreamListener(Sink.class)`
* let it print a message out
* and store the name
- Let's write a `FraudTests` class
* we want to see if our listener will work fine if we send it our
POJO
* let's write a test `should_store_info_about_fraud`
* make it a SpringBoot test
* `@Autowired FraudListener`
* `@Autowired Sink`
* given: a `new Fraud("marcin")`
* when: `sink.input().send(MessageBuilder.withPayload(fraud).build());`
* then: `fraudListener.name == "marcin"`
- The test passes - let's go to the producer
*PRODUCER*
- `EnableBinding(Source.class)`
- Set properties
* `spring.cloud.stream.bindings.output.destination=frauds`
* Yup, that's a typo over there ^^
* `spring.cloud.stream.bindings.output.contentType=application/json`
* `server.port=6544`
- Let's write a contract for messaging
* label `trigger_a_fraud`
* input method `triggerMethod()`
* output to destination `frauds`
* body `surname: "Long"`
- Create a `Fraud` pojo in the `FraudController`
- `FraudController` will need a `@PostMapping("/message")` method called `message` that will use `Source`
to send a message with `new Fraud("Long")`
- Let's run `./mvnw clean install` and generate tests
* they will fail cause we have a missing `triggerMethod()`
- Let's create the `triggerMethod()` in the `BaseClass`
* Also we need to add the Spring context with `@AutoConfigureMessageVerifier`
* `@Autowired FraudController`
* call in the `triggerMethod()` the `fraudController.message()`
Let's try to make both apps work! Let's run them together
[source,bash]
----
curl -X POST http://localhost:6544/message
----
Nothing happens... Even though the tests passed. That's for 2 reasons
- the destination is wrong. One is sending to `frauds` the other
listening to `fraud`
- the POJO is wrong. Once expects `name` the other `surname`
Time to fix the consumer
*CONSUMER*
- Let's use Stub Runner
- `@AutoConfigureStubRunner(workOffline = true, ids = "com.example:fraud-detection")`
- `@Autowired StubTrigger`
- `stubTrigger.trigger("trigger_a_fraud");`
* the test won't pass - let's update the destination
- `spring.cloud.stream.bindings.input.destination=frauds`
* let's run again the tests - still they don't pass cause
the name is wrong
- let's change `Fraud` to use `surname`
* now if we rerun the tests they pass
Let's run both apps again, send the CURL - now they should work
==== REST Docs
*PRODUCER*
- We need to write a test for the `message` endpoint
- We'll write a test called `FraudControllerTests`
- Use the test slices `AutoConfigureMockMvc` and `@AutoConfigureRestDocs(outputDir = "target/snippets")`
+
[source,java]
----
@RunWith(SpringRunner.class)
@SpringBootTest(classes = FraudDetectionApplication.class)
@AutoConfigureRestDocs(outputDir = "target/snippets")
@AutoConfigureMockMvc
----
- Write a simple test to see if status OK happens when you send
a POST to `/message/`
+
[souce,java]
----
@Autowired private MockMvc mockMvc;
@Test
public void should_accept_a_post_message() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.post("/message"))
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(MockMvcRestDocumentation.document("message"));
}
----
- Time to configure the build to package stuff properly
- We want to disable the default Spring Cloud Contract packaging
approach
* You can do it by setting `true`
property
- Add the `src/assembly/stub.xml`
+
[source,xml]
----
stubs
jar
false
${project.build.directory}/snippets/stubs
META-INF/${project.groupId}/${project.artifactId}/${project.version}/mappings
**/*
${project.build.directory}/stubs/META-INF/${project.groupId}/${project.artifactId}/${project.version}/mappings
META-INF/${project.groupId}/${project.artifactId}/${project.version}/mappings
**/*
${basedir}/src/test/resources/contracts
META-INF/${project.groupId}/${project.artifactId}/${project.version}/contracts
**/*.groovy
----
- Add the assembly plugin setup
+
[source,xml]
----
org.apache.maven.plugins
maven-assembly-plugin
stub
prepare-package
single
false
true
${basedir}/src/assembly/stub.xml
----
- Run `./mvnw clean install`
*CONSUMER*
- Reuse the previously created setup and just send a `post`
method to `/messages`. Expect status `200`
==== Discovery
*CONSUMER*
- Add `@EnableDiscoveryClient`
- Add `@Bean @LoadBalanced` for `RestTemplate`
- Add `spring.application.name=car-rental`
- In `FraudTests` we'll add another test (we need `@AutoConfigureStubRunner`)
* `@Autowired RestTemplate`
* Use the `RestTemplate` to call `"http://fraud-detection/frauds"`
* The test passes cause SC-Contract redirects the calls via artifact id
==== Stub Runner Boot
- Clone Stub Runner for Eureka & Rabbit from https://github.com/spring-cloud-samples/github-analytics-stub-runner-boot
- Build it locally
- `java -jar target/github-analytics-stub-runner-boot-0.0.1.M1.jar --stubrunner.workoffline=true --stubrunner.ids=com.example:fraud-detection`
- Show http://localhost:8083/stubs
- Show http://localhost:8083/triggers
- Show http://localhost:8761 with registered apps
- Run `car-rental`
- `curl -X POST http://localhost:8083/triggers/trigger_a_fraud`
- Check that in the logs we see that a message was received
- Write a test that isn't annotated with `@AutoConfigureStubRunner`
and inject `RestTemplate` - you can shoot a request to
`fraud-detection` and a stub will respond
* or write a POST endpoint to `/rent` that will receive a
`{"name":"marcin"}` and then it will call `fraud-detection/frauds`
to retrieve a list of frauds. If that string contains the `name`
then set status code `406` and text `NO`. Otherwise `200` and `yes`
and run a curl `curl -X POST -d '{"name":"marcin"}' http://localhost:8765`
==== Stub Runner Boot with a NodeJs app
- Install `npm`
- Install the `request` module `npm install request`
- Ensure that Stub Runner Boot is not running
- Go to `nodejs` folder and execute
+
```
$ node app.js
```
- You will get sth like this
+
```
ERROR - status [404]
```
- Next run stub runner boot and show how easy it is to start a stub (this assumes that `fraud-detection` stubs were installed locally)
+
```
$ git clone https://github.com/spring-cloud-samples/stub-runner-boot
$ cd stub-runner-boot
$ ./mvnw clean install -DskipTests
$ java -jar target/stub-runner-boot-1.1.0.RELEASE.jar --stubrunner.workOffline=true --stubrunner.ids="com.example:fraud-detection:+:9876"
```
or use Spring Cloud CLI
```
$ spring cloud stubrunner
```
- You will get sth like this
+
```javascript
["marcin","josh"]
```