{"id":22772305,"url":"https://github.com/pactflow/example-consumer-js-sns","last_synced_at":"2025-04-15T06:48:17.466Z","repository":{"id":42568570,"uuid":"309280422","full_name":"pactflow/example-consumer-js-sns","owner":"pactflow","description":"Example JS SNS Consumer","archived":false,"fork":false,"pushed_at":"2025-04-14T20:26:38.000Z","size":857,"stargazers_count":2,"open_issues_count":2,"forks_count":5,"subscribers_count":10,"default_branch":"master","last_synced_at":"2025-04-15T06:48:13.017Z","etag":null,"topics":["example"],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pactflow.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-11-02T06:40:59.000Z","updated_at":"2025-04-14T20:26:41.000Z","dependencies_parsed_at":"2025-03-17T01:28:52.544Z","dependency_job_id":"5b559831-24af-4d53-8016-344f6c3023a4","html_url":"https://github.com/pactflow/example-consumer-js-sns","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pactflow%2Fexample-consumer-js-sns","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pactflow%2Fexample-consumer-js-sns/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pactflow%2Fexample-consumer-js-sns/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pactflow%2Fexample-consumer-js-sns/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pactflow","download_url":"https://codeload.github.com/pactflow/example-consumer-js-sns/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249023708,"owners_count":21199958,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["example"],"created_at":"2024-12-11T17:08:03.066Z","updated_at":"2025-04-15T06:48:17.448Z","avatar_url":"https://github.com/pactflow.png","language":"JavaScript","readme":"# Example NodeJS AWS SNS Consumer\n\n[![Build Status](https://github.com/pactflow/example-consumer-js-sns/actions/workflows/build.yml/badge.svg)](https://github.com/pactflow/example-consumer-js-sns/actions)\n\n[![Can I deploy Status](https://test.pactflow.io/pacticipants/pactflow-example-consumer-js-sns/branches/master/latest-version/can-i-deploy/to-environment/production/badge.svg)](https://test.pactflow.io/pacticipants/pactflow-example-consumer-js-sns/branches/master/latest-version/can-i-deploy/to-environment/production/badge)\n\n[![Pact Status](https://test.pactflow.io/pacts/provider/pactflow-example-provider-js-sns/consumer/pactflow-example-consumer-js-sns/latest/badge.svg?label=consumer)](https://test.pactflow.io/pacts/provider/pactflow-example-provider-js-sns/consumer/pactflow-example-consumer-js-sns/latest) (latest pact)\n\n[![Pact Status](https://test.pactflow.io/matrix/provider/pactflow-example-provider-js-sns/latest/master/consumer/pactflow-example-consumer-js-sns/latest/master/badge.svg?label=consumer)](https://test.pactflow.io/pacts/provider/pactflow-example-provider-js-sns/consumer/pactflow-example-consumer-js-sns/latest/prod) (prod/prod pact)\n\nThis is an example of a NodeJS AWS SNS consumer that uses Pact, [PactFlow](https://pactflow.io) and GitHub Actions to ensure that it is compatible with the expectations its consumers have of it.\n\nIt is using a public tenant on PactFlow, which you can access [here](https://test.pactflow.io) using the credentials `dXfltyFMgNOFZAxr8io9wJ37iUpY42M`/`O5AIZWxelWbLvqMd8PkAVycBJh2Psyg1`. The latest version of the Example Consumer/Example Provider pact is published [here](https://test.pactflow.io/pacts/provider/pactflow-example-provider-js-sns/consumer/pactflow-example-consumer-js-sns/latest).\n\nThe project uses a Makefile to simulate a very simple build pipeline with two stages - test and deploy.\n\n* See the canonical consumer example here: https://github.com/pactflow/example-consumer.\n* See also the full [PactFlow CI/CD Workshop](https://docs.pactflow.io/docs/workshops/ci-cd) for which this can be substituted in as the \"consumer\".\n\n## Scenario\n\nIn the following diagram, we'll be testing the \"Consumer\", a Lambda function that receives product updates via the `product` SNS topic.\n\nWe need to be able to test that we are able to receive (consume) product events from the SNS topic as follows:\n\n![SNS Architecture](docs/js-sns.png \"SNS Architecture\") \n\n## Theory\n\nModern distributed architectures are increasingly integrated in a decoupled, asynchronous fashion. Message queues such as ActiveMQ, RabbitMQ, SNS, SQS, Kafka and Kinesis are common, often integrated via small and frequent numbers of microservices (e.g. lambda).\n\nPact supports these use cases, by abstracting away the _protocol_ and focussing on the messages passing between them.\n\nTo reiterate: Pact does not know about the various message queueing technologies - there are simply too many! And more importantly, Pact is really about testing the _messages_ that pass between them, you can still write your standard _functional_ tests using other frameworks designed for such things.\n\nWhen writing tests, Pact takes the place of the intermediary (MQ/broker etc.) and confirms whether or not the consumer is able to _handle_ a given event, or that the provider will be able to _produce_ the correct message.\n\n### How to write tests?\n\nWe recommend that you split the code that is responsible for handling the protocol specific things - in this case the lambda and SNS input - and the piece of code that actually handles the payload.\n\nYou're probably familiar with layered architectures such as Ports and Adaptors (also referred to as a Hexagonal architecture). Following a modular architecture will allow you to do this much more easily:\n\n![Code Modularity](docs/ports-and-adapters.png \"Code Modularity\")\n\nThis code base is setup with this modularity in mind:\n\n* [Lambda Handler](src/lambda/product.js)\n* [Event Service](src/product/product.service.js)\n* Business Logic\n    * [Product](./src/product.js)\n    * [Repository](./src/product.repository.js)\n\nThe target of our [consumer pact test](__tests__/unit/handlers/product.service.pact.test.js) is the [Event Service](src/product/product.service.js), which is responsible for consuming a Product update event, and persisting it to a database (the Repository).\n\nSee also:\n\n* https://dius.com.au/2017/09/22/contract-testing-serverless-and-asynchronous-applications/\n* https://dius.com.au/2018/10/01/contract-testing-serverless-and-asynchronous-applications---part-2/\n\n## Pre-requisites\n\n**Software**:\n\n* [AWS SAM](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install-mac.html)\n* An AWS account (if you want to actual test and run the lambda function)\n*  https://docs.pactflow.io/docs/workshops/ci-cd/set-up-ci/prerequisites/\n\n## Usage\n\nSee also the [PactFlow CI/CD Workshop](https://github.com/pactflow/ci-cd-workshop) for more background.\n\n### Testing\n\n* Run the unit tests: `npm t`\n* Run a (local) lambda integration test: `npm run test:integration`\n\n### Running\n\n* Deploy the actual app: `./script/deploy.sh` (see below for more background)\n* Publish a test event: `npm run publish`\n* View the lambda logs: `npm run logs`\n\nHere is some sample output publishing and viewing the logs:\n```\n➜  example-consumer-js-sns git:(master) ✗ npm run publish                                                                                                                                                                                                                                                    \u003caws:pact-dev\u003e\n\n\u003e product-service@1.0.0 publish /Users/matthewfellows/development/public/example-consumer-js-sns\n\u003e ./scripts/publish.sh\n\nfinding topic\nhave topic: arn:aws:sns:ap-southeast-2:838728264948:pactflow-example-consumer-js-sns-ProductEvent-144XVHN8QP2D3, publishing message\n{\n    \"MessageId\": \"735a2daa-7eaa-53d7-b362-75b0d9227708\"\n}\n\n\u003e product-service@1.0.0 logs /Users/matthewfellows/development/public/example-consumer-js-sns\n\u003e sam logs -n ProductEventHandler --stack-name pactflow-example-consumer-js-sns -t\n\n2020/11/03/[$LATEST]df9d6b71ef1e49789f4ebca64fc19270 2020-11-03T00:25:24.984000 START RequestId: 47e97e7d-52cf-4c83-9133-545749ed2750 Version: $LATEST\n2020/11/03/[$LATEST]df9d6b71ef1e49789f4ebca64fc19270 2020-11-03T00:25:25.012000 2020-11-03T00:25:24.988Z\t47e97e7d-52cf-4c83-9133-545749ed2750\tINFO\t{\n  Records: [\n    {\n      EventSource: 'aws:sns',\n      EventVersion: '1.0',\n      EventSubscriptionArn: 'arn:aws:sns:ap-southeast-2:838728264948:pactflow-example-consumer-js-sns-ProductEvent-144XVHN8QP2D3:efaf0845-3847-4b5d-a4b1-68f33ef524e8',\n      Sns: [Object]\n    }\n  ]\n}\n2020/11/03/[$LATEST]df9d6b71ef1e49789f4ebca64fc19270 2020-11-03T00:25:25.032000 END RequestId: 47e97e7d-52cf-4c83-9133-545749ed2750\n2020/11/03/[$LATEST]df9d6b71ef1e49789f4ebca64fc19270 2020-11-03T00:25:25.032000 REPORT RequestId: 47e97e7d-52cf-4c83-9133-545749ed2750\tDuration: 48.28 ms\tBilled Duration: 100 ms\tMemory Size: 128 MB\tMax Memory Used: 64 MB\tInit Duration: 136.98 ms\n```\n\nIf you edit the file `./scripts/publish.sh` to remove a valid property, or upload invalid JSON you will get something like this:\n\n```\n2020/11/03/[$LATEST]df9d6b71ef1e49789f4ebca64fc19270 2020-11-03T00:36:23.376000 2020-11-03T00:36:23.376Z\t3eb496cd-c663-4ae2-a717-8f261b7ad48c\tERROR\tInvoke Error \t{\"errorType\":\"AssertionError\",\"errorMessage\":\"id is a mandatory field\",\"code\":\"ERR_ASSERTION\",\"generatedMessage\":false,\"expected\":true,\"operator\":\"==\",\"stack\":[\"AssertionError [ERR_ASSERTION]: id is a mandatory field\",\"    at new Product (/var/task/src/product/product.js:5:5)\",\"    at handler (/var/task/src/product/product.handler.js:7:23)\",\"    at /var/task/src/service/product.js:10:44\",\"    at Array.map (\u003canonymous\u003e)\",\"    at Runtime.lambda [as handler] (/var/task/src/service/product.js:10:33)\",\"    at Runtime.handleOnce (/var/runtime/Runtime.js:66:25)\"]}\n2020/11/03/[$LATEST]df9d6b71ef1e49789f4ebca64fc19270 2020-11-03T00:36:23.416000 END RequestId: 3eb496cd-c663-4ae2-a717-8f261b7ad48c\n2020/11/03/[$LATEST]df9d6b71ef1e49789f4ebca64fc19270 2020-11-03T00:36:23.416000 REPORT RequestId: 3eb496cd-c663-4ae2-a717-8f261b7ad48c\tDuration: 75.82 ms\tBilled Duration: 100 ms\tMemory Size: 128 MB\tMax Memory Used: 65 MB\n```\n\n\n-------------\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpactflow%2Fexample-consumer-js-sns","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpactflow%2Fexample-consumer-js-sns","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpactflow%2Fexample-consumer-js-sns/lists"}