diff --git a/.gitignore b/.gitignore
new file mode 100644
index 000000000..6c5d8e791
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,116 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+
+# Dependency directories
+node_modules
+jspm_packages
+
+# Optional npm cache directory
+.npm
+
+### Go template
+# Compiled Object files, Static and Dynamic libs (Shared Objects)
+*.o
+*.a
+*.so
+
+# Folders
+_obj
+_test
+
+# Architecture specific extensions/prefixes
+*.[568vq]
+[568vq].out
+
+*.cgo1.go
+*.cgo2.c
+_cgo_defun.c
+_cgo_gotypes.go
+_cgo_export.*
+
+_testmain.go
+
+*.exe
+*.test
+*.prof
+### Java template
+/target/
+*.class
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.war
+*.ear
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+### OSX template
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+### JetBrains template
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff:
+.idea
+
+## File-based project format:
+*.iws
+*.iml
+
+## Plugin-specific files:
+
+# IntelliJ
+/out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+# Created by .ignore support plugin (hsz.mobi)
+
+# Maven builds
+*/target
+*/*/target
+
+# AWS ECS install scripts generates an SSH key file
+weave-ecs-demo-key.pem
+
+# Load test generates pyc files
+*.pyc
+
+# Ignore Vagrant cache files
+*.vagrant/
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 000000000..267cc9bf9
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,27 @@
+language: java
+sudo: required
+services:
+ - docker
+jdk:
+ - oraclejdk8
+install: true
+
+env:
+ - GROUP=weaveworksdemos COMMIT=$TRAVIS_COMMIT TAG=$TRAVIS_TAG;
+
+script:
+ - set -e
+ - ./scripts/build.sh;
+ - ./test/test.sh unit.py
+ - ./test/test.sh component.py
+# - ./test/test.sh container.py --tag $TAG
+
+after_success:
+ - set -e;
+ - ./test/test.sh coveralls.py
+ - if [ -z "$DOCKER_PASS" ] ; then
+ echo "This is a build triggered by an external PR. Skipping docker push.";
+ exit 0;
+ fi;
+ - docker login -e $DOCKER_EMAIL -u $DOCKER_USER -p $DOCKER_PASS;
+ - ./scripts/push.sh
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 000000000..bf46d5e41
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,6 @@
+FROM java:openjdk-8-alpine
+
+WORKDIR /usr/src/app
+COPY ./target/*.jar ./app.jar
+
+ENTRYPOINT ["java","-Djava.security.egd=file:/dev/urandom","-jar","./app.jar", "--port=80"]
diff --git a/README.md b/README.md
index 83d369d18..4cd96da1c 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,13 @@
# cart
-Carts service for microservices-demo application
+A microservices-demo service that provides user account information.
+
+This build is built, tested and released by travis.
+
+# Test
+`./test/test.sh < python testing file >`. For example: `./test/test.sh unit.py`
+
+# Build
+`GROUP=weaveworksdemos COMMIT=test ./scripts/build.sh`
+
+# Push
+`GROUP=weaveworksdemos COMMIT=test ./scripts/push.sh`
diff --git a/docker/cart/Dockerfile b/docker/cart/Dockerfile
new file mode 100644
index 000000000..d157724b6
--- /dev/null
+++ b/docker/cart/Dockerfile
@@ -0,0 +1,6 @@
+FROM java:openjdk-8-alpine
+
+WORKDIR /usr/src/app
+COPY *.jar ./app.jar
+
+ENTRYPOINT ["java","-Djava.security.egd=file:/dev/urandom","-jar","./app.jar", "--port=80"]
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 000000000..1c0b28a44
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,131 @@
+
+
+ 4.0.0
+
+ works.weave.microservices-demo
+ carts
+ jar
+
+ carts
+ Carts service for microservices-demo application
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 1.4.0.RELEASE
+
+
+
+ UTF-8
+ 1.8
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-rest
+
+
+ org.springframework.boot
+ spring-boot-starter-data-mongodb
+
+
+ org.springframework.data
+ spring-data-rest-hal-browser
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ de.flapdoodle.embed
+ de.flapdoodle.embed.mongo
+ 1.50.5
+ test
+
+
+ com.openpojo
+ openpojo
+ 0.8.4
+ test
+
+
+
+
+ carts
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 2.19.1
+
+
+ **/Unit*.java
+
+
+ **/IT*.java
+
+
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+ 2.18.1
+
+
+ **/IT*.java
+
+
+ **/Unit*.java
+
+
+
+
+
+ integration-test
+ verify
+
+
+
+
+
+ org.jacoco
+ jacoco-maven-plugin
+ 0.7.6.201602180812
+
+
+ prepare-agent
+
+ prepare-agent
+
+
+
+
+
+ org.eluder.coveralls
+ coveralls-maven-plugin
+ 4.2.0
+
+
+
+
+
+
+ spring-releases
+ https://repo.spring.io/libs-release
+
+
+
+
+ spring-releases
+ https://repo.spring.io/libs-release
+
+
+
diff --git a/scripts/build.sh b/scripts/build.sh
new file mode 100755
index 000000000..acb657dda
--- /dev/null
+++ b/scripts/build.sh
@@ -0,0 +1,31 @@
+#!/usr/bin/env bash
+
+set -ev
+
+SCRIPT_DIR=$(dirname "$0")
+
+if [[ -z "$GROUP" ]] ; then
+ echo "Cannot find GROUP env var"
+ exit 1
+fi
+
+if [[ -z "$COMMIT" ]] ; then
+ echo "Cannot find COMMIT env var"
+ exit 1
+fi
+
+if [[ "$(uname)" == "Darwin" ]]; then
+ DOCKER_CMD=docker
+else
+ DOCKER_CMD="sudo docker"
+fi
+CODE_DIR=$(cd $SCRIPT_DIR/..; pwd)
+echo $CODE_DIR
+$DOCKER_CMD run --rm -v $HOME/.m2:/root/.m2 -v $CODE_DIR:/usr/src/mymaven -w /usr/src/mymaven maven:3.2-jdk-8 mvn -DskipTests package
+
+cp $CODE_DIR/target/*.jar $CODE_DIR/docker/cart
+
+for m in ./docker/*/; do
+ REPO=${GROUP}/$(basename $m)
+ $DOCKER_CMD build -t ${REPO}:${COMMIT} $CODE_DIR/$m;
+done;
diff --git a/scripts/push.sh b/scripts/push.sh
new file mode 100755
index 000000000..e993474f5
--- /dev/null
+++ b/scripts/push.sh
@@ -0,0 +1,55 @@
+#!/usr/bin/env bash
+
+set -ev
+
+if [[ -z "$GROUP" ]] ; then
+ echo "Cannot find GROUP env var"
+ exit 1
+fi
+
+if [[ -z "$COMMIT" ]] ; then
+ echo "Cannot find COMMIT env var"
+ exit 1
+fi
+
+push() {
+ DOCKER_PUSH=1;
+ while [ $DOCKER_PUSH -gt 0 ] ; do
+ echo "Pushing $1";
+ docker push $1;
+ DOCKER_PUSH=$(echo $?);
+ if [[ "$DOCKER_PUSH" -gt 0 ]] ; then
+ echo "Docker push failed with exit code $DOCKER_PUSH";
+ fi;
+ done;
+}
+
+tag_and_push_all() {
+ if [[ -z "$1" ]] ; then
+ echo "Please pass the tag"
+ exit 1
+ else
+ TAG=$1
+ fi
+ for m in ./docker/*/; do
+ REPO=${GROUP}/$(basename $m)
+ if [[ "$COMMIT" != "$TAG" ]]; then
+ docker tag ${REPO}:${COMMIT} ${REPO}:${TAG}
+ fi
+ push "$REPO:$TAG";
+ done;
+}
+
+# Always push commit
+tag_and_push_all $COMMIT
+
+# Push snapshot when in master
+if [ "$TRAVIS_BRANCH" == "master" ]; then
+ tag_and_push_all snapshot
+fi;
+
+# Push tag and latest when tagged
+if [ -n "$TRAVIS_TAG" ]; then
+ tag_and_push_all ${TRAVIS_TAG}
+ tag_and_push_all latest
+fi;
diff --git a/src/main/java/works/weave/socks/cart/CartApplication.java b/src/main/java/works/weave/socks/cart/CartApplication.java
new file mode 100644
index 000000000..3cd695f88
--- /dev/null
+++ b/src/main/java/works/weave/socks/cart/CartApplication.java
@@ -0,0 +1,12 @@
+package works.weave.socks.cart;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class CartApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(CartApplication.class, args);
+ }
+}
diff --git a/src/main/java/works/weave/socks/cart/action/FirstResultOrDefault.java b/src/main/java/works/weave/socks/cart/action/FirstResultOrDefault.java
new file mode 100644
index 000000000..73ee1e81f
--- /dev/null
+++ b/src/main/java/works/weave/socks/cart/action/FirstResultOrDefault.java
@@ -0,0 +1,19 @@
+package works.weave.socks.cart.action;
+
+import java.util.Collection;
+import java.util.function.Supplier;
+
+public class FirstResultOrDefault implements Supplier {
+ private final Collection collection;
+ private final Supplier nonePresent;
+
+ public FirstResultOrDefault(final Collection collection, final Supplier nonePresent) {
+ this.collection = collection;
+ this.nonePresent = nonePresent;
+ }
+
+ @Override
+ public T get() {
+ return collection.stream().findFirst().orElseGet(nonePresent);
+ }
+}
diff --git a/src/main/java/works/weave/socks/cart/cart/CartContentsResource.java b/src/main/java/works/weave/socks/cart/cart/CartContentsResource.java
new file mode 100644
index 000000000..05a98c231
--- /dev/null
+++ b/src/main/java/works/weave/socks/cart/cart/CartContentsResource.java
@@ -0,0 +1,47 @@
+package works.weave.socks.cart.cart;
+
+import org.slf4j.Logger;
+import works.weave.socks.cart.entities.Cart;
+import works.weave.socks.cart.entities.Item;
+
+import java.util.List;
+import java.util.function.Supplier;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+public class CartContentsResource implements Contents- {
+ private final Logger LOG = getLogger(getClass());
+
+ private final CartDAO cartRepository;
+ private final Supplier> parent;
+
+ public CartContentsResource(CartDAO cartRepository, Supplier> parent) {
+ this.cartRepository = cartRepository;
+ this.parent = parent;
+ }
+
+ @Override
+ public Supplier
> contents() {
+ return () -> parentCart().contents();
+ }
+
+ @Override
+ public Runnable add(Supplier- item) {
+ return () -> {
+ LOG.debug("Adding for user: " + parent.get().value().get().toString() + ", " + item.get());
+ cartRepository.save(parentCart().add(item.get()));
+ };
+ }
+
+ @Override
+ public Runnable delete(Supplier
- item) {
+ return () -> {
+ LOG.debug("Deleting for user: " + parent.get().value().get().toString() + ", " + item.get());
+ cartRepository.save(parentCart().remove(item.get()));
+ };
+ }
+
+ private Cart parentCart() {
+ return parent.get().value().get();
+ }
+}
diff --git a/src/main/java/works/weave/socks/cart/cart/CartDAO.java b/src/main/java/works/weave/socks/cart/cart/CartDAO.java
new file mode 100644
index 000000000..9d921e8e7
--- /dev/null
+++ b/src/main/java/works/weave/socks/cart/cart/CartDAO.java
@@ -0,0 +1,40 @@
+package works.weave.socks.cart.cart;
+
+import works.weave.socks.cart.entities.Cart;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public interface CartDAO {
+ void delete(Cart cart);
+
+ Cart save(Cart cart);
+
+ List findByCustomerId(String customerId);
+
+ class Fake implements CartDAO {
+ private Map cartStore = new HashMap<>();
+
+ @Override
+ public void delete(Cart cart) {
+ cartStore.remove(cart.customerId);
+ }
+
+ @Override
+ public Cart save(Cart cart) {
+ return cartStore.put(cart.customerId, cart);
+ }
+
+ @Override
+ public List findByCustomerId(String customerId) {
+ Cart cart = cartStore.get(customerId);
+ if (cart != null) {
+ return Collections.singletonList(cart);
+ } else {
+ return Collections.emptyList();
+ }
+ }
+ }
+}
diff --git a/src/main/java/works/weave/socks/cart/cart/CartResource.java b/src/main/java/works/weave/socks/cart/cart/CartResource.java
new file mode 100644
index 000000000..3f2895262
--- /dev/null
+++ b/src/main/java/works/weave/socks/cart/cart/CartResource.java
@@ -0,0 +1,46 @@
+package works.weave.socks.cart.cart;
+
+import works.weave.socks.cart.action.FirstResultOrDefault;
+import works.weave.socks.cart.entities.Cart;
+
+import java.util.function.Supplier;
+
+public class CartResource implements Resource, HasContents {
+ private final CartDAO cartRepository;
+ private final String customerId;
+
+ public CartResource(CartDAO cartRepository, String customerId) {
+ this.cartRepository = cartRepository;
+ this.customerId = customerId;
+ }
+
+ @Override
+ public Runnable destroy() {
+ return () -> cartRepository.delete(value().get());
+ }
+
+ @Override
+ public Supplier create() {
+ return () -> cartRepository.save(new Cart(customerId));
+ }
+
+ @Override
+ public Supplier value() {
+ return new FirstResultOrDefault<>(
+ cartRepository.findByCustomerId(customerId),
+ () -> {
+ create().get();
+ return value().get();
+ });
+ }
+
+ @Override
+ public Runnable merge(Cart toMerge) {
+ return () -> toMerge.contents().forEach(item -> contents().get().add(() -> item).run());
+ }
+
+ @Override
+ public Supplier contents() {
+ return () -> new CartContentsResource(cartRepository, () -> this);
+ }
+}
diff --git a/src/main/java/works/weave/socks/cart/cart/Contents.java b/src/main/java/works/weave/socks/cart/cart/Contents.java
new file mode 100644
index 000000000..5c68848cd
--- /dev/null
+++ b/src/main/java/works/weave/socks/cart/cart/Contents.java
@@ -0,0 +1,14 @@
+package works.weave.socks.cart.cart;
+
+import works.weave.socks.cart.entities.Item;
+
+import java.util.List;
+import java.util.function.Supplier;
+
+public interface Contents {
+ Supplier
> contents();
+
+ Runnable add(Supplier- item);
+
+ Runnable delete(Supplier
- item);
+}
diff --git a/src/main/java/works/weave/socks/cart/cart/HasContents.java b/src/main/java/works/weave/socks/cart/cart/HasContents.java
new file mode 100644
index 000000000..62052fbe8
--- /dev/null
+++ b/src/main/java/works/weave/socks/cart/cart/HasContents.java
@@ -0,0 +1,7 @@
+package works.weave.socks.cart.cart;
+
+import java.util.function.Supplier;
+
+public interface HasContents {
+ Supplier contents();
+}
diff --git a/src/main/java/works/weave/socks/cart/cart/Resource.java b/src/main/java/works/weave/socks/cart/cart/Resource.java
new file mode 100644
index 000000000..1c6b269a5
--- /dev/null
+++ b/src/main/java/works/weave/socks/cart/cart/Resource.java
@@ -0,0 +1,47 @@
+package works.weave.socks.cart.cart;
+
+import works.weave.socks.cart.entities.Cart;
+
+import java.util.function.Supplier;
+
+public interface Resource {
+ Runnable destroy();
+
+ Supplier create();
+
+ Supplier value();
+
+ Runnable merge(T toMerge);
+
+ class CartFake implements Resource {
+ private final String customerId;
+ private Cart cart = null;
+
+ public CartFake(String customerId) {
+ this.customerId = customerId;
+ }
+
+ @Override
+ public Runnable destroy() {
+ return () -> cart = null;
+ }
+
+ @Override
+ public Supplier create() {
+ return () -> cart = new Cart(customerId);
+ }
+
+ @Override
+ public Supplier value() {
+ if (cart == null) {
+ create().get();
+ }
+ return () -> cart;
+ }
+
+ @Override
+ public Runnable merge(Cart toMerge) {
+ return null;
+ }
+ }
+}
diff --git a/src/main/java/works/weave/socks/cart/configuration/BeanConfiguration.java b/src/main/java/works/weave/socks/cart/configuration/BeanConfiguration.java
new file mode 100644
index 000000000..f9caa3305
--- /dev/null
+++ b/src/main/java/works/weave/socks/cart/configuration/BeanConfiguration.java
@@ -0,0 +1,58 @@
+package works.weave.socks.cart.configuration;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import works.weave.socks.cart.cart.CartDAO;
+import works.weave.socks.cart.entities.Cart;
+import works.weave.socks.cart.entities.Item;
+import works.weave.socks.cart.item.ItemDAO;
+import works.weave.socks.cart.repositories.CartRepository;
+import works.weave.socks.cart.repositories.ItemRepository;
+
+import java.util.List;
+
+@Configuration
+public class BeanConfiguration {
+ @Bean
+ @Autowired
+ public CartDAO getCartDao(CartRepository cartRepository) {
+ return new CartDAO() {
+ @Override
+ public void delete(Cart cart) {
+ cartRepository.delete(cart);
+ }
+
+ @Override
+ public Cart save(Cart cart) {
+ return cartRepository.save(cart);
+ }
+
+ @Override
+ public List findByCustomerId(String customerId) {
+ return cartRepository.findByCustomerId(customerId);
+ }
+ };
+ }
+
+ @Bean
+ @Autowired
+ public ItemDAO getItemDao(ItemRepository itemRepository) {
+ return new ItemDAO() {
+ @Override
+ public Item save(Item item) {
+ return itemRepository.save(item);
+ }
+
+ @Override
+ public void destroy(Item item) {
+ itemRepository.delete(item);
+ }
+
+ @Override
+ public Item findOne(String id) {
+ return itemRepository.findOne(id);
+ }
+ };
+ }
+}
diff --git a/src/main/java/works/weave/socks/cart/configuration/ValidationConfiguration.java b/src/main/java/works/weave/socks/cart/configuration/ValidationConfiguration.java
new file mode 100644
index 000000000..248a4fde4
--- /dev/null
+++ b/src/main/java/works/weave/socks/cart/configuration/ValidationConfiguration.java
@@ -0,0 +1,19 @@
+package works.weave.socks.cart.configuration;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.mongodb.core.mapping.event.ValidatingMongoEventListener;
+import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
+
+@Configuration
+public class ValidationConfiguration {
+ @Bean
+ public ValidatingMongoEventListener validatingMongoEventListener() {
+ return new ValidatingMongoEventListener(validator());
+ }
+
+ @Bean
+ public LocalValidatorFactoryBean validator() {
+ return new LocalValidatorFactoryBean();
+ }
+}
diff --git a/src/main/java/works/weave/socks/cart/controllers/CartsController.java b/src/main/java/works/weave/socks/cart/controllers/CartsController.java
new file mode 100644
index 000000000..d1326ef5b
--- /dev/null
+++ b/src/main/java/works/weave/socks/cart/controllers/CartsController.java
@@ -0,0 +1,42 @@
+package works.weave.socks.cart.controllers;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.*;
+import works.weave.socks.cart.cart.CartDAO;
+import works.weave.socks.cart.cart.CartResource;
+import works.weave.socks.cart.entities.Cart;
+
+@RestController
+@RequestMapping(path = "/carts")
+public class CartsController {
+ private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+ @Autowired
+ private CartDAO cartDAO;
+
+ @ResponseStatus(HttpStatus.OK)
+ @RequestMapping(value = "/{customerId}", produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.GET)
+ public Cart get(@PathVariable String customerId) {
+ return new CartResource(cartDAO, customerId).value().get();
+ }
+
+ @ResponseStatus(HttpStatus.ACCEPTED)
+ @RequestMapping(value = "/{customerId}", method = RequestMethod.DELETE)
+ public void delete(@PathVariable String customerId) {
+ new CartResource(cartDAO, customerId).destroy().run();
+ }
+
+ @ResponseStatus(HttpStatus.ACCEPTED)
+ @RequestMapping(value = "/{customerId}/merge", method = RequestMethod.GET)
+ public void mergeCarts(@PathVariable String customerId, @RequestParam(value = "sessionId") String sessionId) {
+ logger.debug("Merge carts request received for ids: " + customerId + " and " + sessionId);
+ CartResource sessionCart = new CartResource(cartDAO, sessionId);
+ CartResource customerCart = new CartResource(cartDAO, customerId);
+ customerCart.merge(sessionCart.value().get()).run();
+ delete(sessionId);
+ }
+}
diff --git a/src/main/java/works/weave/socks/cart/controllers/ItemsController.java b/src/main/java/works/weave/socks/cart/controllers/ItemsController.java
new file mode 100644
index 000000000..c596aee4e
--- /dev/null
+++ b/src/main/java/works/weave/socks/cart/controllers/ItemsController.java
@@ -0,0 +1,83 @@
+package works.weave.socks.cart.controllers;
+
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.*;
+import works.weave.socks.cart.cart.CartDAO;
+import works.weave.socks.cart.cart.CartResource;
+import works.weave.socks.cart.entities.Item;
+import works.weave.socks.cart.item.FoundItem;
+import works.weave.socks.cart.item.ItemDAO;
+import works.weave.socks.cart.item.ItemResource;
+
+import java.util.List;
+import java.util.function.Supplier;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+@RestController
+@RequestMapping(value = "/carts/{customerId:.*}/items")
+public class ItemsController {
+ private final Logger LOG = getLogger(getClass());
+
+ @Autowired
+ private ItemDAO itemDAO;
+ @Autowired
+ private CartsController cartsController;
+ @Autowired
+ private CartDAO cartDAO;
+
+ @ResponseStatus(HttpStatus.OK)
+ @RequestMapping(value = "/{itemId:.*}", produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.GET)
+ public Item get(@PathVariable String customerId, @PathVariable String itemId) {
+ return new FoundItem(() -> getItems(customerId), () -> new Item(itemId)).get();
+ }
+
+ @ResponseStatus(HttpStatus.OK)
+ @RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.GET)
+ public List
- getItems(@PathVariable String customerId) {
+ return cartsController.get(customerId).contents();
+ }
+
+ @ResponseStatus(HttpStatus.CREATED)
+ @RequestMapping(consumes = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.POST)
+ public Item addToCart(@PathVariable String customerId, @RequestBody Item item) {
+ // If the item does not exist in the cart, create new one in the repository.
+ FoundItem foundItem = new FoundItem(() -> cartsController.get(customerId).contents(), () -> item);
+ if (!foundItem.hasItem()) {
+ Supplier
- newItem = new ItemResource(itemDAO, () -> item).create();
+ LOG.debug("Did not find item. Creating item for user: " + customerId + ", " + newItem.get());
+ new CartResource(cartDAO, customerId).contents().get().add(newItem).run();
+ return item;
+ } else {
+ Item newItem = new Item(foundItem.get(), foundItem.get().quantity() + 1);
+ LOG.debug("Found item in cart. Incrementing for user: " + customerId + ", " + newItem);
+ updateItem(customerId, newItem);
+ return newItem;
+ }
+ }
+
+ @ResponseStatus(HttpStatus.ACCEPTED)
+ @RequestMapping(value = "/{itemId:.*}", method = RequestMethod.DELETE)
+ public void removeItem(@PathVariable String customerId, @PathVariable String itemId) {
+ FoundItem foundItem = new FoundItem(() -> getItems(customerId), () -> new Item(itemId));
+ Item item = foundItem.get();
+
+ LOG.debug("Removing item from cart: " + item);
+ new CartResource(cartDAO, customerId).contents().get().delete(() -> item).run();
+
+ LOG.debug("Removing item from repository: " + item);
+ new ItemResource(itemDAO, () -> item).destroy().run();
+ }
+
+ @ResponseStatus(HttpStatus.ACCEPTED)
+ @RequestMapping(consumes = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.PATCH)
+ public void updateItem(@PathVariable String customerId, @RequestBody Item item) {
+ // Merge old and new items
+ ItemResource itemResource = new ItemResource(itemDAO, () -> get(customerId, item.itemId()));
+ LOG.debug("Merging item in cart for user: " + customerId + ", " + item);
+ itemResource.merge(item).run();
+ }
+}
diff --git a/src/main/java/works/weave/socks/cart/entities/Cart.java b/src/main/java/works/weave/socks/cart/entities/Cart.java
new file mode 100644
index 000000000..a4a713d89
--- /dev/null
+++ b/src/main/java/works/weave/socks/cart/entities/Cart.java
@@ -0,0 +1,70 @@
+package works.weave.socks.cart.entities;
+
+import org.springframework.data.annotation.Id;
+import org.springframework.data.mongodb.core.mapping.DBRef;
+import org.springframework.data.mongodb.core.mapping.Document;
+
+import javax.validation.constraints.NotNull;
+import java.util.ArrayList;
+import java.util.List;
+
+@Document
+public class Cart {
+ @NotNull
+ public String customerId; // Public instead of getters/setters.
+ @Id
+ private String id;
+ @DBRef
+ private List
- items = new ArrayList<>();
+
+ public Cart(String customerId) {
+ this.customerId = customerId;
+ }
+
+ public Cart() {
+ this(null);
+ }
+
+ public List
- contents() {
+ return items;
+ }
+
+ public Cart add(Item item) {
+ items.add(item);
+ return this;
+ }
+
+ public Cart remove(Item item) {
+ items.remove(item);
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return "Cart{" +
+ "id='" + id + '\'' +
+ ", customerId='" + customerId + '\'' +
+ ", items=" + items +
+ '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ Cart cart = (Cart) o;
+
+ if (customerId != null ? !customerId.equals(cart.customerId) : cart.customerId != null) return false;
+ if (id != null ? !id.equals(cart.id) : cart.id != null) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = customerId != null ? customerId.hashCode() : 0;
+ result = 31 * result + (id != null ? id.hashCode() : 0);
+ return result;
+ }
+}
diff --git a/src/main/java/works/weave/socks/cart/entities/Item.java b/src/main/java/works/weave/socks/cart/entities/Item.java
new file mode 100644
index 000000000..5171eaee4
--- /dev/null
+++ b/src/main/java/works/weave/socks/cart/entities/Item.java
@@ -0,0 +1,106 @@
+package works.weave.socks.cart.entities;
+
+import org.springframework.data.annotation.Id;
+import org.springframework.data.mongodb.core.mapping.Document;
+
+import javax.validation.constraints.NotNull;
+
+@Document
+public class Item {
+ @Id
+ private String id;
+
+ @NotNull(message = "Item Id must not be null")
+ private String itemId;
+ private int quantity;
+ private float unitPrice;
+
+ public Item(String id, String itemId, int quantity, float unitPrice) {
+ this.id = id;
+ this.itemId = itemId;
+ this.quantity = quantity;
+ this.unitPrice = unitPrice;
+ }
+
+ public Item() {
+ this(null, "", 1, 0F);
+ }
+
+ public Item(String itemId) {
+ this(null, itemId, 1, 0F);
+ }
+
+ public Item(Item item, String id) {
+ this(id, item.itemId, item.quantity, item.unitPrice);
+ }
+
+ public Item(Item item, int quantity) {
+ this(item.id(), item.itemId, quantity, item.unitPrice);
+ }
+
+ public String id() {
+ return id;
+ }
+
+ public String itemId() {
+ return itemId;
+ }
+
+ public int quantity() {
+ return quantity;
+ }
+
+ @Override
+ public String toString() {
+ return "Item{" +
+ "id='" + id + '\'' +
+ ", itemId='" + itemId + '\'' +
+ ", quantity=" + quantity +
+ ", unitPrice=" + unitPrice +
+ '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ Item item = (Item) o;
+
+ return itemId != null ? itemId.equals(item.itemId) : item.itemId == null;
+ }
+
+ // ****** Crappy getter/setters for Jackson JSON invoking ********
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getItemId() {
+ return itemId;
+ }
+
+ public void setItemId(String itemId) {
+ this.itemId = itemId;
+ }
+
+ public int getQuantity() {
+ return quantity;
+ }
+
+ public void setQuantity(int quantity) {
+ this.quantity = quantity;
+ }
+
+ public float getUnitPrice() {
+ return unitPrice;
+ }
+
+ public void setUnitPrice(float unitPrice) {
+ this.unitPrice = unitPrice;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/works/weave/socks/cart/item/FoundItem.java b/src/main/java/works/weave/socks/cart/item/FoundItem.java
new file mode 100644
index 000000000..07c5dbaa3
--- /dev/null
+++ b/src/main/java/works/weave/socks/cart/item/FoundItem.java
@@ -0,0 +1,37 @@
+package works.weave.socks.cart.item;
+
+import org.slf4j.Logger;
+import works.weave.socks.cart.entities.Item;
+
+import java.util.List;
+import java.util.function.Supplier;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+public class FoundItem implements Supplier
- {
+ private final Logger LOG = getLogger(getClass());
+ private final Supplier
> items;
+ private final Supplier- item;
+
+ public FoundItem(Supplier
> items, Supplier- item) {
+ this.items = items;
+ this.item = item;
+ }
+
+ @Override
+ public Item get() {
+ return items.get().stream()
+ .filter(item.get()::equals)
+ .findFirst()
+ .orElseThrow(() -> new IllegalArgumentException("Cannot find item in cart"));
+ }
+
+ public boolean hasItem() {
+ boolean present = items.get().stream()
+ .filter(item.get()::equals)
+ .findFirst()
+ .isPresent();
+ LOG.debug((present ? "Found" : "Didn't find") + " item: " + item.get() + ", in: " + items.get());
+ return present;
+ }
+}
diff --git a/src/main/java/works/weave/socks/cart/item/ItemDAO.java b/src/main/java/works/weave/socks/cart/item/ItemDAO.java
new file mode 100644
index 000000000..5f66760a0
--- /dev/null
+++ b/src/main/java/works/weave/socks/cart/item/ItemDAO.java
@@ -0,0 +1,35 @@
+package works.weave.socks.cart.item;
+
+import works.weave.socks.cart.entities.Item;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public interface ItemDAO {
+ Item save(Item item);
+
+ void destroy(Item item);
+
+ Item findOne(String id);
+
+ class Fake implements ItemDAO {
+ private Map store = new HashMap<>();
+
+ @Override
+ public Item save(Item item) {
+ return store.put(item.itemId(), item);
+ }
+
+ @Override
+ public void destroy(Item item) {
+ store.remove(item.itemId());
+
+ }
+
+ @Override
+ public Item findOne(String id) {
+ return store.entrySet().stream().filter(i -> i.getValue().id().equals(id)).map(Map.Entry::getValue)
+ .findFirst().orElse(null);
+ }
+ }
+}
diff --git a/src/main/java/works/weave/socks/cart/item/ItemResource.java b/src/main/java/works/weave/socks/cart/item/ItemResource.java
new file mode 100644
index 000000000..1753f2a13
--- /dev/null
+++ b/src/main/java/works/weave/socks/cart/item/ItemResource.java
@@ -0,0 +1,36 @@
+package works.weave.socks.cart.item;
+
+import works.weave.socks.cart.cart.Resource;
+import works.weave.socks.cart.entities.Item;
+
+import java.util.function.Supplier;
+
+public class ItemResource implements Resource
- {
+ private final ItemDAO itemRepository;
+ private final Supplier
- item;
+
+ public ItemResource(ItemDAO itemRepository, Supplier
- item) {
+ this.itemRepository = itemRepository;
+ this.item = item;
+ }
+
+ @Override
+ public Runnable destroy() {
+ return () -> itemRepository.destroy(value().get());
+ }
+
+ @Override
+ public Supplier
- create() {
+ return () -> itemRepository.save(item.get());
+ }
+
+ @Override
+ public Supplier
- value() {
+ return item; // Basically a null method. Gets the item from the supplier.
+ }
+
+ @Override
+ public Runnable merge(Item toMerge) {
+ return () -> itemRepository.save(new Item(value().get(), toMerge.quantity()));
+ }
+}
diff --git a/src/main/java/works/weave/socks/cart/repositories/CartRepository.java b/src/main/java/works/weave/socks/cart/repositories/CartRepository.java
new file mode 100644
index 000000000..47c68d997
--- /dev/null
+++ b/src/main/java/works/weave/socks/cart/repositories/CartRepository.java
@@ -0,0 +1,14 @@
+package works.weave.socks.cart.repositories;
+
+import org.springframework.data.mongodb.repository.MongoRepository;
+import org.springframework.data.repository.query.Param;
+import org.springframework.data.rest.core.annotation.RepositoryRestResource;
+import works.weave.socks.cart.entities.Cart;
+
+import java.util.List;
+
+@RepositoryRestResource(exported = false)
+public interface CartRepository extends MongoRepository {
+ List findByCustomerId(@Param("custId") String id);
+}
+
diff --git a/src/main/java/works/weave/socks/cart/repositories/ItemRepository.java b/src/main/java/works/weave/socks/cart/repositories/ItemRepository.java
new file mode 100644
index 000000000..0c832818a
--- /dev/null
+++ b/src/main/java/works/weave/socks/cart/repositories/ItemRepository.java
@@ -0,0 +1,10 @@
+package works.weave.socks.cart.repositories;
+
+import org.springframework.data.mongodb.repository.MongoRepository;
+import org.springframework.data.rest.core.annotation.RepositoryRestResource;
+import works.weave.socks.cart.entities.Item;
+
+@RepositoryRestResource
+public interface ItemRepository extends MongoRepository
- {
+}
+
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
new file mode 100644
index 000000000..6001fe6ec
--- /dev/null
+++ b/src/main/resources/application.properties
@@ -0,0 +1,2 @@
+server.port=${port:8081}
+spring.data.mongodb.uri=mongodb://${db:cart-db}:27017/data
diff --git a/src/test/java/works/weave/socks/cart/action/UnitFirstResultOrDefault.java b/src/test/java/works/weave/socks/cart/action/UnitFirstResultOrDefault.java
new file mode 100644
index 000000000..b2834b118
--- /dev/null
+++ b/src/test/java/works/weave/socks/cart/action/UnitFirstResultOrDefault.java
@@ -0,0 +1,33 @@
+package works.weave.socks.cart.action;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.junit.Assert.assertThat;
+
+public class UnitFirstResultOrDefault {
+ @Test
+ public void whenEmptyUsesDefault() {
+ String defaultValue = "test";
+ FirstResultOrDefault CUT = new FirstResultOrDefault<>(Collections.emptyList(), () -> defaultValue);
+ assertThat(CUT.get(), equalTo(defaultValue));
+ }
+
+ @Test
+ public void whenNotEmptyUseFirst() {
+ String testValue = "test";
+ FirstResultOrDefault CUT = new FirstResultOrDefault<>(Arrays.asList(testValue), () -> "nonDefault");
+ assertThat(CUT.get(), equalTo(testValue));
+ }
+
+ @Test
+ public void whenMultipleNotEmptyUseFirst() {
+ String testValue = "test";
+ FirstResultOrDefault CUT = new FirstResultOrDefault<>(Arrays.asList(testValue, "test2"), () ->
+ "nonDefault");
+ assertThat(CUT.get(), equalTo(testValue));
+ }
+}
diff --git a/src/test/java/works/weave/socks/cart/cart/UnitCartContentsResource.java b/src/test/java/works/weave/socks/cart/cart/UnitCartContentsResource.java
new file mode 100644
index 000000000..9d17e7742
--- /dev/null
+++ b/src/test/java/works/weave/socks/cart/cart/UnitCartContentsResource.java
@@ -0,0 +1,42 @@
+package works.weave.socks.cart.cart;
+
+import org.hamcrest.collection.IsCollectionWithSize;
+import org.junit.Test;
+import works.weave.socks.cart.entities.Cart;
+import works.weave.socks.cart.entities.Item;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsInAnyOrder;
+
+public class UnitCartContentsResource {
+ private final String customerId = "testId";
+ private final CartDAO.Fake fakeDAO = new CartDAO.Fake();
+ private final Resource fakeCartResource = new Resource.CartFake(customerId);
+
+ @Test
+ public void shouldAddAndReturnContents() {
+ CartContentsResource contentsResource = new CartContentsResource(fakeDAO, () -> fakeCartResource);
+ Item item = new Item("testId");
+ contentsResource.add(() -> item).run();
+ assertThat(contentsResource.contents().get(), IsCollectionWithSize.hasSize(1));
+ assertThat(contentsResource.contents().get(), containsInAnyOrder(item));
+ }
+
+ @Test
+ public void shouldStartEmpty() {
+ CartContentsResource contentsResource = new CartContentsResource(fakeDAO, () -> fakeCartResource);
+ assertThat(contentsResource.contents().get(), IsCollectionWithSize.hasSize(0));
+ }
+
+ @Test
+ public void shouldDeleteItemFromCart() {
+ CartContentsResource contentsResource = new CartContentsResource(fakeDAO, () -> fakeCartResource);
+ Item item = new Item("testId");
+ contentsResource.add(() -> item).run();
+ assertThat(contentsResource.contents().get(), IsCollectionWithSize.hasSize(1));
+ assertThat(contentsResource.contents().get(), containsInAnyOrder(item));
+ Item item2 = new Item(item.itemId());
+ contentsResource.delete(() -> item2).run();
+ assertThat(contentsResource.contents().get(), IsCollectionWithSize.hasSize(0));
+ }
+}
diff --git a/src/test/java/works/weave/socks/cart/cart/UnitCartResource.java b/src/test/java/works/weave/socks/cart/cart/UnitCartResource.java
new file mode 100644
index 000000000..674f3b4b9
--- /dev/null
+++ b/src/test/java/works/weave/socks/cart/cart/UnitCartResource.java
@@ -0,0 +1,83 @@
+package works.weave.socks.cart.cart;
+
+import org.junit.Test;
+import works.weave.socks.cart.entities.Cart;
+import works.weave.socks.cart.entities.Item;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.Matchers.anyOf;
+import static org.hamcrest.Matchers.*;
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.junit.Assert.assertThat;
+
+public class UnitCartResource {
+
+ private final String customerId = "testId";
+ private final CartDAO.Fake fake = new CartDAO.Fake();
+
+ @Test
+ public void whenCartExistsUseThat() {
+ Cart cart = new Cart(customerId);
+ fake.save(cart);
+ CartResource cartResource = new CartResource(fake, customerId);
+ assertThat(cartResource.value().get(), equalTo(cart));
+ }
+
+ @Test
+ public void whenCartDoesntExistCreateNew() {
+ CartResource cartResource = new CartResource(fake, customerId);
+ assertThat(cartResource.value().get(), is(notNullValue()));
+ assertThat(cartResource.value().get().customerId, is(equalTo(customerId)));
+ }
+
+ @Test
+ public void whenDestroyRemoveItem() {
+ Cart cart = new Cart(customerId);
+ fake.save(cart);
+ CartResource cartResource = new CartResource(fake, customerId);
+ cartResource.destroy().run();
+ assertThat(fake.findByCustomerId(customerId), is(empty()));
+ }
+
+ @Test
+ public void whenDestroyOnEmptyStillEmpty() {
+ CartResource cartResource = new CartResource(fake, customerId);
+ cartResource.destroy().run();
+ assertThat(fake.findByCustomerId(customerId), is(empty()));
+ }
+
+ @Test
+ public void whenCreateDoCreate() {
+ CartResource cartResource = new CartResource(fake, customerId);
+ cartResource.create().get();
+ assertThat(fake.findByCustomerId(customerId), is(not(empty())));
+ }
+
+ @Test
+ public void contentsShouldBeEmptyWhenNew() {
+ CartResource cartResource = new CartResource(fake, customerId);
+ cartResource.create().get();
+ assertThat(cartResource.contents().get().contents().get(), is(empty()));
+ }
+
+ @Test
+ public void mergedItemsShouldBeInCart() {
+ String person1 = "person1";
+ String person2 = "person2";
+ Item person1Item = new Item("item1");
+ Item person2Item = new Item("item2");
+ CartResource cartResource = new CartResource(fake, person1);
+ cartResource.contents().get().add(() -> person1Item).run();
+ CartResource cartResourceToMerge = new CartResource(fake, person2);
+ cartResourceToMerge.contents().get().add(() -> person2Item).run();
+ cartResource.merge(cartResourceToMerge.value().get()).run();
+ assertThat(cartResource.contents().get().contents().get(), hasSize(2));
+ assertThat(cartResource.contents().get().contents().get().get(0), anyOf(equalTo(person1Item), equalTo
+ (person2Item)));
+ assertThat(cartResource.contents().get().contents().get().get(1), anyOf(equalTo(person1Item), equalTo
+ (person2Item)));
+ assertThat(cartResourceToMerge.contents().get().contents().get(), hasSize(1));
+ }
+}
diff --git a/src/test/java/works/weave/socks/cart/controllers/UnitCartsController.java b/src/test/java/works/weave/socks/cart/controllers/UnitCartsController.java
new file mode 100644
index 000000000..274dc8233
--- /dev/null
+++ b/src/test/java/works/weave/socks/cart/controllers/UnitCartsController.java
@@ -0,0 +1,96 @@
+package works.weave.socks.cart.controllers;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import works.weave.socks.cart.cart.CartDAO;
+import works.weave.socks.cart.entities.Cart;
+import works.weave.socks.cart.entities.Item;
+import works.weave.socks.cart.item.ItemDAO;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
+import static org.hamcrest.collection.IsEmptyCollection.empty;
+import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder;
+import static org.junit.Assert.assertThat;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration
+public class UnitCartsController {
+
+ @Autowired
+ private ItemsController itemsController;
+
+ @Autowired
+ private CartDAO cartDAO;
+
+ @Autowired
+ private CartsController cartsController;
+
+
+ @Test
+ public void shouldGetCart() {
+ String customerId = "customerIdGet";
+ Cart cart = new Cart(customerId);
+ cartDAO.save(cart);
+ Cart gotCart = cartsController.get(customerId);
+ assertThat(gotCart, is(equalTo(cart)));
+ assertThat(cartDAO.findByCustomerId(customerId).get(0), is(equalTo(cart)));
+ }
+
+ @Test
+ public void shouldDeleteCart() {
+ String customerId = "customerIdGet";
+ Cart cart = new Cart(customerId);
+ cartDAO.save(cart);
+ cartsController.delete(customerId);
+ assertThat(cartDAO.findByCustomerId(customerId), is(empty()));
+ }
+
+ @Test
+ public void shouldMergeItemsInCartsTogether() {
+ String customerId1 = "customerId1";
+ Cart cart1 = new Cart(customerId1);
+ Item itemId1 = new Item("itemId1");
+ cart1.add(itemId1);
+ cartDAO.save(cart1);
+ String customerId2 = "customerId2";
+ Cart cart2 = new Cart(customerId2);
+ Item itemId2 = new Item("itemId2");
+ cart2.add(itemId2);
+ cartDAO.save(cart2);
+
+ cartsController.mergeCarts(customerId1, customerId2);
+ assertThat(cartDAO.findByCustomerId(customerId1).get(0).contents(), is(hasSize(2)));
+ assertThat(cartDAO.findByCustomerId(customerId1).get(0).contents(), is(containsInAnyOrder(itemId1, itemId2)));
+ assertThat(cartDAO.findByCustomerId(customerId2), is(empty()));
+ }
+
+ @Configuration
+ static class ItemsControllerTestConfiguration {
+ @Bean
+ public ItemsController itemsController() {
+ return new ItemsController();
+ }
+
+ @Bean
+ public CartsController cartsController() {
+ return new CartsController();
+ }
+
+ @Bean
+ public ItemDAO itemDAO() {
+ return new ItemDAO.Fake();
+ }
+
+ @Bean
+ public CartDAO cartDAO() {
+ return new CartDAO.Fake();
+ }
+ }
+}
diff --git a/src/test/java/works/weave/socks/cart/controllers/UnitItemsController.java b/src/test/java/works/weave/socks/cart/controllers/UnitItemsController.java
new file mode 100644
index 000000000..ba07440e7
--- /dev/null
+++ b/src/test/java/works/weave/socks/cart/controllers/UnitItemsController.java
@@ -0,0 +1,95 @@
+package works.weave.socks.cart.controllers;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import works.weave.socks.cart.cart.CartDAO;
+import works.weave.socks.cart.entities.Item;
+import works.weave.socks.cart.item.ItemDAO;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
+import static org.junit.Assert.assertThat;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration
+public class UnitItemsController {
+
+ @Autowired
+ private ItemsController itemsController;
+
+ @Autowired
+ private ItemDAO itemDAO;
+
+ @Autowired
+ private CartsController cartsController;
+
+ @Test
+ public void whenNewItemAdd() {
+ Item item = new Item("id", "itemId", 1, 0F);
+ String customerId = "customerIdAdd";
+ itemsController.addToCart(customerId, item);
+ assertThat(itemsController.getItems(customerId), is(hasSize(1)));
+ assertThat(itemsController.getItems(customerId), is(org.hamcrest.CoreMatchers.hasItem(item)));
+ }
+
+ @Test
+ public void whenExistIncrementQuantity() {
+ Item item = new Item("id", "itemId", 1, 0F);
+ String customerId = "customerIdIncrement";
+ itemsController.addToCart(customerId, item);
+ itemsController.addToCart(customerId, item);
+ assertThat(itemsController.getItems(customerId), is(hasSize(1)));
+ assertThat(itemsController.getItems(customerId), is(org.hamcrest.CoreMatchers.hasItem(item)));
+ assertThat(itemDAO.findOne(item.id()).quantity(), is(equalTo(2)));
+ }
+
+ @Test
+ public void shouldRemoveItemFromCart() {
+ Item item = new Item("id", "itemId", 1, 0F);
+ String customerId = "customerIdRemove";
+ itemsController.addToCart(customerId, item);
+ assertThat(itemsController.getItems(customerId), is(hasSize(1)));
+ itemsController.removeItem(customerId, item.itemId());
+ assertThat(itemsController.getItems(customerId), is(hasSize(0)));
+ }
+
+ @Test
+ public void shouldSetQuantity() {
+ Item item = new Item("id", "itemId", 1, 0F);
+ String customerId = "customerIdQuantity";
+ itemsController.addToCart(customerId, item);
+ assertThat(itemsController.getItems(customerId).get(0).quantity(), is(equalTo(item.quantity())));
+ Item anotherItem = new Item(item, 15);
+ itemsController.updateItem(customerId, anotherItem);
+ assertThat(itemDAO.findOne(item.id()).quantity(), is(equalTo(anotherItem.quantity())));
+ }
+
+ @Configuration
+ static class ItemsControllerTestConfiguration {
+ @Bean
+ public ItemsController itemsController() {
+ return new ItemsController();
+ }
+
+ @Bean
+ public CartsController cartsController() {
+ return new CartsController();
+ }
+
+ @Bean
+ public ItemDAO itemDAO() {
+ return new ItemDAO.Fake();
+ }
+
+ @Bean
+ public CartDAO cartDAO() {
+ return new CartDAO.Fake();
+ }
+ }
+}
diff --git a/src/test/java/works/weave/socks/cart/item/UnitFoundItem.java b/src/test/java/works/weave/socks/cart/item/UnitFoundItem.java
new file mode 100644
index 000000000..70ae862f3
--- /dev/null
+++ b/src/test/java/works/weave/socks/cart/item/UnitFoundItem.java
@@ -0,0 +1,23 @@
+package works.weave.socks.cart.item;
+
+import org.junit.Test;
+import works.weave.socks.cart.entities.Item;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.junit.Assert.assertThat;
+
+public class UnitFoundItem {
+ @Test
+ public void findOneItem() {
+ List
- list = new ArrayList<>();
+ String testId = "testId";
+ Item testAnswer = new Item(testId);
+ list.add(testAnswer);
+ FoundItem foundItem = new FoundItem(() -> list, () -> testAnswer);
+ assertThat(foundItem.get(), is(equalTo(testAnswer)));
+ }
+}
diff --git a/src/test/java/works/weave/socks/cart/item/UnitItemResource.java b/src/test/java/works/weave/socks/cart/item/UnitItemResource.java
new file mode 100644
index 000000000..068d9500b
--- /dev/null
+++ b/src/test/java/works/weave/socks/cart/item/UnitItemResource.java
@@ -0,0 +1,34 @@
+package works.weave.socks.cart.item;
+
+import org.junit.Test;
+import works.weave.socks.cart.entities.Item;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.Matchers.nullValue;
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.junit.Assert.assertThat;
+
+
+public class UnitItemResource {
+ private ItemDAO itemDAO = new ItemDAO.Fake();
+
+ @Test
+ public void testCreateAndDestroy() {
+ Item item = new Item("itemId", "testId", 1, 0F);
+ ItemResource itemResource = new ItemResource(itemDAO, () -> item);
+ itemResource.create().get();
+ assertThat(itemDAO.findOne(item.id()), is(equalTo(item)));
+ itemResource.destroy().run();
+ assertThat(itemDAO.findOne(item.id()), is(nullValue()));
+ }
+
+ @Test
+ public void mergedItemShouldHaveNewQuantity() {
+ Item item = new Item("itemId", "testId", 1, 0F);
+ ItemResource itemResource = new ItemResource(itemDAO, () -> item);
+ assertThat(itemResource.value().get(), is(equalTo(item)));
+ Item newItem = new Item(item, 10);
+ itemResource.merge(newItem).run();
+ assertThat(itemDAO.findOne(item.id()).quantity(), is(equalTo(newItem.quantity())));
+ }
+}
diff --git a/src/test/java/works/weave/socks/cart/repositories/ITCartRepository.java b/src/test/java/works/weave/socks/cart/repositories/ITCartRepository.java
new file mode 100644
index 000000000..a0f3d6d1a
--- /dev/null
+++ b/src/test/java/works/weave/socks/cart/repositories/ITCartRepository.java
@@ -0,0 +1,53 @@
+package works.weave.socks.cart.repositories;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.test.context.junit4.SpringRunner;
+import works.weave.socks.cart.entities.Cart;
+
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+
+@RunWith(SpringRunner.class)
+@EnableAutoConfiguration
+public class ITCartRepository {
+ @Autowired
+ private CartRepository cartRepository;
+
+ @Before
+ public void removeAllData() {
+ cartRepository.deleteAll();
+ }
+
+ @Test
+ public void testCartSave() {
+ Cart original = new Cart("customerId");
+ Cart saved = cartRepository.save(original);
+
+ assertEquals(1, cartRepository.count());
+ assertEquals(original, saved);
+ }
+
+ @Test
+ public void testCartGetDefault() {
+ Cart original = new Cart("customerId");
+ Cart saved = cartRepository.save(original);
+
+ assertEquals(1, cartRepository.count());
+ assertEquals(original, saved);
+ }
+
+ @Test
+ public void testSearchCustomerById() {
+ Cart original = new Cart("customerId");
+ cartRepository.save(original);
+
+ List found = cartRepository.findByCustomerId(original.customerId);
+ assertEquals(1, found.size());
+ assertEquals(original, found.get(0));
+ }
+}
diff --git a/src/test/java/works/weave/socks/cart/repositories/ITItemRepository.java b/src/test/java/works/weave/socks/cart/repositories/ITItemRepository.java
new file mode 100644
index 000000000..93bb87afb
--- /dev/null
+++ b/src/test/java/works/weave/socks/cart/repositories/ITItemRepository.java
@@ -0,0 +1,32 @@
+package works.weave.socks.cart.repositories;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.test.context.junit4.SpringRunner;
+import works.weave.socks.cart.entities.Item;
+
+import static org.junit.Assert.assertEquals;
+
+@RunWith(SpringRunner.class)
+@EnableAutoConfiguration
+public class ITItemRepository {
+ @Autowired
+ private ItemRepository itemRepository;
+
+ @Before
+ public void removeAllData() {
+ itemRepository.deleteAll();
+ }
+
+ @Test
+ public void testCustomerSave() {
+ Item original = new Item("id", "itemId", 1, 0.99F);
+ Item saved = itemRepository.save(original);
+
+ assertEquals(1, itemRepository.count());
+ assertEquals(original, saved);
+ }
+}
diff --git a/test/Dockerfile b/test/Dockerfile
new file mode 100644
index 000000000..8b921b33d
--- /dev/null
+++ b/test/Dockerfile
@@ -0,0 +1,16 @@
+FROM python:3.6-alpine
+
+RUN apk add --no-cache \
+ ca-certificates \
+ curl \
+ openssl
+
+ENV DOCKER_BUCKET get.docker.com
+ENV DOCKER_VERSION 1.8.3
+
+RUN set -x \
+ && curl -fSL "https://${DOCKER_BUCKET}/builds/Linux/x86_64/docker-${DOCKER_VERSION}.tgz" -o docker.tgz \
+ && tar -xzvf docker.tgz \
+ && docker -v
+
+RUN pip install requests
diff --git a/test/component.py b/test/component.py
new file mode 100644
index 000000000..262300705
--- /dev/null
+++ b/test/component.py
@@ -0,0 +1,19 @@
+import os
+import unittest
+from os.path import expanduser
+
+from util.Docker import Docker
+
+
+class JavaServices(unittest.TestCase):
+ def test_maven(self):
+ script_dir = os.path.dirname(os.path.realpath(__file__))
+ code_dir = script_dir + "/.."
+ home = expanduser("~")
+ command = ['docker', 'run', '--rm', '-v', home + '/.m2:/root/.m2', '-v', code_dir + ':/usr/src/mymaven', '-w',
+ '/usr/src/mymaven', 'maven:3.2-jdk-8', 'mvn', 'integration-test']
+ print(Docker().execute(command))
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/test/container.py b/test/container.py
new file mode 100644
index 000000000..426fb3f58
--- /dev/null
+++ b/test/container.py
@@ -0,0 +1,59 @@
+import argparse
+import sys
+import unittest
+from time import sleep
+
+from util.Api import Api
+from util.Docker import Docker
+from util.Dredd import Dredd
+
+
+class CartContainerTest(unittest.TestCase):
+ TAG = "latest"
+ container_name = Docker().random_container_name('cart')
+ mongo_container_name = Docker().random_container_name('cart-db')
+
+ def __init__(self, methodName='runTest'):
+ super(CartContainerTest, self).__init__(methodName)
+ self.ip = ""
+
+ def setUp(self):
+ Docker().start_container(container_name=self.mongo_container_name, image="mongo", host="cart-db")
+ command = ['docker', 'run',
+ '-d',
+ '--name', CartContainerTest.container_name,
+ '-h', 'cart',
+ '--link',
+ CartContainerTest.mongo_container_name,
+ 'weaveworksdemos/cart:' + self.TAG]
+ Docker().execute(command)
+ self.ip = Docker().get_container_ip(CartContainerTest.container_name)
+
+ def tearDown(self):
+ Docker().kill_and_remove(CartContainerTest.container_name)
+ Docker().kill_and_remove(CartContainerTest.mongo_container_name)
+
+ def test_api_validated(self):
+ limit = 60
+ while Api().noResponse('http://' + self.ip + ':80/carts/579f21ae98684924944651bf'):
+ if limit == 0:
+ self.fail("Couldn't get the API running")
+ limit = limit - 1
+ sleep(1)
+
+ out = Dredd().test_against_endpoint("carts/carts.json", CartContainerTest.container_name, "http://cart/",
+ "mongodb://cart-db:27017/data", self.mongo_container_name)
+ self.assertGreater(out.find("0 failing"), -1)
+ self.assertGreater(out.find("0 errors"), -1)
+ print(out)
+
+
+if __name__ == '__main__':
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--tag', default="latest", help='The tag of the image to use. (default: latest)')
+ parser.add_argument('unittest_args', nargs='*')
+ args = parser.parse_args()
+ CartContainerTest.TAG = args.tag
+ # Now set the sys.argv to the unittest_args (leaving sys.argv[0] alone)
+ sys.argv[1:] = args.unittest_args
+ unittest.main()
diff --git a/test/coveralls.py b/test/coveralls.py
new file mode 100644
index 000000000..097b10792
--- /dev/null
+++ b/test/coveralls.py
@@ -0,0 +1,36 @@
+import os
+import unittest
+from os.path import expanduser
+
+from util.Docker import Docker
+
+
+class JavaServices(unittest.TestCase):
+ def test_maven(self):
+ script_dir = os.path.dirname(os.path.realpath(__file__))
+ code_dir = script_dir + "/.."
+ home = expanduser("~")
+ command = ['docker', 'run', '--rm',
+ '-v', home + '/.m2:/root/.m2',
+ '-v', code_dir + ':/usr/src/mymaven',
+ '-w', '/usr/src/mymaven',
+ 'maven:3.2-jdk-8',
+ 'mvn',
+ '-DrepoToken=' + os.getenv('COVERALLS_TOKEN'),
+ '-DserviceJobId=' + os.getenv('TRAVIS_JOB_ID'),
+ '-Dbranch=' + os.getenv('TRAVIS_BRANCH'),
+ '-DpullRequest=' + os.getenv('TRAVIS_PULL_REQUEST'),
+ '-DserviceName=' + os.getenv('TRAVIS'),
+ 'verify',
+ 'jacoco:report',
+ 'coveralls:report']
+ print("Coveralls command: ",
+ '-DserviceJobId=' + os.getenv('TRAVIS_JOB_ID'),
+ '-Dbranch=' + os.getenv('TRAVIS_BRANCH'),
+ '-DpullRequest=' + os.getenv('TRAVIS_PULL_REQUEST'),
+ '-DserviceName=' + 'TRAVIS')
+ print(Docker().execute(command))
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/test/test.sh b/test/test.sh
new file mode 100755
index 000000000..f84b64db1
--- /dev/null
+++ b/test/test.sh
@@ -0,0 +1,34 @@
+#!/usr/bin/env bash
+
+set -ev
+
+SCRIPT_DIR=`dirname "$0"`
+SCRIPT_NAME=`basename "$0"`
+SSH_OPTS=-oStrictHostKeyChecking=no
+
+if [[ "$(uname)" == "Darwin" ]]; then
+ DOCKER_CMD=docker
+else
+ DOCKER_CMD="sudo docker"
+fi
+
+if [[ -z $($DOCKER_CMD images | grep test-container) ]] ; then
+ echo "Building test container"
+ docker build -t test-container $SCRIPT_DIR > /dev/null
+fi
+
+echo "Testing $1"
+CODE_DIR=$(cd $SCRIPT_DIR/..; pwd)
+echo "$@"
+$DOCKER_CMD run \
+ --rm \
+ --name test \
+ -v /var/run/docker.sock:/var/run/docker.sock \
+ -v $CODE_DIR:$CODE_DIR -w $CODE_DIR \
+ -e COVERALLS_TOKEN=$COVERALLS_TOKEN \
+ -e TRAVIS_JOB_ID=$TRAVIS_JOB_ID \
+ -e TRAVIS_BRANCH=$TRAVIS_BRANCH \
+ -e TRAVIS_PULL_REQUEST=$TRAVIS_PULL_REQUEST \
+ -e TRAVIS=$TRAVIS \
+ test-container \
+ sh -c export PYTHONPATH=\$PYTHONPATH:\$PWD/test ; python test/"$@"
diff --git a/test/unit.py b/test/unit.py
new file mode 100644
index 000000000..0b8acc597
--- /dev/null
+++ b/test/unit.py
@@ -0,0 +1,19 @@
+import os
+import unittest
+from os.path import expanduser
+
+from util.Docker import Docker
+
+
+class JavaServices(unittest.TestCase):
+ def test_maven(self):
+ script_dir = os.path.dirname(os.path.realpath(__file__))
+ code_dir = script_dir + "/.."
+ home = expanduser("~")
+ command = ['docker', 'run', '--rm', '-v', home + '/.m2:/root/.m2', '-v', code_dir + ':/usr/src/mymaven', '-w',
+ '/usr/src/mymaven', 'maven:3.2-jdk-8', 'mvn', 'test']
+ print(Docker().execute(command))
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/test/util/Api.py b/test/util/Api.py
new file mode 100644
index 000000000..34953e00b
--- /dev/null
+++ b/test/util/Api.py
@@ -0,0 +1,10 @@
+import requests
+
+
+class Api:
+ def noResponse(self, url):
+ try:
+ r = requests.get(url, timeout=5)
+ except requests.exceptions.ConnectionError:
+ return True
+ return r.status_code > 299
diff --git a/test/util/Docker.py b/test/util/Docker.py
new file mode 100644
index 000000000..c739f03d7
--- /dev/null
+++ b/test/util/Docker.py
@@ -0,0 +1,39 @@
+import re
+from random import random
+from subprocess import Popen, PIPE
+
+
+# From http://blog.bordage.pro/avoid-docker-py/
+class Docker:
+ def kill_and_remove(self, ctr_name):
+ command = ['docker', 'rm', '-f', ctr_name]
+ self.execute(command)
+
+ def random_container_name(self, prefix):
+ retstr = prefix + '-'
+ for i in range(5):
+ retstr += chr(int(round(random() * (122 - 97) + 97)))
+ return retstr
+
+ def get_container_ip(self, ctr_name):
+ command = ['docker', 'inspect',
+ '--format', '\'{{.NetworkSettings.IPAddress}}\'',
+ ctr_name]
+ return re.sub(r'[^0-9.]*', '', self.execute(command))
+
+ def execute(self, command):
+ print("Running: " + ' '.join(command))
+ p = Popen(command, stdout=PIPE, stderr=PIPE)
+ out = p.stdout.read()
+ stderr = p.stderr.read()
+ if p.wait() != 0:
+ p.stdout.close()
+ p.stderr.close()
+ raise RuntimeError(str(stderr.decode('utf-8')))
+ p.stdout.close()
+ p.stderr.close()
+ return str(out.decode('utf-8'))
+
+ def start_container(self, container_name="", image="", cmd="", host=""):
+ command = ['docker', 'run', '-d', '-h', host, '--name', container_name, image]
+ self.execute(command)
diff --git a/test/util/Dredd.py b/test/util/Dredd.py
new file mode 100644
index 000000000..03b0f899a
--- /dev/null
+++ b/test/util/Dredd.py
@@ -0,0 +1,25 @@
+from util.Docker import Docker
+
+
+class Dredd:
+ image = 'weaveworksdemos/openapi'
+ container_name = ''
+
+ def test_against_endpoint(self, json_spec, endpoint_container_name, api_endpoint, mongo_endpoint_url,
+ mongo_container_name):
+ self.container_name = Docker().random_container_name('openapi')
+ command = ['docker', 'run',
+ '-h', 'openapi',
+ '--name', self.container_name,
+ '--link', mongo_container_name,
+ '--link', endpoint_container_name,
+ '--env', "MONGO_ENDPOINT={0}".format(mongo_endpoint_url),
+ Dredd.image,
+ "/usr/src/app/{0}".format(json_spec),
+ api_endpoint,
+ "-f",
+ "/usr/src/app/hooks.js"
+ ]
+ out = Docker().execute(command)
+ Docker().kill_and_remove(self.container_name)
+ return out
diff --git a/test/util/__init__.py b/test/util/__init__.py
new file mode 100644
index 000000000..e69de29bb