What is Pact?
Pact is a code-first tool for testing HTTP and message integrations using contract tests. https://docs.pact.io/
What is contract test?
The responsibility of the contract test is to validate the provider APIs based on the given contract. A service that produces data can change over time, it’s important that the contracts with each service that consumes data from it continue to meet expectations. Contract tests provide a mechanism to explicitly verify that a provider meets a contract.
Three main parties in the contract tests are
Consumer: A client who wants to receive the data. An application or service that makes use of the functionality or data from another application to do its job.
Provider: An API server who provides data to clients. An application or service that provides functionality or data for other applications to use via an API.
Pact Broker(Pactflow): The Pact Broker is an application for sharing the contracts and verification results between the consumer and the provider.
Contract Testing Process (HTTP)
The process looks like this :
The consumer writes a unit test of its behaviour using a Mock provided by Pact
Pact writes the interactions into a contract file (as a JSON document)
The consumer publishes the contract to a broker (or shares the file in some other way)
Pact retrieves the contracts and replays the requests against a locally running provider
The provider should stub out its dependencies during a Pact test, to ensure tests are fast and more deterministic.
How to Write Contract Test
Consumer Pact Test
There are mainly 4 steps involved in consumer side for generating pact
Pact DSL is used to register request and response with mock server
Consumer tests fires a real request to the mock provider (created by pact framework)
Mock provider compares the expected and actual request and returns the response is comparison is successful.
The consumer tests confirms that the response is understood correctly.
Steps :
Need to install the package first :
# install pact_dart as a dev dependency flutter pub add --dev pact_dart # download and install the required libraries flutter pub run pact_dart:install # 🚀 now write some tests!
or add manually to your package's pubspec.yaml :
dev_dependencies: pact_dart: ^0.5.0
Create/Write a test for XXXClient which interacts with external service. Example, we are testing our Cards API client.
Setup the consumer and provider name
pact = PactMockService(consumer-service-name, provider-service-name)
In
.given
we give a text which is called a state, state is very important because using the same state provider will replay the request to verify later.
// ignore_for_file: invalid_use_of_visible_for_testing_member import 'package:data_abstraction/entity/auth/user_presence_check_args.dart'; import 'package:flutter_safi/common/http/digital_bank_client.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mobile_data/datasource/remote/card_remote_datasource.dart'; import 'package:mocktail/mocktail.dart'; import 'package:module_common/common/network/http_method_enum.dart'; import 'package:module_common/common/network/signing/request_signer.dart'; import 'package:module_common/common/wrappers/uuid_wrapper.dart'; import 'package:module_common/data/models/__mocks__/mock_card_model.dart'; import 'package:pact_dart/pact_dart.dart'; import '../test/__mocks__/mock_event_bus_cubit.dart'; void main() { late PactMockService pact; final uuid = UuidWrapper(); final eventBusCubit = MockEventBusCubit(); late MockRequestSigner requestSigner; late DigitalBankClient _client; late CardRemoteDatasource cardRemoteDatasource; // ignore: constant_identifier_names const CHARACTER_REGEX = r'^[a-zA-Z ]*$'; const mockSafiCuid = 'ee6d61a7-7482-4c7a-a7db-29b774690375'; const directory = 'contract_test'; setUpAll(() { registerFallbackValue(SigningLevel.silentSigning); registerFallbackValue(const UserPresenceCheckArgs.ui()); }); setUp(() { pact = PactMockService( 'safi-mobile-app', 'card-manager', ); requestSigner = MockRequestSigner(); _client = DigitalBankClient.forContractTest( host: 'http://${pact.addr}', tykToken: 'token', uuid: uuid, requestSigner: requestSigner, eventBus: eventBusCubit, ); cardRemoteDatasource = CardRemoteDatasource.forContractTest( _client, uuid, ); }); tearDown(() { reset(requestSigner); pact.reset(); }); test('GET : /v2/cards/{customerId}', () async { const customerId = 'ee6d61a7-7482-4c7a-a7db-29b774690375'; when(() => requestSigner.createHeaders( body: any(named: 'body'), signingLevel: any(named: 'signingLevel'), userPresenceCheckArgs: any(named: 'userPresenceCheckArgs'), path: any(named: 'path'), method: HttpMethodType.get, )).thenAnswer((_) async => { 'safi-cuid': mockSafiCuid, }); pact .newInteraction() .given('GET : /v2/cards/{customerId}') .uponReceiving('A request to get cards by card id') .withRequest( 'GET', '/v2/cards/$customerId', ) .willRespondWith( 200, body: PactMatchers.EachLike( [ { 'cardId': PactMatchers.uuid('08d8a7b9-5259-430d-8374-076592bfb4f5'), 'accountNo': PactMatchers.SomethingLike('0443482226'), 'accountType': PactMatchers.SomethingLike('MAIN_ACCOUNT'), 'customerId': PactMatchers.SomethingLike( 'ee6d61a7-7482-4c7a-a7db-29b774690375'), 'cardType': PactMatchers.SomethingLike('DD1'), 'cardPicture': PactMatchers.SomethingLike( 'https://storage.googleapis.com/test-safi-dev/ee6d61a7-7482-4c7a-a7db-29b774690375/ee6d61a7-7482-4c7a-a7db-29b774690375_08d8a7b9-5259-430d-8374-076592bfb4f5.png'), 'namePrinted': PactMatchers.SomethingLike(false), 'dynamicCVV': PactMatchers.SomethingLike(false), 'internationalBlockingFlag': PactMatchers.SomethingLike(false), 'embossName': PactMatchers.Term(CHARACTER_REGEX, 'Maria Santos'), 'maskingCardNo': PactMatchers.SomethingLike('0435411490164376'), 'expiration': PactMatchers.SomethingLike('20270902'), 'cardStatus': PactMatchers.SomethingLike('ACTIVE'), 'orderingNumber': PactMatchers.IntegerLike(1), 'key': PactMatchers.SomethingLike('TJD0ZsH'), 'createdAt': null, 'updatedAt': null, 'cardDelivery': null, 'cardLimit': null, }, ], ), ); pact.run(secure: false); final cards = await cardRemoteDatasource.getCards(customerId); expect(cards, hasLength(1)); pact.writePactFile(directory: directory); }); test('POST : /v2/cards/{cardId}/change-name', () async { const cardId = '08d8a7b9-5259-430d-8374-076592bfb4f5'; const customerId = 'ee6d61a7-7482-4c7a-a7db-29b774690375'; const updatedName = 'Maria Santos'; when(() => requestSigner.createHeaders( body: any(named: 'body'), signingLevel: any(named: 'signingLevel'), userPresenceCheckArgs: any(named: 'userPresenceCheckArgs'), path: any(named: 'path'), method: HttpMethodType.post, )).thenAnswer((_) async => { 'safi-cuid': mockSafiCuid, }); pact .newInteraction() .given('POST : /v2/cards/{cardId}/change-name') .uponReceiving('A request to change card name') .withRequest('POST', '/v2/cards/$cardId/change-name', body: { 'newCardName': updatedName }, headers: { 'safi-cuid': '$customerId', }).willRespondWith( 200, body: { 'cardId': PactMatchers.uuid('08d8a7b9-5259-430d-8374-076592bfb4f5'), 'accountNo': PactMatchers.SomethingLike('0443482226'), 'accountType': PactMatchers.SomethingLike('MAIN_ACCOUNT'), 'customerId': PactMatchers.SomethingLike('ee6d61a7-7482-4c7a-a7db-29b774690375'), 'cardType': PactMatchers.SomethingLike('DD1'), 'cardPicture': PactMatchers.SomethingLike( 'https://storage.googleapis.com/test-safi-dev/ee6d61a7-7482-4c7a-a7db-29b774690375/ee6d61a7-7482-4c7a-a7db-29b774690375_08d8a7b9-5259-430d-8374-076592bfb4f5.png'), 'namePrinted': PactMatchers.SomethingLike(false), 'dynamicCVV': PactMatchers.SomethingLike(false), 'internationalBlockingFlag': PactMatchers.SomethingLike(false), 'maskingCardNo': PactMatchers.SomethingLike('0435411490164376'), 'expiration': PactMatchers.SomethingLike('20270902'), 'cardStatus': PactMatchers.SomethingLike('ACTIVE'), 'orderingNumber': PactMatchers.SomethingLike(1), 'key': PactMatchers.SomethingLike('TJD0ZsH'), 'createdAt': null, 'updatedAt': null, 'embossName': PactMatchers.Term(CHARACTER_REGEX, 'Maria Santos'), 'cardDelivery': null, 'cardLimit': null, }, ); pact.run(secure: false); final cards = await cardRemoteDatasource.changeCardName( cardId: cardId, customerId: customerId, updatedName: updatedName, ); expect( cards.cardName, equals(updatedName), reason: 'Pact did respond with expected body', ); pact.writePactFile(directory: directory); }); test('POST - BlockCard : /v2/cards/{cardId}/card-lock', () async { const cardId = '08d8a7b9-5259-430d-8374-076592bfb4f5'; const customerId = 'ee6d61a7-7482-4c7a-a7db-29b774690375'; when(() => requestSigner.createHeaders( body: any(named: 'body'), signingLevel: any(named: 'signingLevel'), userPresenceCheckArgs: any(named: 'userPresenceCheckArgs'), path: any(named: 'path'), method: HttpMethodType.post, )).thenAnswer((_) async => { 'safi-cuid': mockSafiCuid, }); pact .newInteraction() .given('POST - BlockCard : /v2/cards/{cardId}/card-lock') .uponReceiving('A request to block card by card id') .withRequest('POST', '/v2/cards/$cardId/card-lock', body: { 'function': 'BLOCK', }, headers: { 'safi-cuid': '$customerId', }).willRespondWith( 200, body: { 'cardId': PactMatchers.uuid('08d8a7b9-5259-430d-8374-076592bfb4f5'), 'accountNo': PactMatchers.SomethingLike('0443482226'), 'accountType': PactMatchers.SomethingLike('MAIN_ACCOUNT'), 'customerId': PactMatchers.SomethingLike('ee6d61a7-7482-4c7a-a7db-29b774690375'), 'cardType': PactMatchers.SomethingLike('DD1'), 'cardPicture': PactMatchers.SomethingLike( 'https://storage.googleapis.com/test-safi-dev/ee6d61a7-7482-4c7a-a7db-29b774690375/ee6d61a7-7482-4c7a-a7db-29b774690375_08d8a7b9-5259-430d-8374-076592bfb4f5.png'), 'namePrinted': PactMatchers.SomethingLike(false), 'dynamicCVV': PactMatchers.SomethingLike(false), 'internationalBlockingFlag': PactMatchers.SomethingLike(false), 'maskingCardNo': PactMatchers.SomethingLike('0435411490164376'), 'expiration': PactMatchers.SomethingLike('20270902'), 'cardStatus': PactMatchers.SomethingLike('PERMANENT_BLOCKED'), 'orderingNumber': PactMatchers.SomethingLike(1), 'key': PactMatchers.SomethingLike('TJD0ZsH'), 'createdAt': null, 'updatedAt': null, 'embossName': PactMatchers.Term(CHARACTER_REGEX, 'Maria Santos'), 'cardDelivery': null, 'cardLimit': { 'id': PactMatchers.uuid('08d8a7b9-5259-430d-8374-076592bfb4f5'), 'limitAmountCardTransaction': PactMatchers.SomethingLike(10000.0), 'isActiveCardTransaction': PactMatchers.SomethingLike(true), 'limitAmountAtmWithdrawal': PactMatchers.SomethingLike(10000.0), 'isActiveAtmWithdrawal': PactMatchers.SomethingLike(true), 'limitAmountOnlineTransaction': PactMatchers.SomethingLike(10000.0), 'isActiveOnlineTransaction': PactMatchers.SomethingLike(true), 'createdAt': null, 'updatedAt': null }, }, ); pact.run(secure: false); final cards = await cardRemoteDatasource.blockCard( cardId: cardId, customerId: customerId, ); expect( cards.cardStatus.status, equals('PERMANENT_BLOCKED'), reason: 'Pact did respond with expected body', ); pact.writePactFile(directory: directory); }); test('POST - FreezeCard : /v2/cards/{cardId}/card-lock', () async { const cardId = '08d8a7b9-5259-430d-8374-076592bfb4f5'; const customerId = 'ee6d61a7-7482-4c7a-a7db-29b774690375'; when(() => requestSigner.createHeaders( body: any(named: 'body'), signingLevel: any(named: 'signingLevel'), userPresenceCheckArgs: any(named: 'userPresenceCheckArgs'), path: any(named: 'path'), method: HttpMethodType.post, )).thenAnswer((_) async => { 'safi-cuid': mockSafiCuid, }); pact .newInteraction() .given('POST - FreezeCard : /v2/cards/{cardId}/card-lock') .uponReceiving('A request to freeze card by card id') .withRequest('POST', '/v2/cards/$cardId/card-lock', body: { 'function': 'FREEZE', }, headers: { 'safi-cuid': '$customerId', }).willRespondWith( 200, body: { 'cardId': PactMatchers.uuid('08d8a7b9-5259-430d-8374-076592bfb4f5'), 'accountNo': PactMatchers.SomethingLike('0443482226'), 'accountType': PactMatchers.SomethingLike('MAIN_ACCOUNT'), 'customerId': PactMatchers.SomethingLike('ee6d61a7-7482-4c7a-a7db-29b774690375'), 'cardType': PactMatchers.SomethingLike('DD1'), 'cardPicture': PactMatchers.SomethingLike( 'https://storage.googleapis.com/test-safi-dev/ee6d61a7-7482-4c7a-a7db-29b774690375/ee6d61a7-7482-4c7a-a7db-29b774690375_08d8a7b9-5259-430d-8374-076592bfb4f5.png'), 'namePrinted': PactMatchers.SomethingLike(false), 'dynamicCVV': PactMatchers.SomethingLike(false), 'internationalBlockingFlag': PactMatchers.SomethingLike(false), 'maskingCardNo': PactMatchers.SomethingLike('0435411490164376'), 'expiration': PactMatchers.SomethingLike('20270902'), 'cardStatus': PactMatchers.SomethingLike('LOCKED'), 'orderingNumber': PactMatchers.SomethingLike(1), 'key': PactMatchers.SomethingLike('TJD0ZsH'), 'createdAt': null, 'updatedAt': null, 'embossName': PactMatchers.Term(CHARACTER_REGEX, 'Maria Santos'), 'cardDelivery': null, 'cardLimit': { 'id': PactMatchers.uuid('08d8a7b9-5259-430d-8374-076592bfb4f5'), 'limitAmountCardTransaction': PactMatchers.SomethingLike(10000.0), 'isActiveCardTransaction': PactMatchers.SomethingLike(true), 'limitAmountAtmWithdrawal': PactMatchers.SomethingLike(10000.0), 'isActiveAtmWithdrawal': PactMatchers.SomethingLike(true), 'limitAmountOnlineTransaction': PactMatchers.SomethingLike(10000.0), 'isActiveOnlineTransaction': PactMatchers.SomethingLike(true), 'createdAt': null, 'updatedAt': null }, }, ); pact.run(secure: false); final cards = await cardRemoteDatasource.freezeCard( cardId: cardId, customerId: customerId, ); expect( cards.cardStatus.status, equals('LOCKED'), reason: 'Pact did respond with expected body', ); pact.writePactFile(directory: directory); }); test('POST : /v2/cards/{cardId}/update-limit', () async { const cardId = '08d8a7b9-5259-430d-8374-076592bfb4f5'; const customerId = 'ee6d61a7-7482-4c7a-a7db-29b774690375'; const limitAmountCardTransaction = 10000.0; const limitAmountAtmWithdrawal = 10000.0; const limitAmountOnlineTransaction = 10000.0; const isActiveCardTransaction = true; const isActiveAtmWithdrawal = true; const isActiveOnlineTransaction = true; when(() => requestSigner.createHeaders( body: any(named: 'body'), signingLevel: any(named: 'signingLevel'), userPresenceCheckArgs: any(named: 'userPresenceCheckArgs'), path: any(named: 'path'), method: HttpMethodType.post, )).thenAnswer((_) async => { 'safi-cuid': mockSafiCuid, }); pact .newInteraction() .given('POST : /v2/cards/{cardId}/update-limit') .uponReceiving('A request to update card limit by card id') .withRequest('POST', '/v2/cards/$cardId/update-limit', body: { 'limitAmountCardTransaction': limitAmountCardTransaction, 'limitAmountAtmWithdrawal': limitAmountAtmWithdrawal, 'limitAmountOnlineTransaction': limitAmountOnlineTransaction, 'isActiveCardTransaction': isActiveCardTransaction, 'isActiveAtmWithdrawal': isActiveAtmWithdrawal, 'isActiveOnlineTransaction': isActiveOnlineTransaction, }, headers: { 'safi-cuid': '$customerId', }).willRespondWith( 200, body: { 'cardId': PactMatchers.uuid('08d8a7b9-5259-430d-8374-076592bfb4f5'), 'accountNo': PactMatchers.SomethingLike('0443482226'), 'accountType': PactMatchers.SomethingLike('MAIN_ACCOUNT'), 'customerId': PactMatchers.SomethingLike('ee6d61a7-7482-4c7a-a7db-29b774690375'), 'cardType': PactMatchers.SomethingLike('DD1'), 'cardPicture': PactMatchers.SomethingLike( 'https://storage.googleapis.com/test-safi-dev/ee6d61a7-7482-4c7a-a7db-29b774690375/ee6d61a7-7482-4c7a-a7db-29b774690375_08d8a7b9-5259-430d-8374-076592bfb4f5.png'), 'namePrinted': PactMatchers.SomethingLike(false), 'dynamicCVV': PactMatchers.SomethingLike(false), 'internationalBlockingFlag': PactMatchers.SomethingLike(false), 'maskingCardNo': PactMatchers.SomethingLike('0435411490164376'), 'expiration': PactMatchers.SomethingLike('20270902'), 'cardStatus': PactMatchers.SomethingLike('PRINTED'), 'orderingNumber': PactMatchers.SomethingLike(1), 'key': PactMatchers.SomethingLike('TJD0ZsH'), 'createdAt': null, 'updatedAt': null, 'embossName': PactMatchers.Term(CHARACTER_REGEX, 'Maria Santos'), 'cardDelivery': null, 'cardLimit': { 'id': PactMatchers.uuid('08d8a7b9-5259-430d-8374-076592bfb4f5'), 'limitAmountCardTransaction': PactMatchers.SomethingLike(10000.0), 'isActiveCardTransaction': PactMatchers.SomethingLike(true), 'limitAmountAtmWithdrawal': PactMatchers.SomethingLike(10000.0), 'isActiveAtmWithdrawal': PactMatchers.SomethingLike(true), 'limitAmountOnlineTransaction': PactMatchers.SomethingLike(10000.0), 'isActiveOnlineTransaction': PactMatchers.SomethingLike(true), 'createdAt': null, 'updatedAt': null }, }, ); pact.run(secure: false); final cards = await cardRemoteDatasource.updateCardLimit( customerId: customerId, cardLimit: MockCardModel.mockCardLimit, ); expect( cards, cards, reason: 'Pact did respond with expected body', ); pact.writePactFile( directory: directory, ); }); test('POST : /v2/cards/sign-url', () async { const url = 'https://storage.googleapis.com/test-safi-dev/8f7f8a2c-14a3-422d-b1dd-6abc6325597d/8f7f8a2c-14a3-422d-b1dd-6abc6325597d_7935d71c-9912-47ce-ab54-5f9ac0635e8f.png'; when(() => requestSigner.createHeaders( body: any(named: 'body'), signingLevel: any(named: 'signingLevel'), userPresenceCheckArgs: any(named: 'userPresenceCheckArgs'), path: any(named: 'path'), method: HttpMethodType.post, )).thenAnswer((_) async => { 'safi-cuid': mockSafiCuid, }); pact .newInteraction() .given('POST : /v2/cards/sign-url') .uponReceiving('A request to get card images') .withRequest('POST', '/v2/cards/sign-url', body: { 'url': url, }).willRespondWith( 200, body: { 'url': PactMatchers.SomethingLike( 'https://storage.googleapis.com/test-safi-dev/8f7f8a2c-14a3-422d-b1dd-6abc6325597d/8f7f8a2c-14a3-422d-b1dd-6abc6325597d_7935d71c-9912-47ce-ab54-5f9ac0635e8f.png?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=card-manager%40acquired-badge-348405.iam.gserviceaccount.com%2F20221023%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20221023T135804Z&X-Goog-Expires=600&X-Goog-SignedHeaders=host&X-Goog-Signature=74bd46a5dae8b8a41ab93b9a17ce3a32adfb547bf135d62f83af92068babc0ef61c88b0fb8ccccd5f96ce9fe3535c5d2a5ca0ae9ccc74c1ea652d6208b2ad409c83bfd3e7fbc91c0168ffe7f94c66ed259628e81deba08c53ada241acb1b0f1668008b77f7272345e4a856f543408ff861c88cd7320fc96738cc679e0512ed36c5671111e6ff2a0967026e0c4f9540d71c0a7f3bf2384aaa8da84d86dc1dbb472aadaedb65302f6cf325a0d8ec809fdd41dd697048a93d8171767946de0a1d36beefbfc1c9bb962b28b4be230d00cb18a30ba383c346db3034d4907ced033b0ff78e0296a6507e3ba172eb46b96fdd22f94f8f935f0d0d0eb41a4c1f57f0e336') }, ); pact.run(secure: false); final cards = await cardRemoteDatasource.getCardImage(url); expect( cards, equals(cards), reason: 'Pact did respond with expected body', ); pact.writePactFile(directory: directory); }); test('POST : /v2/cards/{customerId}/activate', () async { const lastCardNumber = '164376'; const pin = '112233'; const cardId = '08d8a7b9-5259-430d-8374-076592bfb4f5'; const customerId = 'ee6d61a7-7482-4c7a-a7db-29b774690375'; when(() => requestSigner.createHeaders( body: any(named: 'body'), signingLevel: any(named: 'signingLevel'), userPresenceCheckArgs: any(named: 'userPresenceCheckArgs'), path: any(named: 'path'), method: HttpMethodType.post, )).thenAnswer((_) async => { 'safi-cuid': mockSafiCuid, }); pact .newInteraction() .given('POST : /v2/cards/{customerId}/activate') .uponReceiving('A request to activated card by customer id') .withRequest('POST', '/v2/cards/$customerId/activate', body: { 'cardId': cardId, 'lastCardNumber': lastCardNumber, 'pin': pin, }).willRespondWith( 200, body: { 'cardId': PactMatchers.uuid('08d8a7b9-5259-430d-8374-076592bfb4f5'), 'accountNo': PactMatchers.SomethingLike('0443482226'), 'accountType': PactMatchers.SomethingLike('MAIN_ACCOUNT'), 'customerId': PactMatchers.SomethingLike('ee6d61a7-7482-4c7a-a7db-29b774690375'), 'cardType': PactMatchers.SomethingLike('DD1'), 'cardPicture': PactMatchers.SomethingLike( 'https://storage.googleapis.com/test-safi-dev/ee6d61a7-7482-4c7a-a7db-29b774690375/ee6d61a7-7482-4c7a-a7db-29b774690375_08d8a7b9-5259-430d-8374-076592bfb4f5.png'), 'namePrinted': PactMatchers.SomethingLike(false), 'dynamicCVV': PactMatchers.SomethingLike(false), 'internationalBlockingFlag': PactMatchers.SomethingLike(false), 'maskingCardNo': PactMatchers.SomethingLike('0435411490164376'), 'expiration': PactMatchers.SomethingLike('20270902'), 'cardStatus': PactMatchers.SomethingLike('ACTIVE'), 'orderingNumber': PactMatchers.SomethingLike(1), 'key': PactMatchers.SomethingLike('TJD0ZsH'), 'createdAt': null, 'updatedAt': null, 'embossName': PactMatchers.Term(CHARACTER_REGEX, 'Maria Santos'), 'cardDelivery': null, 'cardLimit': null, }, ); pact.run(secure: false); final cards = await cardRemoteDatasource.activatedCard( cardId: cardId, customerId: customerId, lastCardNumber: lastCardNumber, pin: pin, ); expect( cards.cardId, cardId, reason: 'Pact did respond with expected body', ); pact.writePactFile(directory: directory); }); test('POST - UnfreezeCard : /v2/cards/{cardId}/card-lock', () async { const cardId = '08d8a7b9-5259-430d-8374-076592bfb4f5'; const customerId = 'ee6d61a7-7482-4c7a-a7db-29b774690375'; when(() => requestSigner.createHeaders( body: any(named: 'body'), signingLevel: any(named: 'signingLevel'), userPresenceCheckArgs: any(named: 'userPresenceCheckArgs'), path: any(named: 'path'), method: HttpMethodType.post, )).thenAnswer((_) async => { 'safi-cuid': mockSafiCuid, }); pact .newInteraction() .given('POST - UnfreezeCard : /v2/cards/{cardId}/card-lock') .uponReceiving('A request to unfreeze cards by card id') .withRequest('POST', '/v2/cards/$cardId/card-lock', body: { 'function': 'UNFREEZE', }, headers: { 'safi-cuid': '$customerId', }).willRespondWith( 200, body: { 'cardId': PactMatchers.uuid('08d8a7b9-5259-430d-8374-076592bfb4f5'), 'accountNo': PactMatchers.SomethingLike('0443482226'), 'accountType': PactMatchers.SomethingLike('MAIN_ACCOUNT'), 'customerId': PactMatchers.SomethingLike('ee6d61a7-7482-4c7a-a7db-29b774690375'), 'cardType': PactMatchers.SomethingLike('DD1'), 'cardPicture': PactMatchers.SomethingLike( 'https://storage.googleapis.com/test-safi-dev/ee6d61a7-7482-4c7a-a7db-29b774690375/ee6d61a7-7482-4c7a-a7db-29b774690375_08d8a7b9-5259-430d-8374-076592bfb4f5.png'), 'namePrinted': PactMatchers.SomethingLike(false), 'dynamicCVV': PactMatchers.SomethingLike(false), 'internationalBlockingFlag': PactMatchers.SomethingLike(false), 'maskingCardNo': PactMatchers.SomethingLike('0435411490164376'), 'expiration': PactMatchers.SomethingLike('20270902'), 'cardStatus': PactMatchers.SomethingLike('ACTIVE'), 'orderingNumber': PactMatchers.SomethingLike(1), 'key': PactMatchers.SomethingLike('TJD0ZsH'), 'createdAt': null, 'updatedAt': null, 'embossName': PactMatchers.Term(CHARACTER_REGEX, 'Maria Santos'), 'cardDelivery': null, 'cardLimit': { 'id': PactMatchers.uuid('08d8a7b9-5259-430d-8374-076592bfb4f5'), 'limitAmountCardTransaction': PactMatchers.SomethingLike(10000.0), 'isActiveCardTransaction': PactMatchers.SomethingLike(true), 'limitAmountAtmWithdrawal': PactMatchers.SomethingLike(10000.0), 'isActiveAtmWithdrawal': PactMatchers.SomethingLike(true), 'limitAmountOnlineTransaction': PactMatchers.SomethingLike(10000.0), 'isActiveOnlineTransaction': PactMatchers.SomethingLike(true), 'createdAt': null, 'updatedAt': null }, }, ); pact.run(secure: false); final cards = await cardRemoteDatasource.unfreezeCard( cardId: cardId, customerId: customerId, ); expect( cards.cardStatus.status, equals('ACTIVE'), reason: 'Pact did respond with expected body', ); pact.writePactFile(directory: directory); }); test('GET : /v2/cards/{cardId}/details', () async { const cardId = '08d8a7b9-5259-430d-8374-076592bfb4f5'; const customerId = 'ee6d61a7-7482-4c7a-a7db-29b774690375'; const isSyncTracking = false; const isIncludeDelivery = false; when(() => requestSigner.createHeaders( body: any(named: 'body'), signingLevel: any(named: 'signingLevel'), userPresenceCheckArgs: any(named: 'userPresenceCheckArgs'), path: any(named: 'path'), method: HttpMethodType.get, )).thenAnswer((_) async => { 'safi-cuid': mockSafiCuid, }); pact .newInteraction() .given('GET : /v2/cards/{cardId}/details') .uponReceiving('A request to get card details by card id') .withRequest('GET', '/v2/cards/$cardId/details', headers: { 'safi-cuid': '$customerId', }, query: { 'isSyncTracking': isSyncTracking.toString(), 'isIncludeDelivery': isIncludeDelivery.toString(), }).willRespondWith( 200, body: { 'cardId': PactMatchers.uuid('08d8a7b9-5259-430d-8374-076592bfb4f5'), 'accountNo': PactMatchers.SomethingLike('0443482226'), 'accountType': PactMatchers.SomethingLike('MAIN_ACCOUNT'), 'customerId': PactMatchers.SomethingLike('ee6d61a7-7482-4c7a-a7db-29b774690375'), 'cardType': PactMatchers.SomethingLike('DD1'), 'cardPicture': PactMatchers.SomethingLike( 'https://storage.googleapis.com/test-safi-dev/ee6d61a7-7482-4c7a-a7db-29b774690375/ee6d61a7-7482-4c7a-a7db-29b774690375_08d8a7b9-5259-430d-8374-076592bfb4f5.png'), 'namePrinted': PactMatchers.SomethingLike(false), 'dynamicCVV': PactMatchers.SomethingLike(false), 'internationalBlockingFlag': PactMatchers.SomethingLike(false), 'maskingCardNo': PactMatchers.SomethingLike('0435411490164376'), 'expiration': PactMatchers.SomethingLike('20270902'), 'cardStatus': PactMatchers.SomethingLike('ACTIVE'), 'orderingNumber': PactMatchers.SomethingLike(1), 'key': PactMatchers.SomethingLike('TJD0ZsH'), 'createdAt': null, 'updatedAt': null, 'embossName': PactMatchers.Term(CHARACTER_REGEX, 'Maria Santos'), 'cardDelivery': null, 'cardLimit': { 'id': PactMatchers.uuid('08d8a7b9-5259-430d-8374-076592bfb4f5'), 'limitAmountCardTransaction': PactMatchers.SomethingLike(10000.0), 'isActiveCardTransaction': PactMatchers.SomethingLike(true), 'limitAmountAtmWithdrawal': PactMatchers.SomethingLike(10000.0), 'isActiveAtmWithdrawal': PactMatchers.SomethingLike(true), 'limitAmountOnlineTransaction': PactMatchers.SomethingLike(10000.0), 'isActiveOnlineTransaction': PactMatchers.SomethingLike(true), 'createdAt': null, 'updatedAt': null }, }, ); pact.run(secure: false); final cards = await cardRemoteDatasource.getCardById( cardId, isSyncTracking, isIncludeDelivery, ); expect( cards.cardId, cardId, reason: 'Pact did respond with expected body', ); pact.writePactFile(directory: directory); }); test('POST : /v2/cards/change-pin', () async { const cardId = '08d8a7b9-5259-430d-8374-076592bfb4f5'; const currentPin = '123123'; const newPin = '112233'; const customerId = 'ee6d61a7-7482-4c7a-a7db-29b774690375'; when(() => requestSigner.createHeaders( body: any(named: 'body'), signingLevel: any(named: 'signingLevel'), userPresenceCheckArgs: any(named: 'userPresenceCheckArgs'), path: any(named: 'path'), method: HttpMethodType.post, )).thenAnswer((_) async => { 'safi-cuid': mockSafiCuid, }); pact .newInteraction() .given('POST : /v2/cards/change-pin') .uponReceiving('A request to change pin card') .withRequest('POST', '/v2/cards/change-pin', body: { 'cardId': cardId, 'newPin': newPin, 'oldPin': currentPin, }, headers: { 'safi-cuid': '$customerId', }).willRespondWith( 200, body: { 'cardId': PactMatchers.uuid('08d8a7b9-5259-430d-8374-076592bfb4f5'), 'accountNo': PactMatchers.SomethingLike('0443482226'), 'accountType': PactMatchers.SomethingLike('MAIN_ACCOUNT'), 'customerId': PactMatchers.SomethingLike('ee6d61a7-7482-4c7a-a7db-29b774690375'), 'cardType': PactMatchers.SomethingLike('DD1'), 'cardPicture': PactMatchers.SomethingLike( 'https://storage.googleapis.com/test-safi-dev/ee6d61a7-7482-4c7a-a7db-29b774690375/ee6d61a7-7482-4c7a-a7db-29b774690375_08d8a7b9-5259-430d-8374-076592bfb4f5.png'), 'namePrinted': PactMatchers.SomethingLike(false), 'dynamicCVV': PactMatchers.SomethingLike(false), 'internationalBlockingFlag': PactMatchers.SomethingLike(false), 'maskingCardNo': PactMatchers.SomethingLike('0435411490164376'), 'expiration': PactMatchers.SomethingLike('20270902'), 'cardStatus': PactMatchers.SomethingLike('ACTIVE'), 'orderingNumber': PactMatchers.SomethingLike(1), 'key': PactMatchers.SomethingLike('TJD0ZsH'), 'createdAt': null, 'updatedAt': null, 'embossName': PactMatchers.Term(CHARACTER_REGEX, 'Maria Santos'), 'cardDelivery': null, 'cardLimit': null, }, ); pact.run(secure: false); final cards = await cardRemoteDatasource.changePin( customerId: customerId, cardId: cardId, newPin: newPin, currentPin: currentPin, ); expect( cards, cards, reason: 'Pact did respond with expected body', ); pact.writePactFile(directory: directory); }); } class MockRequestSigner extends Mock implements RequestSigner {}
{ "consumer": { "name": "safi-mobile-app" }, "interactions": [ { "description": "A request to get card details by card id", "providerStates": [ { "name": "GET : /v2/cards/{cardId}/details" } ], "request": { "headers": { "safi-cuid": "ee6d61a7-7482-4c7a-a7db-29b774690375" }, "method": "GET", "path": "/v2/cards/08d8a7b9-5259-430d-8374-076592bfb4f5/details", "query": { "isIncludeDelivery": [ "false" ], "isSyncTracking": [ "false" ] } }, "response": { "body": { "accountNo": "0443482226", "accountType": "MAIN_ACCOUNT", "cardDelivery": null, "cardId": "08d8a7b9-5259-430d-8374-076592bfb4f5", "cardLimit": { "createdAt": null, "id": "08d8a7b9-5259-430d-8374-076592bfb4f5", "isActiveAtmWithdrawal": true, "isActiveCardTransaction": true, "isActiveOnlineTransaction": true, "limitAmountAtmWithdrawal": 10000.0, "limitAmountCardTransaction": 10000.0, "limitAmountOnlineTransaction": 10000.0, "updatedAt": null }, "cardPicture": "https://storage.googleapis.com/test-safi-dev/ee6d61a7-7482-4c7a-a7db-29b774690375/ee6d61a7-7482-4c7a-a7db-29b774690375_08d8a7b9-5259-430d-8374-076592bfb4f5.png", "cardStatus": "ACTIVE", "cardType": "DD1", "createdAt": null, "customerId": "ee6d61a7-7482-4c7a-a7db-29b774690375", "dynamicCVV": false, "embossName": "Maria Santos", "expiration": "20270902", "internationalBlockingFlag": false, "key": "TJD0ZsH", "maskingCardNo": "0435411490164376", "namePrinted": false, "orderingNumber": 1, "updatedAt": null }, "headers": { "Content-Type": "application/json" }, "matchingRules": { "body": { "$.accountNo": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.accountType": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.cardId": { "combine": "AND", "matchers": [ { "match": "regex", "regex": "^[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}$" } ] }, "$.cardLimit.id": { "combine": "AND", "matchers": [ { "match": "regex", "regex": "^[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}$" } ] }, "$.cardLimit.isActiveAtmWithdrawal": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.cardLimit.isActiveCardTransaction": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.cardLimit.isActiveOnlineTransaction": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.cardLimit.limitAmountAtmWithdrawal": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.cardLimit.limitAmountCardTransaction": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.cardLimit.limitAmountOnlineTransaction": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.cardPicture": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.cardStatus": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.cardType": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.customerId": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.dynamicCVV": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.embossName": { "combine": "AND", "matchers": [ { "match": "regex", "regex": "^[a-zA-Z ]*$" } ] }, "$.expiration": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.internationalBlockingFlag": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.key": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.maskingCardNo": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.namePrinted": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.orderingNumber": { "combine": "AND", "matchers": [ { "match": "type" } ] } } }, "status": 200 } }, { "description": "A request to get cards by card id", "providerStates": [ { "name": "GET : /v2/cards/{customerId}" } ], "request": { "method": "GET", "path": "/v2/cards/ee6d61a7-7482-4c7a-a7db-29b774690375" }, "response": { "body": [ { "accountNo": "0443482226", "accountType": "MAIN_ACCOUNT", "cardDelivery": null, "cardId": "08d8a7b9-5259-430d-8374-076592bfb4f5", "cardLimit": null, "cardPicture": "https://storage.googleapis.com/test-safi-dev/ee6d61a7-7482-4c7a-a7db-29b774690375/ee6d61a7-7482-4c7a-a7db-29b774690375_08d8a7b9-5259-430d-8374-076592bfb4f5.png", "cardStatus": "ACTIVE", "cardType": "DD1", "createdAt": null, "customerId": "ee6d61a7-7482-4c7a-a7db-29b774690375", "dynamicCVV": false, "embossName": "Maria Santos", "expiration": "20270902", "internationalBlockingFlag": false, "key": "TJD0ZsH", "maskingCardNo": "0435411490164376", "namePrinted": false, "orderingNumber": 1, "updatedAt": null } ], "headers": { "Content-Type": "application/json" }, "matchingRules": { "body": { "$": { "combine": "AND", "matchers": [ { "match": "type", "min": 1 } ] }, "$[*].accountNo": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$[*].accountType": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$[*].cardId": { "combine": "AND", "matchers": [ { "match": "regex", "regex": "^[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}$" } ] }, "$[*].cardPicture": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$[*].cardStatus": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$[*].cardType": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$[*].customerId": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$[*].dynamicCVV": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$[*].embossName": { "combine": "AND", "matchers": [ { "match": "regex", "regex": "^[a-zA-Z ]*$" } ] }, "$[*].expiration": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$[*].internationalBlockingFlag": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$[*].key": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$[*].maskingCardNo": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$[*].namePrinted": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$[*].orderingNumber": { "combine": "AND", "matchers": [ { "match": "integer" } ] } } }, "status": 200 } }, { "description": "A request to block card by card id", "providerStates": [ { "name": "POST - BlockCard : /v2/cards/{cardId}/card-lock" } ], "request": { "body": { "function": "BLOCK" }, "headers": { "Content-Type": "application/json", "safi-cuid": "ee6d61a7-7482-4c7a-a7db-29b774690375" }, "method": "POST", "path": "/v2/cards/08d8a7b9-5259-430d-8374-076592bfb4f5/card-lock" }, "response": { "body": { "accountNo": "0443482226", "accountType": "MAIN_ACCOUNT", "cardDelivery": null, "cardId": "08d8a7b9-5259-430d-8374-076592bfb4f5", "cardLimit": { "createdAt": null, "id": "08d8a7b9-5259-430d-8374-076592bfb4f5", "isActiveAtmWithdrawal": true, "isActiveCardTransaction": true, "isActiveOnlineTransaction": true, "limitAmountAtmWithdrawal": 10000.0, "limitAmountCardTransaction": 10000.0, "limitAmountOnlineTransaction": 10000.0, "updatedAt": null }, "cardPicture": "https://storage.googleapis.com/test-safi-dev/ee6d61a7-7482-4c7a-a7db-29b774690375/ee6d61a7-7482-4c7a-a7db-29b774690375_08d8a7b9-5259-430d-8374-076592bfb4f5.png", "cardStatus": "PERMANENT_BLOCKED", "cardType": "DD1", "createdAt": null, "customerId": "ee6d61a7-7482-4c7a-a7db-29b774690375", "dynamicCVV": false, "embossName": "Maria Santos", "expiration": "20270902", "internationalBlockingFlag": false, "key": "TJD0ZsH", "maskingCardNo": "0435411490164376", "namePrinted": false, "orderingNumber": 1, "updatedAt": null }, "headers": { "Content-Type": "application/json" }, "matchingRules": { "body": { "$.accountNo": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.accountType": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.cardId": { "combine": "AND", "matchers": [ { "match": "regex", "regex": "^[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}$" } ] }, "$.cardLimit.id": { "combine": "AND", "matchers": [ { "match": "regex", "regex": "^[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}$" } ] }, "$.cardLimit.isActiveAtmWithdrawal": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.cardLimit.isActiveCardTransaction": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.cardLimit.isActiveOnlineTransaction": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.cardLimit.limitAmountAtmWithdrawal": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.cardLimit.limitAmountCardTransaction": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.cardLimit.limitAmountOnlineTransaction": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.cardPicture": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.cardStatus": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.cardType": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.customerId": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.dynamicCVV": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.embossName": { "combine": "AND", "matchers": [ { "match": "regex", "regex": "^[a-zA-Z ]*$" } ] }, "$.expiration": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.internationalBlockingFlag": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.key": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.maskingCardNo": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.namePrinted": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.orderingNumber": { "combine": "AND", "matchers": [ { "match": "type" } ] } } }, "status": 200 } }, { "description": "A request to freeze card by card id", "providerStates": [ { "name": "POST - FreezeCard : /v2/cards/{cardId}/card-lock" } ], "request": { "body": { "function": "FREEZE" }, "headers": { "Content-Type": "application/json", "safi-cuid": "ee6d61a7-7482-4c7a-a7db-29b774690375" }, "method": "POST", "path": "/v2/cards/08d8a7b9-5259-430d-8374-076592bfb4f5/card-lock" }, "response": { "body": { "accountNo": "0443482226", "accountType": "MAIN_ACCOUNT", "cardDelivery": null, "cardId": "08d8a7b9-5259-430d-8374-076592bfb4f5", "cardLimit": { "createdAt": null, "id": "08d8a7b9-5259-430d-8374-076592bfb4f5", "isActiveAtmWithdrawal": true, "isActiveCardTransaction": true, "isActiveOnlineTransaction": true, "limitAmountAtmWithdrawal": 10000.0, "limitAmountCardTransaction": 10000.0, "limitAmountOnlineTransaction": 10000.0, "updatedAt": null }, "cardPicture": "https://storage.googleapis.com/test-safi-dev/ee6d61a7-7482-4c7a-a7db-29b774690375/ee6d61a7-7482-4c7a-a7db-29b774690375_08d8a7b9-5259-430d-8374-076592bfb4f5.png", "cardStatus": "LOCKED", "cardType": "DD1", "createdAt": null, "customerId": "ee6d61a7-7482-4c7a-a7db-29b774690375", "dynamicCVV": false, "embossName": "Maria Santos", "expiration": "20270902", "internationalBlockingFlag": false, "key": "TJD0ZsH", "maskingCardNo": "0435411490164376", "namePrinted": false, "orderingNumber": 1, "updatedAt": null }, "headers": { "Content-Type": "application/json" }, "matchingRules": { "body": { "$.accountNo": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.accountType": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.cardId": { "combine": "AND", "matchers": [ { "match": "regex", "regex": "^[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}$" } ] }, "$.cardLimit.id": { "combine": "AND", "matchers": [ { "match": "regex", "regex": "^[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}$" } ] }, "$.cardLimit.isActiveAtmWithdrawal": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.cardLimit.isActiveCardTransaction": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.cardLimit.isActiveOnlineTransaction": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.cardLimit.limitAmountAtmWithdrawal": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.cardLimit.limitAmountCardTransaction": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.cardLimit.limitAmountOnlineTransaction": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.cardPicture": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.cardStatus": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.cardType": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.customerId": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.dynamicCVV": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.embossName": { "combine": "AND", "matchers": [ { "match": "regex", "regex": "^[a-zA-Z ]*$" } ] }, "$.expiration": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.internationalBlockingFlag": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.key": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.maskingCardNo": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.namePrinted": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.orderingNumber": { "combine": "AND", "matchers": [ { "match": "type" } ] } } }, "status": 200 } }, { "description": "A request to unfreeze cards by card id", "providerStates": [ { "name": "POST - UnfreezeCard : /v2/cards/{cardId}/card-lock" } ], "request": { "body": { "function": "UNFREEZE" }, "headers": { "Content-Type": "application/json", "safi-cuid": "ee6d61a7-7482-4c7a-a7db-29b774690375" }, "method": "POST", "path": "/v2/cards/08d8a7b9-5259-430d-8374-076592bfb4f5/card-lock" }, "response": { "body": { "accountNo": "0443482226", "accountType": "MAIN_ACCOUNT", "cardDelivery": null, "cardId": "08d8a7b9-5259-430d-8374-076592bfb4f5", "cardLimit": { "createdAt": null, "id": "08d8a7b9-5259-430d-8374-076592bfb4f5", "isActiveAtmWithdrawal": true, "isActiveCardTransaction": true, "isActiveOnlineTransaction": true, "limitAmountAtmWithdrawal": 10000.0, "limitAmountCardTransaction": 10000.0, "limitAmountOnlineTransaction": 10000.0, "updatedAt": null }, "cardPicture": "https://storage.googleapis.com/test-safi-dev/ee6d61a7-7482-4c7a-a7db-29b774690375/ee6d61a7-7482-4c7a-a7db-29b774690375_08d8a7b9-5259-430d-8374-076592bfb4f5.png", "cardStatus": "ACTIVE", "cardType": "DD1", "createdAt": null, "customerId": "ee6d61a7-7482-4c7a-a7db-29b774690375", "dynamicCVV": false, "embossName": "Maria Santos", "expiration": "20270902", "internationalBlockingFlag": false, "key": "TJD0ZsH", "maskingCardNo": "0435411490164376", "namePrinted": false, "orderingNumber": 1, "updatedAt": null }, "headers": { "Content-Type": "application/json" }, "matchingRules": { "body": { "$.accountNo": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.accountType": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.cardId": { "combine": "AND", "matchers": [ { "match": "regex", "regex": "^[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}$" } ] }, "$.cardLimit.id": { "combine": "AND", "matchers": [ { "match": "regex", "regex": "^[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}$" } ] }, "$.cardLimit.isActiveAtmWithdrawal": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.cardLimit.isActiveCardTransaction": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.cardLimit.isActiveOnlineTransaction": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.cardLimit.limitAmountAtmWithdrawal": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.cardLimit.limitAmountCardTransaction": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.cardLimit.limitAmountOnlineTransaction": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.cardPicture": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.cardStatus": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.cardType": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.customerId": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.dynamicCVV": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.embossName": { "combine": "AND", "matchers": [ { "match": "regex", "regex": "^[a-zA-Z ]*$" } ] }, "$.expiration": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.internationalBlockingFlag": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.key": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.maskingCardNo": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.namePrinted": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.orderingNumber": { "combine": "AND", "matchers": [ { "match": "type" } ] } } }, "status": 200 } }, { "description": "A request to change pin card", "providerStates": [ { "name": "POST : /v2/cards/change-pin" } ], "request": { "body": { "cardId": "08d8a7b9-5259-430d-8374-076592bfb4f5", "newPin": "112233", "oldPin": "123123" }, "headers": { "Content-Type": "application/json", "safi-cuid": "ee6d61a7-7482-4c7a-a7db-29b774690375" }, "method": "POST", "path": "/v2/cards/change-pin" }, "response": { "body": { "accountNo": "0443482226", "accountType": "MAIN_ACCOUNT", "cardDelivery": null, "cardId": "08d8a7b9-5259-430d-8374-076592bfb4f5", "cardLimit": null, "cardPicture": "https://storage.googleapis.com/test-safi-dev/ee6d61a7-7482-4c7a-a7db-29b774690375/ee6d61a7-7482-4c7a-a7db-29b774690375_08d8a7b9-5259-430d-8374-076592bfb4f5.png", "cardStatus": "ACTIVE", "cardType": "DD1", "createdAt": null, "customerId": "ee6d61a7-7482-4c7a-a7db-29b774690375", "dynamicCVV": false, "embossName": "Maria Santos", "expiration": "20270902", "internationalBlockingFlag": false, "key": "TJD0ZsH", "maskingCardNo": "0435411490164376", "namePrinted": false, "orderingNumber": 1, "updatedAt": null }, "headers": { "Content-Type": "application/json" }, "matchingRules": { "body": { "$.accountNo": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.accountType": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.cardId": { "combine": "AND", "matchers": [ { "match": "regex", "regex": "^[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}$" } ] }, "$.cardPicture": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.cardStatus": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.cardType": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.customerId": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.dynamicCVV": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.embossName": { "combine": "AND", "matchers": [ { "match": "regex", "regex": "^[a-zA-Z ]*$" } ] }, "$.expiration": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.internationalBlockingFlag": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.key": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.maskingCardNo": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.namePrinted": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.orderingNumber": { "combine": "AND", "matchers": [ { "match": "type" } ] } } }, "status": 200 } }, { "description": "A request to get card images", "providerStates": [ { "name": "POST : /v2/cards/sign-url" } ], "request": { "body": { "url": "https://storage.googleapis.com/test-safi-dev/8f7f8a2c-14a3-422d-b1dd-6abc6325597d/8f7f8a2c-14a3-422d-b1dd-6abc6325597d_7935d71c-9912-47ce-ab54-5f9ac0635e8f.png" }, "headers": { "Content-Type": "application/json" }, "method": "POST", "path": "/v2/cards/sign-url" }, "response": { "body": { "url": "https://storage.googleapis.com/test-safi-dev/8f7f8a2c-14a3-422d-b1dd-6abc6325597d/8f7f8a2c-14a3-422d-b1dd-6abc6325597d_7935d71c-9912-47ce-ab54-5f9ac0635e8f.png?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=card-manager%40acquired-badge-348405.iam.gserviceaccount.com%2F20221023%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20221023T135804Z&X-Goog-Expires=600&X-Goog-SignedHeaders=host&X-Goog-Signature=74bd46a5dae8b8a41ab93b9a17ce3a32adfb547bf135d62f83af92068babc0ef61c88b0fb8ccccd5f96ce9fe3535c5d2a5ca0ae9ccc74c1ea652d6208b2ad409c83bfd3e7fbc91c0168ffe7f94c66ed259628e81deba08c53ada241acb1b0f1668008b77f7272345e4a856f543408ff861c88cd7320fc96738cc679e0512ed36c5671111e6ff2a0967026e0c4f9540d71c0a7f3bf2384aaa8da84d86dc1dbb472aadaedb65302f6cf325a0d8ec809fdd41dd697048a93d8171767946de0a1d36beefbfc1c9bb962b28b4be230d00cb18a30ba383c346db3034d4907ced033b0ff78e0296a6507e3ba172eb46b96fdd22f94f8f935f0d0d0eb41a4c1f57f0e336" }, "headers": { "Content-Type": "application/json" }, "matchingRules": { "body": { "$.url": { "combine": "AND", "matchers": [ { "match": "type" } ] } } }, "status": 200 } }, { "description": "A request to change card name", "providerStates": [ { "name": "POST : /v2/cards/{cardId}/change-name" } ], "request": { "body": { "newCardName": "Maria Santos" }, "headers": { "Content-Type": "application/json", "safi-cuid": "ee6d61a7-7482-4c7a-a7db-29b774690375" }, "method": "POST", "path": "/v2/cards/08d8a7b9-5259-430d-8374-076592bfb4f5/change-name" }, "response": { "body": { "accountNo": "0443482226", "accountType": "MAIN_ACCOUNT", "cardDelivery": null, "cardId": "08d8a7b9-5259-430d-8374-076592bfb4f5", "cardLimit": null, "cardPicture": "https://storage.googleapis.com/test-safi-dev/ee6d61a7-7482-4c7a-a7db-29b774690375/ee6d61a7-7482-4c7a-a7db-29b774690375_08d8a7b9-5259-430d-8374-076592bfb4f5.png", "cardStatus": "ACTIVE", "cardType": "DD1", "createdAt": null, "customerId": "ee6d61a7-7482-4c7a-a7db-29b774690375", "dynamicCVV": false, "embossName": "Maria Santos", "expiration": "20270902", "internationalBlockingFlag": false, "key": "TJD0ZsH", "maskingCardNo": "0435411490164376", "namePrinted": false, "orderingNumber": 1, "updatedAt": null }, "headers": { "Content-Type": "application/json" }, "matchingRules": { "body": { "$.accountNo": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.accountType": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.cardId": { "combine": "AND", "matchers": [ { "match": "regex", "regex": "^[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}$" } ] }, "$.cardPicture": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.cardStatus": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.cardType": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.customerId": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.dynamicCVV": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.embossName": { "combine": "AND", "matchers": [ { "match": "regex", "regex": "^[a-zA-Z ]*$" } ] }, "$.expiration": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.internationalBlockingFlag": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.key": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.maskingCardNo": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.namePrinted": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.orderingNumber": { "combine": "AND", "matchers": [ { "match": "type" } ] } } }, "status": 200 } }, { "description": "A request to update card limit by card id", "providerStates": [ { "name": "POST : /v2/cards/{cardId}/update-limit" } ], "request": { "body": { "isActiveAtmWithdrawal": true, "isActiveCardTransaction": true, "isActiveOnlineTransaction": true, "limitAmountAtmWithdrawal": 10000.0, "limitAmountCardTransaction": 10000.0, "limitAmountOnlineTransaction": 10000.0 }, "headers": { "Content-Type": "application/json", "safi-cuid": "ee6d61a7-7482-4c7a-a7db-29b774690375" }, "method": "POST", "path": "/v2/cards/08d8a7b9-5259-430d-8374-076592bfb4f5/update-limit" }, "response": { "body": { "accountNo": "0443482226", "accountType": "MAIN_ACCOUNT", "cardDelivery": null, "cardId": "08d8a7b9-5259-430d-8374-076592bfb4f5", "cardLimit": { "createdAt": null, "id": "08d8a7b9-5259-430d-8374-076592bfb4f5", "isActiveAtmWithdrawal": true, "isActiveCardTransaction": true, "isActiveOnlineTransaction": true, "limitAmountAtmWithdrawal": 10000.0, "limitAmountCardTransaction": 10000.0, "limitAmountOnlineTransaction": 10000.0, "updatedAt": null }, "cardPicture": "https://storage.googleapis.com/test-safi-dev/ee6d61a7-7482-4c7a-a7db-29b774690375/ee6d61a7-7482-4c7a-a7db-29b774690375_08d8a7b9-5259-430d-8374-076592bfb4f5.png", "cardStatus": "PRINTED", "cardType": "DD1", "createdAt": null, "customerId": "ee6d61a7-7482-4c7a-a7db-29b774690375", "dynamicCVV": false, "embossName": "Maria Santos", "expiration": "20270902", "internationalBlockingFlag": false, "key": "TJD0ZsH", "maskingCardNo": "0435411490164376", "namePrinted": false, "orderingNumber": 1, "updatedAt": null }, "headers": { "Content-Type": "application/json" }, "matchingRules": { "body": { "$.accountNo": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.accountType": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.cardId": { "combine": "AND", "matchers": [ { "match": "regex", "regex": "^[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}$" } ] }, "$.cardLimit.id": { "combine": "AND", "matchers": [ { "match": "regex", "regex": "^[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}$" } ] }, "$.cardLimit.isActiveAtmWithdrawal": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.cardLimit.isActiveCardTransaction": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.cardLimit.isActiveOnlineTransaction": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.cardLimit.limitAmountAtmWithdrawal": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.cardLimit.limitAmountCardTransaction": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.cardLimit.limitAmountOnlineTransaction": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.cardPicture": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.cardStatus": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.cardType": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.customerId": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.dynamicCVV": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.embossName": { "combine": "AND", "matchers": [ { "match": "regex", "regex": "^[a-zA-Z ]*$" } ] }, "$.expiration": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.internationalBlockingFlag": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.key": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.maskingCardNo": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.namePrinted": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.orderingNumber": { "combine": "AND", "matchers": [ { "match": "type" } ] } } }, "status": 200 } }, { "description": "A request to activated card by customer id", "providerStates": [ { "name": "POST : /v2/cards/{customerId}/activate" } ], "request": { "body": { "cardId": "08d8a7b9-5259-430d-8374-076592bfb4f5", "lastCardNumber": "164376", "pin": "112233" }, "headers": { "Content-Type": "application/json" }, "method": "POST", "path": "/v2/cards/ee6d61a7-7482-4c7a-a7db-29b774690375/activate" }, "response": { "body": { "accountNo": "0443482226", "accountType": "MAIN_ACCOUNT", "cardDelivery": null, "cardId": "08d8a7b9-5259-430d-8374-076592bfb4f5", "cardLimit": null, "cardPicture": "https://storage.googleapis.com/test-safi-dev/ee6d61a7-7482-4c7a-a7db-29b774690375/ee6d61a7-7482-4c7a-a7db-29b774690375_08d8a7b9-5259-430d-8374-076592bfb4f5.png", "cardStatus": "ACTIVE", "cardType": "DD1", "createdAt": null, "customerId": "ee6d61a7-7482-4c7a-a7db-29b774690375", "dynamicCVV": false, "embossName": "Maria Santos", "expiration": "20270902", "internationalBlockingFlag": false, "key": "TJD0ZsH", "maskingCardNo": "0435411490164376", "namePrinted": false, "orderingNumber": 1, "updatedAt": null }, "headers": { "Content-Type": "application/json" }, "matchingRules": { "body": { "$.accountNo": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.accountType": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.cardId": { "combine": "AND", "matchers": [ { "match": "regex", "regex": "^[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}$" } ] }, "$.cardPicture": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.cardStatus": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.cardType": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.customerId": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.dynamicCVV": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.embossName": { "combine": "AND", "matchers": [ { "match": "regex", "regex": "^[a-zA-Z ]*$" } ] }, "$.expiration": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.internationalBlockingFlag": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.key": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.maskingCardNo": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.namePrinted": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.orderingNumber": { "combine": "AND", "matchers": [ { "match": "type" } ] } } }, "status": 200 } } ], "metadata": { "pactRust": { "version": "0.1.2" }, "pactSpecification": { "version": "3.0.0" } }, "provider": { "name": "card-manager" } }
Provider Pact Test
In provider verification, each request is sent to the provider, and the actual response it generates is compared with the minimal expected response described in the consumer test.
Provider verification passes if each request generates a response that contains at least the data described in the minimal expected response.
To simulate the provider behaviour we have created a provider service using Micronaut.
Steps :
We need to set it up first, for example like this picture in the build.gradle.kts
And then create a provider service file
Annotated with
@Tag("contract-verification")
Provider states allow you to set up data on the provider before the interaction is run, so that it can make a response that matches what the consumer expects.
package ph.safibank.cardmanager.contract_test.provider import au.com.dius.pact.provider.junit.Provider import au.com.dius.pact.provider.junit.State import au.com.dius.pact.provider.junit.StateChangeAction import au.com.dius.pact.provider.junit.loader.PactBroker import au.com.dius.pact.provider.junit.loader.PactBrokerAuth import au.com.dius.pact.provider.junit5.HttpTestTarget import au.com.dius.pact.provider.junit5.PactVerificationContext import au.com.dius.pact.provider.junit5.PactVerificationInvocationContextProvider import io.micronaut.test.annotation.MockBean import io.micronaut.test.extensions.junit5.annotation.MicronautTest import io.mockk.clearAllMocks import io.mockk.every import io.mockk.mockk import jakarta.inject.Inject import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Tag import org.junit.jupiter.api.TestTemplate import org.junit.jupiter.api.extension.ExtendWith import ph.safibank.cardmanager.common.ParentLocalTest import ph.safibank.cardmanager.mock.CardMock import ph.safibank.cardmanager.service.BucketService import ph.safibank.cardmanager.service.CardService import ph.safibank.cardmanager.service.impl.BucketServiceImpl import ph.safibank.cardmanager.service.impl.CardServiceImpl import java.net.URL @Tag("contract-verification") @MicronautTest @Provider("card-manager") @PactBroker(authentication = PactBrokerAuth(token = "\${pactbroker.auth.token}")) class CardContractVerificationTest { @Inject lateinit var cardService: CardService @Inject lateinit var bucketService: BucketService @MockBean(CardServiceImpl::class) fun cardService(): CardService = mockk<CardService>() @MockBean(BucketServiceImpl::class) fun bucketService(): BucketService = mockk<BucketService>() private val cardMock: CardMock = CardMock() @TestTemplate @ExtendWith(PactVerificationInvocationContextProvider::class) fun pactVerificationTestTemplate(context: PactVerificationContext) { context.verifyInteraction() context.target = HttpTestTarget() } @BeforeEach fun setup() { clearAllMocks() every { bucketService.getImageUrl(any()) } returns URL("https://storage.googleapis.com/test-safi-dev/8f7f8a2c-14a3-422d-b1dd-6abc6325597d/8f7f8a2c-14a3-422d-b1dd-6abc6325597d_7935d71c-9912-47ce-ab54-5f9ac0635e8f.png?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=card-manager%40acquired-badge-348405.iam.gserviceaccount.com%2F20221023%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20221023T135804Z&X-Goog-Expires=600&X-Goog-SignedHeaders=host&X-Goog-Signature=74bd46a5dae8b8a41ab93b9a17ce3a32adfb547bf135d62f83af92068babc0ef61c88b0fb8ccccd5f96ce9fe3535c5d2a5ca0ae9ccc74c1ea652d6208b2ad409c83bfd3e7fbc91c0168ffe7f94c66ed259628e81deba08c53ada241acb1b0f1668008b77f7272345e4a856f543408ff861c88cd7320fc96738cc679e0512ed36c5671111e6ff2a0967026e0c4f9540d71c0a7f3bf2384aaa8da84d86dc1dbb472aadaedb65302f6cf325a0d8ec809fdd41dd697048a93d8171767946de0a1d36beefbfc1c9bb962b28b4be230d00cb18a30ba383c346db3034d4907ced033b0ff78e0296a6507e3ba172eb46b96fdd22f94f8f935f0d0d0eb41a4c1f57f0e336") every { bucketService.getObjectsByDirectory(any()) } returns listOf() } @State(value = ["GET : /v2/cards/{customerId}"]) fun getCards() { every { cardService.getAllByCustomerId(any()) } returns listOf(cardMock.getCardEntity().toCard()) } @State(value = ["POST : /v2/cards/{cardId}/change-name"]) fun changeCardName() { every { cardService.changeName(any()) } returns cardMock.getChangeNameResponse().toCard() } @State(value = ["POST - BlockCard : /v2/cards/{cardId}/card-lock"]) fun blockCard() { every { cardService.blockCard(any(), any(), any(), any()) } returns cardMock.getCardEntity().toCard() } @State(value = ["POST - FreezeCard : /v2/cards/{cardId}/card-lock"]) fun freezeCard() { every { cardService.freezeCard(any(), any(), any(), any()) } returns cardMock.getCardEntity().toCard() } @State(value = ["POST - UnfreezeCard : /v2/cards/{cardId}/card-lock"]) fun unFreezeCard() { every { cardService.unfreezeCard(any(), any(), any()) } returns cardMock.getCardEntity().toCard() } @State(value = ["POST : /v2/cards/{cardId}/update-limit"]) fun updateCardLimit() { every { cardService.updateCardLimit(any(), any(), any()) } returns cardMock.getCardEntity().toCard() } @State(value = ["POST : /v2/cards/sign-url"]) fun getCardImage() { every { bucketService.getImageUrl(any()) } returns URL("https://storage.googleapis.com/test-safi-dev/8f7f8a2c-14a3-422d-b1dd-6abc6325597d/8f7f8a2c-14a3-422d-b1dd-6abc6325597d_7935d71c-9912-47ce-ab54-5f9ac0635e8f.png?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=card-manager%40acquired-badge-348405.iam.gserviceaccount.com%2F20221023%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20221023T135804Z&X-Goog-Expires=600&X-Goog-SignedHeaders=host&X-Goog-Signature=74bd46a5dae8b8a41ab93b9a17ce3a32adfb547bf135d62f83af92068babc0ef61c88b0fb8ccccd5f96ce9fe3535c5d2a5ca0ae9ccc74c1ea652d6208b2ad409c83bfd3e7fbc91c0168ffe7f94c66ed259628e81deba08c53ada241acb1b0f1668008b77f7272345e4a856f543408ff861c88cd7320fc96738cc679e0512ed36c5671111e6ff2a0967026e0c4f9540d71c0a7f3bf2384aaa8da84d86dc1dbb472aadaedb65302f6cf325a0d8ec809fdd41dd697048a93d8171767946de0a1d36beefbfc1c9bb962b28b4be230d00cb18a30ba383c346db3034d4907ced033b0ff78e0296a6507e3ba172eb46b96fdd22f94f8f935f0d0d0eb41a4c1f57f0e336") } @State(value = ["POST : /v2/cards/{customerId}/activate"]) fun activatedCard() { every { cardService.activateCard(any()) } returns cardMock.getCardEntity().toCard() } @State(value = ["POST : /v2/cards/change-pin"]) fun changePin() { every { cardService.changePin(any()) } returns cardMock.getCardEntity().toCard() } @State(value = ["GET : /v2/cards/{cardId}/details"]) fun getCardById() { every { cardService.getCardByCardId(any(), any()) } returns cardMock.getCardEntity().toCard() } }
3. After that, we run this test, and if everything passes, the ouput should look like this:
That’s all from the provider side, and now we can see both sides are done, and contract tests are completed.
Can I Deploy
Before you deploy a new version of a service to a higher environment, you need to know whether or not the version you're about to deploy is compatible with the versions of the other services/apps that already exist in that environment.
Pactflow provides this feature to helps us deploy safely in practice.
This is an example of when we could deploy :
This is an example of when we couldn’t deploy :
We could trace the error with the click URL in the verification results.
Publish Contract Test to Pactflow With Github Actions
Now that we have created and verified our provider contract, we need to share the contract to our consumers. This is where Pactflow comes in to the picture. This step is referred to as "publishing" the provider contract.
We will publish our contracts to Pactflow or we call them pact brokers using github actions. Following are some of the steps to publish a contract.
Steps :
First we should publish the consumer →
pact-publish-consumer
Second, publish the provider →
pact-publish-provider
Then, check if we can deploy the provider →
pact-can-i-deploy-provider
After that, it will record the deployment provider →
pact-record-deployment-provider
And then, it will create a tag for provider →
pact-create-tag-provider
After the provider is done, then it will check if we can deploy the consumer →
pact-can-i-deploy-consumer
Then, it will record the deployment consumer →
pact-record-deployment-consumer
And then, it will create a tag for consumer →
pact-create-tag-consumer
name: Publish Contract Test on: workflow_call: inputs: environment-variable: required: true type: string microservice-name: required: true type: string consumer-name: required: true type: string provider-name: required: true type: string env: VERSION: ${{ github.sha }} PACT_BROKER_BASE_URL: https://safi.pactflow.io PACT_BROKER_TOKEN: "s_nEbsQhRZseQHqFzhDfaQ" jobs: pact-publish-consumer: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: pactflow/actions/publish-pact-files@v1.0.0 env: version: ${{ env.VERSION }} pactfiles: ${{ github.workspace }}/app/app_safi/contract_test application_name: ${{ inputs.consumer-name }} pact-can-i-deploy-consumer: needs: pact-record-deployment-provider runs-on: ubuntu-latest steps: - uses: pactflow/actions/can-i-deploy@v0.0.4 env: version: ${{ env.VERSION }} to_environment: ${{ inputs.environment-variable }} application_name: ${{ inputs.consumer-name }} pact_broker: ${{ env.PACT_BROKER_BASE_URL }} pact_broker_token: ${{ env.PACT_BROKER_TOKEN }} pact-record-deployment-consumer: needs: pact-can-i-deploy-consumer runs-on: ubuntu-latest steps: - uses: pactflow/actions/record-deployment@v1.0.0 env: version: ${{ env.VERSION }} application_name: ${{ inputs.consumer-name }} environment: ${{ inputs.environment-variable }} pact-create-tag-consumer: needs: pact-record-deployment-consumer runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set Version App id: version working-directory: ./app/app_safi run: | export versionApp=$(yq eval '.version' pubspec.yaml) echo "::set-output name=version::$versionApp" - uses: pactflow/actions/create-version-tag@v1.0.0 env: tag: ${{ steps.version.outputs.version }} version: ${{ env.VERSION }} environment: ${{ inputs.environment-variable }} application_name: ${{ inputs.consumer-name }} pact-publish-provider: needs: pact-publish-consumer runs-on: ubuntu-latest steps: - name: Check out repository code uses: actions/checkout@v2 - name: Set up JDK 17 uses: actions/setup-java@v3 with: java-version: "17" distribution: "temurin" - name: Build microservice working-directory: services/${{ inputs.microservice-name }} run: | ./gradlew clean build pact-can-i-deploy-provider: needs: pact-publish-provider runs-on: ubuntu-latest steps: - uses: pactflow/actions/can-i-deploy@v0.0.4 env: to_environment: ${{ inputs.environment-variable }} application_name: ${{ inputs.provider-name }} pact_broker: ${{ env.PACT_BROKER_BASE_URL }} pact_broker_token: ${{ env.PACT_BROKER_TOKEN }} version: "0.1-SNAPSHOT" pact-record-deployment-provider: needs: pact-can-i-deploy-provider runs-on: ubuntu-latest steps: - uses: pactflow/actions/record-deployment@v1.0.0 env: environment: ${{ inputs.environment-variable }} application_name: ${{ inputs.provider-name }} version: "0.1-SNAPSHOT" pact-create-tag-provider: needs: pact-record-deployment-provider runs-on: ubuntu-latest steps: - uses: pactflow/actions/create-version-tag@v1.0.0 env: environment: ${{ inputs.environment-variable }} tag: ${{ inputs.environment-variable }} application_name: ${{ inputs.provider-name }} version: "0.1-SNAPSHOT"
name: Safi Mobile App Contract Test on: workflow_call: inputs: environment-variable: required: true type: string jobs: card-contract-test: name: Publish Card Contract to Pactflow uses: ./.github/workflows/safi-mobile-app-publish-contract-ci.yml with: environment-variable: ${{ inputs.environment-variable }} microservice-name: "card-manager" consumer-name: "safi-mobile-app" provider-name: "card-manager"
Example on Github Actions should look like this :
After the CI has been done, in the dashboard of Pactflow will look like this :
In a nutshell, if we want to visualize the flow it’s below.
Best Practices
Contract tests should focus on the messages (requests and responses) rather than the behavior.
Pact tests should be data-independent.
Use the broker to integrate Pact with your CI infrastructure.
Use pact for contract testing, not functional testing.
Only make assertions about the things that will affect the consumer if they change.
Make the latest pact available to the provider.
References :
Attachments:
aBM1-wpTdN8cJ5ZJ4gO--K5ouezPGHWIAwSWXtHuIDoBvV-viTR2RFieLpdZ1j07nYQpR7_pfebUfAVb389pfhb6Kwlua-PXQ6MT1rTdfT1hKtAgxEJwj0_0kec-37Mp7mmSXKO9eamCGKNuPJOPErpJoN5g-6cq5dpgdbIb1WvUrhklJO_8n4dxFg (image/png)
0OLpdPGWa-4u3ica1T2ZxBQQNp89Qw91Y9bI8Ht4NJalr_I0E0XCW1WwA10jP9_DdRHS9NphUlpTbbv38LHn5rOkMmHO0DTuHpfipwR-d0yhBeMpy8Zkn8ix9Gwlwt7NsxpZnfo0wkc2hjLf2yugYI6aPmYw45NAT1TkTnXSsdMRnBjfPch_GxmA1A (image/png)
zdWp3LQ7MYr6d7mcQRQ5OlYNYM7reAUgFBePfTVnIKsDX_eYZmQ37zRraKMZLuOXV9vFn4EAAjINiyVyljeQ_ISy9dUUpozJBBcxg43Aabn2DODUFw5X97HCUqd76DfP4HCvbiQwDxHnk1DT-GeszViwKPcIyXw7Wb3elcm1wUjF_YyJ50nQkV5FQw (image/png)
Es0aI_etTm7yghb2oFIljTCWR01zln7ogBnqEaCGwc36HYZj3bqGyZI-P68_8x-vDsupfX7csXtfoI-Q71B5UTl0NWUUvQEJFw9HyvpfOr8enU1P_s6hJIMSZsX4pb31_3IQDH18SCJC_Bd7_OUn37h9KWi53gKVBo7J1vWKeFZPv2XOth1c8f02zg (image/png)
5oMW70q00VaTuKTViW7lfVxzS7PVQKdjxUFUCzqEiFHS1l6FK1pZzv-8eA-1VotG_ab2OAtvFV0bNpB-gua3o3lC6OlJyos4BeJXfkca69Wh8n8pyGg--hiRbXtOKYo7YbnjTrRCpyvxoSM95xATql2XeJ5xkF5Eh0jarwdCG1U6uBep_pfU-H5iNg (image/png)
ae7sUGOhOz9K-2QZX-RkZusL4i4G71AXR9YDpgLr72M1Kbydowi6VXQA4i0RqTBm42mLIIpllxGmyOouKUSLhYfLMxZGS8v_RyI2C56RwzpCMfqYQ7wvfvQA1FkuyvUSRgzc-EjwCFH5EtZE8Q0zQD3UHkylTkRLc2yW4O4oUFiXyaiIGjoO1qI8sw (image/png)
hB_MlO4fAqDke6On_ubKCpXU-Sm3UBGu-3L3NlM1nzMJRhc5I2eFXfA4JeRdG3jrjKhdkE2x37N6sYK_vvVyu-RNiQ56i-d1q3WM2HJR9JHgTLNQoLphoSxfru5wXqH9HkyJnAWxRvs0NnE_BcixZCv0XWMgw_XzmTPslu1oXaz6pnJLN7lnBV__9A (image/png)
ieMtu1sJJ1LbosfMMzthA9ymySH1Wy8e2EGSq7c7DB67izk_nh_lPI2Gtez3atFOlqIoppq1Mejcpj1vDfNfX2UUxnW2I5ufySq-5AOfc8FEh6GpHeGyFZCqPALo8TgtvejfkiD-jrYPb0scjICp4Q1Hrk1fHQhtLL0-qcWH_TWnUTXzzH8e_S3f7w (image/png)
EUXO5XGWsS9k9urMtCMM1rP5XfS7kuXNwIowwB8olHW4gxfVQM0j5gP6Pf-mzKzLCvm3i7DFi04Y-0FiIiPAvjUBWeaZTRaA0ySNSOq3PHnMpzPSObm-Wc8K2I-8t_MTJYm0i0Em5RyoLqObL2SyHUwBYvRbx2JLTo69uKufHJ1aff6PcFdeHbduYA (image/png)
364ZVjdEPo0OmI4cdMdeJ1npvLNSpv4_zfnKp59VaMA61rzx35ev-SBM681epQdqFkzG2p7NG05unqmU5eRrtsQj1FAjNBrKHvlvKmMJKe5zBldTBV7KdLKq1wjNtClKx6iJfYIS25bxE3HXZLXU618LdGGpD8TvHVsfTW-OaUcR3FF_bvpQFeuzLg (image/png)
8nAHODPOTFJUwpOhxBeDFVp036ZGA_oWJmgasZeqGpfb93ZJqQXxopI2CbT4jYF_TKdfz6cOrZ-yfHXfL6sy-ztSiulKrHc-0IHfiP6cNoZgvwAHnp4U3F84xtUhLn9mR2SQmm7IMSmoBSW3AJ6VuRFaiPVFQZz4kBJu_xWWxR4Hz8Tfvy852fxxig (image/png)
Screen Shot 2022-09-18 at 15.13.04.png (image/png)
5oMW70q00VaTuKTViW7lfVxzS7PVQKdjxUFUCzqEiFHS1l6FK1pZzv-8eA-1VotG_ab2OAtvFV0bNpB-gua3o3lC6OlJyos4BeJXfkca69Wh8n8pyGg--hiRbXtOKYo7YbnjTrRCpyvxoSM95xATql2XeJ5xkF5Eh0jarwdCG1U6uBep_pfU-H5iNg (image/png)
Es0aI_etTm7yghb2oFIljTCWR01zln7ogBnqEaCGwc36HYZj3bqGyZI-P68_8x-vDsupfX7csXtfoI-Q71B5UTl0NWUUvQEJFw9HyvpfOr8enU1P_s6hJIMSZsX4pb31_3IQDH18SCJC_Bd7_OUn37h9KWi53gKVBo7J1vWKeFZPv2XOth1c8f02zg (image/png)
6qvJjAqn2jlu8nAgjKoRYOpzVLAF2wNXl7MJCZPNwQrOmRwE9UFM0mwG1dUmu5XhFZsl1Sh18zw-49gtT5qgpbu8pc9tdCok7Hp7IW0F-gmnngv_AZnQC10JVt047zK8LyvxO1Ea5oqfb5an1ySASlBBmN0z9QA5-_4o1uSICt0rPaErM37gv2IH2fe7 (image/png)
4QUt3fLiddeatuOmgpMq-oYMumSsq0kZukvxlLxqIl5-t1CLHZasla1s2FSMxrsbuWgZ1OrXZ-VOuCGzM2GgRQUIvfaD5nBnHAZRtyiv_OWNB29mmJ3QTsICErxh7vEzBGvXJF1nfROPV5QFaj7vyUgQq7x4G16qnx1ldiNGGpoVFaSHrk0YR5jT9nm_ (image/png)
Screen Shot 2022-09-19 at 16.02.37.png (image/png)
image-20220919-090408.png (image/png)
Screen Shot 2022-12-06 at 15.49.14.png (image/png)
Screen Shot 2022-12-06 at 15.49.59.png (image/png)
Screen Shot 2022-12-06 at 15.50.21.png (image/png)
Screen Shot 2022-12-06 at 16.03.21.png (image/png)
Screen Shot 2022-12-06 at 16.04.09.png (image/png)
-894jTQ0Vl3usEaEbxNG-XVyY72BC0FrzBOFgtdP5cg4jrDlsjZsNoPr4I8hfLu334Ff4TLiv1HczwebdEgNGlU-Ynql-7N5DzRb_MgJDY-mpIuReyCbB8T10hg3YhaebeEOxIBNk-txQor-3_DZkVJkRO0SP03nr7vyvImbnwlpcrRL5D30LC0hdx1VwA (image/png)
Screen Shot 2022-12-08 at 08.56.10.png (image/png)
Screen Shot 2022-12-09 at 04.44.40.png (image/png)
Screen Shot 2022-12-09 at 04.48.06.png (image/png)
Screen Shot 2022-12-09 at 07.33.03.png (image/png)
Screen Shot 2022-12-09 at 07.37.58.png (image/png)
Screen Shot 2022-12-09 at 06.27.10.png (image/png)
Screen Shot 2022-12-09 at 07.38.28.png (image/png)
Screen Shot 2022-12-09 at 06.27.10.png (image/png)
Screen Shot 2022-12-09 at 08.31.33.png (image/png)