├── .github ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── main.yaml ├── .gitignore ├── .travis.yml ├── Dockerfile ├── LICENSE ├── README.md ├── api-spec ├── carts.json ├── hooks.js ├── mock.json └── routes.json ├── docker-compose-zipkin.yml ├── docker └── carts │ └── Dockerfile ├── pom.xml ├── scripts ├── build.sh └── push.sh ├── src ├── main │ ├── java │ │ └── works │ │ │ └── weave │ │ │ └── socks │ │ │ └── cart │ │ │ ├── CartApplication.java │ │ │ ├── action │ │ │ └── FirstResultOrDefault.java │ │ │ ├── cart │ │ │ ├── CartContentsResource.java │ │ │ ├── CartDAO.java │ │ │ ├── CartResource.java │ │ │ ├── Contents.java │ │ │ ├── HasContents.java │ │ │ └── Resource.java │ │ │ ├── configuration │ │ │ ├── BeanConfiguration.java │ │ │ ├── MongoConfiguration.java │ │ │ ├── ValidationConfiguration.java │ │ │ └── WebMvcConfig.java │ │ │ ├── controllers │ │ │ ├── CartsController.java │ │ │ ├── HealthCheckController.java │ │ │ └── ItemsController.java │ │ │ ├── entities │ │ │ ├── Cart.java │ │ │ ├── HealthCheck.java │ │ │ └── Item.java │ │ │ ├── item │ │ │ ├── FoundItem.java │ │ │ ├── ItemDAO.java │ │ │ └── ItemResource.java │ │ │ ├── middleware │ │ │ └── HTTPMonitoringInterceptor.java │ │ │ └── repositories │ │ │ ├── CartRepository.java │ │ │ └── ItemRepository.java │ └── resources │ │ └── application.properties └── test │ └── java │ └── works │ └── weave │ └── socks │ └── cart │ ├── action │ └── UnitFirstResultOrDefault.java │ ├── cart │ ├── UnitCartContentsResource.java │ └── UnitCartResource.java │ ├── controllers │ ├── UnitCartsController.java │ ├── UnitHealthCheckController.java │ └── UnitItemsController.java │ ├── item │ ├── UnitFoundItem.java │ └── UnitItemResource.java │ └── repositories │ ├── ITCartRepository.java │ └── ITItemRepository.java └── test ├── Dockerfile ├── component.py ├── container.py ├── coveralls.py ├── test.sh ├── unit.py └── util ├── Api.py ├── Docker.py ├── Dredd.py └── __init__.py /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution guidelines 2 | 3 | We'd love to accept your contributions; large or small. Simply submit an issue or pull request via Github and involve one of the active members. Simple! But please read the rest of this document to ensure we're all on the same page. 4 | 5 | ## General Rules 6 | 7 | - Be kind and polite. Written language often does not convey the sentiment, so make sure you use lots of jokes and emoticons to get the sentiment across. 8 | - Prefer best practice. Everyone has their preferred style, but try to conform to current best practices. We don't enforce any strict rules. 9 | - Test your code to the best of your abilities. See the testing documentation for the correct scope of your test. 10 | 11 | ## Bug reports or feature requests 12 | 13 | Please open an issue if you have found an issue or have an idea for a new feature. Please follow the bug reporting guidelines if you submit an issue. 14 | 15 | ## New Contributors 16 | 17 | We have a list of issues on Github with "HelpWanted" labels attributed to them. These represent tasks that we don't have time to do, are self-contained and relatively easy to implement. If you'd like to contribute, but don't know where to start, [look here](https://github.com/microservices-demo/microservices-demo/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aopen%20label%3AHelpWanted). 18 | 19 | ## Direction 20 | 21 | This project does have a general direction, but we're happy to consider deviating or pivoting from the direction we're currently heading. See the introductory material for details regarding direction. 22 | 23 | With that said, there is absolutely nothing stopping you from submitting a PR. If you've taken the effort to contribute, someone will make the effort to review. 24 | 25 | ## License 26 | 27 | This project is Apache v2.0 licenced. Submitting and merging a PR implies you accept these terms. 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | - Add labels appropriate to the issue 2 | - Describe the expected behaviour and the actual behaviour 3 | - Describe steps to reproduce the problem 4 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | - Read the contribution guidelines 2 | - Include a reference to a related issue in this repository 3 | - A description of the changes proposed in the pull request -------------------------------------------------------------------------------- /.github/workflows/main.yaml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | branches: 6 | - "*" # run for branches 7 | tags: 8 | - "*" # run for tags 9 | pull_request: 10 | branches: 11 | - "*" # run for branches 12 | tags: 13 | - "*" # run for tags 14 | 15 | jobs: 16 | test: 17 | runs-on: ubuntu-latest 18 | env: 19 | GROUP: weaveworksdemos 20 | COMMIT: ${{ github.sha }} 21 | REPO: carts 22 | steps: 23 | - uses: actions/checkout@v2 24 | 25 | 26 | - name: Set up JDK 1.8 27 | uses: actions/setup-java@v1 28 | with: 29 | java-version: 1.8 30 | 31 | # 32 | # 33 | # Build 34 | - name: Build jar files 35 | run: ./scripts/build.sh 36 | 37 | # 38 | # 39 | # Push to dockerhub 40 | - name: Push to Docker Hub 41 | uses: docker/build-push-action@v1 42 | if: startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/master' 43 | with: 44 | username: ${{ secrets.DOCKER_USER }} 45 | password: ${{ secrets.DOCKER_PASS }} 46 | repository: ${{ env.GROUP }}/${{ env.REPO }} 47 | tag_with_ref: true 48 | tag_with_sha: true 49 | path: docker/carts 50 | dockerfile: docker/carts/Dockerfile 51 | 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Dependency directories 7 | node_modules 8 | jspm_packages 9 | 10 | # Optional npm cache directory 11 | .npm 12 | 13 | ### Go template 14 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 15 | *.o 16 | *.a 17 | *.so 18 | 19 | # Folders 20 | _obj 21 | _test 22 | 23 | # Architecture specific extensions/prefixes 24 | *.[568vq] 25 | [568vq].out 26 | 27 | *.cgo1.go 28 | *.cgo2.c 29 | _cgo_defun.c 30 | _cgo_gotypes.go 31 | _cgo_export.* 32 | 33 | _testmain.go 34 | 35 | *.exe 36 | *.test 37 | *.prof 38 | ### Java template 39 | /target/ 40 | *.class 41 | 42 | # Mobile Tools for Java (J2ME) 43 | .mtj.tmp/ 44 | 45 | # Package Files # 46 | *.war 47 | *.ear 48 | 49 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 50 | hs_err_pid* 51 | ### OSX template 52 | .DS_Store 53 | .AppleDouble 54 | .LSOverride 55 | 56 | # Icon must end with two \r 57 | Icon 58 | 59 | # Thumbnails 60 | ._* 61 | 62 | # Files that might appear in the root of a volume 63 | .DocumentRevisions-V100 64 | .fseventsd 65 | .Spotlight-V100 66 | .TemporaryItems 67 | .Trashes 68 | .VolumeIcon.icns 69 | 70 | # Directories potentially created on remote AFP share 71 | .AppleDB 72 | .AppleDesktop 73 | Network Trash Folder 74 | Temporary Items 75 | .apdisk 76 | ### JetBrains template 77 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 78 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 79 | 80 | # User-specific stuff: 81 | .idea 82 | 83 | ## File-based project format: 84 | *.iws 85 | *.iml 86 | 87 | ## Plugin-specific files: 88 | 89 | # IntelliJ 90 | /out/ 91 | 92 | # mpeltonen/sbt-idea plugin 93 | .idea_modules/ 94 | 95 | # JIRA plugin 96 | atlassian-ide-plugin.xml 97 | 98 | # Crashlytics plugin (for Android Studio and IntelliJ) 99 | com_crashlytics_export_strings.xml 100 | crashlytics.properties 101 | crashlytics-build.properties 102 | fabric.properties 103 | # Created by .ignore support plugin (hsz.mobi) 104 | 105 | # Maven builds 106 | */target 107 | */*/target 108 | 109 | # AWS ECS install scripts generates an SSH key file 110 | weave-ecs-demo-key.pem 111 | 112 | # Load test generates pyc files 113 | *.pyc 114 | 115 | # Ignore Vagrant cache files 116 | *.vagrant/ 117 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | sudo: required 3 | services: 4 | - docker 5 | jdk: 6 | - oraclejdk11 7 | install: true 8 | 9 | env: 10 | - GROUP=weaveworksdemos COMMIT=$TRAVIS_COMMIT TAG=$TRAVIS_TAG REPO=carts; 11 | 12 | script: 13 | - set -e 14 | - travis_wait ./scripts/build.sh; 15 | 16 | after_success: 17 | - set -e; 18 | - if [ -z "$DOCKER_PASS" ] ; then 19 | echo "This is a build triggered by an external PR. Skipping docker push."; 20 | exit 0; 21 | fi; 22 | - docker login -u $DOCKER_USER -p $DOCKER_PASS; 23 | - ./scripts/push.sh 24 | notifications: 25 | slack: 26 | secure: jHh+ksay1dxGEMxxep6XllCR5VwsmuSyIpLgG9bUKpG4fPR948K5Ev2E/vyobrqbTi5JMWasXd3ecwzV1QIN8QvvQ33Fx70oSp9Cit5gvNmLWVplHkWiMnnLD33IMCbtDHK69FjH3GQyJ5HfgjiJSF2nTFaCArYImrCcnU+ENTXW9L3rlYm5ElnP/NfWbrqxIF4EOiCKHMM5kpFYWO8mYLTVilHhPLAz94IVV2OUTe4NUdBJgXolAJqg28QTcfrk5x0OudDTc3Ssa3f/ezrE0LgrzeWdekcrNdYt+YZYaQxiPOIhNyZu0RxU+46ip4XhUS6qSXxW1Kpf0RXkgdmTTbYxF31D4GG+SoTjbBONZqK+qw3AkuYxTQYZnl50n+Hd1gtKY31qCwFEAVUU/tYDI5oKUdUaDsiHU5J/Yt+YMPoyWZ7FOZYwSNy2xs78XGXqQnYn35lsBv0BmYo/1mPUFbrlZgVO5b5wki7NhuLKnqHo9GvkavG71py1VjJTShq5um4NrPBYZ/Y9aZtZD6E2BQPJBgciwGtoFdmaTzmPtwPkWMA+SlcAtpcQlFATXM7xS5324XYK9+okDoqybj1cmHIk1FLnM9nx3r+TUOGsUrlY2ZO9FIrljgII3H8vzcUCbwMwzaQvhsBCFBFZQ6umjbHxnRN9zRe52c4zkkCSAZc= 27 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM java:openjdk-8-alpine 2 | 3 | WORKDIR /usr/src/app 4 | COPY ./target/*.jar ./app.jar 5 | 6 | ENTRYPOINT ["java","-Djava.security.egd=file:/dev/urandom","-jar","./app.jar", "--port=80"] 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/microservices-demo/carts.svg?branch=master)](https://travis-ci.org/microservices-demo/carts) [![Coverage Status](https://coveralls.io/repos/github/microservices-demo/carts/badge.svg?branch=master)](https://coveralls.io/github/microservices-demo/carts?branch=master) 2 | [![](https://images.microbadger.com/badges/image/weaveworksdemos/cart.svg)](http://microbadger.com/images/weaveworksdemos/cart "Get your own image badge on microbadger.com") 3 | 4 | # DEPRECATED: cart 5 | A microservices-demo service that provides shopping carts for users. 6 | 7 | This build is built, tested and released by travis. 8 | 9 | # API Spec 10 | 11 | Checkout the API Spec [here](http://microservices-demo.github.io/api/index?url=https://raw.githubusercontent.com/microservices-demo/carts/master/api-spec/cart.json) 12 | 13 | # Build 14 | 15 | ## Java 16 | 17 | `mvn -DskipTests package` 18 | 19 | ## Docker 20 | 21 | `GROUP=weaveworksdemos COMMIT=test ./scripts/build.sh` 22 | 23 | # Test 24 | 25 | `./test/test.sh < python testing file >`. For example: `./test/test.sh unit.py` 26 | 27 | # Run 28 | 29 | `mvn spring-boot:run` 30 | 31 | # Check 32 | 33 | `curl http://localhost:8081/health` 34 | 35 | # Use 36 | 37 | `curl http://localhost:8081` 38 | 39 | # Push 40 | 41 | `GROUP=weaveworksdemos COMMIT=test ./scripts/push.sh` 42 | -------------------------------------------------------------------------------- /api-spec/carts.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "version": "", 5 | "title": "Carts and items", 6 | "description": "Carts and items resources", 7 | "license": { 8 | "name": "MIT", 9 | "url": "http://github.com/gruntjs/grunt/blob/master/LICENSE-MIT" 10 | } 11 | }, 12 | "host": "carts", 13 | "basePath": "/", 14 | "securityDefinitions": {}, 15 | "schemes": [ 16 | "http" 17 | ], 18 | "consumes": [ 19 | "application/json;charset=UTF-8", 20 | "text/plain" 21 | ], 22 | "paths": { 23 | "/carts/{customerId}": { 24 | "get": { 25 | "description": "", 26 | "operationId": "Get cart", 27 | "produces": [ 28 | "application/json;charset=UTF-8" 29 | ], 30 | "parameters": [ 31 | { 32 | "name": "customerId", 33 | "in": "path", 34 | "required": true, 35 | "type": "string", 36 | "x-example": "1" 37 | } 38 | ], 39 | "responses": { 40 | "200": { 41 | "description": "Returns cart", 42 | "schema": { 43 | "$ref": "#/definitions/Getcartresponse" 44 | } 45 | } 46 | } 47 | }, 48 | "delete": { 49 | "description": "", 50 | "operationId": "Delete cart", 51 | "parameters": [ 52 | { 53 | "name": "customerId", 54 | "in": "path", 55 | "required": true, 56 | "type": "string", 57 | "x-example": "1" 58 | } 59 | ], 60 | "responses": { 61 | "202": { 62 | "description": "" 63 | } 64 | } 65 | } 66 | }, 67 | "/carts/{customerId}/items": { 68 | "post": { 69 | "description": "", 70 | "operationId": "Add an item to the cart", 71 | "produces": [ 72 | "application/json;charset=UTF-8" 73 | ], 74 | "parameters": [ 75 | { 76 | "name": "customerId", 77 | "in": "path", 78 | "required": true, 79 | "type": "string", 80 | "x-example": "579f21ae98684924944651bf" 81 | }, 82 | { 83 | "name": "body", 84 | "in": "body", 85 | "required": true, 86 | "schema": { 87 | "$ref": "#/definitions/CartItem", 88 | "example": { 89 | "itemId":"819e1fbf-8b7e-4f6d-811f-693534916a8b", 90 | "quantity": 20, 91 | "unitPrice" : 99.0 92 | } 93 | } 94 | } 95 | ], 96 | "responses": { 97 | "201": { 98 | "description": "", 99 | "schema": { 100 | "$ref": "#/definitions/CartItem" 101 | } 102 | } 103 | } 104 | }, 105 | "patch": { 106 | "description": "Update an item", 107 | "operationId": "Update item", 108 | "produces": [ 109 | "application/json;charset=UTF-8" 110 | ], 111 | "parameters": [ 112 | { 113 | "name": "customerId", 114 | "in": "path", 115 | "required": true, 116 | "type": "string", 117 | "x-example": "579f21ae98684924944651bf" 118 | }, 119 | { 120 | "name": "body", 121 | "in": "body", 122 | "required": true, 123 | "schema": { 124 | "type": "object" 125 | } 126 | } 127 | ], 128 | "responses": { 129 | "200": { 130 | "description": "" 131 | } 132 | } 133 | } 134 | }, 135 | "/carts/{customerId}/items/{itemId}": { 136 | "delete": { 137 | "description": "Delete cart item", 138 | "operationId": "delete", 139 | "parameters": [ 140 | { 141 | "name": "itemId", 142 | "in": "path", 143 | "required": true, 144 | "type": "string", 145 | "x-example": "819e1fbf-8b7e-4f6d-811f-693534916a8b" 146 | }, 147 | { 148 | "name": "customerId", 149 | "in": "path", 150 | "required": true, 151 | "type": "string", 152 | "x-example": "579f21ae98684924944651bf" 153 | } 154 | ], 155 | "responses": { 156 | "202": { 157 | "description": "Delete response" 158 | } 159 | } 160 | } 161 | } 162 | }, 163 | "definitions": { 164 | "Getcartresponse": { 165 | "title": "Get cart response", 166 | "type": "object", 167 | "properties": { 168 | "customerId": { 169 | "type": "string" 170 | } 171 | }, 172 | "required": [ 173 | "customerId" 174 | ] 175 | }, 176 | "CartItem": { 177 | "title": "Cart item", 178 | "type": "object", 179 | "properties": { 180 | "itemId": { 181 | "type": "string" 182 | }, 183 | "quantity": { 184 | "type": "integer" 185 | }, 186 | "unitPrice": { 187 | "type": "number" 188 | } 189 | }, 190 | "required": [ 191 | "itemId", 192 | "quantity", 193 | "unitPrice" 194 | ] 195 | } 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /api-spec/hooks.js: -------------------------------------------------------------------------------- 1 | const hooks = require('hooks'); 2 | const {MongoClient} = require('mongodb'); 3 | const ObjectID = require('mongodb').ObjectID; 4 | 5 | let db; 6 | 7 | const address = [ 8 | {"_id":ObjectID("579f21ae98684924944651bd"),"_class":"works.weave.socks.users.entities.Address","number":"69","street":"Wilson Street","city":"Hartlepool","postcode":"TS26 8JU","country":"United Kingdom"}, 9 | {"_id":ObjectID("579f21ae98684924944651c0"),"_class":"works.weave.socks.users.entities.Address","number":"122","street":"Radstone WayNet","city":"Northampton","postcode":"NN2 8NT","country":"United Kingdom"}, 10 | {"_id":ObjectID("579f21ae98684924944651c3"),"_class":"works.weave.socks.users.entities.Address","number":"3","street":"Radstone Way","city":"Northampton","postcode":"NN2 8NT","country":"United Kingdom"} 11 | ]; 12 | 13 | 14 | const card = [ 15 | {"_id":ObjectID("579f21ae98684924944651be"),"_class":"works.weave.socks.users.entities.Card","longNum":"8575776807334952","expires":"08/19","ccv":"014"}, 16 | {"_id":ObjectID("579f21ae98684924944651c1"),"_class":"works.weave.socks.users.entities.Card","longNum":"8918468841895184","expires":"08/19","ccv":"597"}, 17 | {"_id":ObjectID("579f21ae98684924944651c4"),"_class":"works.weave.socks.users.entities.Card","longNum":"6426429851404909","expires":"08/19","ccv":"381"} 18 | ]; 19 | 20 | const cart = [ 21 | {"_id":ObjectID("579f21de98689ebf2bf1cd2f"),"_class":"works.weave.socks.cart.entities.Cart","customerId":"579f21ae98684924944651bf","items":[{"$ref":"item","$id":ObjectID("579f227698689ebf2bf1cd31")},{"$ref":"item","$id":ObjectID("579f22ac98689ebf2bf1cd32")}]}, 22 | {"_id":ObjectID("579f21e298689ebf2bf1cd30"),"_class":"works.weave.socks.cart.entities.Cart","customerId":"579f21ae98684924944651bfaa","items":[]} 23 | ]; 24 | 25 | 26 | const item = [ 27 | {"_id":ObjectID("579f227698689ebf2bf1cd31"),"_class":"works.weave.socks.cart.entities.Item","itemId":"819e1fbf-8b7e-4f6d-811f-693534916a8b","quantity":20,"unitPrice":99.0} 28 | ]; 29 | 30 | 31 | const customer = [ 32 | {"_id":"579f21ae98684924944651bf","_class":"works.weave.socks.users.entities.Customer","firstName":"Eve","lastName":"Berger","username":"Eve_Berger","addresses":[{"$ref":"address","$id":ObjectID("579f21ae98684924944651bd")}],"cards":[{"$ref":"card","$id":ObjectID("579f21ae98684924944651be")}] 33 | }, 34 | {"_id":"579f21ae98684924944651c2","_class":"works.weave.socks.users.entities.Customer","firstName":"User","lastName":"Name","username":"user","addresses":[{"$ref":"address","$id":ObjectID("579f21ae98684924944651c0")}],"cards":[{"$ref":"card","$id":ObjectID("579f21ae98684924944651c1")}]}, 35 | {"_id":"579f21ae98684924944651c5","_class":"works.weave.socks.users.entities.Customer","firstName":"User1","lastName":"Name1","username":"user1","addresses":[{"$ref":"address","$id":ObjectID("579f21ae98684924944651c3")}],"cards":[{"$ref":"card","$id":ObjectID("579f21ae98684924944651c4")}]} 36 | ]; 37 | 38 | 39 | // Setup database connection before Dredd starts testing 40 | hooks.beforeAll((transactions, done) => { 41 | var MongoEndpoint = process.env.MONGO_ENDPOINT || 'mongodb://localhost:32769/data'; 42 | MongoClient.connect(MongoEndpoint, function(err, conn) { 43 | if (err) { 44 | console.error(err); 45 | } 46 | db = conn; 47 | done(err); 48 | }); 49 | }); 50 | 51 | hooks.beforeEach((transaction, done) => { 52 | db.dropDatabase(function (err, res) { 53 | var promisesToKeep = [ 54 | db.collection('customer').insertMany(customer), 55 | db.collection('card').insertMany(card), 56 | db.collection('cart').insertMany(cart), 57 | db.collection('address').insertMany(address), 58 | db.collection('item').insertMany(item) 59 | ]; 60 | Promise.all(promisesToKeep).then(function(vls) { 61 | done(); 62 | }, function(vls) { 63 | done(); 64 | }); 65 | }) 66 | 67 | }); 68 | 69 | 70 | hooks.before("/carts/{customerId}/items > POST", function(transaction, done) { 71 | transaction.request.headers['Content-Type'] = 'application/json'; 72 | transaction.request.body = JSON.stringify( 73 | { 74 | "itemId":"819e1fbf-8b7e-4f6d-811f-693534916a8b", 75 | "quantity": 20, 76 | "unitPrice" : 99.0 77 | } 78 | ); 79 | 80 | done(); 81 | }); 82 | 83 | // TODO: Can't make POST and PUT work, skipping for now 84 | 85 | // hooks.before("/carts/{customerId}/items > POST", function(transaction, done) { 86 | // transaction.skip = true; 87 | // done(); 88 | // }); 89 | 90 | hooks.before("/carts/{customerId}/items > PATCH", function(transaction, done) { 91 | transaction.skip = true; 92 | done(); 93 | }); 94 | -------------------------------------------------------------------------------- /api-spec/mock.json: -------------------------------------------------------------------------------- 1 | { 2 | "carts": [ 3 | { 4 | "id": "57a98d98e4b00679b4a830af" 5 | } 6 | ], 7 | "items": [ 8 | { 9 | "id": 1, 10 | "quantity": 10, 11 | "unitPrice": 1.99, 12 | "cartsId": "57a98d98e4b00679b4a830af" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /api-spec/routes.json: -------------------------------------------------------------------------------- 1 | { 2 | "/carts/:cartsId/items/:id": "/items/:id" 3 | } 4 | -------------------------------------------------------------------------------- /docker-compose-zipkin.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | carts: 5 | image: weaveworksdemos/carts 6 | hostname: carts 7 | restart: always 8 | cap_drop: 9 | - all 10 | cap_add: 11 | - NET_BIND_SERVICE 12 | read_only: true 13 | tmpfs: 14 | - /tmp:rw,noexec,nosuid 15 | environment: 16 | - reschedule=on-node-failure 17 | - ZIPKIN_HOST=zipkin 18 | - ZIPKIN_ENABLED=true 19 | ports: 20 | - "8081:80" 21 | zipkin: 22 | image: openzipkin/zipkin 23 | hostname: zipkin 24 | restart: always 25 | cap_drop: 26 | - all 27 | cap_add: 28 | - CHOWN 29 | - SETGID 30 | - SETUID 31 | read_only: true 32 | tmpfs: 33 | - /tmp:rw,noexec,nosuid 34 | environment: 35 | - reschedule=on-node-failure 36 | ports: 37 | - "9411:9411" 38 | -------------------------------------------------------------------------------- /docker/carts/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM weaveworksdemos/msd-java:jre-latest 2 | 3 | WORKDIR /usr/src/app 4 | COPY *.jar ./app.jar 5 | 6 | RUN chown -R ${SERVICE_USER}:${SERVICE_GROUP} ./app.jar 7 | 8 | USER ${SERVICE_USER} 9 | 10 | ARG BUILD_DATE 11 | ARG BUILD_VERSION 12 | ARG COMMIT 13 | 14 | LABEL org.label-schema.vendor="Weaveworks" \ 15 | org.label-schema.build-date="${BUILD_DATE}" \ 16 | org.label-schema.version="${BUILD_VERSION}" \ 17 | org.label-schema.name="Socks Shop: Cart" \ 18 | org.label-schema.description="REST API for Cart service" \ 19 | org.label-schema.url="https://github.com/microservices-demo/carts" \ 20 | org.label-schema.vcs-url="github.com:microservices-demo/carts.git" \ 21 | org.label-schema.vcs-ref="${COMMIT}" \ 22 | org.label-schema.schema-version="1.0" 23 | 24 | ENTRYPOINT ["/usr/local/bin/java.sh","-jar","./app.jar", "--port=80"] 25 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | works.weave.microservices-demo 8 | carts 9 | jar 10 | 11 | carts 12 | Carts service for microservices-demo application 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 2.0.4.RELEASE 18 | 19 | 20 | 21 | UTF-8 22 | 1.8 23 | 0.0.21 24 | 25 | 26 | 27 | 28 | org.springframework.boot 29 | spring-boot-starter-data-rest 30 | 31 | 32 | org.springframework.boot 33 | spring-boot-starter-data-mongodb 34 | 35 | 36 | org.springframework.cloud 37 | spring-cloud-starter-zipkin 38 | 1.1.0.RELEASE 39 | 40 | 41 | io.prometheus 42 | simpleclient_spring_boot 43 | ${prometheus.version} 44 | 45 | 46 | io.prometheus 47 | simpleclient_hotspot 48 | ${prometheus.version} 49 | 50 | 51 | io.prometheus 52 | simpleclient_servlet 53 | ${prometheus.version} 54 | 55 | 56 | org.springframework.data 57 | spring-data-rest-hal-browser 58 | 59 | 60 | org.springframework.boot 61 | spring-boot-starter-test 62 | test 63 | 64 | 65 | de.flapdoodle.embed 66 | de.flapdoodle.embed.mongo 67 | 1.50.5 68 | test 69 | 70 | 71 | com.openpojo 72 | openpojo 73 | 0.8.4 74 | test 75 | 76 | 77 | 78 | 79 | carts 80 | 81 | 82 | org.springframework.boot 83 | spring-boot-maven-plugin 84 | 85 | 86 | org.apache.maven.plugins 87 | maven-surefire-plugin 88 | 2.19.1 89 | 90 | 91 | **/Unit*.java 92 | 93 | 94 | **/IT*.java 95 | 96 | 97 | 98 | 99 | org.apache.maven.plugins 100 | maven-failsafe-plugin 101 | 2.18.1 102 | 103 | 104 | **/IT*.java 105 | 106 | 107 | **/Unit*.java 108 | 109 | 110 | 111 | 112 | 113 | integration-test 114 | verify 115 | 116 | 117 | 118 | 119 | 120 | org.jacoco 121 | jacoco-maven-plugin 122 | 0.7.6.201602180812 123 | 124 | 125 | prepare-agent 126 | 127 | prepare-agent 128 | 129 | 130 | 131 | 132 | 133 | org.eluder.coveralls 134 | coveralls-maven-plugin 135 | 4.2.0 136 | 137 | 138 | 139 | 140 | 141 | 142 | spring-releases 143 | https://repo.spring.io/libs-release 144 | 145 | 146 | 147 | 148 | spring-releases 149 | https://repo.spring.io/libs-release 150 | 151 | 152 | 153 | -------------------------------------------------------------------------------- /scripts/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -ev 4 | 5 | export BUILD_VERSION="0.0.2-SNAPSHOT" 6 | export BUILD_DATE=`date +%Y-%m-%dT%T%z` 7 | 8 | SCRIPT_DIR=$(dirname "$0") 9 | 10 | if [[ -z "$GROUP" ]] ; then 11 | echo "Cannot find GROUP env var" 12 | exit 1 13 | fi 14 | 15 | if [[ -z "$COMMIT" ]] ; then 16 | echo "Cannot find COMMIT env var" 17 | exit 1 18 | fi 19 | 20 | if [[ "$(uname)" == "Darwin" ]]; then 21 | DOCKER_CMD=docker 22 | else 23 | DOCKER_CMD="sudo docker" 24 | fi 25 | CODE_DIR=$(cd $SCRIPT_DIR/..; pwd) 26 | echo $CODE_DIR 27 | $DOCKER_CMD run --rm -v $HOME/.m2:/root/.m2 -v $CODE_DIR:/usr/src/mymaven -w /usr/src/mymaven maven:3.6-jdk-11 mvn -q -DskipTests package 28 | 29 | cp $CODE_DIR/target/*.jar $CODE_DIR/docker/carts 30 | 31 | for m in ./docker/*/; do 32 | REPO=${GROUP}/$(basename $m) 33 | $DOCKER_CMD build \ 34 | --build-arg BUILD_VERSION=$BUILD_VERSION \ 35 | --build-arg BUILD_DATE=$BUILD_DATE \ 36 | --build-arg COMMIT=$COMMIT \ 37 | -t ${REPO}:${COMMIT} $CODE_DIR/$m; 38 | done; 39 | -------------------------------------------------------------------------------- /scripts/push.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -ev 4 | 5 | if [[ -z "$GROUP" ]] ; then 6 | echo "Cannot find GROUP env var" 7 | exit 1 8 | fi 9 | 10 | if [[ -z "$COMMIT" ]] ; then 11 | echo "Cannot find COMMIT env var" 12 | exit 1 13 | fi 14 | 15 | push() { 16 | DOCKER_PUSH=1; 17 | while [ $DOCKER_PUSH -gt 0 ] ; do 18 | echo "Pushing $1"; 19 | docker push $1; 20 | DOCKER_PUSH=$(echo $?); 21 | if [[ "$DOCKER_PUSH" -gt 0 ]] ; then 22 | echo "Docker push failed with exit code $DOCKER_PUSH"; 23 | fi; 24 | done; 25 | } 26 | 27 | tag_and_push_all() { 28 | if [[ -z "$1" ]] ; then 29 | echo "Please pass the tag" 30 | exit 1 31 | else 32 | TAG=$1 33 | fi 34 | for m in ./docker/*/; do 35 | REPO=${GROUP}/$(basename $m) 36 | if [[ "$COMMIT" != "$TAG" ]]; then 37 | docker tag ${REPO}:${COMMIT} ${REPO}:${TAG} 38 | fi 39 | push "$REPO:$TAG"; 40 | done; 41 | } 42 | 43 | # Push snapshot when in master 44 | if [ "$TRAVIS_BRANCH" == "master" ] && [ "$TRAVIS_PULL_REQUEST" == "false" ]; then 45 | tag_and_push_all master-${COMMIT:0:8} 46 | fi; 47 | 48 | # Push tag and latest when tagged 49 | if [ -n "$TRAVIS_TAG" ]; then 50 | tag_and_push_all ${TRAVIS_TAG} 51 | tag_and_push_all latest 52 | fi; 53 | -------------------------------------------------------------------------------- /src/main/java/works/weave/socks/cart/CartApplication.java: -------------------------------------------------------------------------------- 1 | package works.weave.socks.cart; 2 | 3 | import io.prometheus.client.spring.boot.EnablePrometheusEndpoint; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | 7 | @SpringBootApplication 8 | @EnablePrometheusEndpoint 9 | public class CartApplication { 10 | public static void main(String[] args) { 11 | SpringApplication.run(CartApplication.class, args); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/works/weave/socks/cart/action/FirstResultOrDefault.java: -------------------------------------------------------------------------------- 1 | package works.weave.socks.cart.action; 2 | 3 | import java.util.Collection; 4 | import java.util.function.Supplier; 5 | 6 | public class FirstResultOrDefault implements Supplier { 7 | private final Collection collection; 8 | private final Supplier nonePresent; 9 | 10 | public FirstResultOrDefault(final Collection collection, final Supplier nonePresent) { 11 | this.collection = collection; 12 | this.nonePresent = nonePresent; 13 | } 14 | 15 | @Override 16 | public T get() { 17 | return collection.stream().findFirst().orElseGet(nonePresent); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/works/weave/socks/cart/cart/CartContentsResource.java: -------------------------------------------------------------------------------- 1 | package works.weave.socks.cart.cart; 2 | 3 | import org.slf4j.Logger; 4 | import works.weave.socks.cart.entities.Cart; 5 | import works.weave.socks.cart.entities.Item; 6 | 7 | import java.util.List; 8 | import java.util.function.Supplier; 9 | 10 | import static org.slf4j.LoggerFactory.getLogger; 11 | 12 | public class CartContentsResource implements Contents { 13 | private final Logger LOG = getLogger(getClass()); 14 | 15 | private final CartDAO cartRepository; 16 | private final Supplier> parent; 17 | 18 | public CartContentsResource(CartDAO cartRepository, Supplier> parent) { 19 | this.cartRepository = cartRepository; 20 | this.parent = parent; 21 | } 22 | 23 | @Override 24 | public Supplier> contents() { 25 | return () -> parentCart().contents(); 26 | } 27 | 28 | @Override 29 | public Runnable add(Supplier item) { 30 | return () -> { 31 | LOG.debug("Adding for user: " + parent.get().value().get().toString() + ", " + item.get()); 32 | cartRepository.save(parentCart().add(item.get())); 33 | }; 34 | } 35 | 36 | @Override 37 | public Runnable delete(Supplier item) { 38 | return () -> { 39 | LOG.debug("Deleting for user: " + parent.get().value().get().toString() + ", " + item.get()); 40 | cartRepository.save(parentCart().remove(item.get())); 41 | }; 42 | } 43 | 44 | private Cart parentCart() { 45 | return parent.get().value().get(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/works/weave/socks/cart/cart/CartDAO.java: -------------------------------------------------------------------------------- 1 | package works.weave.socks.cart.cart; 2 | 3 | import works.weave.socks.cart.entities.Cart; 4 | 5 | import java.util.Collections; 6 | import java.util.HashMap; 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | public interface CartDAO { 11 | void delete(Cart cart); 12 | 13 | Cart save(Cart cart); 14 | 15 | List findByCustomerId(String customerId); 16 | 17 | class Fake implements CartDAO { 18 | private Map cartStore = new HashMap<>(); 19 | 20 | @Override 21 | public void delete(Cart cart) { 22 | cartStore.remove(cart.customerId); 23 | } 24 | 25 | @Override 26 | public Cart save(Cart cart) { 27 | return cartStore.put(cart.customerId, cart); 28 | } 29 | 30 | @Override 31 | public List findByCustomerId(String customerId) { 32 | Cart cart = cartStore.get(customerId); 33 | if (cart != null) { 34 | return Collections.singletonList(cart); 35 | } else { 36 | return Collections.emptyList(); 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/works/weave/socks/cart/cart/CartResource.java: -------------------------------------------------------------------------------- 1 | package works.weave.socks.cart.cart; 2 | 3 | import works.weave.socks.cart.action.FirstResultOrDefault; 4 | import works.weave.socks.cart.entities.Cart; 5 | 6 | import java.util.function.Supplier; 7 | 8 | public class CartResource implements Resource, HasContents { 9 | private final CartDAO cartRepository; 10 | private final String customerId; 11 | 12 | public CartResource(CartDAO cartRepository, String customerId) { 13 | this.cartRepository = cartRepository; 14 | this.customerId = customerId; 15 | } 16 | 17 | @Override 18 | public Runnable destroy() { 19 | return () -> cartRepository.delete(value().get()); 20 | } 21 | 22 | @Override 23 | public Supplier create() { 24 | return () -> cartRepository.save(new Cart(customerId)); 25 | } 26 | 27 | @Override 28 | public Supplier value() { 29 | return new FirstResultOrDefault<>( 30 | cartRepository.findByCustomerId(customerId), 31 | () -> { 32 | create().get(); 33 | return value().get(); 34 | }); 35 | } 36 | 37 | @Override 38 | public Runnable merge(Cart toMerge) { 39 | return () -> toMerge.contents().forEach(item -> contents().get().add(() -> item).run()); 40 | } 41 | 42 | @Override 43 | public Supplier contents() { 44 | return () -> new CartContentsResource(cartRepository, () -> this); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/works/weave/socks/cart/cart/Contents.java: -------------------------------------------------------------------------------- 1 | package works.weave.socks.cart.cart; 2 | 3 | import works.weave.socks.cart.entities.Item; 4 | 5 | import java.util.List; 6 | import java.util.function.Supplier; 7 | 8 | public interface Contents { 9 | Supplier> contents(); 10 | 11 | Runnable add(Supplier item); 12 | 13 | Runnable delete(Supplier item); 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/works/weave/socks/cart/cart/HasContents.java: -------------------------------------------------------------------------------- 1 | package works.weave.socks.cart.cart; 2 | 3 | import java.util.function.Supplier; 4 | 5 | public interface HasContents { 6 | Supplier contents(); 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/works/weave/socks/cart/cart/Resource.java: -------------------------------------------------------------------------------- 1 | package works.weave.socks.cart.cart; 2 | 3 | import works.weave.socks.cart.entities.Cart; 4 | 5 | import java.util.function.Supplier; 6 | 7 | public interface Resource { 8 | Runnable destroy(); 9 | 10 | Supplier create(); 11 | 12 | Supplier value(); 13 | 14 | Runnable merge(T toMerge); 15 | 16 | class CartFake implements Resource { 17 | private final String customerId; 18 | private Cart cart = null; 19 | 20 | public CartFake(String customerId) { 21 | this.customerId = customerId; 22 | } 23 | 24 | @Override 25 | public Runnable destroy() { 26 | return () -> cart = null; 27 | } 28 | 29 | @Override 30 | public Supplier create() { 31 | return () -> cart = new Cart(customerId); 32 | } 33 | 34 | @Override 35 | public Supplier value() { 36 | if (cart == null) { 37 | create().get(); 38 | } 39 | return () -> cart; 40 | } 41 | 42 | @Override 43 | public Runnable merge(Cart toMerge) { 44 | return null; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/works/weave/socks/cart/configuration/BeanConfiguration.java: -------------------------------------------------------------------------------- 1 | package works.weave.socks.cart.configuration; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import works.weave.socks.cart.cart.CartDAO; 7 | import works.weave.socks.cart.entities.Cart; 8 | import works.weave.socks.cart.entities.Item; 9 | import works.weave.socks.cart.item.ItemDAO; 10 | import works.weave.socks.cart.repositories.CartRepository; 11 | import works.weave.socks.cart.repositories.ItemRepository; 12 | 13 | import java.util.List; 14 | 15 | @Configuration 16 | public class BeanConfiguration { 17 | @Bean 18 | @Autowired 19 | public CartDAO getCartDao(CartRepository cartRepository) { 20 | return new CartDAO() { 21 | @Override 22 | public void delete(Cart cart) { 23 | cartRepository.delete(cart); 24 | } 25 | 26 | @Override 27 | public Cart save(Cart cart) { 28 | return cartRepository.save(cart); 29 | } 30 | 31 | @Override 32 | public List findByCustomerId(String customerId) { 33 | return cartRepository.findByCustomerId(customerId); 34 | } 35 | }; 36 | } 37 | 38 | @Bean 39 | @Autowired 40 | public ItemDAO getItemDao(ItemRepository itemRepository) { 41 | return new ItemDAO() { 42 | @Override 43 | public Item save(Item item) { 44 | return itemRepository.save(item); 45 | } 46 | 47 | @Override 48 | public void destroy(Item item) { 49 | itemRepository.delete(item); 50 | } 51 | 52 | @Override 53 | public Item findOne(String id) { 54 | return itemRepository.findById(id).orElse(null); 55 | } 56 | }; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/works/weave/socks/cart/configuration/MongoConfiguration.java: -------------------------------------------------------------------------------- 1 | package works.weave.socks.cart.configuration; 2 | 3 | import org.springframework.boot.autoconfigure.AutoConfigureBefore; 4 | import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | 8 | import com.mongodb.MongoClientOptions; 9 | 10 | @Configuration 11 | @AutoConfigureBefore(MongoAutoConfiguration.class) 12 | public class MongoConfiguration { 13 | 14 | @Bean 15 | public MongoClientOptions optionsProvider() { 16 | MongoClientOptions.Builder optionsBuilder = new MongoClientOptions.Builder(); 17 | optionsBuilder.serverSelectionTimeout(10000); 18 | MongoClientOptions options = optionsBuilder.build(); 19 | return options; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/works/weave/socks/cart/configuration/ValidationConfiguration.java: -------------------------------------------------------------------------------- 1 | package works.weave.socks.cart.configuration; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.data.mongodb.core.mapping.event.ValidatingMongoEventListener; 6 | import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; 7 | 8 | @Configuration 9 | public class ValidationConfiguration { 10 | @Bean 11 | public ValidatingMongoEventListener validatingMongoEventListener() { 12 | return new ValidatingMongoEventListener(validator()); 13 | } 14 | 15 | @Bean 16 | public LocalValidatorFactoryBean validator() { 17 | return new LocalValidatorFactoryBean(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/works/weave/socks/cart/configuration/WebMvcConfig.java: -------------------------------------------------------------------------------- 1 | package works.weave.socks.cart.configuration; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.web.servlet.handler.MappedInterceptor; 6 | import works.weave.socks.cart.middleware.HTTPMonitoringInterceptor; 7 | 8 | @Configuration 9 | public class WebMvcConfig { 10 | @Bean 11 | HTTPMonitoringInterceptor httpMonitoringInterceptor() { 12 | return new HTTPMonitoringInterceptor(); 13 | } 14 | 15 | @Bean 16 | public MappedInterceptor myMappedInterceptor(HTTPMonitoringInterceptor interceptor) { 17 | return new MappedInterceptor(new String[]{"/**"}, interceptor); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/works/weave/socks/cart/controllers/CartsController.java: -------------------------------------------------------------------------------- 1 | package works.weave.socks.cart.controllers; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.http.HttpStatus; 7 | import org.springframework.http.MediaType; 8 | import org.springframework.web.bind.annotation.*; 9 | import works.weave.socks.cart.cart.CartDAO; 10 | import works.weave.socks.cart.cart.CartResource; 11 | import works.weave.socks.cart.entities.Cart; 12 | 13 | 14 | @RestController 15 | @RequestMapping(path = "/carts") 16 | public class CartsController { 17 | private final Logger logger = LoggerFactory.getLogger(this.getClass()); 18 | 19 | @Autowired 20 | private CartDAO cartDAO; 21 | 22 | @ResponseStatus(HttpStatus.OK) 23 | @RequestMapping(value = "/{customerId}", produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.GET) 24 | public Cart get(@PathVariable String customerId) { 25 | return new CartResource(cartDAO, customerId).value().get(); 26 | } 27 | 28 | @ResponseStatus(HttpStatus.ACCEPTED) 29 | @RequestMapping(value = "/{customerId}", method = RequestMethod.DELETE) 30 | public void delete(@PathVariable String customerId) { 31 | new CartResource(cartDAO, customerId).destroy().run(); 32 | } 33 | 34 | @ResponseStatus(HttpStatus.ACCEPTED) 35 | @RequestMapping(value = "/{customerId}/merge", method = RequestMethod.GET) 36 | public void mergeCarts(@PathVariable String customerId, @RequestParam(value = "sessionId") String sessionId) { 37 | logger.debug("Merge carts request received for ids: " + customerId + " and " + sessionId); 38 | CartResource sessionCart = new CartResource(cartDAO, sessionId); 39 | CartResource customerCart = new CartResource(cartDAO, customerId); 40 | customerCart.merge(sessionCart.value().get()).run(); 41 | delete(sessionId); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/works/weave/socks/cart/controllers/HealthCheckController.java: -------------------------------------------------------------------------------- 1 | package works.weave.socks.cart.controllers; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.data.mongodb.core.MongoTemplate; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.web.bind.annotation.*; 7 | import works.weave.socks.cart.entities.HealthCheck; 8 | 9 | import java.util.ArrayList; 10 | import java.util.Calendar; 11 | import java.util.Date; 12 | import java.util.HashMap; 13 | import java.util.List; 14 | import java.util.Map; 15 | 16 | @RestController 17 | public class HealthCheckController { 18 | 19 | @Autowired 20 | private MongoTemplate mongoTemplate; 21 | 22 | @ResponseStatus(HttpStatus.OK) 23 | @RequestMapping(method = RequestMethod.GET, path = "/health") 24 | public 25 | @ResponseBody 26 | Map> getHealth() { 27 | Map> map = new HashMap>(); 28 | List healthChecks = new ArrayList(); 29 | Date dateNow = Calendar.getInstance().getTime(); 30 | 31 | HealthCheck app = new HealthCheck("carts", "OK", dateNow); 32 | HealthCheck database = new HealthCheck("carts-db", "OK", dateNow); 33 | 34 | try { 35 | mongoTemplate.executeCommand("{ buildInfo: 1 }"); 36 | } catch (Exception e) { 37 | database.setStatus("err"); 38 | } 39 | 40 | healthChecks.add(app); 41 | healthChecks.add(database); 42 | 43 | map.put("health", healthChecks); 44 | return map; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/works/weave/socks/cart/controllers/ItemsController.java: -------------------------------------------------------------------------------- 1 | package works.weave.socks.cart.controllers; 2 | 3 | import org.slf4j.Logger; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.http.MediaType; 7 | import org.springframework.web.bind.annotation.*; 8 | import works.weave.socks.cart.cart.CartDAO; 9 | import works.weave.socks.cart.cart.CartResource; 10 | import works.weave.socks.cart.entities.Item; 11 | import works.weave.socks.cart.item.FoundItem; 12 | import works.weave.socks.cart.item.ItemDAO; 13 | import works.weave.socks.cart.item.ItemResource; 14 | 15 | import java.util.List; 16 | import java.util.function.Supplier; 17 | 18 | import static org.slf4j.LoggerFactory.getLogger; 19 | 20 | @RestController 21 | @RequestMapping(value = "/carts/{customerId:.*}/items") 22 | public class ItemsController { 23 | private final Logger LOG = getLogger(getClass()); 24 | 25 | @Autowired 26 | private ItemDAO itemDAO; 27 | @Autowired 28 | private CartsController cartsController; 29 | @Autowired 30 | private CartDAO cartDAO; 31 | 32 | @ResponseStatus(HttpStatus.OK) 33 | @RequestMapping(value = "/{itemId:.*}", produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.GET) 34 | public Item get(@PathVariable String customerId, @PathVariable String itemId) { 35 | return new FoundItem(() -> getItems(customerId), () -> new Item(itemId)).get(); 36 | } 37 | 38 | @ResponseStatus(HttpStatus.OK) 39 | @RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.GET) 40 | public List getItems(@PathVariable String customerId) { 41 | return cartsController.get(customerId).contents(); 42 | } 43 | 44 | @ResponseStatus(HttpStatus.CREATED) 45 | @RequestMapping(consumes = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.POST) 46 | public Item addToCart(@PathVariable String customerId, @RequestBody Item item) { 47 | // If the item does not exist in the cart, create new one in the repository. 48 | FoundItem foundItem = new FoundItem(() -> cartsController.get(customerId).contents(), () -> item); 49 | if (!foundItem.hasItem()) { 50 | Supplier newItem = new ItemResource(itemDAO, () -> item).create(); 51 | LOG.debug("Did not find item. Creating item for user: " + customerId + ", " + newItem.get()); 52 | new CartResource(cartDAO, customerId).contents().get().add(newItem).run(); 53 | return item; 54 | } else { 55 | Item newItem = new Item(foundItem.get(), foundItem.get().quantity() + 1); 56 | LOG.debug("Found item in cart. Incrementing for user: " + customerId + ", " + newItem); 57 | updateItem(customerId, newItem); 58 | return newItem; 59 | } 60 | } 61 | 62 | @ResponseStatus(HttpStatus.ACCEPTED) 63 | @RequestMapping(value = "/{itemId:.*}", method = RequestMethod.DELETE) 64 | public void removeItem(@PathVariable String customerId, @PathVariable String itemId) { 65 | FoundItem foundItem = new FoundItem(() -> getItems(customerId), () -> new Item(itemId)); 66 | Item item = foundItem.get(); 67 | 68 | LOG.debug("Removing item from cart: " + item); 69 | new CartResource(cartDAO, customerId).contents().get().delete(() -> item).run(); 70 | 71 | LOG.debug("Removing item from repository: " + item); 72 | new ItemResource(itemDAO, () -> item).destroy().run(); 73 | } 74 | 75 | @ResponseStatus(HttpStatus.ACCEPTED) 76 | @RequestMapping(consumes = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.PATCH) 77 | public void updateItem(@PathVariable String customerId, @RequestBody Item item) { 78 | // Merge old and new items 79 | ItemResource itemResource = new ItemResource(itemDAO, () -> get(customerId, item.itemId())); 80 | LOG.debug("Merging item in cart for user: " + customerId + ", " + item); 81 | itemResource.merge(item).run(); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/works/weave/socks/cart/entities/Cart.java: -------------------------------------------------------------------------------- 1 | package works.weave.socks.cart.entities; 2 | 3 | import org.springframework.data.annotation.Id; 4 | import org.springframework.data.mongodb.core.mapping.DBRef; 5 | import org.springframework.data.mongodb.core.mapping.Document; 6 | 7 | import javax.validation.constraints.NotNull; 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | @Document 12 | public class Cart { 13 | @NotNull 14 | public String customerId; // Public instead of getters/setters. 15 | @Id 16 | private String id; 17 | @DBRef 18 | private List items = new ArrayList<>(); 19 | 20 | public Cart(String customerId) { 21 | this.customerId = customerId; 22 | } 23 | 24 | public Cart() { 25 | this(null); 26 | } 27 | 28 | public List contents() { 29 | return items; 30 | } 31 | 32 | public Cart add(Item item) { 33 | items.add(item); 34 | return this; 35 | } 36 | 37 | public Cart remove(Item item) { 38 | items.remove(item); 39 | return this; 40 | } 41 | 42 | @Override 43 | public String toString() { 44 | return "Cart{" + 45 | "id='" + id + '\'' + 46 | ", customerId='" + customerId + '\'' + 47 | ", items=" + items + 48 | '}'; 49 | } 50 | 51 | @Override 52 | public boolean equals(Object o) { 53 | if (this == o) return true; 54 | if (o == null || getClass() != o.getClass()) return false; 55 | 56 | Cart cart = (Cart) o; 57 | 58 | if (customerId != null ? !customerId.equals(cart.customerId) : cart.customerId != null) return false; 59 | if (id != null ? !id.equals(cart.id) : cart.id != null) return false; 60 | 61 | return true; 62 | } 63 | 64 | @Override 65 | public int hashCode() { 66 | int result = customerId != null ? customerId.hashCode() : 0; 67 | result = 31 * result + (id != null ? id.hashCode() : 0); 68 | return result; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/works/weave/socks/cart/entities/HealthCheck.java: -------------------------------------------------------------------------------- 1 | package works.weave.socks.cart.entities; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import com.fasterxml.jackson.annotation.JsonFormat; 5 | 6 | import java.util.Calendar; 7 | import java.util.Date; 8 | 9 | @JsonIgnoreProperties(ignoreUnknown = true) 10 | public class HealthCheck { 11 | private String service; 12 | private String status; 13 | 14 | @JsonFormat(pattern="yyyy-MM-dd'T'HH:mm:ss.SSSXXX") 15 | private Date date = Calendar.getInstance().getTime(); 16 | 17 | public HealthCheck() { 18 | 19 | } 20 | 21 | public HealthCheck(String service, String status, Date date) { 22 | this.service = service; 23 | this.status = status; 24 | this.date = date; 25 | } 26 | 27 | @Override 28 | public String toString() { 29 | return "HealthCheck{" + 30 | "service='" + service + '\'' + 31 | ", status='" + status + '\'' + 32 | ", date='" + date + 33 | '}'; 34 | } 35 | 36 | public String getService() { 37 | return service; 38 | } 39 | 40 | public void setService(String service) { 41 | this.service = service; 42 | } 43 | 44 | public String getStatus() { 45 | return status; 46 | } 47 | 48 | public void setStatus(String status) { 49 | this.status = status; 50 | } 51 | 52 | public Date getDate() { 53 | return date; 54 | } 55 | 56 | public void setDate(Date date) { 57 | this.date = date; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/works/weave/socks/cart/entities/Item.java: -------------------------------------------------------------------------------- 1 | package works.weave.socks.cart.entities; 2 | 3 | import org.springframework.data.annotation.Id; 4 | import org.springframework.data.mongodb.core.mapping.Document; 5 | 6 | import javax.validation.constraints.NotNull; 7 | 8 | @Document 9 | public class Item { 10 | @Id 11 | private String id; 12 | 13 | @NotNull(message = "Item Id must not be null") 14 | private String itemId; 15 | private int quantity; 16 | private float unitPrice; 17 | 18 | public Item(String id, String itemId, int quantity, float unitPrice) { 19 | this.id = id; 20 | this.itemId = itemId; 21 | this.quantity = quantity; 22 | this.unitPrice = unitPrice; 23 | } 24 | 25 | public Item() { 26 | this(null, "", 1, 0F); 27 | } 28 | 29 | public Item(String itemId) { 30 | this(null, itemId, 1, 0F); 31 | } 32 | 33 | public Item(Item item, String id) { 34 | this(id, item.itemId, item.quantity, item.unitPrice); 35 | } 36 | 37 | public Item(Item item, int quantity) { 38 | this(item.id(), item.itemId, quantity, item.unitPrice); 39 | } 40 | 41 | public String id() { 42 | return id; 43 | } 44 | 45 | public String itemId() { 46 | return itemId; 47 | } 48 | 49 | public int quantity() { 50 | return quantity; 51 | } 52 | 53 | @Override 54 | public String toString() { 55 | return "Item{" + 56 | "id='" + id + '\'' + 57 | ", itemId='" + itemId + '\'' + 58 | ", quantity=" + quantity + 59 | ", unitPrice=" + unitPrice + 60 | '}'; 61 | } 62 | 63 | @Override 64 | public boolean equals(Object o) { 65 | if (this == o) return true; 66 | if (o == null || getClass() != o.getClass()) return false; 67 | 68 | Item item = (Item) o; 69 | 70 | return itemId != null ? itemId.equals(item.itemId) : item.itemId == null; 71 | } 72 | 73 | // ****** Crappy getter/setters for Jackson JSON invoking ******** 74 | 75 | public String getId() { 76 | return id; 77 | } 78 | 79 | public void setId(String id) { 80 | this.id = id; 81 | } 82 | 83 | public String getItemId() { 84 | return itemId; 85 | } 86 | 87 | public void setItemId(String itemId) { 88 | this.itemId = itemId; 89 | } 90 | 91 | public int getQuantity() { 92 | return quantity; 93 | } 94 | 95 | public void setQuantity(int quantity) { 96 | this.quantity = quantity; 97 | } 98 | 99 | public float getUnitPrice() { 100 | return unitPrice; 101 | } 102 | 103 | public void setUnitPrice(float unitPrice) { 104 | this.unitPrice = unitPrice; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/works/weave/socks/cart/item/FoundItem.java: -------------------------------------------------------------------------------- 1 | package works.weave.socks.cart.item; 2 | 3 | import org.slf4j.Logger; 4 | import works.weave.socks.cart.entities.Item; 5 | 6 | import java.util.List; 7 | import java.util.function.Supplier; 8 | 9 | import static org.slf4j.LoggerFactory.getLogger; 10 | 11 | public class FoundItem implements Supplier { 12 | private final Logger LOG = getLogger(getClass()); 13 | private final Supplier> items; 14 | private final Supplier item; 15 | 16 | public FoundItem(Supplier> items, Supplier item) { 17 | this.items = items; 18 | this.item = item; 19 | } 20 | 21 | @Override 22 | public Item get() { 23 | return items.get().stream() 24 | .filter(item.get()::equals) 25 | .findFirst() 26 | .orElseThrow(() -> new IllegalArgumentException("Cannot find item in cart")); 27 | } 28 | 29 | public boolean hasItem() { 30 | boolean present = items.get().stream() 31 | .filter(item.get()::equals) 32 | .findFirst() 33 | .isPresent(); 34 | LOG.debug((present ? "Found" : "Didn't find") + " item: " + item.get() + ", in: " + items.get()); 35 | return present; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/works/weave/socks/cart/item/ItemDAO.java: -------------------------------------------------------------------------------- 1 | package works.weave.socks.cart.item; 2 | 3 | import works.weave.socks.cart.entities.Item; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | public interface ItemDAO { 9 | Item save(Item item); 10 | 11 | void destroy(Item item); 12 | 13 | Item findOne(String id); 14 | 15 | class Fake implements ItemDAO { 16 | private Map store = new HashMap<>(); 17 | 18 | @Override 19 | public Item save(Item item) { 20 | return store.put(item.itemId(), item); 21 | } 22 | 23 | @Override 24 | public void destroy(Item item) { 25 | store.remove(item.itemId()); 26 | 27 | } 28 | 29 | @Override 30 | public Item findOne(String id) { 31 | return store.entrySet().stream().filter(i -> i.getValue().id().equals(id)).map(Map.Entry::getValue) 32 | .findFirst().orElse(null); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/works/weave/socks/cart/item/ItemResource.java: -------------------------------------------------------------------------------- 1 | package works.weave.socks.cart.item; 2 | 3 | import works.weave.socks.cart.cart.Resource; 4 | import works.weave.socks.cart.entities.Item; 5 | 6 | import java.util.function.Supplier; 7 | 8 | public class ItemResource implements Resource { 9 | private final ItemDAO itemRepository; 10 | private final Supplier item; 11 | 12 | public ItemResource(ItemDAO itemRepository, Supplier item) { 13 | this.itemRepository = itemRepository; 14 | this.item = item; 15 | } 16 | 17 | @Override 18 | public Runnable destroy() { 19 | return () -> itemRepository.destroy(value().get()); 20 | } 21 | 22 | @Override 23 | public Supplier create() { 24 | return () -> itemRepository.save(item.get()); 25 | } 26 | 27 | @Override 28 | public Supplier value() { 29 | return item; // Basically a null method. Gets the item from the supplier. 30 | } 31 | 32 | @Override 33 | public Runnable merge(Item toMerge) { 34 | return () -> itemRepository.save(new Item(value().get(), toMerge.quantity())); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/works/weave/socks/cart/middleware/HTTPMonitoringInterceptor.java: -------------------------------------------------------------------------------- 1 | package works.weave.socks.cart.middleware; 2 | 3 | import io.prometheus.client.Histogram; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.beans.factory.annotation.Value; 6 | import org.springframework.context.ApplicationContext; 7 | import org.springframework.data.rest.core.config.RepositoryRestConfiguration; 8 | import org.springframework.data.rest.core.mapping.ResourceMappings; 9 | import org.springframework.data.rest.webmvc.RepositoryRestHandlerMapping; 10 | import org.springframework.data.rest.webmvc.support.JpaHelper; 11 | import org.springframework.web.servlet.HandlerInterceptor; 12 | import org.springframework.web.servlet.ModelAndView; 13 | import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition; 14 | import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; 15 | 16 | import javax.servlet.http.HttpServletRequest; 17 | import javax.servlet.http.HttpServletResponse; 18 | import java.util.HashSet; 19 | import java.util.Set; 20 | 21 | public class HTTPMonitoringInterceptor implements HandlerInterceptor { 22 | static final Histogram requestLatency = Histogram.build() 23 | .name("http_request_duration_seconds") 24 | .help("Request duration in seconds.") 25 | .labelNames("service", "method", "path", "status_code") 26 | .register(); 27 | 28 | private static final String startTimeKey = "startTime"; 29 | @Autowired 30 | ResourceMappings mappings; 31 | @Autowired 32 | JpaHelper jpaHelper; 33 | @Autowired 34 | RepositoryRestConfiguration repositoryConfiguration; 35 | @Autowired 36 | ApplicationContext applicationContext; 37 | @Autowired 38 | RequestMappingHandlerMapping requestMappingHandlerMapping; 39 | private Set urlPatterns; 40 | @Value("${spring.application.name:orders}") 41 | private String serviceName; 42 | 43 | @Override 44 | public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse 45 | httpServletResponse, Object o) throws Exception { 46 | httpServletRequest.setAttribute(startTimeKey, System.nanoTime()); 47 | return true; 48 | } 49 | 50 | @Override 51 | public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse 52 | httpServletResponse, Object o, ModelAndView modelAndView) throws Exception { 53 | long start = (long) httpServletRequest.getAttribute(startTimeKey); 54 | long elapsed = System.nanoTime() - start; 55 | double seconds = (double) elapsed / 1000000000.0; 56 | String matchedUrl = getMatchingURLPattern(httpServletRequest); 57 | if (!matchedUrl.equals("")) { 58 | requestLatency.labels( 59 | serviceName, 60 | httpServletRequest.getMethod(), 61 | matchedUrl, 62 | Integer.toString(httpServletResponse.getStatus()) 63 | ).observe(seconds); 64 | } 65 | } 66 | 67 | @Override 68 | public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse 69 | httpServletResponse, Object o, Exception e) throws Exception { 70 | } 71 | 72 | private String getMatchingURLPattern(HttpServletRequest httpServletRequest) { 73 | String res = ""; 74 | for (PatternsRequestCondition pattern : getUrlPatterns()) { 75 | if (pattern.getMatchingCondition(httpServletRequest) != null && 76 | !httpServletRequest.getServletPath().equals("/error")) { 77 | res = pattern.getMatchingCondition(httpServletRequest).getPatterns().iterator() 78 | .next(); 79 | break; 80 | } 81 | } 82 | return res; 83 | } 84 | 85 | private Set getUrlPatterns() { 86 | if (this.urlPatterns == null) { 87 | this.urlPatterns = new HashSet<>(); 88 | requestMappingHandlerMapping.getHandlerMethods().forEach((mapping, handlerMethod) -> 89 | urlPatterns.add(mapping.getPatternsCondition())); 90 | RepositoryRestHandlerMapping repositoryRestHandlerMapping = new 91 | RepositoryRestHandlerMapping(mappings, repositoryConfiguration); 92 | repositoryRestHandlerMapping.setJpaHelper(jpaHelper); 93 | repositoryRestHandlerMapping.setApplicationContext(applicationContext); 94 | repositoryRestHandlerMapping.afterPropertiesSet(); 95 | repositoryRestHandlerMapping.getHandlerMethods().forEach((mapping, handlerMethod) -> 96 | urlPatterns.add(mapping.getPatternsCondition())); 97 | } 98 | return this.urlPatterns; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/works/weave/socks/cart/repositories/CartRepository.java: -------------------------------------------------------------------------------- 1 | package works.weave.socks.cart.repositories; 2 | 3 | import org.springframework.data.mongodb.repository.MongoRepository; 4 | import org.springframework.data.repository.query.Param; 5 | import org.springframework.data.rest.core.annotation.RepositoryRestResource; 6 | import works.weave.socks.cart.entities.Cart; 7 | 8 | import java.util.List; 9 | 10 | @RepositoryRestResource(exported = false) 11 | public interface CartRepository extends MongoRepository { 12 | List findByCustomerId(@Param("custId") String id); 13 | } 14 | 15 | -------------------------------------------------------------------------------- /src/main/java/works/weave/socks/cart/repositories/ItemRepository.java: -------------------------------------------------------------------------------- 1 | package works.weave.socks.cart.repositories; 2 | 3 | import org.springframework.data.mongodb.repository.MongoRepository; 4 | import org.springframework.data.rest.core.annotation.RepositoryRestResource; 5 | import works.weave.socks.cart.entities.Item; 6 | 7 | @RepositoryRestResource 8 | public interface ItemRepository extends MongoRepository { 9 | } 10 | 11 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port=${port:8081} 2 | spring.data.mongodb.uri=mongodb://${db:carts-db}:27017/data 3 | endpoints.health.enabled=false 4 | spring.zipkin.baseUrl=http://${zipkin_host:zipkin}:9411/ 5 | spring.zipkin.enabled=${zipkin_enabled:false} 6 | spring.sleuth.sampler.percentage=1.0 7 | spring.application.name=carts 8 | # Disable actuator metrics endpoints 9 | endpoints.metrics.enabled=false 10 | endpoints.prometheus.id=metrics 11 | -------------------------------------------------------------------------------- /src/test/java/works/weave/socks/cart/action/UnitFirstResultOrDefault.java: -------------------------------------------------------------------------------- 1 | package works.weave.socks.cart.action; 2 | 3 | import org.junit.Test; 4 | 5 | import java.util.Arrays; 6 | import java.util.Collections; 7 | 8 | import static org.hamcrest.core.IsEqual.equalTo; 9 | import static org.junit.Assert.assertThat; 10 | 11 | public class UnitFirstResultOrDefault { 12 | @Test 13 | public void whenEmptyUsesDefault() { 14 | String defaultValue = "test"; 15 | FirstResultOrDefault CUT = new FirstResultOrDefault<>(Collections.emptyList(), () -> defaultValue); 16 | assertThat(CUT.get(), equalTo(defaultValue)); 17 | } 18 | 19 | @Test 20 | public void whenNotEmptyUseFirst() { 21 | String testValue = "test"; 22 | FirstResultOrDefault CUT = new FirstResultOrDefault<>(Arrays.asList(testValue), () -> "nonDefault"); 23 | assertThat(CUT.get(), equalTo(testValue)); 24 | } 25 | 26 | @Test 27 | public void whenMultipleNotEmptyUseFirst() { 28 | String testValue = "test"; 29 | FirstResultOrDefault CUT = new FirstResultOrDefault<>(Arrays.asList(testValue, "test2"), () -> 30 | "nonDefault"); 31 | assertThat(CUT.get(), equalTo(testValue)); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/test/java/works/weave/socks/cart/cart/UnitCartContentsResource.java: -------------------------------------------------------------------------------- 1 | package works.weave.socks.cart.cart; 2 | 3 | import org.hamcrest.collection.IsCollectionWithSize; 4 | import org.junit.Test; 5 | import works.weave.socks.cart.entities.Cart; 6 | import works.weave.socks.cart.entities.Item; 7 | 8 | import static org.hamcrest.MatcherAssert.assertThat; 9 | import static org.hamcrest.Matchers.containsInAnyOrder; 10 | 11 | public class UnitCartContentsResource { 12 | private final String customerId = "testId"; 13 | private final CartDAO.Fake fakeDAO = new CartDAO.Fake(); 14 | private final Resource fakeCartResource = new Resource.CartFake(customerId); 15 | 16 | @Test 17 | public void shouldAddAndReturnContents() { 18 | CartContentsResource contentsResource = new CartContentsResource(fakeDAO, () -> fakeCartResource); 19 | Item item = new Item("testId"); 20 | contentsResource.add(() -> item).run(); 21 | assertThat(contentsResource.contents().get(), IsCollectionWithSize.hasSize(1)); 22 | assertThat(contentsResource.contents().get(), containsInAnyOrder(item)); 23 | } 24 | 25 | @Test 26 | public void shouldStartEmpty() { 27 | CartContentsResource contentsResource = new CartContentsResource(fakeDAO, () -> fakeCartResource); 28 | assertThat(contentsResource.contents().get(), IsCollectionWithSize.hasSize(0)); 29 | } 30 | 31 | @Test 32 | public void shouldDeleteItemFromCart() { 33 | CartContentsResource contentsResource = new CartContentsResource(fakeDAO, () -> fakeCartResource); 34 | Item item = new Item("testId"); 35 | contentsResource.add(() -> item).run(); 36 | assertThat(contentsResource.contents().get(), IsCollectionWithSize.hasSize(1)); 37 | assertThat(contentsResource.contents().get(), containsInAnyOrder(item)); 38 | Item item2 = new Item(item.itemId()); 39 | contentsResource.delete(() -> item2).run(); 40 | assertThat(contentsResource.contents().get(), IsCollectionWithSize.hasSize(0)); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/test/java/works/weave/socks/cart/cart/UnitCartResource.java: -------------------------------------------------------------------------------- 1 | package works.weave.socks.cart.cart; 2 | 3 | import org.junit.Test; 4 | import works.weave.socks.cart.entities.Cart; 5 | import works.weave.socks.cart.entities.Item; 6 | 7 | import static org.hamcrest.CoreMatchers.is; 8 | import static org.hamcrest.CoreMatchers.not; 9 | import static org.hamcrest.CoreMatchers.notNullValue; 10 | import static org.hamcrest.Matchers.anyOf; 11 | import static org.hamcrest.Matchers.*; 12 | import static org.hamcrest.core.IsEqual.equalTo; 13 | import static org.junit.Assert.assertThat; 14 | 15 | public class UnitCartResource { 16 | 17 | private final String customerId = "testId"; 18 | private final CartDAO.Fake fake = new CartDAO.Fake(); 19 | 20 | @Test 21 | public void whenCartExistsUseThat() { 22 | Cart cart = new Cart(customerId); 23 | fake.save(cart); 24 | CartResource cartResource = new CartResource(fake, customerId); 25 | assertThat(cartResource.value().get(), equalTo(cart)); 26 | } 27 | 28 | @Test 29 | public void whenCartDoesntExistCreateNew() { 30 | CartResource cartResource = new CartResource(fake, customerId); 31 | assertThat(cartResource.value().get(), is(notNullValue())); 32 | assertThat(cartResource.value().get().customerId, is(equalTo(customerId))); 33 | } 34 | 35 | @Test 36 | public void whenDestroyRemoveItem() { 37 | Cart cart = new Cart(customerId); 38 | fake.save(cart); 39 | CartResource cartResource = new CartResource(fake, customerId); 40 | cartResource.destroy().run(); 41 | assertThat(fake.findByCustomerId(customerId), is(empty())); 42 | } 43 | 44 | @Test 45 | public void whenDestroyOnEmptyStillEmpty() { 46 | CartResource cartResource = new CartResource(fake, customerId); 47 | cartResource.destroy().run(); 48 | assertThat(fake.findByCustomerId(customerId), is(empty())); 49 | } 50 | 51 | @Test 52 | public void whenCreateDoCreate() { 53 | CartResource cartResource = new CartResource(fake, customerId); 54 | cartResource.create().get(); 55 | assertThat(fake.findByCustomerId(customerId), is(not(empty()))); 56 | } 57 | 58 | @Test 59 | public void contentsShouldBeEmptyWhenNew() { 60 | CartResource cartResource = new CartResource(fake, customerId); 61 | cartResource.create().get(); 62 | assertThat(cartResource.contents().get().contents().get(), is(empty())); 63 | } 64 | 65 | @Test 66 | public void mergedItemsShouldBeInCart() { 67 | String person1 = "person1"; 68 | String person2 = "person2"; 69 | Item person1Item = new Item("item1"); 70 | Item person2Item = new Item("item2"); 71 | CartResource cartResource = new CartResource(fake, person1); 72 | cartResource.contents().get().add(() -> person1Item).run(); 73 | CartResource cartResourceToMerge = new CartResource(fake, person2); 74 | cartResourceToMerge.contents().get().add(() -> person2Item).run(); 75 | cartResource.merge(cartResourceToMerge.value().get()).run(); 76 | assertThat(cartResource.contents().get().contents().get(), hasSize(2)); 77 | assertThat(cartResource.contents().get().contents().get().get(0), anyOf(equalTo(person1Item), equalTo 78 | (person2Item))); 79 | assertThat(cartResource.contents().get().contents().get().get(1), anyOf(equalTo(person1Item), equalTo 80 | (person2Item))); 81 | assertThat(cartResourceToMerge.contents().get().contents().get(), hasSize(1)); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/test/java/works/weave/socks/cart/controllers/UnitCartsController.java: -------------------------------------------------------------------------------- 1 | package works.weave.socks.cart.controllers; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.test.context.ContextConfiguration; 9 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 10 | import works.weave.socks.cart.cart.CartDAO; 11 | import works.weave.socks.cart.entities.Cart; 12 | import works.weave.socks.cart.entities.Item; 13 | import works.weave.socks.cart.item.ItemDAO; 14 | 15 | import static org.hamcrest.CoreMatchers.equalTo; 16 | import static org.hamcrest.CoreMatchers.is; 17 | import static org.hamcrest.collection.IsCollectionWithSize.hasSize; 18 | import static org.hamcrest.collection.IsEmptyCollection.empty; 19 | import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder; 20 | import static org.junit.Assert.assertThat; 21 | 22 | @RunWith(SpringJUnit4ClassRunner.class) 23 | @ContextConfiguration 24 | public class UnitCartsController { 25 | 26 | @Autowired 27 | private ItemsController itemsController; 28 | 29 | @Autowired 30 | private CartDAO cartDAO; 31 | 32 | @Autowired 33 | private CartsController cartsController; 34 | 35 | 36 | @Test 37 | public void shouldGetCart() { 38 | String customerId = "customerIdGet"; 39 | Cart cart = new Cart(customerId); 40 | cartDAO.save(cart); 41 | Cart gotCart = cartsController.get(customerId); 42 | assertThat(gotCart, is(equalTo(cart))); 43 | assertThat(cartDAO.findByCustomerId(customerId).get(0), is(equalTo(cart))); 44 | } 45 | 46 | @Test 47 | public void shouldDeleteCart() { 48 | String customerId = "customerIdGet"; 49 | Cart cart = new Cart(customerId); 50 | cartDAO.save(cart); 51 | cartsController.delete(customerId); 52 | assertThat(cartDAO.findByCustomerId(customerId), is(empty())); 53 | } 54 | 55 | @Test 56 | public void shouldMergeItemsInCartsTogether() { 57 | String customerId1 = "customerId1"; 58 | Cart cart1 = new Cart(customerId1); 59 | Item itemId1 = new Item("itemId1"); 60 | cart1.add(itemId1); 61 | cartDAO.save(cart1); 62 | String customerId2 = "customerId2"; 63 | Cart cart2 = new Cart(customerId2); 64 | Item itemId2 = new Item("itemId2"); 65 | cart2.add(itemId2); 66 | cartDAO.save(cart2); 67 | 68 | cartsController.mergeCarts(customerId1, customerId2); 69 | assertThat(cartDAO.findByCustomerId(customerId1).get(0).contents(), is(hasSize(2))); 70 | assertThat(cartDAO.findByCustomerId(customerId1).get(0).contents(), is(containsInAnyOrder(itemId1, itemId2))); 71 | assertThat(cartDAO.findByCustomerId(customerId2), is(empty())); 72 | } 73 | 74 | @Configuration 75 | static class ItemsControllerTestConfiguration { 76 | @Bean 77 | public ItemsController itemsController() { 78 | return new ItemsController(); 79 | } 80 | 81 | @Bean 82 | public CartsController cartsController() { 83 | return new CartsController(); 84 | } 85 | 86 | @Bean 87 | public ItemDAO itemDAO() { 88 | return new ItemDAO.Fake(); 89 | } 90 | 91 | @Bean 92 | public CartDAO cartDAO() { 93 | return new CartDAO.Fake(); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/test/java/works/weave/socks/cart/controllers/UnitHealthCheckController.java: -------------------------------------------------------------------------------- 1 | package works.weave.socks.cart.controllers; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.data.mongodb.core.MongoTemplate; 9 | import org.springframework.test.context.ContextConfiguration; 10 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 11 | import static org.hamcrest.CoreMatchers.equalTo; 12 | import static org.hamcrest.CoreMatchers.is; 13 | import static org.junit.Assert.assertThat; 14 | import static org.mockito.Mockito.*; 15 | import works.weave.socks.cart.entities.HealthCheck; 16 | 17 | 18 | import java.util.ArrayList; 19 | import java.util.Calendar; 20 | import java.util.Date; 21 | import java.util.HashMap; 22 | import java.util.List; 23 | import java.util.Map; 24 | 25 | @RunWith(SpringJUnit4ClassRunner.class) 26 | @ContextConfiguration 27 | public class UnitHealthCheckController { 28 | 29 | @Autowired 30 | private HealthCheckController healthCheckController; 31 | 32 | @Test 33 | public void shouldGetHealth() { 34 | Map> results = this.healthCheckController.getHealth(); 35 | assertThat(results.get("health").size(), is(equalTo(2))); 36 | } 37 | 38 | @Configuration 39 | static class HealthCheckControllerTestConfiguration { 40 | @Bean 41 | public HealthCheckController healthCheckController() { 42 | return new HealthCheckController(); 43 | } 44 | 45 | @Bean 46 | public MongoTemplate mongoTemplate() { 47 | MongoTemplate mongoTemplate = mock(MongoTemplate.class); 48 | return mongoTemplate; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/test/java/works/weave/socks/cart/controllers/UnitItemsController.java: -------------------------------------------------------------------------------- 1 | package works.weave.socks.cart.controllers; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.test.context.ContextConfiguration; 9 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 10 | import works.weave.socks.cart.cart.CartDAO; 11 | import works.weave.socks.cart.entities.Item; 12 | import works.weave.socks.cart.item.ItemDAO; 13 | 14 | import static org.hamcrest.CoreMatchers.equalTo; 15 | import static org.hamcrest.CoreMatchers.is; 16 | import static org.hamcrest.collection.IsCollectionWithSize.hasSize; 17 | import static org.junit.Assert.assertThat; 18 | 19 | @RunWith(SpringJUnit4ClassRunner.class) 20 | @ContextConfiguration 21 | public class UnitItemsController { 22 | 23 | @Autowired 24 | private ItemsController itemsController; 25 | 26 | @Autowired 27 | private ItemDAO itemDAO; 28 | 29 | @Autowired 30 | private CartsController cartsController; 31 | 32 | @Test 33 | public void whenNewItemAdd() { 34 | Item item = new Item("id", "itemId", 1, 0F); 35 | String customerId = "customerIdAdd"; 36 | itemsController.addToCart(customerId, item); 37 | assertThat(itemsController.getItems(customerId), is(hasSize(1))); 38 | assertThat(itemsController.getItems(customerId), is(org.hamcrest.CoreMatchers.hasItem(item))); 39 | } 40 | 41 | @Test 42 | public void whenExistIncrementQuantity() { 43 | Item item = new Item("id", "itemId", 1, 0F); 44 | String customerId = "customerIdIncrement"; 45 | itemsController.addToCart(customerId, item); 46 | itemsController.addToCart(customerId, item); 47 | assertThat(itemsController.getItems(customerId), is(hasSize(1))); 48 | assertThat(itemsController.getItems(customerId), is(org.hamcrest.CoreMatchers.hasItem(item))); 49 | assertThat(itemDAO.findOne(item.id()).quantity(), is(equalTo(2))); 50 | } 51 | 52 | @Test 53 | public void shouldRemoveItemFromCart() { 54 | Item item = new Item("id", "itemId", 1, 0F); 55 | String customerId = "customerIdRemove"; 56 | itemsController.addToCart(customerId, item); 57 | assertThat(itemsController.getItems(customerId), is(hasSize(1))); 58 | itemsController.removeItem(customerId, item.itemId()); 59 | assertThat(itemsController.getItems(customerId), is(hasSize(0))); 60 | } 61 | 62 | @Test 63 | public void shouldSetQuantity() { 64 | Item item = new Item("id", "itemId", 1, 0F); 65 | String customerId = "customerIdQuantity"; 66 | itemsController.addToCart(customerId, item); 67 | assertThat(itemsController.getItems(customerId).get(0).quantity(), is(equalTo(item.quantity()))); 68 | Item anotherItem = new Item(item, 15); 69 | itemsController.updateItem(customerId, anotherItem); 70 | assertThat(itemDAO.findOne(item.id()).quantity(), is(equalTo(anotherItem.quantity()))); 71 | } 72 | 73 | @Configuration 74 | static class ItemsControllerTestConfiguration { 75 | @Bean 76 | public ItemsController itemsController() { 77 | return new ItemsController(); 78 | } 79 | 80 | @Bean 81 | public CartsController cartsController() { 82 | return new CartsController(); 83 | } 84 | 85 | @Bean 86 | public ItemDAO itemDAO() { 87 | return new ItemDAO.Fake(); 88 | } 89 | 90 | @Bean 91 | public CartDAO cartDAO() { 92 | return new CartDAO.Fake(); 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/test/java/works/weave/socks/cart/item/UnitFoundItem.java: -------------------------------------------------------------------------------- 1 | package works.weave.socks.cart.item; 2 | 3 | import org.junit.Test; 4 | import works.weave.socks.cart.entities.Item; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | import static org.hamcrest.CoreMatchers.is; 10 | import static org.hamcrest.core.IsEqual.equalTo; 11 | import static org.junit.Assert.assertThat; 12 | 13 | public class UnitFoundItem { 14 | @Test 15 | public void findOneItem() { 16 | List list = new ArrayList<>(); 17 | String testId = "testId"; 18 | Item testAnswer = new Item(testId); 19 | list.add(testAnswer); 20 | FoundItem foundItem = new FoundItem(() -> list, () -> testAnswer); 21 | assertThat(foundItem.get(), is(equalTo(testAnswer))); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/test/java/works/weave/socks/cart/item/UnitItemResource.java: -------------------------------------------------------------------------------- 1 | package works.weave.socks.cart.item; 2 | 3 | import org.junit.Test; 4 | import works.weave.socks.cart.entities.Item; 5 | 6 | import static org.hamcrest.CoreMatchers.is; 7 | import static org.hamcrest.Matchers.nullValue; 8 | import static org.hamcrest.core.IsEqual.equalTo; 9 | import static org.junit.Assert.assertThat; 10 | 11 | 12 | public class UnitItemResource { 13 | private ItemDAO itemDAO = new ItemDAO.Fake(); 14 | 15 | @Test 16 | public void testCreateAndDestroy() { 17 | Item item = new Item("itemId", "testId", 1, 0F); 18 | ItemResource itemResource = new ItemResource(itemDAO, () -> item); 19 | itemResource.create().get(); 20 | assertThat(itemDAO.findOne(item.id()), is(equalTo(item))); 21 | itemResource.destroy().run(); 22 | assertThat(itemDAO.findOne(item.id()), is(nullValue())); 23 | } 24 | 25 | @Test 26 | public void mergedItemShouldHaveNewQuantity() { 27 | Item item = new Item("itemId", "testId", 1, 0F); 28 | ItemResource itemResource = new ItemResource(itemDAO, () -> item); 29 | assertThat(itemResource.value().get(), is(equalTo(item))); 30 | Item newItem = new Item(item, 10); 31 | itemResource.merge(newItem).run(); 32 | assertThat(itemDAO.findOne(item.id()).quantity(), is(equalTo(newItem.quantity()))); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/test/java/works/weave/socks/cart/repositories/ITCartRepository.java: -------------------------------------------------------------------------------- 1 | package works.weave.socks.cart.repositories; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 8 | import org.springframework.test.context.junit4.SpringRunner; 9 | import works.weave.socks.cart.entities.Cart; 10 | 11 | import java.util.List; 12 | 13 | import static org.junit.Assert.assertEquals; 14 | 15 | @RunWith(SpringRunner.class) 16 | @EnableAutoConfiguration 17 | public class ITCartRepository { 18 | @Autowired 19 | private CartRepository cartRepository; 20 | 21 | @Before 22 | public void removeAllData() { 23 | cartRepository.deleteAll(); 24 | } 25 | 26 | @Test 27 | public void testCartSave() { 28 | Cart original = new Cart("customerId"); 29 | Cart saved = cartRepository.save(original); 30 | 31 | assertEquals(1, cartRepository.count()); 32 | assertEquals(original, saved); 33 | } 34 | 35 | @Test 36 | public void testCartGetDefault() { 37 | Cart original = new Cart("customerId"); 38 | Cart saved = cartRepository.save(original); 39 | 40 | assertEquals(1, cartRepository.count()); 41 | assertEquals(original, saved); 42 | } 43 | 44 | @Test 45 | public void testSearchCustomerById() { 46 | Cart original = new Cart("customerId"); 47 | cartRepository.save(original); 48 | 49 | List found = cartRepository.findByCustomerId(original.customerId); 50 | assertEquals(1, found.size()); 51 | assertEquals(original, found.get(0)); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/test/java/works/weave/socks/cart/repositories/ITItemRepository.java: -------------------------------------------------------------------------------- 1 | package works.weave.socks.cart.repositories; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 8 | import org.springframework.test.context.junit4.SpringRunner; 9 | import works.weave.socks.cart.entities.Item; 10 | 11 | import static org.junit.Assert.assertEquals; 12 | 13 | @RunWith(SpringRunner.class) 14 | @EnableAutoConfiguration 15 | public class ITItemRepository { 16 | @Autowired 17 | private ItemRepository itemRepository; 18 | 19 | @Before 20 | public void removeAllData() { 21 | itemRepository.deleteAll(); 22 | } 23 | 24 | @Test 25 | public void testCustomerSave() { 26 | Item original = new Item("id", "itemId", 1, 0.99F); 27 | Item saved = itemRepository.save(original); 28 | 29 | assertEquals(1, itemRepository.count()); 30 | assertEquals(original, saved); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.6-alpine 2 | 3 | RUN apk add --no-cache \ 4 | ca-certificates \ 5 | curl \ 6 | openssl 7 | 8 | ENV DOCKER_BUCKET get.docker.com 9 | ENV DOCKER_VERSION 1.8.3 10 | 11 | RUN set -x \ 12 | && curl -fSL "https://${DOCKER_BUCKET}/builds/Linux/x86_64/docker-${DOCKER_VERSION}.tgz" -o docker.tgz \ 13 | && tar -xzvf docker.tgz \ 14 | && docker -v 15 | 16 | RUN pip install requests 17 | -------------------------------------------------------------------------------- /test/component.py: -------------------------------------------------------------------------------- 1 | import os 2 | import unittest 3 | from os.path import expanduser 4 | 5 | from util.Docker import Docker 6 | 7 | 8 | class JavaServices(unittest.TestCase): 9 | def test_maven(self): 10 | script_dir = os.path.dirname(os.path.realpath(__file__)) 11 | code_dir = script_dir + "/.." 12 | home = expanduser("~") 13 | command = ['docker', 'run', '--rm', '-v', home + '/.m2:/root/.m2', '-v', code_dir + ':/usr/src/mymaven', '-w', 14 | '/usr/src/mymaven', 'maven:3.6-jdk-11', 'mvn', 'integration-test'] 15 | print(Docker().execute(command)) 16 | 17 | 18 | if __name__ == '__main__': 19 | unittest.main() 20 | -------------------------------------------------------------------------------- /test/container.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import sys 3 | import unittest 4 | import os 5 | from util.Api import Api 6 | from time import sleep 7 | 8 | from util.Docker import Docker 9 | from util.Dredd import Dredd 10 | 11 | class CartContainerTest(unittest.TestCase): 12 | TAG = "latest" 13 | COMMIT = "" 14 | container_name = Docker().random_container_name('carts') 15 | mongo_container_name = Docker().random_container_name('carts-db') 16 | def __init__(self, methodName='runTest'): 17 | super(CartContainerTest, self).__init__(methodName) 18 | self.ip = "" 19 | 20 | def setUp(self): 21 | Docker().start_container(container_name=self.mongo_container_name, image="mongo", host="carts-db") 22 | command = ['docker', 'run', 23 | '-d', 24 | '--name', CartContainerTest.container_name, 25 | '-h', 'carts', 26 | '--link', 27 | CartContainerTest.mongo_container_name, 28 | 'weaveworksdemos/carts:' + self.COMMIT] 29 | Docker().execute(command) 30 | self.ip = Docker().get_container_ip(CartContainerTest.container_name) 31 | 32 | def tearDown(self): 33 | Docker().kill_and_remove(CartContainerTest.container_name) 34 | Docker().kill_and_remove(CartContainerTest.mongo_container_name) 35 | 36 | def test_api_validated(self): 37 | limit = 30 38 | while Api().noResponse('http://' + self.ip + ':80/carts/'): 39 | if limit == 0: 40 | self.fail("Couldn't get the API running") 41 | limit = limit - 1 42 | sleep(1) 43 | 44 | out = Dredd().test_against_endpoint( 45 | "carts", "http://carts/", 46 | links=[self.mongo_container_name, self.container_name], 47 | env=[("MONGO_ENDPOINT", "mongodb://carts-db:27017/data")], 48 | dump_streams=True) 49 | self.assertGreater(out.find("0 failing"), -1) 50 | self.assertGreater(out.find("0 errors"), -1) 51 | print(out) 52 | 53 | if __name__ == '__main__': 54 | parser = argparse.ArgumentParser() 55 | default_tag = "latest" 56 | parser.add_argument('--tag', default=default_tag, help='The tag of the image to use. (default: latest)') 57 | parser.add_argument('unittest_args', nargs='*') 58 | args = parser.parse_args() 59 | CartContainerTest.TAG = args.tag 60 | 61 | if CartContainerTest.TAG == "": 62 | CartContainerTest.TAG = default_tag 63 | 64 | CartContainerTest.COMMIT = os.environ["COMMIT"] 65 | # Now set the sys.argv to the unittest_args (leaving sys.argv[0] alone) 66 | sys.argv[1:] = args.unittest_args 67 | unittest.main() 68 | -------------------------------------------------------------------------------- /test/coveralls.py: -------------------------------------------------------------------------------- 1 | import os 2 | import unittest 3 | from os.path import expanduser 4 | 5 | from util.Docker import Docker 6 | 7 | 8 | class JavaServices(unittest.TestCase): 9 | def test_maven(self): 10 | script_dir = os.path.dirname(os.path.realpath(__file__)) 11 | code_dir = script_dir + "/.." 12 | home = expanduser("~") 13 | command = ['docker', 'run', '--rm', 14 | '-v', home + '/.m2:/root/.m2', 15 | '-v', code_dir + ':/usr/src/mymaven', 16 | '-w', '/usr/src/mymaven', 17 | 'maven:3.6-jdk-11', 18 | 'mvn', 19 | '-DrepoToken=' + os.getenv('COVERALLS_TOKEN'), 20 | '-DserviceJobId=' + os.getenv('TRAVIS_JOB_ID'), 21 | '-Dbranch=' + os.getenv('TRAVIS_BRANCH'), 22 | '-DpullRequest=' + os.getenv('TRAVIS_PULL_REQUEST'), 23 | '-DserviceName=' + os.getenv('TRAVIS'), 24 | 'verify', 25 | 'jacoco:report', 26 | 'coveralls:report'] 27 | print("Coveralls command: ", 28 | '-DserviceJobId=' + os.getenv('TRAVIS_JOB_ID'), 29 | '-Dbranch=' + os.getenv('TRAVIS_BRANCH'), 30 | '-DpullRequest=' + os.getenv('TRAVIS_PULL_REQUEST'), 31 | '-DserviceName=' + 'TRAVIS') 32 | print(Docker().execute(command)) 33 | 34 | 35 | if __name__ == '__main__': 36 | unittest.main() 37 | -------------------------------------------------------------------------------- /test/test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -ev 4 | 5 | SCRIPT_DIR=`dirname "$0"` 6 | SCRIPT_NAME=`basename "$0"` 7 | SSH_OPTS=-oStrictHostKeyChecking=no 8 | 9 | if [[ "$(uname)" == "Darwin" ]]; then 10 | DOCKER_CMD=docker 11 | else 12 | DOCKER_CMD="sudo docker" 13 | fi 14 | 15 | if [[ -z $($DOCKER_CMD images | grep test-container) ]] ; then 16 | echo "Building test container" 17 | docker build -t test-container $SCRIPT_DIR > /dev/null 18 | fi 19 | 20 | echo "Testing $1" 21 | CODE_DIR=$(cd $SCRIPT_DIR/..; pwd) 22 | echo "$@" 23 | $DOCKER_CMD run \ 24 | --rm \ 25 | --name test \ 26 | -v /var/run/docker.sock:/var/run/docker.sock \ 27 | -v $CODE_DIR:$CODE_DIR -w $CODE_DIR \ 28 | -e COVERALLS_TOKEN=$COVERALLS_TOKEN \ 29 | -e TRAVIS_JOB_ID=$TRAVIS_JOB_ID \ 30 | -e TRAVIS_BRANCH=$TRAVIS_BRANCH \ 31 | -e TRAVIS_PULL_REQUEST=$TRAVIS_PULL_REQUEST \ 32 | -e TRAVIS=$TRAVIS \ 33 | -e TAG=$TAG \ 34 | -e COMMIT=$COMMIT \ 35 | test-container \ 36 | sh -c "export PYTHONPATH=\$PYTHONPATH:\$PWD/test ; python test/$@" 37 | -------------------------------------------------------------------------------- /test/unit.py: -------------------------------------------------------------------------------- 1 | import os 2 | import unittest 3 | from os.path import expanduser 4 | 5 | from util.Docker import Docker 6 | 7 | 8 | class JavaServices(unittest.TestCase): 9 | def test_maven(self): 10 | script_dir = os.path.dirname(os.path.realpath(__file__)) 11 | code_dir = script_dir + "/.." 12 | home = expanduser("~") 13 | command = ['docker', 'run', '--rm', '-v', home + '/.m2:/root/.m2', '-v', code_dir + ':/usr/src/mymaven', '-w', 14 | '/usr/src/mymaven', 'maven:3.6-jdk-11', 'mvn', '-q', 'test'] 15 | print(Docker().execute(command)) 16 | 17 | 18 | if __name__ == '__main__': 19 | unittest.main() 20 | -------------------------------------------------------------------------------- /test/util/Api.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | class Api: 4 | def noResponse(self, url): 5 | try: 6 | r = requests.get(url, timeout=5) 7 | except requests.exceptions.ConnectionError: 8 | return True 9 | return False 10 | -------------------------------------------------------------------------------- /test/util/Docker.py: -------------------------------------------------------------------------------- 1 | import re 2 | from subprocess import Popen, PIPE 3 | from random import random 4 | 5 | # From http://blog.bordage.pro/avoid-docker-py/ 6 | class Docker: 7 | def kill_and_remove(self, ctr_name): 8 | command = ['docker', 'rm', '-f', ctr_name] 9 | try: 10 | self.execute(command) 11 | return True 12 | except RuntimeError as e: 13 | print(e) 14 | return False 15 | 16 | def random_container_name(self, prefix): 17 | retstr = prefix + '-' 18 | for i in range(5): 19 | retstr += chr(int(round(random() * (122-97) + 97))) 20 | return retstr 21 | 22 | def get_container_ip(self, ctr_name): 23 | command = ['docker', 'inspect', 24 | '--format', '\'{{.NetworkSettings.IPAddress}}\'', 25 | ctr_name] 26 | return re.sub(r'[^0-9.]*', '', self.execute(command)) 27 | 28 | def execute(self, command, dump_streams=False): 29 | print("Running: " + ' '.join(command)) 30 | p = Popen(command, stdout=PIPE, stderr=PIPE) 31 | out, err = p.communicate() 32 | if dump_streams == True: 33 | print(out.decode('utf-8')) 34 | print(err.decode('utf-8')) 35 | return str(out.decode('utf-8')) 36 | 37 | def start_container(self, container_name="", image="", cmd="", host=""): 38 | command = ['docker', 'run', '-d', '-h', host, '--name', container_name, image] 39 | self.execute(command) 40 | -------------------------------------------------------------------------------- /test/util/Dredd.py: -------------------------------------------------------------------------------- 1 | from util.Docker import Docker 2 | from util.Api import Api 3 | import os 4 | import unittest 5 | 6 | class Dredd: 7 | image = 'weaveworksdemos/openapi:snapshot' 8 | container_name = '' 9 | def test_against_endpoint(self, service, api_endpoint, links=[], env=[], dump_streams=False): 10 | self.container_name = Docker().random_container_name('openapi') 11 | command = ['docker', 'run', 12 | '-h', 'openapi', 13 | '--name', self.container_name, 14 | '-v', "{0}:{1}".format(os.getcwd() + "/api-spec/", "/tmp/specs/")] 15 | 16 | if links != []: 17 | [command.extend(["--link", x]) for x in links] 18 | 19 | if env != []: 20 | [command.extend(["--env", "{}={}".format(x[0], x[1])]) for x in env] 21 | 22 | command.extend([Dredd.image, 23 | "/tmp/specs/{0}.json".format(service), 24 | api_endpoint, 25 | "-f", 26 | "/tmp/specs/hooks.js".format(service)]) 27 | out = Docker().execute(command, dump_streams=dump_streams) 28 | 29 | Docker().kill_and_remove(self.container_name) 30 | return out 31 | -------------------------------------------------------------------------------- /test/util/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microservices-demo/carts/f4e80051668c6a83359c41b03a27baefa590adc8/test/util/__init__.py --------------------------------------------------------------------------------