Skip to content
This repository has been archived by the owner on Sep 17, 2024. It is now read-only.

[PACT] feat: add Contract tests for Kibana Fleet #339

Closed
wants to merge 45 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
82ec16c
chore: add sequence diagrams for Fleet APIs
mdelapenya Sep 29, 2020
edb05f4
chore: add a unit test mocking Kibana response
mdelapenya Sep 29, 2020
d599c47
chore: bump minor version for Jeffail/gabs
mdelapenya Sep 29, 2020
bc67521
feat: first Pact tests for the E2E tests consumer
mdelapenya Sep 29, 2020
e67ac8d
chore: notice dependencies
mdelapenya Sep 29, 2020
19dcd35
chore: apply basic auth in request headers
mdelapenya Sep 29, 2020
c7a3199
chore: move pacts to the root dir
mdelapenya Sep 30, 2020
d1deb1a
chore: move pact to the root directory
mdelapenya Sep 30, 2020
dc95dcf
chore: use a struct for the expected response
mdelapenya Sep 30, 2020
81ba767
chore: rename variable
mdelapenya Sep 30, 2020
53e0679
chore: add first contract test for the provider
mdelapenya Sep 30, 2020
67ca63c
chore: set provider headers
mdelapenya Sep 30, 2020
b87e441
chore: add a test for getting an existing integration
mdelapenya Sep 30, 2020
23468ba
chore: add a test for a non-existing integration
mdelapenya Sep 30, 2020
3834cc7
fix: kibana returns a 500 when the integration is not found
mdelapenya Sep 30, 2020
ebd8a75
chore: add request headers in consumer contract
mdelapenya Sep 30, 2020
9a717ff
fix: in master kibana returns a 404 not found, as expected
mdelapenya Sep 30, 2020
cdcf262
docs: document pact contracts
mdelapenya Oct 1, 2020
b7ca07a
chore: add missing license header to tests
mdelapenya Oct 1, 2020
4394dee
chore: startup and tear down runtime dependencies
mdelapenya Oct 1, 2020
9bcc623
chore: make sure the files are regen
mdelapenya Oct 1, 2020
3a3a9fd
docs: fix typo
mdelapenya Oct 1, 2020
a304d8f
chore: decouple the destruction of runtime deps for pact tests
mdelapenya Oct 1, 2020
0396fce
feat(ci): integrate contract tests in the CI
mdelapenya Oct 1, 2020
f171fcb
chore: archive pact logs
mdelapenya Oct 1, 2020
d64ff6a
fix: forgot to add the when expression
mdelapenya Oct 1, 2020
b7d1e7a
chore: make the contract tests timeout to be configurable
mdelapenya Oct 1, 2020
1d84d84
fix: add docker login for private images
mdelapenya Oct 1, 2020
ce53845
chore: leverage Go build tags to skip pact tests from unit stage
mdelapenya Oct 1, 2020
87a56c3
chore: add missing license header to test
mdelapenya Oct 1, 2020
e4ce4a4
fix: use declarative instead of classic IF
mdelapenya Oct 1, 2020
114ea7c
chore: move contract tests stage to later in the pipeline
mdelapenya Oct 1, 2020
f0c8968
fixup
mdelapenya Oct 2, 2020
945876d
fix(ci): use proper path (again)
mdelapenya Oct 2, 2020
8b67b46
fix: typo
mdelapenya Oct 8, 2020
1097e35
fix: typo
mdelapenya Oct 8, 2020
0190346
Merge branch 'master' into pact
mdelapenya Oct 8, 2020
0864146
Merge branch 'master' into pact
mdelapenya Oct 13, 2020
bbae5b0
chore: update fleet APIs
mdelapenya Oct 13, 2020
87832b9
chore: update fleet paths in contract
mdelapenya Oct 13, 2020
b5d1a4f
chore: bump eastic-endpoint version
mdelapenya Oct 13, 2020
f6d67f7
fix: update fleet path
mdelapenya Oct 13, 2020
c52de78
fix: EPR returns 502 with not found integrations
mdelapenya Oct 13, 2020
0aa330e
Revert "fix: EPR returns 502 with not found integrations"
mdelapenya Oct 13, 2020
bb6c7b3
chore: update diagrams with new endpoints
mdelapenya Oct 13, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions .ci/Jenkinsfile
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,38 @@ pipeline {
}
}
}
stage('Contract Tests') {
agent { label 'ubuntu-18.04 && immutable && docker' }
options { skipDefaultCheckout() }
when {
beforeAgent true
expression { return env.SKIP_TESTS == "false" }
}
steps {
withGithubNotify(context: 'Contract Tests') {
deleteDir()
unstash 'source'
withGoEnv(version: "${GO_VERSION}"){
whenTrue(isInstalled(tool: 'docker', flag: '--version')) {
dockerLogin(secret: "${DOCKER_ELASTIC_SECRET}", registry: "${DOCKER_REGISTRY}")
}
dir(BASE_DIR){
sh script: 'make verify-provider', label: 'Verify provider'
}
}
}
}
post {
always {
archiveArtifacts allowEmptyArchive: true, artifacts: "${BASE_DIR}/pact-log/pact.log"
withGoEnv(version: "${GO_VERSION}"){
dir(BASE_DIR){
sh script: 'make destroy-pact-provider-deps', label: 'Destroy runtime dependencies'
}
}
}
}
}
stage('End-To-End Tests') {
options { skipDefaultCheckout() }
environment {
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
bin
outputs

pact
pact.log
65 changes: 65 additions & 0 deletions CONTRACT_TESTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Contract tests for the E2E Testing project using PACT

> We are basically extracting contents from [Pact's docs](https://docs.pact.io)

## Introduction

### What is contract testing?
_**Contract testing** is a technique for testing an integration point by checking each application in isolation to ensure the messages it sends or receives conform to a shared understanding that is documented in a "contract"._

For applications that communicate via HTTP, these "messages" would be the HTTP request and response, and for an application that used queues, this would be the message that goes on the queue.

In practice, a common way of implementing contract tests (and the way Pact does it) is to check that all the calls to your test doubles [return the same results](https://martinfowler.com/bliki/ContractTest.html) as a call to the real application would.

### When would I use contract testing?
Contract testing is immediately applicable anywhere where you have two services that need to communicate - such as an API client and a web front-end. Although a single client and a single service is a common use case, contract testing really shines in an environment with many services (as is common for a microservice architecture). Having well-formed contract tests makes it easy for developers to avoid version hell. Contract testing is the killer app for microservice development and deployment.

### Contract testing terminology
In general, a contract is between a _consumer_ (for example, a client that wants to receive some data) and a _provider_ (for example, an API on a server that provides the data the client needs). In microservice architectures, the traditional terms _client_ and _server_ are not always appropriate -- for example, when communication is achieved through message queues. For this reason, we stick to _consumer_ and _provider_ in this documentation.

### Consumer Driven Contracts
Pact is a code-first [consumer-driven](http://martinfowler.com/articles/consumerDrivenContracts.html) contract testing tool, and is generally used by developers and testers who code. The contract is generated during the execution of the automated consumer tests. A major advantage of this pattern is that only parts of the communication that are actually used by the consumer(s) get tested. This in turn means that any provider behaviour not used by current consumers is free to change without breaking tests.

Unlike a schema or specification (eg. OAS), which is a static artefact that describes all possible states of a resource, a Pact contract is enforced by executing a collection of test cases, each of which describes a single concrete request/response pair - Pact is, in effect, "contract by example".

### Provider contract testing
The term "contract testing", or "provider contract testing", is sometimes used in other literature and documentation in the context of a standalone provider application (rather than in the context of an integration). When used in this context, "contract testing" means: a technique for ensuring a provider's actual behaviour conforms to its documented contract (for example, an Open API specification). This type of contract testing helps avoid integration failures by ensuring the provider code and documentation are in sync with each other. On its own, however, it does not provide any test based assurance that the consumers are calling the provider in the correct manner, or that the provider can meet all its consumers' expectations, and hence, it is not as effective in preventing integration bugs.

## Fleet contracts
We are already running E2E tests for Fleet, defining the scenarios and behaviours using Gherkin (see [here](./e2e/_suites/ingest-manager/features)). As explained above, we now want to verify that the contracts between Kibana Fleet and this project is not broken because of changes in the APIs the tests consume.

For that reason, we are going to use Pact to get notified whenever the contracts are not satisfied. The steps that we are folowing are:

1. Create unit tests mocking the HTTP requests/responses for Fleet APIs. As we all know, this approach will always work, as we predefine the mocks, but on the other hand we won't get notified if/when the provider changes, so we must be reactively checking for updates.
1. Create a contract/pact for the consumer (the e2e tests), with the behaviours we expect from Fleet. The result will be a JSON file with the specs.
1. Store the contract in some place that is accesible by the provider. In this case, we are using the file system to store them, but Pact recommends using a [Broker](https://github.com/pact-foundation/pact_broker).
1. Verify the contract/pact from the provider side. The provider is able to check if a change breaks all its consumers by itself, as it has access to the contracts they provided (via file system or the aforementioned Broker).

>Ideally, the provider project should be the one responsible of the verification but, for simplification, we are creating that verification in this project. Kibana should implement a way to verify contracts/pacts from its own build system, so any engineer is able to check if he/she breaks the consumers.

### Executing the tests
From root directory, use the following Make targets:

#### Generate the contracts for the consumer
```shell
$ make pact-consumer
```

It will execute Pact (using the [Go implementation](https://github.com/pact-foundation/pact-go/)) to generate the contract for the consumer (the E2E tests), generating a [JSON file](./pacts/e2e_testing_framework-fleet.json) representing the contract. This file will contain all the scenarios defined in the contract, highlighting the following sections:

- **consumer**: the name of the consumer
- **provider**: the name of the provider
- **interactions**: the scenarios executed in the contract, defining the format of the HTTP request, HTTP headers and HTTP response for each one. It will use regex to handle the response values, so that it's not coupled with the real response data returned by the provider.
- **meta**: Pact specification and version (2.0.0)


#### Verify the contracts from provider side
We want to verify that the provider satisfies the contracts. For that reason, we need the provider to be started first, and to achieve it, we are providing the following Make commands:

```shell
$ make prepare-pact-provider-deps # will run an Elasticsearch, Kibana andd Package Registry
$ make pact-provider # will run the verification of the contracts by itself
$ make verify-provider # will tear down the runtime dependencies
Comment on lines +61 to +62
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

verify-provider seems to call some other make goals -> https://github.com/elastic/e2e-testing/pull/339/files#diff-b67911656ef5d18c4ae36cb6741b7965R47

Is the comment correct?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it's on purpose: verify is the last piece in the chain, defining a well-known life cycle: prepare > verify > destroy

I did this that way to support decoupling the destroy goal in the post stage of the CI pipeline (or to be run manually)

```

Each target depends on the one above it, so running `make verify-provider` will run the run the full life cycle.
44 changes: 44 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
export PACT_DIR = $(PWD)/pacts
export PACT_LOG_DIR = $(PWD)/pact-log
export PATH := $(PWD)/pact/bin:$(PATH)
export PATH

TIMEOUT_FACTOR?=1
WAIT_SECONDS = $(shell expr 30 \* $(TIMEOUT_FACTOR))

FLEET_KIBANA_CONFIG := $(PWD)/e2e/_suites/fleet/configurations/kibana.config.yml

.PHONY: clean
clean: clean-workspace clean-docker

Expand All @@ -12,3 +22,37 @@ clean-workspace:
.PHONY: install
install:
go get -v -t ./...

.PHONY: install-pact
install-pact:
@if [ ! -d pact/bin ]; then\
echo "--- Installing Pact CLI dependencies";\
curl -fsSL https://raw.githubusercontent.com/pact-foundation/pact-ruby-standalone/master/install.sh | bash;\
fi

.PHONY: pact-consumer
pact-consumer: export PACT_TEST := true
pact-consumer: install-pact
@echo "--- 🔨Running Consumer Pact tests "
cd cli && go test -count=1 github.com/elastic/e2e-testing/cli/services -run 'TestPactConsumer'

.PHONY: destroy-pact-provider-deps
destroy-pact-provider-deps:
@echo "--- 🚒 Stopping Fleet dependencies"
cd cli && go run main.go stop profile fleet

.PHONY: prepare-pact-provider-deps
prepare-pact-provider-deps: install-pact
@rm -fr ~/.op/compose
@echo "--- 🚄 Starting Fleet dependencies"
cd cli && kibanaConfigPath="$(FLEET_KIBANA_CONFIG)" go run main.go run profile fleet

.PHONY: pact-provider
pact-provider: prepare-pact-provider-deps
@echo "--- ⏸️ Pausing $(WAIT_SECONDS) seconds waiting for Kibana to be ready"
@sleep $(WAIT_SECONDS)
@echo "--- 🔨 Running Provider Pact tests"
cd cli && go test -count=1 -tags=integration github.com/elastic/e2e-testing/cli/services -run "TestPactProvider"

.PHONY: verify-provider
verify-provider: pact-provider destroy-pact-provider-deps
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ This repository contains:
- MySQL
- Redis
- vSphere
1. Contract tests for the integration between Fleet and the tests, using [pact.io](https://pact.io/). For further information, please read [CONTRACT_TESTING.md](CONTRACT_TESTING.md).

## Contributing

Expand Down
2 changes: 1 addition & 1 deletion cli/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,4 @@ notice:

.PHONY: test
test:
go test -v -timeout=$(TEST_TIMEOUT) ./...
go test -v -timeout=$(TEST_TIMEOUT) ./... --tags unit
Loading