Skip to content
This repository has been archived by the owner on Mar 11, 2021. It is now read-only.

WIP: Create contract tests for interaction with Auth service. #659

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
48 changes: 48 additions & 0 deletions .make/test.mk
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,14 @@ ALL_PKGS_EXCLUDE_PATTERN = 'vendor\|app\|tool\/cli\|design\|client\|test'
GOANALYSIS_PKGS_EXCLUDE_PATTERN="vendor|app|client|tool/cli"
GOANALYSIS_DIRS=$(shell go list -f {{.Dir}} ./... | grep -v -E $(GOANALYSIS_PKGS_EXCLUDE_PATTERN))

# Folder with contract tests
CONTRACT_TESTS=$(CUR_DIR)/test/contracts

# Configuration of contract tests
PACT_VERSION ?= 1.0.0
PACT_BROKER_URL ?= http://pact-broker-pact-broker.193b.starter-ca-central-1.openshiftapps.com
Copy link
Contributor

Choose a reason for hiding this comment

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

We can't relay on OSO-starter to run the broker. Need a better place for that.

Choose a reason for hiding this comment

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

Maybe the same home as sentry and ike bots? /cc @kbsingh

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah.. it should be preview or prod cluster. @pmacik talk to the SD team.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@alexeykazakov @bartoszmajsak Thanks for the suggestion, created appropriate SD ticket for that (https://gitlab.cee.redhat.com/dtsd/housekeeping/issues/2383)

PACT_PROVIDER_BASE_URL ?= https://auth.openshift.io

#-------------------------------------------------------------------------------
# Normal test targets
#
Expand Down Expand Up @@ -159,6 +167,7 @@ test-templates-flags:
test-unit: test-templates-flags prebuild-check clean-coverage-unit $(COV_PATH_UNIT)

.PHONY: test-unit-no-coverage

## Runs the unit tests and WITHOUT producing coverage files for each package.
test-unit-no-coverage: test-templates-flags prebuild-check $(SOURCES)
$(call log-info,"Running test: $@")
Expand All @@ -183,6 +192,45 @@ test-integration-no-coverage: prebuild-check migrate-database $(SOURCES)
$(eval TEST_PACKAGES:=$(shell go list ./... | grep -v $(ALL_PKGS_EXCLUDE_PATTERN)))
F8_DEVELOPER_MODE_ENABLED=1 F8_RESOURCE_DATABASE=1 F8_RESOURCE_UNIT_TEST=0 F8_POSTGRES_DATABASE=postgres go test -v $(TEST_PACKAGES)

.PHONY: test-contract-auth-consumer
## Runs the consumer side contract tests of the Auth service and produces pact files.
test-contract-auth-consumers:
cd $(CONTRACT_TESTS)/auth && \
PACT_DIR=$(CONTRACT_TESTS)/pacts \
PACT_VERSION=$(PACT_VERSION) \
PACT_BROKER_URL=$(PACT_BROKER_URL) \
./consumer-contracts.sh

.PHONY: test-contract-auth-publish
## Publishes the generated files to a Pact broker.
test-contract-auth-publish:
cd $(CONTRACT_TESTS)/auth && \
PACT_VERSION=$(PACT_VERSION) \
PACT_BROKER_URL=$(PACT_BROKER_URL) \
./publish-contracts.sh

.PHONY: test-contract-auth-verify
## Verifies the contracts against the living provider. The pact files are taken from pact directory.
test-contract-auth-verify:
cd $(CONTRACT_TESTS)/auth && \
PACT_PROVIDER_BASE_URL=$(PACT_PROVIDER_BASE_URL) \
./verify-contracts.sh

.PHONY: test-contract-auth-verify-broker
## Verifies the contracts against the living provider. The pact files are taken from the Pact broker.
test-contract-auth-verify-broker:
cd $(CONTRACT_TESTS)/auth && \
PACT_VERSION=$(PACT_VERSION) \
PACT_BROKER_URL=$(PACT_BROKER_URL) \
PACT_PROVIDER_BASE_URL=$(PACT_PROVIDER_BASE_URL) \
./verify-contracts-broker.sh

.PHONY: clean-test-contract-auth
## Runs the consumer side contract tests and produces pact files.
clean-test-contract-auth:
cd $(CONTRACT_TESTS)/auth && \
rm -rvf pacts log logs

.PHONY: test-remote
## Runs the remote tests and produces coverage files for each package.
test-remote: prebuild-check clean-coverage-remote $(COV_PATH_REMOTE)
Expand Down
4 changes: 4 additions & 0 deletions test/contracts/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
**/*log
**/*logs
**/*pacts
.password
59 changes: 59 additions & 0 deletions test/contracts/auth/auth_api_status.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Package contracts contains a runnable Consumer Pact test example.
package contracts

import (
"fmt"
"log"
"net/http"
"testing"

"github.com/pact-foundation/pact-go/dsl"
)

// AuthAPIStatus defines contract of /api/status endpoint
func AuthAPIStatus(t *testing.T, pact *dsl.Pact) {
// Pass in test case
var test = func() error {
u := fmt.Sprintf("http://localhost:%d/api/status", pact.Server.Port)
req, err := http.NewRequest("GET", u, nil)

req.Header.Set("Content-Type", "application/json")
if err != nil {
return err
}

_, err = http.DefaultClient.Do(req)
if err != nil {
return err
}
return err
}

type STATUS struct {
buildTime string `json:"buildTime" pact:"example=2018-10-05T10:03:04Z"`
commit string `json:"commit" pact:"example=0f9921980549b2baeb43f6f16cbe794f430f498c"`
configurationStatus string `json:"configurationStatus" pact:"example=OK"`
databaseStatus string `json:"databaseStatus" pact:"example=OK"`
startTime string `json:"startTime" pact:"example=2018-10-09T15:04:50Z"`
}

// Set up our expected interactions.
pact.
AddInteraction().
UponReceiving("A request to get status").
WithRequest(dsl.Request{
Method: "GET",
Path: dsl.String("/api/status"),
Headers: dsl.MapMatcher{"Content-Type": dsl.String("application/json")},
}).
WillRespondWith(dsl.Response{
Status: 200,
Headers: dsl.MapMatcher{"Content-Type": dsl.String("application/vnd.status+json")},
Body: dsl.Match(STATUS{}),
})

// Verify
if err := pact.Verify(test); err != nil {
log.Fatalf("Error on Verify: %v", err)
}
}
264 changes: 264 additions & 0 deletions test/contracts/auth/auth_api_user.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
// Package contracts contains a runnable Consumer Pact test example.
package contracts

import (
"fmt"
"log"
"net/http"
"os"
"testing"

"github.com/pact-foundation/pact-go/dsl"
)

type Data struct {
Attributes struct {
Bio string `json:"bio" pact:"example=n/a"`
Cluster string `json:"cluster" pact:"example=https://api.starter-us-east-2a.openshift.com/"`
Company string `json:"company" pact:"example=n/a"`
ContextInformation struct {
RecentContexts []struct {
User string `json:"user" pact:"example=c46445eb-2448-4c91-916a-2c1de3e6f63e"`
} `json:"recentContexts"`
RecentSpaces []string `json:"recentSpaces"`
} `json:"contextInformation"`
CreatedAt string `json:"created-at" pact:"example=2018-03-16T14:34:31.615511Z"`
Email string `json:"email" pact:"example=osio-ci+ee10@redhat.com"`
EmailPrivate bool `json:"emailPrivate" pact:"example=false"`
EmailVerified bool `json:"emailVerified" pact:"example=true"`
FeatureLevel string `json:"featureLevel" pact:"example=internal"`
FullName string `json:"fullName" pact:"example=Osio10 Automated Tests"`
IdentityID string `json:"identityID" pact:"example=c46445eb-2448-4c91-916a-2c1de3e6f63e"`
ImageURL string `json:"imageURL" pact:"example=n/a"`
ProviderType string `json:"providerType" pact:"example=kc"`
RegistrationCompleted bool `json:"registrationCompleted" pact:"example=true"`
UpdatedAt string `json:"updated-at" pact:"example=2018-05-30T11:05:23.513612Z"`
URL string `json:"url" pact:"example=n/a"`
UserID string `json:"userID" pact:"example=5f41b66e-6f84-42b3-ab5f-8d9ef21149b1"`
Username string `json:"username" pact:"example=osio-ci-ee10"`
} `json:"attributes"`
ID string `json:"id" pact:"example=c46445eb-2448-4c91-916a-2c1de3e6f63e"`
Links struct {
Related string `json:"related" pact:"example=https://auth.openshift.io/api/users/c46445eb-2448-4c91-916a-2c1de3e6f63e"`
Self string `json:"self" pact:"example=https://auth.openshift.io/api/users/c46445eb-2448-4c91-916a-2c1de3e6f63e"`
} `json:"links"`
Type string `json:"type" pact:"example=identities"`
}

type User struct {
data Data `json:"data"`
}

type Users struct {
data []Data `json:"data"`
}

type InvalidToken struct {
Errors []struct {
Code string `json:"code" pact:"example=token_validation_failed"`
Detail string `json:"detail" pact:"example=token is invalid"`
ID string `json:"id" pact:"example=76J0ww+6"`
Status string `json:"status" pact:"example=401"`
Title string `json:"title" pact:"example=Unauthorized"`
} `json:"errors"`
}

type MissingToken struct {
Errors []struct {
Code string `json:"code" pact:"example=jwt_security_error"`
Detail string `json:"detail" pact:"example=missing header \"Authorization\""`
ID string `json:"id" pact:"example=FRzHbogQ"`
Status string `json:"status" pact:"example=401"`
Title string `json:"title" pact:"example=Unauthorized"`
} `json:"errors"`
}

const jwsRegex = "[a-zA-Z0-9\\-_]+?\\.?[a-zA-Z0-9\\-_]+?\\.?([a-zA-Z0-9\\-_]+)?"
const userNameRegex = "[a-zA-Z\\-0-9]+"

// AuthAPIUserByNameConsumer defines contract of /api/users?filter[username]=<user_name> endpoint
func AuthAPIUserByNameConsumer(t *testing.T, pact *dsl.Pact) {
userName := os.Getenv("OSIO_USERNAME")

// Pass in test case
var test = func() error {
url := fmt.Sprintf("http://localhost:%d/api/users?filter[username]=%s", pact.Server.Port, userName)
req, err := http.NewRequest("GET", url, nil)

req.Header.Set("Content-Type", "application/json")
if err != nil {
return err
}

_, err = http.DefaultClient.Do(req)
if err != nil {
return err
}
return err
}

// Set up our expected interactions.
pact.
AddInteraction().
UponReceiving("A request to get user's information by name").
WithRequest(dsl.Request{
Method: "GET",
Path: dsl.String("/api/users"),
Query: dsl.MapMatcher{
"filter[username]": dsl.Term(
userName,
userNameRegex,
),
},
Headers: dsl.MapMatcher{"Content-Type": dsl.String("application/json")},
}).
WillRespondWith(dsl.Response{
Status: 200,
Headers: dsl.MapMatcher{"Content-Type": dsl.String("application/vnd.api+json")},
Body: dsl.Match(Users{}),
})

// Verify
if err := pact.Verify(test); err != nil {
log.Fatalf("Error on Verify: %v", err)
}
}

// AuthAPIUserByIDConsumer defines contract of /api/users/<user_id> endpoint
func AuthAPIUserByIDConsumer(t *testing.T, pact *dsl.Pact) {
userID := os.Getenv("OSIO_USER_ID")

// Pass in test case
var test = func() error {
url := fmt.Sprintf("http://localhost:%d/api/users/%s", pact.Server.Port, userID)
req, err := http.NewRequest("GET", url, nil)

req.Header.Set("Content-Type", "application/json")
if err != nil {
return err
}

_, err = http.DefaultClient.Do(req)
if err != nil {
return err
}
return err
}

// Set up our expected interactions.
pact.
AddInteraction().
UponReceiving("A request to get user's information by ID").
WithRequest(dsl.Request{
Method: "GET",
Path: dsl.Term(
fmt.Sprintf("/api/users/%s", userID),
fmt.Sprintf("/api/users/%s", userNameRegex),
),
Headers: dsl.MapMatcher{"Content-Type": dsl.String("application/json")},
}).
WillRespondWith(dsl.Response{
Status: 200,
Headers: dsl.MapMatcher{"Content-Type": dsl.String("application/vnd.api+json")},
Body: dsl.Match(User{}),
})

// Verify
if err := pact.Verify(test); err != nil {
log.Fatalf("Error on Verify: %v", err)
}
}

// AuthAPIUserInvalidToken defines contract of /api/user endpoint with invalid auth token
func AuthAPIUserInvalidToken(t *testing.T, pact *dsl.Pact) {

// Base64 encoded '{"alg":"RS256","kid":"1aA2bBc3CDDdEEefff7gGHH_ii9jJjkkkLl2mmm4NNO","typ":"JWT"}somerandombytes'
var invalidToken = "eyJhbGciOiJSUzI1NiIsImtpZCI6IjFhQTJiQmMzQ0REZEVFZWZmZjdnR0hIX2lpOWpKamtra0xsMm1tbTROTk8iLCJ0eXAiOiJKV1QifXNvbWVyYW5kb21ieXRlcw"

// Pass in test case
var test = func() error {
url := fmt.Sprintf("http://localhost:%d/api/user", pact.Server.Port)
req, err := http.NewRequest("GET", url, nil)

req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", invalidToken))
if err != nil {
return err
}

_, err = http.DefaultClient.Do(req)
if err != nil {
return err
}
return err
}

// Set up our expected interactions.
pact.
AddInteraction().
UponReceiving("A request to get user's information with invalid auth token ").
WithRequest(dsl.Request{
Method: "GET",
Path: dsl.String("/api/user"),
Headers: dsl.MapMatcher{
"Content-Type": dsl.String("application/json"),
"Authorization": dsl.Term(
fmt.Sprintf("Bearer %s", invalidToken),
fmt.Sprintf("^Bearer %s$", jwsRegex),
),
},
}).
WillRespondWith(dsl.Response{
Status: 401,
Headers: dsl.MapMatcher{"Content-Type": dsl.String("application/vnd.api+json")},
Body: dsl.Match(InvalidToken{}),
})

// Verify
if err := pact.Verify(test); err != nil {
log.Fatalf("Error on Verify: %v", err)
}
}

// AuthAPIUserNoToken defines contract of /api/user endpoint with invalid auth token
func AuthAPIUserNoToken(t *testing.T, pact *dsl.Pact) {

// Pass in test case
var test = func() error {
url := fmt.Sprintf("http://localhost:%d/api/user", pact.Server.Port)
req, err := http.NewRequest("GET", url, nil)

req.Header.Set("Content-Type", "application/json")
if err != nil {
return err
}

_, err = http.DefaultClient.Do(req)
if err != nil {
return err
}
return err
}

// Set up our expected interactions.
pact.
AddInteraction().
UponReceiving("A request to get user's information with no auth token ").
WithRequest(dsl.Request{
Method: "GET",
Path: dsl.String("/api/user"),
Headers: dsl.MapMatcher{
"Content-Type": dsl.String("application/json"),
},
}).
WillRespondWith(dsl.Response{
Status: 401,
Headers: dsl.MapMatcher{"Content-Type": dsl.String("application/vnd.api+json")},
Body: dsl.Match(MissingToken{}),
})

// Verify
if err := pact.Verify(test); err != nil {
log.Fatalf("Error on Verify: %v", err)
}
}
Loading