├── Makefile ├── README.md ├── broker ├── docker-compose.yml └── nginx │ └── nginx.conf ├── campaign ├── log │ └── pact.log ├── main.go ├── model.go └── provider_test.go ├── go.mod ├── go.sum ├── images ├── our-arch.png └── pact-arch.svg └── product ├── consumer_test.go ├── main.go └── pact-publish.sh /Makefile: -------------------------------------------------------------------------------- 1 | #make consumer VERSION="1.0.0" 2 | consumer: 3 | rm -rf product/pacts 4 | go test ./... -tags=consumer -count=1 5 | sh product/pact-publish.sh $(VERSION) 6 | 7 | provider: 8 | go test ./... -tags=provider -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CDC Pact 2 | 3 | - **Pact** is a consumer-driven contract testing framework. Born out of a microservices boom, Pact was created to solve 4 | the problem of integration testing large, distributed systems. 5 | (Old definition) 6 | - [How Pact works slide from pactflow.io](https://pactflow.io/how-pact-works/#slide-1) 7 | - ✅ Rescue for integration test cost. 8 | - ✅ Gives fast feedback 9 | - ✅ Implement any programming language you want. [For example](https://docs.pact.io/implementation_guides/cli) 10 | - ✅ No dedicated test environments (it works on dev machine) 11 | 12 | # Project Architecture 🚀 13 | 14 | ![](images/our-arch.png) 15 | 16 | # ℹ️ Terminology 17 | 18 | > Interaction: A request and response pair. A pact consists of a collection of _interactions_. 19 | 20 | > Pact file: A file containing the JSON serialised interactions (requests and responses) that were defined in the 21 | consumer tests. This is the Contract. 22 | 23 | > Pact verification: To verify a Pact contract, the requests contained in a Pact file are replayed against the provider 24 | code, and the responses returned are checked to ensure they match those expected in the Pact file. 25 | 26 | > Pact Specification 27 | A provider state name is specified when writing the consumer specs, then, when the pact verification is set up in the provider 28 | the same name will be used to identify the set up code block that should be run before the request is executed. 29 | 30 | # ℹ️ Pact Broker 31 | The Pact Broker is an open source tool that requires you to deploy, administer and host yourself. 32 | It enables you to share your pacts and verification results between projects 33 | 34 | # Pact Generation and Verification Flow 🚀 35 | 36 | ![](images/pact-arch.svg) 37 | 38 | ## Matching on types 39 | 40 | `dsl.Like(content)` tells Pact that value is not important, just focus type match. 41 | 42 | ## Matching on arrays 43 | 44 | `dsl.EachLike(content, min)` tells Pact that the value should be array type, consisting of elements like those passed 45 | in. `min` must be >= 1. 46 | 47 | ``` 48 | "fabrics": dsl.EachLike(dsl.MapMatcher{ 49 | "title": dsl.Like("Pamuk"), 50 | "count": dsl.Integer() 51 | }, 1), 52 | ``` 53 | 54 | ## Matching by regular expression 55 | 56 | `dsl.Term(example, matcher)` tells Pact that the value should match using a given regular expression, using example in 57 | mock responses. 58 | 59 | `"type": Term("admin", "admin|user|guest")` => "type": "admin" 60 | 61 | ## Match common formats 62 | 63 | | method | description | 64 | |-----------------|-------------------------------------------------------------------------------------------------| 65 | | `Identifier()` | Match an ID (e.g. 42) | 66 | | `Integer()` | Match all numbers that are integers (both ints and longs) | 67 | | `Decimal()` | Match all real numbers (floating point and decimal) | 68 | | `HexValue()` | Match all hexadecimal encoded strings | 69 | | `Date()` | Match string containing basic ISO8601 dates (e.g. 2016-01-01) | 70 | | `Timestamp()` | Match a string containing an RFC3339 formatted timestapm (e.g. Mon, 31 Oct 2016 15:21:41 -0400) | 71 | | `Time()` | Match string containing times in ISO date format (e.g. T22:44:30.652Z) | 72 | | `IPv4Address()` | Match string containing IP4 formatted address | 73 | | `IPv6Address()` | Match string containing IP6 formatted address | 74 | | `UUID()` | Match strings containing UUIDs | 75 | 76 | # 🤘 References 💪 77 | 78 | [Pact Docs](https://docs.pact.io/) 79 | [Pact Go](https://github.com/pact-foundation/pact-go) 80 | [Turkish Microservice Architecture Book](https://github.com/suadev/turkish-microservice-architecture-book) 81 | [Pact Broker Docker](https://github.com/pact-foundation/pact-broker-docker) 82 | [Building Microservices](https://samnewman.io/books/) 83 | [Contract testing and how Pact works](https://www.youtube.com/watch?v=IetyhDr48RI) 84 | [Test Double](https://www.martinfowler.com/bliki/TestDouble.html) 85 | [Difference between gobuild and build directive](https://stackoverflow.com/questions/68360688/whats-the-difference-between-gobuild-and-build-directives) -------------------------------------------------------------------------------- /broker/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | postgres: 5 | image: postgres 6 | healthcheck: 7 | test: psql postgres --command "select 1" -U postgres 8 | #volumes: 9 | # - postgres-volume:/var/lib/postgresql/data 10 | environment: 11 | POSTGRES_USER: postgres 12 | POSTGRES_PASSWORD: password 13 | POSTGRES_DB: postgres 14 | 15 | pact-broker: 16 | image: pactfoundation/pact-broker:2.92.0.0 17 | ports: 18 | - "9292:9292" 19 | depends_on: 20 | - postgres 21 | environment: 22 | PACT_BROKER_PORT: '9292' 23 | PACT_BROKER_DATABASE_URL: "postgres://postgres:password@postgres/postgres" 24 | PACT_BROKER_LOG_LEVEL: INFO 25 | PACT_BROKER_SQL_LOG_LEVEL: DEBUG 26 | # PACT_BROKER_DATABASE_CONNECT_MAX_RETRIES is only needed for docker-compose 27 | # because the database takes longer to start up than the puma process 28 | # Should not be needed in production. 29 | PACT_BROKER_DATABASE_CONNECT_MAX_RETRIES: "5" 30 | # The list of allowed base URLs (not setting this makes the app vulnerable to cache poisoning) 31 | # This allows the app to be addressed from the host from within another docker container correctly 32 | PACT_BROKER_BASE_URL: 'https://localhost http://localhost http://localhost:9292 http://pact-broker:9292' 33 | 34 | # Nginx is not necessary, but demonstrates how 35 | # one might use a reverse proxy in front of the broker, 36 | # and includes the use of a self-signed TLS certificate 37 | nginx: 38 | image: nginx:alpine 39 | depends_on: 40 | - pact-broker 41 | volumes: 42 | - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro 43 | ports: 44 | - "80:80" 45 | volumes: 46 | postgres-volume: -------------------------------------------------------------------------------- /broker/nginx/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80 default_server; 3 | server_name localhost; 4 | 5 | location / { 6 | proxy_pass http://pact-broker:9292; 7 | proxy_set_header Host $host; 8 | proxy_set_header X-Real-IP $remote_addr; 9 | } 10 | } -------------------------------------------------------------------------------- /campaign/log/pact.log: -------------------------------------------------------------------------------- 1 | # Logfile created on 2021-12-10 09:12:54 +0300 by logger.rb/66358 2 | I, [2021-12-10T09:12:54.946961 #84038] INFO -- : Running example 'Verifying a pact between ProductService and CampaignService Given i get new product price with specified discount rate A request for campaign with GET /products/1/discount?rate=30 returns a response which has status code 200' 3 | I, [2021-12-10T09:12:54.958259 #84038] INFO -- : Sending GET request to path: "/products/1/discount?rate=30" with headers: {"X_PACT_PROVIDER_STATES"=>[{"name"=>"i get new product price with specified discount rate"}]}, see debug logs for body 4 | D, [2021-12-10T09:12:54.958330 #84038] DEBUG -- : body : 5 | I, [2021-12-10T09:12:54.969535 #84038] INFO -- : Received response with status: 200, headers: {"Content-Length"=>"38", "Content-Type"=>"application/json", "Date"=>"Fri, 10 Dec 2021 06:12:54 GMT"}, see debug logs for body 6 | D, [2021-12-10T09:12:54.969605 #84038] DEBUG -- : body: {"id":1,"name":"Product 1","price":70} 7 | I, [2021-12-10T09:12:54.971856 #84038] INFO -- : Running example 'Verifying a pact between ProductService and CampaignService Given i get campaign not found error when the product has no discount A request for campaign without discounted product with GET /products/2/discount?rate=30 returns a response which has status code 406' 8 | I, [2021-12-10T09:12:54.981191 #84038] INFO -- : Sending GET request to path: "/products/2/discount?rate=30" with headers: {"X_PACT_PROVIDER_STATES"=>[{"name"=>"i get campaign not found error when the product has no discount"}]}, see debug logs for body 9 | D, [2021-12-10T09:12:54.981262 #84038] DEBUG -- : body : 10 | I, [2021-12-10T09:12:54.983568 #84038] INFO -- : Received response with status: 406, headers: {"Content-Length"=>"48", "Content-Type"=>"application/json", "Date"=>"Fri, 10 Dec 2021 06:12:54 GMT"}, see debug logs for body 11 | D, [2021-12-10T09:12:54.983635 #84038] DEBUG -- : body: {"message":"No campaign found for this product"} 12 | I, [2021-12-10T09:12:54.984489 #84038] INFO -- : Running example 'Verifying a pact between ProductService and CampaignService Given i get the product does not exist A request for campaign with nonexist product with GET /products/3/discount?rate=30 returns a response which has status code 404' 13 | I, [2021-12-10T09:12:54.994350 #84038] INFO -- : Sending GET request to path: "/products/3/discount?rate=30" with headers: {"X_PACT_PROVIDER_STATES"=>[{"name"=>"i get the product does not exist"}]}, see debug logs for body 14 | D, [2021-12-10T09:12:54.994436 #84038] DEBUG -- : body : 15 | I, [2021-12-10T09:12:54.996421 #84038] INFO -- : Received response with status: 404, headers: {"Content-Length"=>"9", "Content-Type"=>"text/plain; charset=utf-8", "Date"=>"Fri, 10 Dec 2021 06:12:54 GMT"}, see debug logs for body 16 | D, [2021-12-10T09:12:54.996475 #84038] DEBUG -- : body: Not Found 17 | I, [2021-12-10T09:13:26.862467 #84223] INFO -- : Running example 'Verifying a pact between ProductService and CampaignService Given i get new product price with specified discount rate A request for campaign with GET /products/1/discount?rate=30 returns a response which has status code 200' 18 | I, [2021-12-10T09:13:26.873614 #84223] INFO -- : Sending GET request to path: "/products/1/discount?rate=30" with headers: {"X_PACT_PROVIDER_STATES"=>[{"name"=>"i get new product price with specified discount rate"}]}, see debug logs for body 19 | D, [2021-12-10T09:13:26.873685 #84223] DEBUG -- : body : 20 | I, [2021-12-10T09:13:26.884411 #84223] INFO -- : Received response with status: 200, headers: {"Content-Length"=>"38", "Content-Type"=>"application/json", "Date"=>"Fri, 10 Dec 2021 06:13:26 GMT"}, see debug logs for body 21 | D, [2021-12-10T09:13:26.884475 #84223] DEBUG -- : body: {"id":1,"name":"Product 1","price":70} 22 | I, [2021-12-10T09:13:26.886224 #84223] INFO -- : Running example 'Verifying a pact between ProductService and CampaignService Given i get campaign not found error when the product has no discount A request for campaign without discounted product with GET /products/2/discount?rate=30 returns a response which has status code 406' 23 | I, [2021-12-10T09:13:26.895196 #84223] INFO -- : Sending GET request to path: "/products/2/discount?rate=30" with headers: {"X_PACT_PROVIDER_STATES"=>[{"name"=>"i get campaign not found error when the product has no discount"}]}, see debug logs for body 24 | D, [2021-12-10T09:13:26.895265 #84223] DEBUG -- : body : 25 | I, [2021-12-10T09:13:26.897102 #84223] INFO -- : Received response with status: 406, headers: {"Content-Length"=>"48", "Content-Type"=>"application/json", "Date"=>"Fri, 10 Dec 2021 06:13:26 GMT"}, see debug logs for body 26 | D, [2021-12-10T09:13:26.897139 #84223] DEBUG -- : body: {"message":"No campaign found for this product"} 27 | I, [2021-12-10T09:13:26.897880 #84223] INFO -- : Running example 'Verifying a pact between ProductService and CampaignService Given i get the product does not exist A request for campaign with nonexist product with GET /products/3/discount?rate=30 returns a response which has status code 404' 28 | I, [2021-12-10T09:13:26.906549 #84223] INFO -- : Sending GET request to path: "/products/3/discount?rate=30" with headers: {"X_PACT_PROVIDER_STATES"=>[{"name"=>"i get the product does not exist"}]}, see debug logs for body 29 | D, [2021-12-10T09:13:26.906605 #84223] DEBUG -- : body : 30 | I, [2021-12-10T09:13:26.908201 #84223] INFO -- : Received response with status: 404, headers: {"Content-Length"=>"9", "Content-Type"=>"text/plain; charset=utf-8", "Date"=>"Fri, 10 Dec 2021 06:13:26 GMT"}, see debug logs for body 31 | D, [2021-12-10T09:13:26.908237 #84223] DEBUG -- : body: Not Found 32 | I, [2021-12-10T09:14:57.910472 #84734] INFO -- : Running example 'Verifying a pact between ProductService and CampaignService Given i get new product price with specified discount rate A request for campaign with GET /products/1/discount?rate=30 returns a response which has status code 200' 33 | I, [2021-12-10T09:14:57.921590 #84734] INFO -- : Sending GET request to path: "/products/1/discount?rate=30" with headers: {"X_PACT_PROVIDER_STATES"=>[{"name"=>"i get new product price with specified discount rate"}]}, see debug logs for body 34 | D, [2021-12-10T09:14:57.921658 #84734] DEBUG -- : body : 35 | I, [2021-12-10T09:14:57.934197 #84734] INFO -- : Received response with status: 200, headers: {"Content-Length"=>"45", "Content-Type"=>"application/json", "Date"=>"Fri, 10 Dec 2021 06:14:57 GMT"}, see debug logs for body 36 | D, [2021-12-10T09:14:57.934288 #84734] DEBUG -- : body: {"id":1,"productName":"Product 1","price":70} 37 | I, [2021-12-10T09:14:57.939098 #84734] INFO -- : Running example 'Verifying a pact between ProductService and CampaignService Given i get campaign not found error when the product has no discount A request for campaign without discounted product with GET /products/2/discount?rate=30 returns a response which has status code 406' 38 | I, [2021-12-10T09:14:57.948101 #84734] INFO -- : Sending GET request to path: "/products/2/discount?rate=30" with headers: {"X_PACT_PROVIDER_STATES"=>[{"name"=>"i get campaign not found error when the product has no discount"}]}, see debug logs for body 39 | D, [2021-12-10T09:14:57.948164 #84734] DEBUG -- : body : 40 | I, [2021-12-10T09:14:57.950020 #84734] INFO -- : Received response with status: 406, headers: {"Content-Length"=>"48", "Content-Type"=>"application/json", "Date"=>"Fri, 10 Dec 2021 06:14:57 GMT"}, see debug logs for body 41 | D, [2021-12-10T09:14:57.950069 #84734] DEBUG -- : body: {"message":"No campaign found for this product"} 42 | I, [2021-12-10T09:14:57.950893 #84734] INFO -- : Running example 'Verifying a pact between ProductService and CampaignService Given i get the product does not exist A request for campaign with nonexist product with GET /products/3/discount?rate=30 returns a response which has status code 404' 43 | I, [2021-12-10T09:14:57.959958 #84734] INFO -- : Sending GET request to path: "/products/3/discount?rate=30" with headers: {"X_PACT_PROVIDER_STATES"=>[{"name"=>"i get the product does not exist"}]}, see debug logs for body 44 | D, [2021-12-10T09:14:57.960022 #84734] DEBUG -- : body : 45 | I, [2021-12-10T09:14:57.961629 #84734] INFO -- : Received response with status: 404, headers: {"Content-Length"=>"9", "Content-Type"=>"text/plain; charset=utf-8", "Date"=>"Fri, 10 Dec 2021 06:14:57 GMT"}, see debug logs for body 46 | D, [2021-12-10T09:14:57.961696 #84734] DEBUG -- : body: Not Found 47 | I, [2021-12-10T09:15:10.365369 #84820] INFO -- : Running example 'Verifying a pact between ProductService and CampaignService Given i get new product price with specified discount rate A request for campaign with GET /products/1/discount?rate=30 returns a response which has status code 200' 48 | I, [2021-12-10T09:15:10.376219 #84820] INFO -- : Sending GET request to path: "/products/1/discount?rate=30" with headers: {"X_PACT_PROVIDER_STATES"=>[{"name"=>"i get new product price with specified discount rate"}]}, see debug logs for body 49 | D, [2021-12-10T09:15:10.376280 #84820] DEBUG -- : body : 50 | I, [2021-12-10T09:15:10.386817 #84820] INFO -- : Received response with status: 200, headers: {"Content-Length"=>"38", "Content-Type"=>"application/json", "Date"=>"Fri, 10 Dec 2021 06:15:10 GMT"}, see debug logs for body 51 | D, [2021-12-10T09:15:10.386896 #84820] DEBUG -- : body: {"id":1,"name":"Product 1","price":70} 52 | I, [2021-12-10T09:15:10.388528 #84820] INFO -- : Running example 'Verifying a pact between ProductService and CampaignService Given i get campaign not found error when the product has no discount A request for campaign without discounted product with GET /products/2/discount?rate=30 returns a response which has status code 406' 53 | I, [2021-12-10T09:15:10.397581 #84820] INFO -- : Sending GET request to path: "/products/2/discount?rate=30" with headers: {"X_PACT_PROVIDER_STATES"=>[{"name"=>"i get campaign not found error when the product has no discount"}]}, see debug logs for body 54 | D, [2021-12-10T09:15:10.397632 #84820] DEBUG -- : body : 55 | I, [2021-12-10T09:15:10.399441 #84820] INFO -- : Received response with status: 406, headers: {"Content-Length"=>"48", "Content-Type"=>"application/json", "Date"=>"Fri, 10 Dec 2021 06:15:10 GMT"}, see debug logs for body 56 | D, [2021-12-10T09:15:10.399486 #84820] DEBUG -- : body: {"message":"No campaign found for this product"} 57 | I, [2021-12-10T09:15:10.400222 #84820] INFO -- : Running example 'Verifying a pact between ProductService and CampaignService Given i get the product does not exist A request for campaign with nonexist product with GET /products/3/discount?rate=30 returns a response which has status code 404' 58 | I, [2021-12-10T09:15:10.408768 #84820] INFO -- : Sending GET request to path: "/products/3/discount?rate=30" with headers: {"X_PACT_PROVIDER_STATES"=>[{"name"=>"i get the product does not exist"}]}, see debug logs for body 59 | D, [2021-12-10T09:15:10.408820 #84820] DEBUG -- : body : 60 | I, [2021-12-10T09:15:10.410442 #84820] INFO -- : Received response with status: 404, headers: {"Content-Length"=>"9", "Content-Type"=>"text/plain; charset=utf-8", "Date"=>"Fri, 10 Dec 2021 06:15:10 GMT"}, see debug logs for body 61 | D, [2021-12-10T09:15:10.410484 #84820] DEBUG -- : body: Not Found 62 | I, [2021-12-13T23:09:20.532760 #18274] INFO -- : Running example 'Verifying a pact between ProductService and CampaignService Given i get new product price with specified discount rate A request for campaign with GET /products/1/discount?rate=30 returns a response which has status code 200' 63 | I, [2021-12-13T23:09:20.544853 #18274] INFO -- : Sending GET request to path: "/products/1/discount?rate=30" with headers: {"X_PACT_PROVIDER_STATES"=>[{"name"=>"i get new product price with specified discount rate"}]}, see debug logs for body 64 | D, [2021-12-13T23:09:20.544918 #18274] DEBUG -- : body : 65 | I, [2021-12-13T23:09:20.556345 #18274] INFO -- : Received response with status: 200, headers: {"Content-Length"=>"38", "Content-Type"=>"application/json", "Date"=>"Mon, 13 Dec 2021 20:09:20 GMT"}, see debug logs for body 66 | D, [2021-12-13T23:09:20.556423 #18274] DEBUG -- : body: {"id":1,"name":"Product 1","price":70} 67 | I, [2021-12-13T23:09:20.558502 #18274] INFO -- : Running example 'Verifying a pact between ProductService and CampaignService Given i get campaign not found error when the product has no discount A request for campaign without discounted product with GET /products/2/discount?rate=30 returns a response which has status code 406' 68 | I, [2021-12-13T23:09:20.568249 #18274] INFO -- : Sending GET request to path: "/products/2/discount?rate=30" with headers: {"X_PACT_PROVIDER_STATES"=>[{"name"=>"i get campaign not found error when the product has no discount"}]}, see debug logs for body 69 | D, [2021-12-13T23:09:20.568308 #18274] DEBUG -- : body : 70 | I, [2021-12-13T23:09:20.570316 #18274] INFO -- : Received response with status: 406, headers: {"Content-Length"=>"48", "Content-Type"=>"application/json", "Date"=>"Mon, 13 Dec 2021 20:09:20 GMT"}, see debug logs for body 71 | D, [2021-12-13T23:09:20.570365 #18274] DEBUG -- : body: {"message":"No campaign found for this product"} 72 | I, [2021-12-13T23:09:20.571247 #18274] INFO -- : Running example 'Verifying a pact between ProductService and CampaignService Given i get the product does not exist A request for campaign with nonexist product with GET /products/3/discount?rate=30 returns a response which has status code 404' 73 | I, [2021-12-13T23:09:20.580905 #18274] INFO -- : Sending GET request to path: "/products/3/discount?rate=30" with headers: {"X_PACT_PROVIDER_STATES"=>[{"name"=>"i get the product does not exist"}]}, see debug logs for body 74 | D, [2021-12-13T23:09:20.580964 #18274] DEBUG -- : body : 75 | I, [2021-12-13T23:09:20.582644 #18274] INFO -- : Received response with status: 404, headers: {"Content-Length"=>"9", "Content-Type"=>"text/plain; charset=utf-8", "Date"=>"Mon, 13 Dec 2021 20:09:20 GMT"}, see debug logs for body 76 | D, [2021-12-13T23:09:20.582683 #18274] DEBUG -- : body: Not Found 77 | I, [2021-12-13T23:09:26.439395 #18354] INFO -- : Running example 'Verifying a pact between ProductService and CampaignService Given i get new product price with specified discount rate A request for campaign with GET /products/1/discount?rate=30 returns a response which has status code 200' 78 | I, [2021-12-13T23:09:26.450590 #18354] INFO -- : Sending GET request to path: "/products/1/discount?rate=30" with headers: {"X_PACT_PROVIDER_STATES"=>[{"name"=>"i get new product price with specified discount rate"}]}, see debug logs for body 79 | D, [2021-12-13T23:09:26.450654 #18354] DEBUG -- : body : 80 | I, [2021-12-13T23:09:26.460545 #18354] INFO -- : Received response with status: 200, headers: {"Content-Length"=>"38", "Content-Type"=>"application/json", "Date"=>"Mon, 13 Dec 2021 20:09:26 GMT"}, see debug logs for body 81 | D, [2021-12-13T23:09:26.460620 #18354] DEBUG -- : body: {"id":1,"name":"Product 1","price":70} 82 | I, [2021-12-13T23:09:26.461938 #18354] INFO -- : Running example 'Verifying a pact between ProductService and CampaignService Given i get campaign not found error when the product has no discount A request for campaign without discounted product with GET /products/2/discount?rate=30 returns a response which has status code 406' 83 | I, [2021-12-13T23:09:26.471391 #18354] INFO -- : Sending GET request to path: "/products/2/discount?rate=30" with headers: {"X_PACT_PROVIDER_STATES"=>[{"name"=>"i get campaign not found error when the product has no discount"}]}, see debug logs for body 84 | D, [2021-12-13T23:09:26.471459 #18354] DEBUG -- : body : 85 | I, [2021-12-13T23:09:26.473442 #18354] INFO -- : Received response with status: 406, headers: {"Content-Length"=>"48", "Content-Type"=>"application/json", "Date"=>"Mon, 13 Dec 2021 20:09:26 GMT"}, see debug logs for body 86 | D, [2021-12-13T23:09:26.473505 #18354] DEBUG -- : body: {"message":"No campaign found for this product"} 87 | I, [2021-12-13T23:09:26.474452 #18354] INFO -- : Running example 'Verifying a pact between ProductService and CampaignService Given i get the product does not exist A request for campaign with nonexist product with GET /products/3/discount?rate=30 returns a response which has status code 404' 88 | I, [2021-12-13T23:09:26.484140 #18354] INFO -- : Sending GET request to path: "/products/3/discount?rate=30" with headers: {"X_PACT_PROVIDER_STATES"=>[{"name"=>"i get the product does not exist"}]}, see debug logs for body 89 | D, [2021-12-13T23:09:26.484212 #18354] DEBUG -- : body : 90 | I, [2021-12-13T23:09:26.486115 #18354] INFO -- : Received response with status: 404, headers: {"Content-Length"=>"9", "Content-Type"=>"text/plain; charset=utf-8", "Date"=>"Mon, 13 Dec 2021 20:09:26 GMT"}, see debug logs for body 91 | D, [2021-12-13T23:09:26.486184 #18354] DEBUG -- : body: Not Found 92 | I, [2021-12-13T23:09:53.862086 #18544] INFO -- : Running example 'Verifying a pact between ProductService and CampaignService Given i get new product price with specified discount rate A request for campaign with GET /products/1/discount?rate=30 returns a response which has status code 200' 93 | I, [2021-12-13T23:09:53.872917 #18544] INFO -- : Sending GET request to path: "/products/1/discount?rate=30" with headers: {"X_PACT_PROVIDER_STATES"=>[{"name"=>"i get new product price with specified discount rate"}]}, see debug logs for body 94 | D, [2021-12-13T23:09:53.872987 #18544] DEBUG -- : body : 95 | I, [2021-12-13T23:09:53.882554 #18544] INFO -- : Received response with status: 200, headers: {"Content-Length"=>"38", "Content-Type"=>"application/json", "Date"=>"Mon, 13 Dec 2021 20:09:53 GMT"}, see debug logs for body 96 | D, [2021-12-13T23:09:53.882622 #18544] DEBUG -- : body: {"id":1,"name":"Product 1","price":70} 97 | I, [2021-12-13T23:09:53.883917 #18544] INFO -- : Running example 'Verifying a pact between ProductService and CampaignService Given i get campaign not found error when the product has no discount A request for campaign without discounted product with GET /products/2/discount?rate=30 returns a response which has status code 406' 98 | I, [2021-12-13T23:09:53.893059 #18544] INFO -- : Sending GET request to path: "/products/2/discount?rate=30" with headers: {"X_PACT_PROVIDER_STATES"=>[{"name"=>"i get campaign not found error when the product has no discount"}]}, see debug logs for body 99 | D, [2021-12-13T23:09:53.893124 #18544] DEBUG -- : body : 100 | I, [2021-12-13T23:09:53.895085 #18544] INFO -- : Received response with status: 406, headers: {"Content-Length"=>"48", "Content-Type"=>"application/json", "Date"=>"Mon, 13 Dec 2021 20:09:53 GMT"}, see debug logs for body 101 | D, [2021-12-13T23:09:53.895143 #18544] DEBUG -- : body: {"message":"No campaign found for this product"} 102 | I, [2021-12-13T23:09:53.896027 #18544] INFO -- : Running example 'Verifying a pact between ProductService and CampaignService Given i get the product does not exist A request for campaign wih nonexist product with GET /products/3/discount?rate=30 returns a response which has status code 404' 103 | I, [2021-12-13T23:09:53.905576 #18544] INFO -- : Sending GET request to path: "/products/3/discount?rate=30" with headers: {"X_PACT_PROVIDER_STATES"=>[{"name"=>"i get the product does not exist"}]}, see debug logs for body 104 | D, [2021-12-13T23:09:53.905648 #18544] DEBUG -- : body : 105 | I, [2021-12-13T23:09:53.907801 #18544] INFO -- : Received response with status: 404, headers: {"Content-Length"=>"9", "Content-Type"=>"text/plain; charset=utf-8", "Date"=>"Mon, 13 Dec 2021 20:09:53 GMT"}, see debug logs for body 106 | D, [2021-12-13T23:09:53.907895 #18544] DEBUG -- : body: Not Found 107 | I, [2021-12-13T23:10:00.920687 #18632] INFO -- : Running example 'Verifying a pact between ProductService and CampaignService Given i get new product price with specified discount rate A request for campaign with GET /products/1/discount?rate=30 returns a response which has status code 200' 108 | I, [2021-12-13T23:10:00.933952 #18632] INFO -- : Sending GET request to path: "/products/1/discount?rate=30" with headers: {"X_PACT_PROVIDER_STATES"=>[{"name"=>"i get new product price with specified discount rate"}]}, see debug logs for body 109 | D, [2021-12-13T23:10:00.934060 #18632] DEBUG -- : body : 110 | I, [2021-12-13T23:10:00.947027 #18632] INFO -- : Received response with status: 200, headers: {"Content-Length"=>"38", "Content-Type"=>"application/json", "Date"=>"Mon, 13 Dec 2021 20:10:00 GMT"}, see debug logs for body 111 | D, [2021-12-13T23:10:00.947158 #18632] DEBUG -- : body: {"id":1,"name":"Product 1","price":70} 112 | I, [2021-12-13T23:10:00.948801 #18632] INFO -- : Running example 'Verifying a pact between ProductService and CampaignService Given i get campaign not found error when the product has no discount A request for campaign without discounted product with GET /products/2/discount?rate=30 returns a response which has status code 406' 113 | I, [2021-12-13T23:10:00.958902 #18632] INFO -- : Sending GET request to path: "/products/2/discount?rate=30" with headers: {"X_PACT_PROVIDER_STATES"=>[{"name"=>"i get campaign not found error when the product has no discount"}]}, see debug logs for body 114 | D, [2021-12-13T23:10:00.958996 #18632] DEBUG -- : body : 115 | I, [2021-12-13T23:10:00.961760 #18632] INFO -- : Received response with status: 406, headers: {"Content-Length"=>"48", "Content-Type"=>"application/json", "Date"=>"Mon, 13 Dec 2021 20:10:00 GMT"}, see debug logs for body 116 | D, [2021-12-13T23:10:00.961831 #18632] DEBUG -- : body: {"message":"No campaign found for this product"} 117 | I, [2021-12-13T23:10:00.962690 #18632] INFO -- : Running example 'Verifying a pact between ProductService and CampaignService Given i get the product does not exist A request for campaign with nonexist product with GET /products/3/discount?rate=30 returns a response which has status code 404' 118 | I, [2021-12-13T23:10:00.974058 #18632] INFO -- : Sending GET request to path: "/products/3/discount?rate=30" with headers: {"X_PACT_PROVIDER_STATES"=>[{"name"=>"i get the product does not exist"}]}, see debug logs for body 119 | D, [2021-12-13T23:10:00.974157 #18632] DEBUG -- : body : 120 | I, [2021-12-13T23:10:00.977974 #18632] INFO -- : Received response with status: 404, headers: {"Content-Length"=>"9", "Content-Type"=>"text/plain; charset=utf-8", "Date"=>"Mon, 13 Dec 2021 20:10:00 GMT"}, see debug logs for body 121 | D, [2021-12-13T23:10:00.978048 #18632] DEBUG -- : body: Not Found 122 | I, [2021-12-13T23:10:36.069112 #18905] INFO -- : Running example 'Verifying a pact between ProductService and CampaignService Given i get new product price with specified discount rate A request for campaign with GET /products/1/discount?rate=30 returns a response which has status code 200' 123 | I, [2021-12-13T23:10:36.079810 #18905] INFO -- : Sending GET request to path: "/products/1/discount?rate=30" with headers: {"X_PACT_PROVIDER_STATES"=>[{"name"=>"i get new product price with specified discount rate"}]}, see debug logs for body 124 | D, [2021-12-13T23:10:36.079875 #18905] DEBUG -- : body : 125 | I, [2021-12-13T23:10:36.089651 #18905] INFO -- : Received response with status: 200, headers: {"Content-Length"=>"38", "Content-Type"=>"application/json", "Date"=>"Mon, 13 Dec 2021 20:10:36 GMT"}, see debug logs for body 126 | D, [2021-12-13T23:10:36.089713 #18905] DEBUG -- : body: {"id":1,"name":"Product 1","price":70} 127 | I, [2021-12-13T23:10:36.093405 #18905] INFO -- : Running example 'Verifying a pact between ProductService and CampaignService Given i get campaign not found error when the product has no discount A request for campaign without discounted product with GET /products/2/discount?rate=30 returns a response which has status code 406' 128 | I, [2021-12-13T23:10:36.102520 #18905] INFO -- : Sending GET request to path: "/products/2/discount?rate=30" with headers: {"X_PACT_PROVIDER_STATES"=>[{"name"=>"i get campaign not found error when the product has no discount"}]}, see debug logs for body 129 | D, [2021-12-13T23:10:36.102583 #18905] DEBUG -- : body : 130 | I, [2021-12-13T23:10:36.104354 #18905] INFO -- : Received response with status: 406, headers: {"Content-Length"=>"48", "Content-Type"=>"application/json", "Date"=>"Mon, 13 Dec 2021 20:10:36 GMT"}, see debug logs for body 131 | D, [2021-12-13T23:10:36.104400 #18905] DEBUG -- : body: {"message":"No campaign found for this product"} 132 | I, [2021-12-13T23:10:36.105204 #18905] INFO -- : Running example 'Verifying a pact between ProductService and CampaignService Given i get the product does not exist A request for campaign with nonexist product with GET /products/3/discount?rate=30 returns a response which has status code 404' 133 | I, [2021-12-13T23:10:36.113842 #18905] INFO -- : Sending GET request to path: "/products/3/discount?rate=30" with headers: {"X_PACT_PROVIDER_STATES"=>[{"name"=>"i get the product does not exist"}]}, see debug logs for body 134 | D, [2021-12-13T23:10:36.113890 #18905] DEBUG -- : body : 135 | I, [2021-12-13T23:10:36.115462 #18905] INFO -- : Received response with status: 404, headers: {"Content-Length"=>"9", "Content-Type"=>"text/plain; charset=utf-8", "Date"=>"Mon, 13 Dec 2021 20:10:36 GMT"}, see debug logs for body 136 | D, [2021-12-13T23:10:36.115521 #18905] DEBUG -- : body: Not Found 137 | I, [2021-12-13T23:11:06.724297 #19102] INFO -- : Running example 'Verifying a pact between ProductService and CampaignService Given i get new product price with specified discount rate A request for campaign with GET /products/1/discount?rate=30 returns a response which has status code 200' 138 | I, [2021-12-13T23:11:06.735218 #19102] INFO -- : Sending GET request to path: "/products/1/discount?rate=30" with headers: {"X_PACT_PROVIDER_STATES"=>[{"name"=>"i get new product price with specified discount rate"}]}, see debug logs for body 139 | D, [2021-12-13T23:11:06.735284 #19102] DEBUG -- : body : 140 | I, [2021-12-13T23:11:06.745425 #19102] INFO -- : Received response with status: 200, headers: {"Content-Length"=>"38", "Content-Type"=>"application/json", "Date"=>"Mon, 13 Dec 2021 20:11:06 GMT"}, see debug logs for body 141 | D, [2021-12-13T23:11:06.745498 #19102] DEBUG -- : body: {"id":1,"name":"Product 1","price":70} 142 | I, [2021-12-13T23:11:06.746844 #19102] INFO -- : Running example 'Verifying a pact between ProductService and CampaignService Given i get campaign not found error when the product has no discount A request for campaign without discounted product with GET /products/2/discount?rate=30 returns a response which has status code 406' 143 | I, [2021-12-13T23:11:06.756238 #19102] INFO -- : Sending GET request to path: "/products/2/discount?rate=30" with headers: {"X_PACT_PROVIDER_STATES"=>[{"name"=>"i get campaign not found error when the product has no discount"}]}, see debug logs for body 144 | D, [2021-12-13T23:11:06.756327 #19102] DEBUG -- : body : 145 | I, [2021-12-13T23:11:06.758459 #19102] INFO -- : Received response with status: 406, headers: {"Content-Length"=>"48", "Content-Type"=>"application/json", "Date"=>"Mon, 13 Dec 2021 20:11:06 GMT"}, see debug logs for body 146 | D, [2021-12-13T23:11:06.758511 #19102] DEBUG -- : body: {"message":"No campaign found for this product"} 147 | I, [2021-12-13T23:11:06.759330 #19102] INFO -- : Running example 'Verifying a pact between ProductService and CampaignService Given i get the product does not exist A request for campaign wih nonexist product with GET /products/3/discount?rate=30 returns a response which has status code 404' 148 | I, [2021-12-13T23:11:06.768961 #19102] INFO -- : Sending GET request to path: "/products/3/discount?rate=30" with headers: {"X_PACT_PROVIDER_STATES"=>[{"name"=>"i get the product does not exist"}]}, see debug logs for body 149 | D, [2021-12-13T23:11:06.769030 #19102] DEBUG -- : body : 150 | I, [2021-12-13T23:11:06.770827 #19102] INFO -- : Received response with status: 404, headers: {"Content-Length"=>"9", "Content-Type"=>"text/plain; charset=utf-8", "Date"=>"Mon, 13 Dec 2021 20:11:06 GMT"}, see debug logs for body 151 | D, [2021-12-13T23:11:06.770875 #19102] DEBUG -- : body: Not Found 152 | I, [2021-12-13T23:11:12.501437 #19194] INFO -- : Running example 'Verifying a pact between ProductService and CampaignService Given i get new product price with specified discount rate A request for campaign with GET /products/1/discount?rate=30 returns a response which has status code 200' 153 | I, [2021-12-13T23:11:12.512222 #19194] INFO -- : Sending GET request to path: "/products/1/discount?rate=30" with headers: {"X_PACT_PROVIDER_STATES"=>[{"name"=>"i get new product price with specified discount rate"}]}, see debug logs for body 154 | D, [2021-12-13T23:11:12.512287 #19194] DEBUG -- : body : 155 | I, [2021-12-13T23:11:12.522361 #19194] INFO -- : Received response with status: 200, headers: {"Content-Length"=>"38", "Content-Type"=>"application/json", "Date"=>"Mon, 13 Dec 2021 20:11:12 GMT"}, see debug logs for body 156 | D, [2021-12-13T23:11:12.522437 #19194] DEBUG -- : body: {"id":1,"name":"Product 1","price":70} 157 | I, [2021-12-13T23:11:12.525532 #19194] INFO -- : Running example 'Verifying a pact between ProductService and CampaignService Given i get campaign not found error when the product has no discount A request for campaign without discounted product with GET /products/2/discount?rate=30 returns a response which has status code 406' 158 | I, [2021-12-13T23:11:12.534994 #19194] INFO -- : Sending GET request to path: "/products/2/discount?rate=30" with headers: {"X_PACT_PROVIDER_STATES"=>[{"name"=>"i get campaign not found error when the product has no discount"}]}, see debug logs for body 159 | D, [2021-12-13T23:11:12.535057 #19194] DEBUG -- : body : 160 | I, [2021-12-13T23:11:12.536897 #19194] INFO -- : Received response with status: 406, headers: {"Content-Length"=>"48", "Content-Type"=>"application/json", "Date"=>"Mon, 13 Dec 2021 20:11:12 GMT"}, see debug logs for body 161 | D, [2021-12-13T23:11:12.536941 #19194] DEBUG -- : body: {"message":"No campaign found for this product"} 162 | I, [2021-12-13T23:11:12.537767 #19194] INFO -- : Running example 'Verifying a pact between ProductService and CampaignService Given i get the product does not exist A request for campaign with nonexist product with GET /products/3/discount?rate=30 returns a response which has status code 404' 163 | I, [2021-12-13T23:11:12.547097 #19194] INFO -- : Sending GET request to path: "/products/3/discount?rate=30" with headers: {"X_PACT_PROVIDER_STATES"=>[{"name"=>"i get the product does not exist"}]}, see debug logs for body 164 | D, [2021-12-13T23:11:12.547158 #19194] DEBUG -- : body : 165 | I, [2021-12-13T23:11:12.548937 #19194] INFO -- : Received response with status: 404, headers: {"Content-Length"=>"9", "Content-Type"=>"text/plain; charset=utf-8", "Date"=>"Mon, 13 Dec 2021 20:11:12 GMT"}, see debug logs for body 166 | D, [2021-12-13T23:11:12.548982 #19194] DEBUG -- : body: Not Found 167 | I, [2021-12-13T23:11:24.350332 #19355] INFO -- : Running example 'Verifying a pact between ProductService and CampaignService Given i get new product price with specified discount rate A request for campaign with GET /products/1/discount?rate=30 returns a response which has status code 200' 168 | I, [2021-12-13T23:11:24.363756 #19355] INFO -- : Sending GET request to path: "/products/1/discount?rate=30" with headers: {"X_PACT_PROVIDER_STATES"=>[{"name"=>"i get new product price with specified discount rate"}]}, see debug logs for body 169 | D, [2021-12-13T23:11:24.363838 #19355] DEBUG -- : body : 170 | I, [2021-12-13T23:11:24.376865 #19355] INFO -- : Received response with status: 200, headers: {"Content-Length"=>"38", "Content-Type"=>"application/json", "Date"=>"Mon, 13 Dec 2021 20:11:24 GMT"}, see debug logs for body 171 | D, [2021-12-13T23:11:24.376967 #19355] DEBUG -- : body: {"id":1,"name":"Product 1","price":70} 172 | I, [2021-12-13T23:11:24.378747 #19355] INFO -- : Running example 'Verifying a pact between ProductService and CampaignService Given i get campaign not found error when the product has no discount A request for campaign without discounted product with GET /products/2/discount?rate=30 returns a response which has status code 406' 173 | I, [2021-12-13T23:11:24.390995 #19355] INFO -- : Sending GET request to path: "/products/2/discount?rate=30" with headers: {"X_PACT_PROVIDER_STATES"=>[{"name"=>"i get campaign not found error when the product has no discount"}]}, see debug logs for body 174 | D, [2021-12-13T23:11:24.391072 #19355] DEBUG -- : body : 175 | I, [2021-12-13T23:11:24.393716 #19355] INFO -- : Received response with status: 406, headers: {"Content-Length"=>"48", "Content-Type"=>"application/json", "Date"=>"Mon, 13 Dec 2021 20:11:24 GMT"}, see debug logs for body 176 | D, [2021-12-13T23:11:24.393784 #19355] DEBUG -- : body: {"message":"No campaign found for this product"} 177 | I, [2021-12-13T23:11:24.394692 #19355] INFO -- : Running example 'Verifying a pact between ProductService and CampaignService Given i get the product does not exist A request for campaign with nonexist product with GET /products/3/discount?rate=30 returns a response which has status code 404' 178 | I, [2021-12-13T23:11:24.406551 #19355] INFO -- : Sending GET request to path: "/products/3/discount?rate=30" with headers: {"X_PACT_PROVIDER_STATES"=>[{"name"=>"i get the product does not exist"}]}, see debug logs for body 179 | D, [2021-12-13T23:11:24.406618 #19355] DEBUG -- : body : 180 | I, [2021-12-13T23:11:24.409598 #19355] INFO -- : Received response with status: 404, headers: {"Content-Length"=>"9", "Content-Type"=>"text/plain; charset=utf-8", "Date"=>"Mon, 13 Dec 2021 20:11:24 GMT"}, see debug logs for body 181 | D, [2021-12-13T23:11:24.409668 #19355] DEBUG -- : body: Not Found 182 | I, [2021-12-13T23:17:27.031231 #20787] INFO -- : Running example 'Verifying a pact between ProductService and CampaignService Given i get new product price with specified discount rate A request for campaign with GET /products/1/discount?rate=30 returns a response which has status code 200' 183 | I, [2021-12-13T23:17:27.043458 #20787] INFO -- : Sending GET request to path: "/products/1/discount?rate=30" with headers: {"X_PACT_PROVIDER_STATES"=>[{"name"=>"i get new product price with specified discount rate"}]}, see debug logs for body 184 | D, [2021-12-13T23:17:27.043534 #20787] DEBUG -- : body : 185 | I, [2021-12-13T23:17:27.055337 #20787] INFO -- : Received response with status: 200, headers: {"Content-Length"=>"38", "Content-Type"=>"application/json", "Date"=>"Mon, 13 Dec 2021 20:17:27 GMT"}, see debug logs for body 186 | D, [2021-12-13T23:17:27.055410 #20787] DEBUG -- : body: {"id":1,"name":"Product 1","price":70} 187 | I, [2021-12-13T23:17:27.057116 #20787] INFO -- : Running example 'Verifying a pact between ProductService and CampaignService Given i get campaign not found error when the product has no discount A request for campaign without discounted product with GET /products/2/discount?rate=30 returns a response which has status code 406' 188 | I, [2021-12-13T23:17:27.066782 #20787] INFO -- : Sending GET request to path: "/products/2/discount?rate=30" with headers: {"X_PACT_PROVIDER_STATES"=>[{"name"=>"i get campaign not found error when the product has no discount"}]}, see debug logs for body 189 | D, [2021-12-13T23:17:27.066849 #20787] DEBUG -- : body : 190 | I, [2021-12-13T23:17:27.068880 #20787] INFO -- : Received response with status: 406, headers: {"Content-Length"=>"48", "Content-Type"=>"application/json", "Date"=>"Mon, 13 Dec 2021 20:17:27 GMT"}, see debug logs for body 191 | D, [2021-12-13T23:17:27.068929 #20787] DEBUG -- : body: {"message":"No campaign found for this product"} 192 | I, [2021-12-13T23:17:27.069755 #20787] INFO -- : Running example 'Verifying a pact between ProductService and CampaignService Given i get the product does not exist A request for campaign with nonexist product with GET /products/3/discount?rate=30 returns a response which has status code 404' 193 | I, [2021-12-13T23:17:27.079994 #20787] INFO -- : Sending GET request to path: "/products/3/discount?rate=30" with headers: {"X_PACT_PROVIDER_STATES"=>[{"name"=>"i get the product does not exist"}]}, see debug logs for body 194 | D, [2021-12-13T23:17:27.080065 #20787] DEBUG -- : body : 195 | I, [2021-12-13T23:17:27.082148 #20787] INFO -- : Received response with status: 404, headers: {"Content-Length"=>"9", "Content-Type"=>"text/plain; charset=utf-8", "Date"=>"Mon, 13 Dec 2021 20:17:27 GMT"}, see debug logs for body 196 | D, [2021-12-13T23:17:27.082209 #20787] DEBUG -- : body: Not Found 197 | I, [2021-12-13T23:18:00.557842 #21087] INFO -- : Running example 'Verifying a pact between ProductService and CampaignService Given i get new product price with specified discount rate A request for campaign with GET /products/1/discount?rate=30 returns a response which has status code 200' 198 | I, [2021-12-13T23:18:00.569362 #21087] INFO -- : Sending GET request to path: "/products/1/discount?rate=30" with headers: {"X_PACT_PROVIDER_STATES"=>[{"name"=>"i get new product price with specified discount rate"}]}, see debug logs for body 199 | D, [2021-12-13T23:18:00.569430 #21087] DEBUG -- : body : 200 | I, [2021-12-13T23:18:00.580461 #21087] INFO -- : Received response with status: 200, headers: {"Content-Length"=>"38", "Content-Type"=>"application/json", "Date"=>"Mon, 13 Dec 2021 20:18:00 GMT"}, see debug logs for body 201 | D, [2021-12-13T23:18:00.580544 #21087] DEBUG -- : body: {"id":1,"name":"Product 1","price":70} 202 | I, [2021-12-13T23:18:00.582526 #21087] INFO -- : Running example 'Verifying a pact between ProductService and CampaignService Given i get campaign not found error when the product has no discount A request for campaign without discounted product with GET /products/2/discount?rate=30 returns a response which has status code 406' 203 | I, [2021-12-13T23:18:00.592077 #21087] INFO -- : Sending GET request to path: "/products/2/discount?rate=30" with headers: {"X_PACT_PROVIDER_STATES"=>[{"name"=>"i get campaign not found error when the product has no discount"}]}, see debug logs for body 204 | D, [2021-12-13T23:18:00.592136 #21087] DEBUG -- : body : 205 | I, [2021-12-13T23:18:00.593995 #21087] INFO -- : Received response with status: 406, headers: {"Content-Length"=>"48", "Content-Type"=>"application/json", "Date"=>"Mon, 13 Dec 2021 20:18:00 GMT"}, see debug logs for body 206 | D, [2021-12-13T23:18:00.594039 #21087] DEBUG -- : body: {"message":"No campaign found for this product"} 207 | I, [2021-12-13T23:18:00.594872 #21087] INFO -- : Running example 'Verifying a pact between ProductService and CampaignService Given i get the product does not exist A request for campaign with nonexist product with GET /products/3/discount?rate=30 returns a response which has status code 404' 208 | I, [2021-12-13T23:18:00.604657 #21087] INFO -- : Sending GET request to path: "/products/3/discount?rate=30" with headers: {"X_PACT_PROVIDER_STATES"=>[{"name"=>"i get the product does not exist"}]}, see debug logs for body 209 | D, [2021-12-13T23:18:00.604721 #21087] DEBUG -- : body : 210 | I, [2021-12-13T23:18:00.606632 #21087] INFO -- : Received response with status: 404, headers: {"Content-Length"=>"9", "Content-Type"=>"text/plain; charset=utf-8", "Date"=>"Mon, 13 Dec 2021 20:18:00 GMT"}, see debug logs for body 211 | D, [2021-12-13T23:18:00.606674 #21087] DEBUG -- : body: Not Found 212 | I, [2021-12-20T07:26:00.986640 #50416] INFO -- : Running example 'Verifying a pact between ProductService and CampaignService Given i get new product price with specified discount rate A request for campaign with GET /products/1/discount?rate=30 returns a response which has status code 200' 213 | I, [2021-12-20T07:26:00.997606 #50416] INFO -- : Sending GET request to path: "/products/1/discount?rate=30" with headers: {"X_PACT_PROVIDER_STATES"=>[{"name"=>"i get new product price with specified discount rate"}]}, see debug logs for body 214 | D, [2021-12-20T07:26:00.997666 #50416] DEBUG -- : body : 215 | I, [2021-12-20T07:26:01.008357 #50416] INFO -- : Received response with status: 200, headers: {"Content-Length"=>"38", "Content-Type"=>"application/json", "Date"=>"Mon, 20 Dec 2021 04:26:01 GMT"}, see debug logs for body 216 | D, [2021-12-20T07:26:01.008416 #50416] DEBUG -- : body: {"id":1,"name":"Product 1","price":70} 217 | I, [2021-12-20T07:26:01.010395 #50416] INFO -- : Running example 'Verifying a pact between ProductService and CampaignService Given i get campaign not found error when the product has no discount A request for campaign without discounted product with GET /products/2/discount?rate=30 returns a response which has status code 406' 218 | I, [2021-12-20T07:26:01.019567 #50416] INFO -- : Sending GET request to path: "/products/2/discount?rate=30" with headers: {"X_PACT_PROVIDER_STATES"=>[{"name"=>"i get campaign not found error when the product has no discount"}]}, see debug logs for body 219 | D, [2021-12-20T07:26:01.019630 #50416] DEBUG -- : body : 220 | I, [2021-12-20T07:26:01.021418 #50416] INFO -- : Received response with status: 406, headers: {"Content-Length"=>"48", "Content-Type"=>"application/json", "Date"=>"Mon, 20 Dec 2021 04:26:01 GMT"}, see debug logs for body 221 | D, [2021-12-20T07:26:01.021465 #50416] DEBUG -- : body: {"message":"No campaign found for this product"} 222 | I, [2021-12-20T07:26:01.022242 #50416] INFO -- : Running example 'Verifying a pact between ProductService and CampaignService Given i get the product does not exist A request for campaign with nonexist product with GET /products/3/discount?rate=30 returns a response which has status code 404' 223 | I, [2021-12-20T07:26:01.031218 #50416] INFO -- : Sending GET request to path: "/products/3/discount?rate=30" with headers: {"X_PACT_PROVIDER_STATES"=>[{"name"=>"i get the product does not exist"}]}, see debug logs for body 224 | D, [2021-12-20T07:26:01.031299 #50416] DEBUG -- : body : 225 | I, [2021-12-20T07:26:01.032969 #50416] INFO -- : Received response with status: 404, headers: {"Content-Length"=>"9", "Content-Type"=>"text/plain; charset=utf-8", "Date"=>"Mon, 20 Dec 2021 04:26:01 GMT"}, see debug logs for body 226 | D, [2021-12-20T07:26:01.033015 #50416] DEBUG -- : body: Not Found 227 | -------------------------------------------------------------------------------- /campaign/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gofiber/fiber/v2" 6 | "log" 7 | "net/http" 8 | "strconv" 9 | ) 10 | 11 | var products = Products{} 12 | 13 | func main() { 14 | err := startServer(3000) 15 | if err != nil { 16 | log.Fatal(err) 17 | } 18 | log.Println("Provider Service Listening :3000") 19 | } 20 | 21 | func startServer(port int) error { 22 | products.InitProducts() 23 | 24 | app := fiber.New(fiber.Config{DisableStartupMessage: true}) 25 | app.Get("/products/:id/discount", func(c *fiber.Ctx) error { 26 | productID, _ := c.ParamsInt("id", 0) 27 | discountRate, _ := strconv.ParseFloat(c.Query("rate"), 64) 28 | 29 | product, ok := products[productID] 30 | if !ok { 31 | return c.SendStatus(http.StatusNotFound) 32 | } 33 | 34 | if !product.HasCampaign { 35 | return c. 36 | Status(http.StatusNotAcceptable). 37 | JSON(map[string]interface{}{ 38 | "message": "No campaign found for this product", 39 | }) 40 | } 41 | 42 | discountedPrice := products[productID].Price - (products[productID].Price*discountRate)/100 43 | product.Price = discountedPrice 44 | 45 | return c.JSON(product) 46 | }) 47 | err := app.Listen(fmt.Sprintf(":%d", port)) 48 | return err 49 | } 50 | -------------------------------------------------------------------------------- /campaign/model.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | type Product struct { 4 | ID int `json:"id"` 5 | Name string `json:"name"` 6 | Price float64 `json:"price"` 7 | HasCampaign bool `json:"-"` 8 | } 9 | 10 | type Products map[int]Product 11 | 12 | func (p Products) InitProducts() { 13 | p[1] = Product{ 14 | ID: 1, 15 | Name: "Product 1", 16 | Price: 100, 17 | HasCampaign: true, 18 | } 19 | p[2] = Product{ 20 | ID: 2, 21 | Name: "Product 2", 22 | Price: 100, 23 | HasCampaign: true, 24 | } 25 | p[3] = Product{ 26 | ID: 3, 27 | Name: "Product 3", 28 | Price: 200, 29 | HasCampaign: true, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /campaign/provider_test.go: -------------------------------------------------------------------------------- 1 | //go:build provider 2 | // +build provider 3 | 4 | package main 5 | 6 | import ( 7 | "fmt" 8 | "github.com/k0kubun/pp" 9 | "github.com/pact-foundation/pact-go/dsl" 10 | "github.com/pact-foundation/pact-go/types" 11 | "github.com/pact-foundation/pact-go/utils" 12 | "testing" 13 | ) 14 | 15 | type Settings struct { 16 | Host string 17 | ProviderName string 18 | BrokerBaseURL string 19 | BrokerUsername string // Basic authentication 20 | BrokerPassword string // Basic authentication 21 | ConsumerName string 22 | ConsumerVersion string // a git sha, semantic version number 23 | ConsumerTag string // dev, staging, prod 24 | ProviderVersion string 25 | } 26 | 27 | func (s *Settings) getPactURL(useLocal bool) string { 28 | // Local pact file or remote based urls (Pact Broker) 29 | var pactURL string 30 | 31 | if useLocal { 32 | pactURL = "../product/pacts/productservice-campaignservice.json" 33 | return pactURL 34 | } 35 | 36 | if s.ConsumerVersion == "" { 37 | pactURL = fmt.Sprintf("%s/pacts/provider/%s/consumer/%s/latest/master.json", s.BrokerBaseURL, s.ProviderName, s.ConsumerName) 38 | } else { 39 | pactURL = fmt.Sprintf("%s/pacts/provider/%s/consumer/%s/version/%s.json", s.BrokerBaseURL, s.ProviderName, s.ConsumerName, s.ConsumerVersion) 40 | } 41 | 42 | return pactURL 43 | } 44 | 45 | func (s *Settings) create() { 46 | s.Host = "127.0.0.1" 47 | s.ProviderName = "CampaignService" 48 | s.ConsumerName = "ProductService" 49 | s.BrokerBaseURL = "http://localhost" 50 | s.ConsumerTag = "master" 51 | s.ProviderVersion = "1.0.0" 52 | s.ConsumerVersion = "1.0.4" 53 | } 54 | 55 | func TestProvider(t *testing.T) { 56 | port, _ := utils.GetFreePort() 57 | go startServer(port) 58 | 59 | settings := Settings{} 60 | settings.create() 61 | 62 | pact := dsl.Pact{ 63 | Host: settings.Host, 64 | Provider: settings.ProviderName, 65 | Consumer: settings.ConsumerName, 66 | DisableToolValidityCheck: true, 67 | } 68 | 69 | // The name of the provider state is specified in the given clause of an interaction in the consumer, and then 70 | // used to find the block of code to run in the provider to set up the right data. 71 | // Provider states also allow the consumer to make the same request with different expected responses. 72 | verifyRequest := types.VerifyRequest{ 73 | ProviderBaseURL: fmt.Sprintf("http://%s:%d", settings.Host, port), 74 | ProviderVersion: settings.ProviderVersion, 75 | BrokerUsername: settings.BrokerUsername, 76 | BrokerURL: settings.BrokerBaseURL, 77 | BrokerPassword: settings.BrokerPassword, 78 | Tags: []string{settings.ConsumerTag}, 79 | PactURLs: []string{settings.getPactURL(true)}, 80 | StateHandlers: map[string]types.StateHandler{ 81 | "i get new product price with specified discount rate": func() error { 82 | return nil 83 | }, 84 | "i get campaign not found error when the product has no discount": func() error { 85 | product := products[2] 86 | product.HasCampaign = false 87 | products[2] = product 88 | return nil 89 | }, 90 | "i get the product does not exist": func() error { 91 | // we ensure that products map has no key 3. 92 | delete(products, 3) 93 | return nil 94 | }, 95 | }, 96 | PublishVerificationResults: true, 97 | FailIfNoPactsFound: true, 98 | } 99 | 100 | verifyResponses, err := pact.VerifyProvider(t, verifyRequest) 101 | if err != nil { 102 | t.Fatal(err) 103 | } 104 | 105 | pp.Println(len(verifyResponses), "pact tests run") 106 | } 107 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/Abdulsametileri/cdc-pact-gophercon-2021 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/gofiber/fiber/v2 v2.22.0 7 | github.com/k0kubun/pp v3.0.1+incompatible 8 | github.com/pact-foundation/pact-go v1.6.6 9 | ) 10 | 11 | require ( 12 | github.com/andybalholm/brotli v1.0.2 // indirect 13 | github.com/hashicorp/go-version v1.3.0 // indirect 14 | github.com/hashicorp/logutils v0.0.0-20150609070431-0dc08b1671f3 // indirect 15 | github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 // indirect 16 | github.com/klauspost/compress v1.13.4 // indirect 17 | github.com/mattn/go-colorable v0.1.12 // indirect 18 | github.com/mattn/go-isatty v0.0.14 // indirect 19 | github.com/valyala/bytebufferpool v1.0.0 // indirect 20 | github.com/valyala/fasthttp v1.31.0 // indirect 21 | github.com/valyala/tcplisten v1.0.0 // indirect 22 | golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 // indirect 23 | ) 24 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 3 | github.com/andybalholm/brotli v1.0.2 h1:JKnhI/XQ75uFBTiuzXpzFrUriDPiZjlOSzh6wXogP0E= 4 | github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= 5 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 6 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 7 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 9 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 11 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 12 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 13 | github.com/gin-gonic/gin v1.7.2/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= 14 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 15 | github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= 16 | github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= 17 | github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= 18 | github.com/gofiber/fiber/v2 v2.22.0 h1:+iyKK4ooDH6z0lAHdaWO1AFIB/DZ9AVo6vz8VZIA0EU= 19 | github.com/gofiber/fiber/v2 v2.22.0/go.mod h1:MR1usVH3JHYRyQwMe2eZXRSZHRX38fkV+A7CPB+DlDQ= 20 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 21 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 22 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 23 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 24 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 25 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 26 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 27 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 28 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 29 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 30 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 31 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 32 | github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 33 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 34 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 35 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 36 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 37 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 38 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 39 | github.com/hashicorp/go-version v1.3.0 h1:McDWVJIU/y+u1BRV06dPaLfLCaT7fUTJLp5r04x7iNw= 40 | github.com/hashicorp/go-version v1.3.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= 41 | github.com/hashicorp/logutils v0.0.0-20150609070431-0dc08b1671f3 h1:oD64EFjELI9RY9yoWlfua58r+etdnoIC871z+rr6lkA= 42 | github.com/hashicorp/logutils v0.0.0-20150609070431-0dc08b1671f3/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= 43 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 44 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 45 | github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 46 | github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 h1:uC1QfSlInpQF+M0ao65imhwqKnz3Q2z/d8PWZRMQvDM= 47 | github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= 48 | github.com/k0kubun/pp v3.0.1+incompatible h1:3tqvf7QgUnZ5tXO6pNAZlrvHgl6DvifjDrd9g2S9Z40= 49 | github.com/k0kubun/pp v3.0.1+incompatible/go.mod h1:GWse8YhT0p8pT4ir3ZgBbfZild3tgzSScAn6HmfYukg= 50 | github.com/klauspost/compress v1.13.4 h1:0zhec2I8zGnjWcKyLl6i3gPqKANCCn5e9xmviEEeX6s= 51 | github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= 52 | github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= 53 | github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= 54 | github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= 55 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 56 | github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= 57 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 58 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 59 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 60 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 61 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 62 | github.com/pact-foundation/pact-go v1.6.6 h1:14gUdafTLvblys5QEZYrlCi8aHR9mPc0nNQN0gM2gE4= 63 | github.com/pact-foundation/pact-go v1.6.6/go.mod h1:fyw1tCDIMKf73u9+VeF20uqkhYwyAMY4SVdejF1P+hc= 64 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 65 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 66 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 67 | github.com/spf13/cobra v0.0.0-20160604044732-f447048345b6/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= 68 | github.com/spf13/pflag v0.0.0-20160427162146-cb88ea77998c/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 69 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 70 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 71 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 72 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 73 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 74 | github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= 75 | github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= 76 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 77 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 78 | github.com/valyala/fasthttp v1.31.0 h1:lrauRLII19afgCs2fnWRJ4M5IkV0lo2FqA61uGkNBfE= 79 | github.com/valyala/fasthttp v1.31.0/go.mod h1:2rsYD01CKFrjjsvFxx75KlEUNpWNBY9JWD3K/7o2Cus= 80 | github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= 81 | github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= 82 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 83 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 84 | golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= 85 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 86 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 87 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 88 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 89 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 90 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 91 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 92 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 93 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 94 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 95 | golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 96 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 97 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 98 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 99 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 100 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 101 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 102 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 103 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 104 | golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 105 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 106 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 107 | golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 108 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 109 | golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 h1:foEbQz/B0Oz6YIqu/69kfXPYeFQAuuMYFkjaqXzl5Wo= 110 | golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 111 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 112 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 113 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 114 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 115 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 116 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 117 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 118 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 119 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 120 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 121 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 122 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 123 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 124 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 125 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 126 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 127 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 128 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 129 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 130 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 131 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 132 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 133 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 134 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 135 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 136 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 137 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 138 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 139 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 140 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 141 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 142 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 143 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 144 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 145 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 146 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 147 | -------------------------------------------------------------------------------- /images/our-arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Abdulsametileri/cdc-pact-gophercon-2021/da34377925cac82a5403daae46a951cd480b550a/images/our-arch.png -------------------------------------------------------------------------------- /images/pact-arch.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 16 | ProductServicePactMockServerPactTestTriggerinteractionDefineinteractionPlay & RecordGeneratePactPactBrokerSharePactCampaignServicePactTestAPIReplayinteractionReplay & Verify -------------------------------------------------------------------------------- /product/consumer_test.go: -------------------------------------------------------------------------------- 1 | //go:build consumer 2 | // +build consumer 3 | 4 | package main 5 | 6 | import ( 7 | "fmt" 8 | "github.com/gofiber/fiber/v2" 9 | "github.com/pact-foundation/pact-go/dsl" 10 | "net/http" 11 | "strconv" 12 | "testing" 13 | ) 14 | 15 | func createPact() (pact *dsl.Pact, cleanUp func()) { 16 | pact = &dsl.Pact{ 17 | Host: "localhost", 18 | Consumer: "ProductService", 19 | Provider: "CampaignService", 20 | DisableToolValidityCheck: true, 21 | PactFileWriteMode: "merge", 22 | LogDir: "./pacts/logs", 23 | } 24 | 25 | cleanUp = func() { pact.Teardown() } 26 | 27 | return pact, cleanUp 28 | } 29 | 30 | func Test_IGetNewProductPriceWithSpecifiedDiscountRate(t *testing.T) { 31 | pact, cleanUp := createPact() 32 | defer cleanUp() 33 | 34 | const productIDWithDiscount = 1 35 | const discountRate = 30 36 | 37 | pact. 38 | AddInteraction(). 39 | Given("i get new product price with specified discount rate"). 40 | UponReceiving("A request for campaign"). 41 | WithRequest(dsl.Request{ 42 | Method: http.MethodGet, 43 | Path: dsl.String(fmt.Sprintf("/products/%d/discount", productIDWithDiscount)), 44 | Query: map[string]dsl.Matcher{ 45 | "rate": dsl.Like(strconv.Itoa(discountRate)), 46 | }, 47 | }). 48 | WillRespondWith(dsl.Response{ 49 | Status: http.StatusOK, 50 | Headers: dsl.MapMatcher{ 51 | fiber.HeaderContentType: dsl.String(fiber.MIMEApplicationJSON), 52 | }, 53 | Body: dsl.StructMatcher{ 54 | "id": dsl.Integer(), 55 | "price": dsl.Decimal(), 56 | "name": dsl.Like(""), 57 | }, 58 | }) 59 | 60 | // This is the component that makes the external HTTP Call 61 | var test = func() error { 62 | return makeRequest(pact.Server.Port, productIDWithDiscount, discountRate) 63 | } 64 | 65 | err := pact.Verify(test) 66 | 67 | if err != nil { 68 | t.Fatal(err) 69 | } 70 | } 71 | 72 | func Test_IGetCampaignNotFoundErrorWhenTheProductHasNoDiscount(t *testing.T) { 73 | pact, cleanUp := createPact() 74 | defer cleanUp() 75 | 76 | const productIDWithoutDiscount = 2 77 | const discountRate = 30 78 | 79 | pact. 80 | AddInteraction(). 81 | Given("i get campaign not found error when the product has no discount"). 82 | UponReceiving("A request for campaign without discounted product"). 83 | WithRequest(dsl.Request{ 84 | Method: http.MethodGet, 85 | Path: dsl.String(fmt.Sprintf("/products/%d/discount", productIDWithoutDiscount)), 86 | Query: map[string]dsl.Matcher{ 87 | "rate": dsl.Like(strconv.Itoa(discountRate)), 88 | }, 89 | }). 90 | WillRespondWith(dsl.Response{ 91 | Status: http.StatusNotAcceptable, 92 | Headers: dsl.MapMatcher{ 93 | fiber.HeaderContentType: dsl.String(fiber.MIMEApplicationJSON), 94 | }, 95 | Body: dsl.StructMatcher{ 96 | "message": dsl.Like("No campaign found for this product"), 97 | }, 98 | }) 99 | err := pact.Verify(func() error { 100 | return makeRequest(pact.Server.Port, productIDWithoutDiscount, discountRate) 101 | }) 102 | 103 | if err != nil { 104 | t.Fatal(err) 105 | } 106 | } 107 | 108 | func Test_IGetProductDoesNotExist(t *testing.T) { 109 | pact, cleanUp := createPact() 110 | defer cleanUp() 111 | 112 | const notExistProductID = 3 113 | const discountRate = 30 114 | 115 | pact. 116 | AddInteraction(). 117 | Given("i get the product does not exist"). 118 | UponReceiving("A request for campaign with nonexist product"). 119 | WithRequest(dsl.Request{ 120 | Method: http.MethodGet, 121 | Path: dsl.String(fmt.Sprintf("/products/%d/discount", notExistProductID)), 122 | Query: map[string]dsl.Matcher{ 123 | "rate": dsl.Like(strconv.Itoa(discountRate)), 124 | }, 125 | }). 126 | WillRespondWith(dsl.Response{ 127 | Headers: map[string]dsl.Matcher{ 128 | fiber.HeaderContentType: dsl.String(fiber.MIMETextPlainCharsetUTF8), 129 | }, 130 | Status: http.StatusNotFound, 131 | }) 132 | err := pact.Verify(func() error { 133 | return makeRequest(pact.Server.Port, notExistProductID, discountRate) 134 | }) 135 | 136 | if err != nil { 137 | t.Fatal(err) 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /product/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io" 7 | "log" 8 | "net/http" 9 | ) 10 | 11 | func main() { 12 | if err := makeRequest(3000, 1, 30); err != nil { 13 | log.Fatal(err) 14 | } 15 | } 16 | 17 | func makeRequest(port, productID, discountRate int) error { 18 | resp, err := http.Get(fmt.Sprintf("http://localhost:%d/products/%d/discount?rate=%d", port, productID, discountRate)) 19 | if err != nil { 20 | return errors.New("unable to send the request") 21 | } 22 | 23 | body, err := io.ReadAll(resp.Body) 24 | if err != nil { 25 | return errors.New("unable to read the body") 26 | } 27 | 28 | fmt.Println(string(body)) 29 | 30 | return nil 31 | } 32 | -------------------------------------------------------------------------------- /product/pact-publish.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -x 4 | 5 | VERSION=$1 #like 1.0.0 6 | 7 | curl -X PUT \ 8 | http://localhost/pacts/provider/CampaignService/consumer/ProductService/version/${VERSION} \ 9 | -H "Content-Type: application/json" \ 10 | -d @/Users/abdulsamet.ileri/Desktop/personal/cdc-pact-gophercon-2021/product/pacts/productservice-campaignservice.json --------------------------------------------------------------------------------