diff --git a/composer.json b/composer.json
index b30cc8775b2c0c5ae99184c4842eaf789cab2ab5..e1e90a8395badb514578521b64a55f3d8cd938ac 100644
--- a/composer.json
+++ b/composer.json
@@ -48,7 +48,8 @@
         "ext-curl": "*",
         "ext-iconv": "*",
         "sjparkinson/static-review": "~4.1",
-        "fabpot/php-cs-fixer": "~1.2"
+        "fabpot/php-cs-fixer": "~1.2",
+        "lusitanian/oauth": "~0.3"
     },
     "replace": {
         "magento/module-admin-notification": "self.version",
diff --git a/composer.lock b/composer.lock
index d2fc6da7fe7b83c465d55c071e449341f86e4cb2..990692a84d28d499a7b9a0b3c3e06b19eabecc7e 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
         "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
         "This file is @generated automatically"
     ],
-    "hash": "d3a510fb8a6b17c084148509d02478d4",
+    "hash": "0c7573953aece5e5227468ebff4539da",
     "packages": [
         {
             "name": "composer/composer",
@@ -1923,6 +1923,68 @@
             ],
             "time": "2014-11-08 02:33:31"
         },
+        {
+            "name": "lusitanian/oauth",
+            "version": "v0.3.5",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/Lusitanian/PHPoAuthLib.git",
+                "reference": "ac5a1cd5a4519143728dce2213936eea302edf8a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/Lusitanian/PHPoAuthLib/zipball/ac5a1cd5a4519143728dce2213936eea302edf8a",
+                "reference": "ac5a1cd5a4519143728dce2213936eea302edf8a",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "3.7.*",
+                "predis/predis": "0.8.*@dev",
+                "symfony/http-foundation": "~2.1"
+            },
+            "suggest": {
+                "ext-openssl": "Allows for usage of secure connections with the stream-based HTTP client.",
+                "predis/predis": "Allows using the Redis storage backend.",
+                "symfony/http-foundation": "Allows using the Symfony Session storage backend."
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "0.1-dev"
+                }
+            },
+            "autoload": {
+                "psr-0": {
+                    "OAuth": "src",
+                    "OAuth\\Unit": "tests"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "David Desberg",
+                    "email": "david@daviddesberg.com"
+                },
+                {
+                    "name": "Pieter Hordijk",
+                    "email": "info@pieterhordijk.com"
+                }
+            ],
+            "description": "PHP 5.3+ oAuth 1/2 Library",
+            "keywords": [
+                "Authentication",
+                "authorization",
+                "oauth",
+                "security"
+            ],
+            "time": "2014-09-05 15:19:58"
+        },
         {
             "name": "pdepend/pdepend",
             "version": "2.0.4",
@@ -2001,16 +2063,16 @@
         },
         {
             "name": "phpunit/php-code-coverage",
-            "version": "2.0.13",
+            "version": "2.0.14",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
-                "reference": "0e7d2eec5554f869fa7a4ec2d21e4b37af943ea5"
+                "reference": "ca158276c1200cc27f5409a5e338486bc0b4fc94"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/0e7d2eec5554f869fa7a4ec2d21e4b37af943ea5",
-                "reference": "0e7d2eec5554f869fa7a4ec2d21e4b37af943ea5",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ca158276c1200cc27f5409a5e338486bc0b4fc94",
+                "reference": "ca158276c1200cc27f5409a5e338486bc0b4fc94",
                 "shasum": ""
             },
             "require": {
@@ -2062,7 +2124,7 @@
                 "testing",
                 "xunit"
             ],
-            "time": "2014-12-03 06:41:44"
+            "time": "2014-12-26 13:28:33"
         },
         {
             "name": "phpunit/php-file-iterator",
diff --git a/dev/tests/api-functional/.gitignore b/dev/tests/api-functional/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..77d5f53390c6c26ad0b8b00ad8dd577abedf89e8
--- /dev/null
+++ b/dev/tests/api-functional/.gitignore
@@ -0,0 +1,3 @@
+/*.xml
+/var/
+/config/*.php
diff --git a/dev/tests/api-functional/_files/Magento/TestModule1/Controller/CookieTester.php b/dev/tests/api-functional/_files/Magento/TestModule1/Controller/CookieTester.php
new file mode 100644
index 0000000000000000000000000000000000000000..e6bc716ff5529448ef5542c12cbe4ff0c93f05a8
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule1/Controller/CookieTester.php
@@ -0,0 +1,69 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestModule1\Controller;
+
+use Magento\Framework\App\RequestInterface;
+use Magento\Framework\Stdlib\Cookie\CookieMetadataFactory;
+use Magento\Framework\Stdlib\Cookie\PhpCookieManager;
+
+/**
+ * Controller for testing the CookieManager.
+ *
+ */
+class CookieTester extends \Magento\Framework\App\Action\Action
+{
+    /** @var PhpCookieManager */
+    protected $cookieManager;
+
+    /** @var  CookieMetadataFactory */
+    protected $cookieMetadataFactory;
+
+    /**
+     * @param \Magento\Framework\App\Action\Context $context
+     * @param PhpCookieManager $cookieManager
+     * @param CookieMetadataFactory $cookieMetadataFactory
+     */
+    public function __construct(
+        \Magento\Framework\App\Action\Context $context,
+        PhpCookieManager $cookieManager,
+        CookieMetadataFactory $cookieMetadataFactory
+    ) {
+        $this->cookieManager = $cookieManager;
+        $this->cookieMetadataFacory = $cookieMetadataFactory;
+        parent::__construct($context);
+    }
+
+    /**
+     * Retrieve cookie metadata factory
+     */
+    protected function getCookieMetadataFactory()
+    {
+        return $this->cookieMetadataFacory;
+    }
+
+    /**
+     * Retrieve cookie metadata factory
+     */
+    protected function getCookieManager()
+    {
+        return $this->cookieManager;
+    }
+
+    /**
+     * Dispatch request
+     *
+     * @param RequestInterface $request
+     * @return \Magento\Framework\App\ResponseInterface
+     */
+    public function dispatch(RequestInterface $request)
+    {
+        if (!$this->getRequest()->isDispatched()) {
+            parent::dispatch($request);
+        }
+
+        $result = parent::dispatch($request);
+        return $result;
+    }
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModule1/Controller/CookieTester/DeleteCookie.php b/dev/tests/api-functional/_files/Magento/TestModule1/Controller/CookieTester/DeleteCookie.php
new file mode 100644
index 0000000000000000000000000000000000000000..f7ef99d6b520524f9a088367d6c46ab3df9e47b3
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule1/Controller/CookieTester/DeleteCookie.php
@@ -0,0 +1,21 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestModule1\Controller\CookieTester;
+
+/**
+ * Controller to test deletion of a cookie
+ */
+class DeleteCookie extends \Magento\TestModule1\Controller\CookieTester
+{
+    /**
+     *
+     * @return void
+     */
+    public function execute()
+    {
+        $cookieName = $this->getRequest()->getParam('cookie_name');
+        $this->getCookieManager()->deleteCookie($cookieName);
+    }
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModule1/Controller/CookieTester/SetPublicCookie.php b/dev/tests/api-functional/_files/Magento/TestModule1/Controller/CookieTester/SetPublicCookie.php
new file mode 100644
index 0000000000000000000000000000000000000000..c72395edf16c766a7db3a8306c389ea704072932
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule1/Controller/CookieTester/SetPublicCookie.php
@@ -0,0 +1,49 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestModule1\Controller\CookieTester;
+
+/**
+ */
+class SetPublicCookie extends \Magento\TestModule1\Controller\CookieTester
+{
+    /**
+     * Sets a public cookie with data from url parameters
+     *
+     * @return void
+     */
+    public function execute()
+    {
+        $publicCookieMetadata = $this->getCookieMetadataFactory()->createPublicCookieMetadata();
+
+        $cookieDomain = $this->getRequest()->getParam('cookie_domain');
+        if ($cookieDomain !== null) {
+            $publicCookieMetadata->setDomain($cookieDomain);
+        }
+
+        $cookiePath = $this->getRequest()->getParam('cookie_path');
+        if ($cookiePath !== null) {
+            $publicCookieMetadata->setPath($cookiePath);
+        }
+
+        $cookieDuration = $this->getRequest()->getParam('cookie_duration');
+        if ($cookieDuration !== null) {
+            $publicCookieMetadata->setDuration($cookieDuration);
+        }
+
+        $httpOnly = $this->getRequest()->getParam('cookie_httponly');
+        if ($httpOnly !== null) {
+            $publicCookieMetadata->setHttpOnly($httpOnly);
+        }
+
+        $secure = $this->getRequest()->getParam('cookie_secure');
+        if ($secure !== null) {
+            $publicCookieMetadata->setSecure($secure);
+        }
+
+        $cookieName = $this->getRequest()->getParam('cookie_name');
+        $cookieValue = $this->getRequest()->getParam('cookie_value');
+        $this->getCookieManager()->setPublicCookie($cookieName, $cookieValue, $publicCookieMetadata);
+    }
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModule1/Controller/CookieTester/SetSensitiveCookie.php b/dev/tests/api-functional/_files/Magento/TestModule1/Controller/CookieTester/SetSensitiveCookie.php
new file mode 100644
index 0000000000000000000000000000000000000000..3cb0d47fae43535e7fdda6799e0a7aa4d037eb6f
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule1/Controller/CookieTester/SetSensitiveCookie.php
@@ -0,0 +1,33 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestModule1\Controller\CookieTester;
+
+/**
+ */
+class SetSensitiveCookie extends \Magento\TestModule1\Controller\CookieTester
+{
+    /**
+     * Sets a sensitive cookie with data from url parameters
+     *
+     * @return void
+     */
+    public function execute()
+    {
+        $sensitiveCookieMetadata = $this->getCookieMetadataFactory()->createSensitiveCookieMetadata();
+
+        $cookieDomain = $this->getRequest()->getParam('cookie_domain');
+        if ($cookieDomain !== null) {
+            $sensitiveCookieMetadata->setDomain($cookieDomain);
+        }
+        $cookiePath = $this->getRequest()->getParam('cookie_domain');
+        if ($cookiePath !== null) {
+            $sensitiveCookieMetadata->setPath($cookiePath);
+        }
+
+        $cookieName = $this->getRequest()->getParam('cookie_name');
+        $cookieValue = $this->getRequest()->getParam('cookie_value');
+        $this->getCookieManager()->setSensitiveCookie($cookieName, $cookieValue, $sensitiveCookieMetadata);
+    }
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModule1/Service/V1/AllSoapAndRest.php b/dev/tests/api-functional/_files/Magento/TestModule1/Service/V1/AllSoapAndRest.php
new file mode 100644
index 0000000000000000000000000000000000000000..025d819004d0658f650faf8b0a1bcad6fbff4eb7
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule1/Service/V1/AllSoapAndRest.php
@@ -0,0 +1,108 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestModule1\Service\V1;
+
+use Magento\TestModule1\Service\V1\Entity\CustomAttributeDataObjectBuilder;
+use Magento\TestModule1\Service\V1\Entity\Item;
+use Magento\TestModule1\Service\V1\Entity\ItemBuilder;
+
+class AllSoapAndRest implements \Magento\TestModule1\Service\V1\AllSoapAndRestInterface
+{
+    /**
+     * @var ItemBuilder
+     */
+    protected $itemBuilder;
+
+    /**
+     * @var CustomAttributeDataObjectBuilder
+     */
+    protected $customAttributeDataObjectBuilder;
+
+    /**
+     * @param ItemBuilder $itemBuilder
+     * @param CustomAttributeDataObjectBuilder $customAttributeNestedDataObjectBuilder
+     */
+    public function __construct(
+        ItemBuilder $itemBuilder,
+        CustomAttributeDataObjectBuilder $customAttributeNestedDataObjectBuilder
+    ) {
+        $this->itemBuilder = $itemBuilder;
+        $this->customAttributeDataObjectBuilder = $customAttributeNestedDataObjectBuilder;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function item($itemId)
+    {
+        return $this->itemBuilder->setItemId($itemId)->setName('testProduct1')->create();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function items()
+    {
+        $result1 = $this->itemBuilder->setItemId(1)->setName('testProduct1')->create();
+        $result2 = $this->itemBuilder->setItemId(2)->setName('testProduct2')->create();
+
+        return [$result1, $result2];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function create($name)
+    {
+        return $this->itemBuilder->setItemId(rand())->setName($name)->create();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function update(Item $entityItem)
+    {
+        return $this->itemBuilder->setItemId($entityItem->getItemId())
+            ->setName('Updated' . $entityItem->getName())
+            ->create();
+    }
+
+    public function testOptionalParam($name = null)
+    {
+        if (is_null($name)) {
+            return $this->itemBuilder->setItemId(3)->setName('No Name')->create();
+        } else {
+            return $this->itemBuilder->setItemId(3)->setName($name)->create();
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function itemAnyType(Item $item)
+    {
+        return $item;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getPreconfiguredItem()
+    {
+        $customAttributeDataObject = $this->customAttributeDataObjectBuilder
+            ->setName('nameValue')
+            ->setCustomAttribute('custom_attribute_int', 1)
+            ->create();
+
+        $item = $this->itemBuilder
+            ->setItemId(1)
+            ->setName('testProductAnyType')
+            ->setCustomAttribute('custom_attribute_data_object', $customAttributeDataObject)
+            ->setCustomAttribute('custom_attribute_string', 'someStringValue')
+            ->create();
+
+        return $item;
+    }
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModule1/Service/V1/AllSoapAndRestInterface.php b/dev/tests/api-functional/_files/Magento/TestModule1/Service/V1/AllSoapAndRestInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..d43d1553561de2d74d9fd4fcf6a271b124f02a1d
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule1/Service/V1/AllSoapAndRestInterface.php
@@ -0,0 +1,50 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestModule1\Service\V1;
+
+use Magento\TestModule1\Service\V1\Entity\Item;
+
+interface AllSoapAndRestInterface
+{
+    /**
+     * @param int $itemId
+     * @return \Magento\TestModule1\Service\V1\Entity\Item
+     */
+    public function item($itemId);
+
+    /**
+     * @param string $name
+     * @return \Magento\TestModule1\Service\V1\Entity\Item
+     */
+    public function create($name);
+
+    /**
+     * @param \Magento\TestModule1\Service\V1\Entity\Item $entityItem
+     * @return \Magento\TestModule1\Service\V1\Entity\Item
+     */
+    public function update(Item $entityItem);
+
+    /**
+     * @return \Magento\TestModule1\Service\V1\Entity\Item[]
+     */
+    public function items();
+
+    /**
+     * @param string $name
+     * @return \Magento\TestModule1\Service\V1\Entity\Item
+     */
+    public function testOptionalParam($name = null);
+
+    /**
+     * @param \Magento\TestModule1\Service\V1\Entity\Item $entityItem
+     * @return \Magento\TestModule1\Service\V1\Entity\Item
+     */
+    public function itemAnyType(Item $entityItem);
+
+    /**
+     * @return \Magento\TestModule1\Service\V1\Entity\Item
+     */
+    public function getPreconfiguredItem();
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModule1/Service/V1/Entity/CustomAttributeDataObject.php b/dev/tests/api-functional/_files/Magento/TestModule1/Service/V1/Entity/CustomAttributeDataObject.php
new file mode 100644
index 0000000000000000000000000000000000000000..b3cf77d44933f6d2811611705205d48117a61b94
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule1/Service/V1/Entity/CustomAttributeDataObject.php
@@ -0,0 +1,16 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestModule1\Service\V1\Entity;
+
+class CustomAttributeDataObject extends \Magento\Framework\Api\AbstractExtensibleObject
+{
+    /**
+     * @return string
+     */
+    public function getName()
+    {
+        return $this->_data['name'];
+    }
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModule1/Service/V1/Entity/CustomAttributeDataObjectBuilder.php b/dev/tests/api-functional/_files/Magento/TestModule1/Service/V1/Entity/CustomAttributeDataObjectBuilder.php
new file mode 100644
index 0000000000000000000000000000000000000000..e128de99359f774aa1508e4b08252b1f179c678d
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule1/Service/V1/Entity/CustomAttributeDataObjectBuilder.php
@@ -0,0 +1,20 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+namespace Magento\TestModule1\Service\V1\Entity;
+
+class CustomAttributeDataObjectBuilder extends \Magento\Framework\Api\ExtensibleObjectBuilder
+{
+    /**
+     * @param string $name
+     *
+     * @return $this
+     */
+    public function setName($name)
+    {
+        $this->data['name'] = $name;
+        return $this;
+    }
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModule1/Service/V1/Entity/CustomAttributeNestedDataObject.php b/dev/tests/api-functional/_files/Magento/TestModule1/Service/V1/Entity/CustomAttributeNestedDataObject.php
new file mode 100644
index 0000000000000000000000000000000000000000..6b439713fafdcecd9bcb3e1fe3073b9381cda4fb
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule1/Service/V1/Entity/CustomAttributeNestedDataObject.php
@@ -0,0 +1,16 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestModule1\Service\V1\Entity;
+
+class CustomAttributeNestedDataObject extends \Magento\Framework\Api\AbstractExtensibleObject
+{
+    /**
+     * @return string
+     */
+    public function getName()
+    {
+        return $this->_data['name'];
+    }
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModule1/Service/V1/Entity/CustomAttributeNestedDataObjectBuilder.php b/dev/tests/api-functional/_files/Magento/TestModule1/Service/V1/Entity/CustomAttributeNestedDataObjectBuilder.php
new file mode 100644
index 0000000000000000000000000000000000000000..4bf501ac727da54c801558326e1aa21254292b02
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule1/Service/V1/Entity/CustomAttributeNestedDataObjectBuilder.php
@@ -0,0 +1,20 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+namespace Magento\TestModule1\Service\V1\Entity;
+
+class CustomAttributeNestedDataObjectBuilder extends \Magento\Framework\Api\ExtensibleObjectBuilder
+{
+    /**
+     * @param string $name
+     *
+     * @return $this
+     */
+    public function setName($name)
+    {
+        $this->data['name'] = $name;
+        return $this;
+    }
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModule1/Service/V1/Entity/Eav/AttributeMetadata.php b/dev/tests/api-functional/_files/Magento/TestModule1/Service/V1/Entity/Eav/AttributeMetadata.php
new file mode 100644
index 0000000000000000000000000000000000000000..134623a864f693e9d8a755b46808602970bd066c
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule1/Service/V1/Entity/Eav/AttributeMetadata.php
@@ -0,0 +1,42 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestModule1\Service\V1\Entity\Eav;
+
+use Magento\Framework\Api\AbstractExtensibleObject;
+use Magento\Framework\Api\MetadataObjectInterface;
+
+/**
+ * Class AttributeMetadata
+ */
+class AttributeMetadata extends AbstractExtensibleObject implements MetadataObjectInterface
+{
+    /**#@+
+     * Constants used as keys into $_data
+     */
+    const ATTRIBUTE_ID = 'attribute_id';
+
+    const ATTRIBUTE_CODE = 'attribute_code';
+    /**#@-*/
+
+    /**
+     * Retrieve id of the attribute.
+     *
+     * @return string|null
+     */
+    public function getAttributeId()
+    {
+        return $this->_get(self::ATTRIBUTE_ID);
+    }
+
+    /**
+     * Retrieve code of the attribute.
+     *
+     * @return string|null
+     */
+    public function getAttributeCode()
+    {
+        return $this->_get(self::ATTRIBUTE_CODE);
+    }
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModule1/Service/V1/Entity/Eav/AttributeMetadataBuilder.php b/dev/tests/api-functional/_files/Magento/TestModule1/Service/V1/Entity/Eav/AttributeMetadataBuilder.php
new file mode 100644
index 0000000000000000000000000000000000000000..b96ef2b4f5da204af0511149eea26edd36d4ec4f
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule1/Service/V1/Entity/Eav/AttributeMetadataBuilder.php
@@ -0,0 +1,36 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestModule1\Service\V1\Entity\Eav;
+
+use Magento\Framework\Api\AttributeMetadataBuilderInterface;
+use Magento\Framework\Api\ExtensibleObjectBuilder;
+
+/**
+ * Class AttributeMetadataBuilder
+ */
+class AttributeMetadataBuilder extends ExtensibleObjectBuilder implements AttributeMetadataBuilderInterface
+{
+    /**
+     * Set attribute id
+     *
+     * @param  int $attributeId
+     * @return $this
+     */
+    public function setAttributeId($attributeId)
+    {
+        return $this->_set(AttributeMetadata::ATTRIBUTE_ID, $attributeId);
+    }
+
+    /**
+     * Set attribute code
+     *
+     * @param  string $attributeCode
+     * @return $this
+     */
+    public function setAttributeCode($attributeCode)
+    {
+        return $this->_set(AttributeMetadata::ATTRIBUTE_CODE, $attributeCode);
+    }
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModule1/Service/V1/Entity/Item.php b/dev/tests/api-functional/_files/Magento/TestModule1/Service/V1/Entity/Item.php
new file mode 100644
index 0000000000000000000000000000000000000000..d307ac8f6a8d1fe71a808b529e913df7d4f74cfe
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule1/Service/V1/Entity/Item.php
@@ -0,0 +1,24 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestModule1\Service\V1\Entity;
+
+class Item extends \Magento\Framework\Api\AbstractExtensibleObject
+{
+    /**
+     * @return int
+     */
+    public function getItemId()
+    {
+        return $this->_data['item_id'];
+    }
+
+    /**
+     * @return string
+     */
+    public function getName()
+    {
+        return $this->_data['name'];
+    }
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModule1/Service/V1/Entity/ItemBuilder.php b/dev/tests/api-functional/_files/Magento/TestModule1/Service/V1/Entity/ItemBuilder.php
new file mode 100644
index 0000000000000000000000000000000000000000..c13fd4b8e898074d2dade57b556e7e668ad92a03
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule1/Service/V1/Entity/ItemBuilder.php
@@ -0,0 +1,51 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestModule1\Service\V1\Entity;
+
+class ItemBuilder extends \Magento\Framework\Api\ExtensibleObjectBuilder
+{
+    /**#@+
+     * Custom attribute code constants
+     */
+    const CUSTOM_ATTRIBUTE_1 = 'custom_attribute1';
+    const CUSTOM_ATTRIBUTE_2 = 'custom_attribute2';
+    const CUSTOM_ATTRIBUTE_3 = 'custom_attribute3';
+    /**#@-*/
+
+    /**
+     * @param int $itemId
+     *
+     * @return \Magento\TestModule1\Service\V1\Entity\ItemBuilder
+     */
+    public function setItemId($itemId)
+    {
+        $this->data['item_id'] = $itemId;
+        return $this;
+    }
+
+    /**
+     * @param string $name
+     *
+     * @return \Magento\TestModule1\Service\V1\Entity\ItemBuilder
+     */
+    public function setName($name)
+    {
+        $this->data['name'] = $name;
+        return $this;
+    }
+
+    /**
+     * Template method used to configure the attribute codes for the custom attributes
+     *
+     * @return string[]
+     */
+    public function getCustomAttributesCodes()
+    {
+        return array_merge(
+            parent::getCustomAttributesCodes(),
+            [self::CUSTOM_ATTRIBUTE_1, self::CUSTOM_ATTRIBUTE_2, self::CUSTOM_ATTRIBUTE_3]
+        );
+    }
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModule1/Service/V2/AllSoapAndRest.php b/dev/tests/api-functional/_files/Magento/TestModule1/Service/V2/AllSoapAndRest.php
new file mode 100644
index 0000000000000000000000000000000000000000..ef17f0c5749bef4262d3e9c1f5acb09445f69c0b
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule1/Service/V2/AllSoapAndRest.php
@@ -0,0 +1,85 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestModule1\Service\V2;
+
+use Magento\TestModule1\Service\V2\Entity\Item;
+use Magento\TestModule1\Service\V2\Entity\ItemBuilder;
+
+class AllSoapAndRest implements \Magento\TestModule1\Service\V2\AllSoapAndRestInterface
+{
+    /**
+     * @var ItemBuilder
+     */
+    protected $itemBuilder;
+
+    /**
+     * @param ItemBuilder $itemBuilder
+     */
+    public function __construct(ItemBuilder $itemBuilder)
+    {
+        $this->itemBuilder = $itemBuilder;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function item($id)
+    {
+        return $this->itemBuilder->setId($id)->setName('testProduct1')->setPrice('1')->create();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function items($filters = [], $sortOrder = 'ASC')
+    {
+        $result = [];
+        $firstItem = $this->itemBuilder->setId(1)->setName('testProduct1')->setPrice('1')->create();
+        $secondItem = $this->itemBuilder->setId(2)->setName('testProduct2')->setPrice('2')->create();
+
+        /** Simple filtration implementation */
+        if (!empty($filters)) {
+            /** @var \Magento\Framework\Api\Filter $filter */
+            foreach ($filters as $filter) {
+                if ('id' == $filter->getField() && $filter->getValue() == 1) {
+                    $result[] = $firstItem;
+                } elseif ('id' == $filter->getField() && $filter->getValue() == 2) {
+                    $result[] = $secondItem;
+                }
+            }
+        } else {
+            /** No filter is specified. */
+            $result = [$firstItem, $secondItem];
+        }
+        return $result;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function create($name)
+    {
+        return $this->itemBuilder->setId(rand())->setName($name)->setPrice('10')->create();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function update(Item $entityItem)
+    {
+        return $this->itemBuilder
+            ->setId($entityItem->getId())
+            ->setName('Updated' . $entityItem->getName())
+            ->setPrice('5')->create();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function delete($id)
+    {
+        return $this->itemBuilder->setId($id)->setName('testProduct1')->setPrice('1')->create();
+    }
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModule1/Service/V2/AllSoapAndRestInterface.php b/dev/tests/api-functional/_files/Magento/TestModule1/Service/V2/AllSoapAndRestInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..0507a01422b72fbc9c0af63aaf1800c592a10dcf
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule1/Service/V2/AllSoapAndRestInterface.php
@@ -0,0 +1,51 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestModule1\Service\V2;
+
+use Magento\TestModule1\Service\V2\Entity\Item;
+
+interface AllSoapAndRestInterface
+{
+    /**
+     * Get item.
+     *
+     * @param int $id
+     * @return \Magento\TestModule1\Service\V2\Entity\Item
+     */
+    public function item($id);
+
+    /**
+     * Create item.
+     *
+     * @param string $name
+     * @return \Magento\TestModule1\Service\V2\Entity\Item
+     */
+    public function create($name);
+
+    /**
+     * Update item.
+     *
+     * @param \Magento\TestModule1\Service\V2\Entity\Item $entityItem
+     * @return \Magento\TestModule1\Service\V2\Entity\Item
+     */
+    public function update(Item $entityItem);
+
+    /**
+     * Retrieve a list of items.
+     *
+     * @param \Magento\Framework\Api\Filter[] $filters
+     * @param string $sortOrder
+     * @return \Magento\TestModule1\Service\V2\Entity\Item[]
+     */
+    public function items($filters = [], $sortOrder = 'ASC');
+
+    /**
+     * Delete an item.
+     *
+     * @param int $id
+     * @return \Magento\TestModule1\Service\V2\Entity\Item
+     */
+    public function delete($id);
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModule1/Service/V2/Entity/Item.php b/dev/tests/api-functional/_files/Magento/TestModule1/Service/V2/Entity/Item.php
new file mode 100644
index 0000000000000000000000000000000000000000..79438931a5dd35aee7b9ac923dacfc14417189b8
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule1/Service/V2/Entity/Item.php
@@ -0,0 +1,32 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestModule1\Service\V2\Entity;
+
+class Item extends \Magento\Framework\Api\AbstractExtensibleObject
+{
+    /**
+     * @return int
+     */
+    public function getId()
+    {
+        return $this->_data['id'];
+    }
+
+    /**
+     * @return string
+     */
+    public function getName()
+    {
+        return $this->_data['name'];
+    }
+
+    /**
+     * @return string
+     */
+    public function getPrice()
+    {
+        return $this->_data['price'];
+    }
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModule1/Service/V2/Entity/ItemBuilder.php b/dev/tests/api-functional/_files/Magento/TestModule1/Service/V2/Entity/ItemBuilder.php
new file mode 100644
index 0000000000000000000000000000000000000000..d28d791b6b8fcf4a337000f01ff4db1ea7a7d5de
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule1/Service/V2/Entity/ItemBuilder.php
@@ -0,0 +1,41 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestModule1\Service\V2\Entity;
+
+class ItemBuilder extends \Magento\Framework\Api\ExtensibleObjectBuilder
+{
+    /**
+     * @param int $id
+     *
+     * @return \Magento\TestModule1\Service\V2\Entity\ItemBuilder
+     */
+    public function setId($id)
+    {
+        $this->data['id'] = $id;
+        return $this;
+    }
+
+    /**
+     * @param string $name
+     *
+     * @return \Magento\TestModule1\Service\V2\Entity\ItemBuilder
+     */
+    public function setName($name)
+    {
+        $this->data['name'] = $name;
+        return $this;
+    }
+
+    /**
+     * @param string $price
+     *
+     * @return \Magento\TestModule1\Service\V2\Entity\ItemBuilder
+     */
+    public function setPrice($price)
+    {
+        $this->data['price'] = $price;
+        return $this;
+    }
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModule1/etc/acl.xml b/dev/tests/api-functional/_files/Magento/TestModule1/etc/acl.xml
new file mode 100644
index 0000000000000000000000000000000000000000..e6fcca6add8e3a5f1cb6ba3dd094507e532f0573
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule1/etc/acl.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0"?>
+<!--
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+-->
+<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../lib/internal/Magento/Framework/Acl/etc/acl.xsd">
+    <acl>
+        <resources>
+            <resource id="Magento_Adminhtml::admin">
+                <resource id="Magento_TestModule1::all" title="TestModule1" sortOrder="1">
+                    <resource id="Magento_TestModule1::resource1" title="Resource1" sortOrder="20"/>
+                    <resource id="Magento_TestModule1::resource2" title="Resource2" sortOrder="10"/>
+                    <resource id="Magento_TestModule1::resource3" title="Resource3" sortOrder="30"/>
+                </resource>
+            </resource>
+        </resources>
+    </acl>
+</config>
diff --git a/dev/tests/api-functional/_files/Magento/TestModule1/etc/data_object.xml b/dev/tests/api-functional/_files/Magento/TestModule1/etc/data_object.xml
new file mode 100644
index 0000000000000000000000000000000000000000..f30038e83f43424f97cd1da59114a062ec27db19
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule1/etc/data_object.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0"?>
+<!--
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+-->
+<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../lib/internal/Magento/Framework/Api/etc/data_object.xsd">
+    <custom_attributes for="Magento\TestModule1\Service\V1\Entity\Item">
+        <attribute code="custom_attribute_data_object" type="Magento\TestModule1\Service\V1\Entity\CustomAttributeDataObject" />
+        <attribute code="custom_attribute_string" type="string" />
+    </custom_attributes>
+    <custom_attributes for="Magento\TestModule1\Service\V1\Entity\CustomAttributeDataObject">
+        <attribute code="custom_attribute_nested" type="Magento\TestModule1\Service\V1\Entity\CustomAttributeNestedDataObject" />
+        <attribute code="custom_attribute_int" type="int" />
+    </custom_attributes>
+</config>
diff --git a/dev/tests/api-functional/_files/Magento/TestModule1/etc/di.xml b/dev/tests/api-functional/_files/Magento/TestModule1/etc/di.xml
new file mode 100644
index 0000000000000000000000000000000000000000..977ca625f28c51fc95cbef8906214c51c5b6e62f
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule1/etc/di.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0"?>
+<!--
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+-->
+<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../lib/internal/Magento/Framework/ObjectManager/etc/config.xsd">
+    <preference for="Magento\TestModule1\Service\V1\AllSoapAndRestInterface" type="Magento\TestModule1\Service\V1\AllSoapAndRest" />
+    <preference for="Magento\TestModule1\Service\V2\AllSoapAndRestInterface" type="Magento\TestModule1\Service\V2\AllSoapAndRest" />
+
+    <virtualType name="Magento\TestModule1\Service\Config\TestModule1MetadataConfig" type="Magento\Framework\Api\Config\MetadataConfig">
+        <arguments>
+            <argument name="attributeMetadataBuilder" xsi:type="object">Magento\TestModule1\Service\V1\Entity\Eav\AttributeMetadataBuilder</argument>
+            <argument name="dataObjectClassName" xsi:type="string">Magento\TestModule1\Service\V1\Entity\Item</argument>
+        </arguments>
+    </virtualType>
+    <type name="Magento\TestModule1\Service\V1\Entity\ItemBuilder">
+        <arguments>
+            <argument name="metadataService" xsi:type="object">Magento\TestModule1\Service\Config\TestModule1MetadataConfig</argument>
+        </arguments>
+    </type>
+</config>
diff --git a/dev/tests/api-functional/_files/Magento/TestModule1/etc/frontend/routes.xml b/dev/tests/api-functional/_files/Magento/TestModule1/etc/frontend/routes.xml
new file mode 100644
index 0000000000000000000000000000000000000000..f6984e2d6d74dcb50b9e77f2d348d94ca405ef63
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule1/etc/frontend/routes.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<!--
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+-->
+<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../../lib/internal/Magento/Framework/App/etc/routes.xsd">
+    <router id="standard">
+        <route id="testmoduleone" frontName="testmoduleone">
+            <module name="Magento_TestModule1" />
+        </route>
+    </router>
+</config>
\ No newline at end of file
diff --git a/dev/tests/api-functional/_files/Magento/TestModule1/etc/module.xml b/dev/tests/api-functional/_files/Magento/TestModule1/etc/module.xml
new file mode 100644
index 0000000000000000000000000000000000000000..ba0d441958811635aaa373abe641378deefe97e2
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule1/etc/module.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0"?>
+<!--
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+-->
+<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../lib/internal/Magento/Framework/Module/etc/module.xsd">
+    <module name="Magento_TestModule1" schema_version="1.0"/>
+</config>
diff --git a/dev/tests/api-functional/_files/Magento/TestModule1/etc/webapi.xml b/dev/tests/api-functional/_files/Magento/TestModule1/etc/webapi.xml
new file mode 100644
index 0000000000000000000000000000000000000000..874afa7713840d9a7ed9015a033b4e556dd19ef6
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule1/etc/webapi.xml
@@ -0,0 +1,99 @@
+<?xml version="1.0"?>
+<!--
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+-->
+<routes xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../app/code/Magento/Webapi/etc/webapi.xsd">
+
+    <route method="GET" url="/V1/testmodule1/overwritten">
+        <service class="Magento\TestModule1\Service\V1\AllSoapAndRestInterface" method="item" />
+        <resources>
+            <resource ref="Magento_TestModule1::resource1" />
+        </resources>
+        <data>
+            <parameter name="itemId" force="true">-55</parameter>
+        </data>
+    </route>
+
+    <route method="POST" url="/V1/testmodule1/testOptionalParam">
+        <service class="Magento\TestModule1\Service\V1\AllSoapAndRestInterface" method="testOptionalParam" />
+        <resources>
+            <resource ref="Magento_TestModule1::resource1" />
+        </resources>
+        <data>
+            <parameter name="name">Default Name</parameter>
+        </data>
+    </route>
+
+    <route method="GET" url="/V1/testmodule1/:itemId">
+        <service class="Magento\TestModule1\Service\V1\AllSoapAndRestInterface" method="item" />
+        <resources>
+            <resource ref="Magento_TestModule1::resource1" />
+        </resources>
+    </route>
+    <route method="GET" url="/V1/testmodule1">
+        <service class="Magento\TestModule1\Service\V1\AllSoapAndRestInterface" method="items" />
+        <resources>
+            <resource ref="Magento_TestModule1::resource2" />
+        </resources>
+    </route>
+    <route method="POST" url="/V1/testmodule1">
+        <service class="Magento\TestModule1\Service\V1\AllSoapAndRestInterface" method="create" />
+        <resources>
+            <resource ref="Magento_TestModule1::resource3" />
+        </resources>
+    </route>
+    <route method="PUT" url="/V1/testmodule1/:itemId">
+        <service class="Magento\TestModule1\Service\V1\AllSoapAndRestInterface" method="update" />
+        <resources>
+            <resource ref="Magento_TestModule1::resource1" />
+            <resource ref="Magento_TestModule1::resource2" />
+        </resources>
+    </route>
+    <route method="POST" url="/V1/testmodule1/itemAnyType">
+        <service class="Magento\TestModule1\Service\V1\AllSoapAndRestInterface" method="itemAnyType" />
+        <resources>
+            <resource ref="Magento_TestModule1::resource1" />
+            <resource ref="Magento_TestModule1::resource2" />
+        </resources>
+    </route>
+    <route method="GET" url="/V1/testmodule1/itemPreconfigured">
+        <service class="Magento\TestModule1\Service\V1\AllSoapAndRestInterface" method="getPreconfiguredItem" />
+        <resources>
+            <resource ref="Magento_TestModule1::resource1" />
+            <resource ref="Magento_TestModule1::resource2" />
+        </resources>
+    </route>
+    <route method="GET" url="/V2/testmodule1/:id">
+        <service class="Magento\TestModule1\Service\V2\AllSoapAndRestInterface" method="item" />
+        <resources>
+            <resource ref="Magento_TestModule1::resource1" />
+        </resources>
+    </route>
+    <route method="GET" url="/V2/testmodule1">
+        <service class="Magento\TestModule1\Service\V2\AllSoapAndRestInterface" method="items" />
+        <resources>
+            <resource ref="Magento_TestModule1::resource2" />
+        </resources>
+    </route>
+    <route method="POST" url="/V2/testmodule1">
+        <service class="Magento\TestModule1\Service\V2\AllSoapAndRestInterface" method="create" />
+        <resources>
+            <resource ref="Magento_TestModule1::resource3" />
+        </resources>
+    </route>
+    <route method="PUT" url="/V2/testmodule1/:id">
+        <service class="Magento\TestModule1\Service\V2\AllSoapAndRestInterface" method="update" />
+        <resources>
+            <resource ref="Magento_TestModule1::resource1" />
+            <resource ref="Magento_TestModule1::resource2" />
+        </resources>
+    </route>
+    <route method="DELETE" url="/V2/testmodule1/:id">
+        <service class="Magento\TestModule1\Service\V2\AllSoapAndRestInterface" method="delete" />
+        <resources>
+            <resource ref="Magento_TestModule1::resource1" />
+        </resources>
+    </route>
+</routes>
diff --git a/dev/tests/api-functional/_files/Magento/TestModule2/Service/V1/Entity/Item.php b/dev/tests/api-functional/_files/Magento/TestModule2/Service/V1/Entity/Item.php
new file mode 100644
index 0000000000000000000000000000000000000000..ac51c4663a5a779eb72741564a7533ab26a6a1d2
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule2/Service/V1/Entity/Item.php
@@ -0,0 +1,24 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestModule2\Service\V1\Entity;
+
+class Item extends \Magento\Framework\Api\AbstractExtensibleObject
+{
+    /**
+     * @return int
+     */
+    public function getId()
+    {
+        return $this->_data['id'];
+    }
+
+    /**
+     * @return string
+     */
+    public function getName()
+    {
+        return $this->_data['name'];
+    }
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModule2/Service/V1/Entity/ItemBuilder.php b/dev/tests/api-functional/_files/Magento/TestModule2/Service/V1/Entity/ItemBuilder.php
new file mode 100644
index 0000000000000000000000000000000000000000..79b57a34aec3e6f30bdb0cebb6bd7909d4c4095f
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule2/Service/V1/Entity/ItemBuilder.php
@@ -0,0 +1,28 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestModule2\Service\V1\Entity;
+
+class ItemBuilder extends \Magento\Framework\Api\ExtensibleObjectBuilder
+{
+    /**
+     * @param int $id
+     * @return \Magento\TestModule2\Service\V1\Entity\ItemBuilder
+     */
+    public function setId($id)
+    {
+        $this->data['id'] = $id;
+        return $this;
+    }
+
+    /**
+     * @param string $name
+     * @return \Magento\TestModule2\Service\V1\Entity\ItemBuilder
+     */
+    public function setName($name)
+    {
+        $this->data['name'] = $name;
+        return $this;
+    }
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModule2/Service/V1/NoWebApiXml.php b/dev/tests/api-functional/_files/Magento/TestModule2/Service/V1/NoWebApiXml.php
new file mode 100644
index 0000000000000000000000000000000000000000..9a1128502b8936b229283f756170b32ccb7cf6ca
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule2/Service/V1/NoWebApiXml.php
@@ -0,0 +1,60 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestModule2\Service\V1;
+
+use Magento\TestModule2\Service\V1\Entity\Item;
+use Magento\TestModule2\Service\V1\Entity\ItemBuilder;
+
+class NoWebApiXml implements \Magento\TestModule2\Service\V1\NoWebApiXmlInterface
+{
+    /**
+     * @var ItemBuilder
+     */
+    protected $itemBuilder;
+
+    /**
+     * @param ItemBuilder $itemBuilder
+     */
+    public function __construct(ItemBuilder $itemBuilder)
+    {
+        $this->itemBuilder = $itemBuilder;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function item($id)
+    {
+        return $this->itemBuilder->setId($id)->setName('testProduct1')->create();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function items()
+    {
+        $result1 = $this->itemBuilder->setId(1)->setName('testProduct1')->create();
+
+        $result2 = $this->itemBuilder->setId(2)->setName('testProduct2')->create();
+
+        return [$result1, $result2];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function create($name)
+    {
+        return $this->itemBuilder->setId(rand())->setName($name)->create();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function update(Item $item)
+    {
+        return $this->itemBuilder->setId($item->getId())->setName('Updated' . $item->getName())->create();
+    }
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModule2/Service/V1/NoWebApiXmlInterface.php b/dev/tests/api-functional/_files/Magento/TestModule2/Service/V1/NoWebApiXmlInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..474ef888d510ed92a3ee29430ef984d5cbe3db7d
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule2/Service/V1/NoWebApiXmlInterface.php
@@ -0,0 +1,41 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestModule2\Service\V1;
+
+use Magento\TestModule2\Service\V1\Entity\Item;
+
+interface NoWebApiXmlInterface
+{
+    /**
+     * Get an item.
+     *
+     * @param int $id
+     * @return \Magento\TestModule2\Service\V1\Entity\Item
+     */
+    public function item($id);
+
+    /**
+     * Create an item.
+     *
+     * @param string $name
+     * @return \Magento\TestModule2\Service\V1\Entity\Item
+     */
+    public function create($name);
+
+    /**
+     * Update an item.
+     *
+     * @param \Magento\TestModule2\Service\V1\Entity\Item $item
+     * @return \Magento\TestModule2\Service\V1\Entity\Item
+     */
+    public function update(Item $item);
+
+    /**
+     * Retrieve a list of items.
+     *
+     * @return \Magento\TestModule2\Service\V1\Entity\Item[]
+     */
+    public function items();
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModule2/Service/V1/SubsetRest.php b/dev/tests/api-functional/_files/Magento/TestModule2/Service/V1/SubsetRest.php
new file mode 100644
index 0000000000000000000000000000000000000000..0f6d5e9670aa730d3cd0c7ae3e9a1b7be0efe4dc
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule2/Service/V1/SubsetRest.php
@@ -0,0 +1,68 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestModule2\Service\V1;
+
+use Magento\TestModule2\Service\V1\Entity\Item;
+use Magento\TestModule2\Service\V1\Entity\ItemBuilder;
+
+class SubsetRest implements \Magento\TestModule2\Service\V1\SubsetRestInterface
+{
+    /**
+     * @var ItemBuilder
+     */
+    protected $itemBuilder;
+
+    /**
+     * @param ItemBuilder $itemBuilder
+     */
+    public function __construct(ItemBuilder $itemBuilder)
+    {
+        $this->itemBuilder = $itemBuilder;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function item($id)
+    {
+        return $this->itemBuilder->setId($id)->setName('testItem' . $id)->create();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function items()
+    {
+        $result1 = $this->itemBuilder->setId(1)->setName('testItem1')->create();
+
+        $result2 = $this->itemBuilder->setId(2)->setName('testItem2')->create();
+
+        return [$result1, $result2];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function create($name)
+    {
+        return $this->itemBuilder->setId(rand())->setName($name)->create();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function update(Item $item)
+    {
+        return $this->itemBuilder->setId($item->getId())->setName('Updated' . $item->getName())->create();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function remove($id)
+    {
+        return $this->itemBuilder->setId(1)->create();
+    }
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModule2/Service/V1/SubsetRestInterface.php b/dev/tests/api-functional/_files/Magento/TestModule2/Service/V1/SubsetRestInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..c52fd18fb55b8f8d7b227dab223291eaab298eb2
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule2/Service/V1/SubsetRestInterface.php
@@ -0,0 +1,49 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestModule2\Service\V1;
+
+use Magento\TestModule2\Service\V1\Entity\Item;
+
+interface SubsetRestInterface
+{
+    /**
+     * Return a single item.
+     *
+     * @param int $id
+     * @return \Magento\TestModule2\Service\V1\Entity\Item
+     */
+    public function item($id);
+
+    /**
+     * Return multiple items.
+     *
+     * @return \Magento\TestModule2\Service\V1\Entity\Item[]
+     */
+    public function items();
+
+    /**
+     * Create an item.
+     *
+     * @param string $name
+     * @return \Magento\TestModule2\Service\V1\Entity\Item
+     */
+    public function create($name);
+
+    /**
+     * Update an item.
+     *
+     * @param \Magento\TestModule2\Service\V1\Entity\Item $item
+     * @return \Magento\TestModule2\Service\V1\Entity\Item
+     */
+    public function update(Item $item);
+
+    /**
+     * Delete an item.
+     *
+     * @param int $id
+     * @return \Magento\TestModule2\Service\V1\Entity\Item
+     */
+    public function remove($id);
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModule2/etc/acl.xml b/dev/tests/api-functional/_files/Magento/TestModule2/etc/acl.xml
new file mode 100644
index 0000000000000000000000000000000000000000..d6b32a5bf83fb4ec80d8445d3a6688bd6b5d8ddc
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule2/etc/acl.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0"?>
+<!--
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+-->
+<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../lib/internal/Magento/Framework/Acl/etc/acl.xsd">
+    <acl>
+        <resources>
+            <resource id="Magento_Adminhtml::admin">
+                <resource id="Magento_TestModule2::all" title="TestModule2" sortOrder="1">
+                    <resource id="Magento_TestModule2::resource1" title="Resource1" sortOrder="20"/>
+                    <resource id="Magento_TestModule2::resource2" title="Resource2" sortOrder="10"/>
+                    <resource id="Magento_TestModule2::resource3" title="Resource3" sortOrder="30"/>
+                </resource>
+            </resource>
+        </resources>
+    </acl>
+</config>
diff --git a/dev/tests/api-functional/_files/Magento/TestModule2/etc/di.xml b/dev/tests/api-functional/_files/Magento/TestModule2/etc/di.xml
new file mode 100644
index 0000000000000000000000000000000000000000..ffb7f082e52b06e8f7394958ef859f8f1fcb1a05
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule2/etc/di.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0"?>
+<!--
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+-->
+<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../lib/internal/Magento/Framework/ObjectManager/etc/config.xsd">
+    <preference for="Magento\TestModule2\Service\V1\SubsetRestInterface" type="Magento\TestModule2\Service\V1\SubsetRest" />
+    <preference for="Magento\TestModule2\Service\V1\NoWebApiXmlInterface" type="Magento\TestModule2\Service\V1\NoWebApiXml" />
+</config>
diff --git a/dev/tests/api-functional/_files/Magento/TestModule2/etc/module.xml b/dev/tests/api-functional/_files/Magento/TestModule2/etc/module.xml
new file mode 100644
index 0000000000000000000000000000000000000000..6f6c90cfd914f30c214eba82cf3d97126e42ea60
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule2/etc/module.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0"?>
+<!--
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+-->
+<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../lib/internal/Magento/Framework/Module/etc/module.xsd">
+    <module name="Magento_TestModule2" schema_version="1.0"/>
+</config>
diff --git a/dev/tests/api-functional/_files/Magento/TestModule2/etc/schema/AllSoapNoRestV1.xsd b/dev/tests/api-functional/_files/Magento/TestModule2/etc/schema/AllSoapNoRestV1.xsd
new file mode 100644
index 0000000000000000000000000000000000000000..3e7caaa8d480048a83ea35018572ff62bf034ca9
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule2/etc/schema/AllSoapNoRestV1.xsd
@@ -0,0 +1,239 @@
+<!--
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+-->
+<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+    <xsd:complexType name="ItemRequest">
+        <xsd:annotation>
+            <xsd:documentation/>
+            <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap"/>
+        </xsd:annotation>
+        <xsd:sequence>
+            <xsd:element name="id" type="xsd:int">
+                <xsd:annotation>
+                    <xsd:documentation>Entity ID</xsd:documentation>
+                    <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap">
+                        <inf:min/>
+                        <inf:max/>
+                        <inf:callInfo>
+                            <inf:callName>Item</inf:callName>
+                            <inf:requiredInput>Yes</inf:requiredInput>
+                        </inf:callInfo>
+                    </xsd:appinfo>
+                </xsd:annotation>
+            </xsd:element>
+        </xsd:sequence>
+    </xsd:complexType>
+    <xsd:complexType name="ItemResponse">
+        <xsd:annotation>
+            <xsd:documentation>Response container for the Item call.</xsd:documentation>
+            <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap"/>
+        </xsd:annotation>
+        <xsd:sequence>
+            <xsd:element name="id" type="xsd:string">
+                <xsd:annotation>
+                    <xsd:documentation>Default label</xsd:documentation>
+                    <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap">
+                        <inf:maxLength/>
+                        <inf:callInfo>
+                            <inf:callName>Item</inf:callName>
+                            <inf:returned>Always</inf:returned>
+                        </inf:callInfo>
+                    </xsd:appinfo>
+                </xsd:annotation>
+            </xsd:element>
+        </xsd:sequence>
+    </xsd:complexType>
+
+    <xsd:complexType name="ItemsResponse">
+        <xsd:annotation>
+            <xsd:documentation>Response container for the Items call.</xsd:documentation>
+            <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap"/>
+        </xsd:annotation>
+        <xsd:sequence>
+            <xsd:element minOccurs="0" maxOccurs="unbounded" name="complexObjectArray"
+                         type="ItemsArray">
+                <xsd:annotation>
+                    <xsd:documentation>Default label</xsd:documentation>
+                    <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap">
+                        <inf:maxLength/>
+                        <inf:callInfo>
+                            <inf:callName>Items</inf:callName>
+                            <inf:returned>Always</inf:returned>
+                        </inf:callInfo>
+                    </xsd:appinfo>
+                </xsd:annotation>
+            </xsd:element>
+        </xsd:sequence>
+    </xsd:complexType>
+
+
+    <xsd:complexType name="ItemsArray">
+        <xsd:annotation>
+            <xsd:documentation>Response container for the Items call.</xsd:documentation>
+            <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap"/>
+        </xsd:annotation>
+        <xsd:sequence>
+            <xsd:element name="id" type="xsd:string">
+                <xsd:annotation>
+                    <xsd:documentation>Default label</xsd:documentation>
+                    <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap">
+                        <inf:maxLength/>
+                        <inf:callInfo>
+                            <inf:callName>Items</inf:callName>
+                            <inf:returned>Always</inf:returned>
+                        </inf:callInfo>
+                    </xsd:appinfo>
+                </xsd:annotation>
+            </xsd:element>
+            <xsd:element name="name"  type="xsd:string">
+                <xsd:annotation>
+                    <xsd:documentation>Default label</xsd:documentation>
+                    <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap">
+                        <inf:maxLength/>
+                        <inf:callInfo>
+                            <inf:callName>Item</inf:callName>
+                            <inf:returned>Always</inf:returned>
+                        </inf:callInfo>
+                    </xsd:appinfo>
+                </xsd:annotation>
+            </xsd:element>
+        </xsd:sequence>
+    </xsd:complexType>
+
+    <xsd:complexType name="CreateRequest">
+        <xsd:annotation>
+            <xsd:documentation/>
+            <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap"/>
+        </xsd:annotation>
+        <xsd:sequence>
+            <xsd:element name="name"  type="xsd:string">
+                <xsd:annotation>
+                    <xsd:documentation>Default label</xsd:documentation>
+                    <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap">
+                        <inf:maxLength/>
+                        <inf:callInfo>
+                            <inf:callName>Create</inf:callName>
+                            <inf:returned>Always</inf:returned>
+                        </inf:callInfo>
+                    </xsd:appinfo>
+                </xsd:annotation>
+            </xsd:element>
+        </xsd:sequence>
+    </xsd:complexType>
+    <xsd:complexType name="CreateResponse">
+        <xsd:annotation>
+            <xsd:documentation>Response container for the Create call.</xsd:documentation>
+            <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap"/>
+        </xsd:annotation>
+        <xsd:sequence>
+            <xsd:element name="id" type="xsd:string">
+                <xsd:annotation>
+                    <xsd:documentation>Default label</xsd:documentation>
+                    <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap">
+                        <inf:maxLength/>
+                        <inf:callInfo>
+                            <inf:callName>Create</inf:callName>
+                            <inf:returned>Always</inf:returned>
+                        </inf:callInfo>
+                    </xsd:appinfo>
+                </xsd:annotation>
+            </xsd:element>
+            <xsd:element name="name"  type="xsd:string">
+                <xsd:annotation>
+                    <xsd:documentation>Default label</xsd:documentation>
+                    <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap">
+                        <inf:maxLength/>
+                        <inf:callInfo>
+                            <inf:callName>Create</inf:callName>
+                            <inf:returned>Always</inf:returned>
+                        </inf:callInfo>
+                    </xsd:appinfo>
+                </xsd:annotation>
+            </xsd:element>
+        </xsd:sequence>
+    </xsd:complexType>
+    <xsd:complexType name="UpdateRequest">
+        <xsd:annotation>
+            <xsd:documentation/>
+            <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap"/>
+        </xsd:annotation>
+        <xsd:sequence>
+            <xsd:element name="id" type="xsd:int">
+                <xsd:annotation>
+                    <xsd:documentation>Entity ID</xsd:documentation>
+                    <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap">
+                        <inf:min/>
+                        <inf:max/>
+                        <inf:callInfo>
+                            <inf:callName>Update</inf:callName>
+                            <inf:requiredInput>Yes</inf:requiredInput>
+                        </inf:callInfo>
+                    </xsd:appinfo>
+                </xsd:annotation>
+            </xsd:element>
+        </xsd:sequence>
+    </xsd:complexType>
+    <xsd:complexType name="UpdateResponse">
+        <xsd:annotation>
+            <xsd:documentation>Response container for the Update call.</xsd:documentation>
+            <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap"/>
+        </xsd:annotation>
+        <xsd:sequence>
+            <xsd:element name="id" type="xsd:string">
+                <xsd:annotation>
+                    <xsd:documentation>Default label</xsd:documentation>
+                    <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap">
+                        <inf:maxLength/>
+                        <inf:callInfo>
+                            <inf:callName>Update</inf:callName>
+                            <inf:returned>Always</inf:returned>
+                        </inf:callInfo>
+                    </xsd:appinfo>
+                </xsd:annotation>
+            </xsd:element>
+        </xsd:sequence>
+    </xsd:complexType>
+    <xsd:complexType name="RemoveRequest">
+        <xsd:annotation>
+            <xsd:documentation/>
+            <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap"/>
+        </xsd:annotation>
+        <xsd:sequence>
+            <xsd:element name="id" type="xsd:int">
+                <xsd:annotation>
+                    <xsd:documentation>Entity ID</xsd:documentation>
+                    <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap">
+                        <inf:min/>
+                        <inf:max/>
+                        <inf:callInfo>
+                            <inf:callName>Remove</inf:callName>
+                            <inf:requiredInput>Yes</inf:requiredInput>
+                        </inf:callInfo>
+                    </xsd:appinfo>
+                </xsd:annotation>
+            </xsd:element>
+        </xsd:sequence>
+    </xsd:complexType>
+    <xsd:complexType name="RemoveResponse">
+        <xsd:annotation>
+            <xsd:documentation>Response container for the Remove call.</xsd:documentation>
+            <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap"/>
+        </xsd:annotation>
+        <xsd:sequence>
+            <xsd:element name="id" type="xsd:string">
+                <xsd:annotation>
+                    <xsd:documentation>Default label</xsd:documentation>
+                    <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap">
+                        <inf:maxLength/>
+                        <inf:callInfo>
+                            <inf:callName>Remove</inf:callName>
+                            <inf:returned>Always</inf:returned>
+                        </inf:callInfo>
+                    </xsd:appinfo>
+                </xsd:annotation>
+            </xsd:element>
+        </xsd:sequence>
+    </xsd:complexType>
+</xsd:schema>
diff --git a/dev/tests/api-functional/_files/Magento/TestModule2/etc/schema/NoWebApiXmlV1.xsd b/dev/tests/api-functional/_files/Magento/TestModule2/etc/schema/NoWebApiXmlV1.xsd
new file mode 100644
index 0000000000000000000000000000000000000000..3e7caaa8d480048a83ea35018572ff62bf034ca9
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule2/etc/schema/NoWebApiXmlV1.xsd
@@ -0,0 +1,239 @@
+<!--
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+-->
+<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+    <xsd:complexType name="ItemRequest">
+        <xsd:annotation>
+            <xsd:documentation/>
+            <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap"/>
+        </xsd:annotation>
+        <xsd:sequence>
+            <xsd:element name="id" type="xsd:int">
+                <xsd:annotation>
+                    <xsd:documentation>Entity ID</xsd:documentation>
+                    <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap">
+                        <inf:min/>
+                        <inf:max/>
+                        <inf:callInfo>
+                            <inf:callName>Item</inf:callName>
+                            <inf:requiredInput>Yes</inf:requiredInput>
+                        </inf:callInfo>
+                    </xsd:appinfo>
+                </xsd:annotation>
+            </xsd:element>
+        </xsd:sequence>
+    </xsd:complexType>
+    <xsd:complexType name="ItemResponse">
+        <xsd:annotation>
+            <xsd:documentation>Response container for the Item call.</xsd:documentation>
+            <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap"/>
+        </xsd:annotation>
+        <xsd:sequence>
+            <xsd:element name="id" type="xsd:string">
+                <xsd:annotation>
+                    <xsd:documentation>Default label</xsd:documentation>
+                    <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap">
+                        <inf:maxLength/>
+                        <inf:callInfo>
+                            <inf:callName>Item</inf:callName>
+                            <inf:returned>Always</inf:returned>
+                        </inf:callInfo>
+                    </xsd:appinfo>
+                </xsd:annotation>
+            </xsd:element>
+        </xsd:sequence>
+    </xsd:complexType>
+
+    <xsd:complexType name="ItemsResponse">
+        <xsd:annotation>
+            <xsd:documentation>Response container for the Items call.</xsd:documentation>
+            <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap"/>
+        </xsd:annotation>
+        <xsd:sequence>
+            <xsd:element minOccurs="0" maxOccurs="unbounded" name="complexObjectArray"
+                         type="ItemsArray">
+                <xsd:annotation>
+                    <xsd:documentation>Default label</xsd:documentation>
+                    <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap">
+                        <inf:maxLength/>
+                        <inf:callInfo>
+                            <inf:callName>Items</inf:callName>
+                            <inf:returned>Always</inf:returned>
+                        </inf:callInfo>
+                    </xsd:appinfo>
+                </xsd:annotation>
+            </xsd:element>
+        </xsd:sequence>
+    </xsd:complexType>
+
+
+    <xsd:complexType name="ItemsArray">
+        <xsd:annotation>
+            <xsd:documentation>Response container for the Items call.</xsd:documentation>
+            <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap"/>
+        </xsd:annotation>
+        <xsd:sequence>
+            <xsd:element name="id" type="xsd:string">
+                <xsd:annotation>
+                    <xsd:documentation>Default label</xsd:documentation>
+                    <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap">
+                        <inf:maxLength/>
+                        <inf:callInfo>
+                            <inf:callName>Items</inf:callName>
+                            <inf:returned>Always</inf:returned>
+                        </inf:callInfo>
+                    </xsd:appinfo>
+                </xsd:annotation>
+            </xsd:element>
+            <xsd:element name="name"  type="xsd:string">
+                <xsd:annotation>
+                    <xsd:documentation>Default label</xsd:documentation>
+                    <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap">
+                        <inf:maxLength/>
+                        <inf:callInfo>
+                            <inf:callName>Item</inf:callName>
+                            <inf:returned>Always</inf:returned>
+                        </inf:callInfo>
+                    </xsd:appinfo>
+                </xsd:annotation>
+            </xsd:element>
+        </xsd:sequence>
+    </xsd:complexType>
+
+    <xsd:complexType name="CreateRequest">
+        <xsd:annotation>
+            <xsd:documentation/>
+            <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap"/>
+        </xsd:annotation>
+        <xsd:sequence>
+            <xsd:element name="name"  type="xsd:string">
+                <xsd:annotation>
+                    <xsd:documentation>Default label</xsd:documentation>
+                    <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap">
+                        <inf:maxLength/>
+                        <inf:callInfo>
+                            <inf:callName>Create</inf:callName>
+                            <inf:returned>Always</inf:returned>
+                        </inf:callInfo>
+                    </xsd:appinfo>
+                </xsd:annotation>
+            </xsd:element>
+        </xsd:sequence>
+    </xsd:complexType>
+    <xsd:complexType name="CreateResponse">
+        <xsd:annotation>
+            <xsd:documentation>Response container for the Create call.</xsd:documentation>
+            <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap"/>
+        </xsd:annotation>
+        <xsd:sequence>
+            <xsd:element name="id" type="xsd:string">
+                <xsd:annotation>
+                    <xsd:documentation>Default label</xsd:documentation>
+                    <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap">
+                        <inf:maxLength/>
+                        <inf:callInfo>
+                            <inf:callName>Create</inf:callName>
+                            <inf:returned>Always</inf:returned>
+                        </inf:callInfo>
+                    </xsd:appinfo>
+                </xsd:annotation>
+            </xsd:element>
+            <xsd:element name="name"  type="xsd:string">
+                <xsd:annotation>
+                    <xsd:documentation>Default label</xsd:documentation>
+                    <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap">
+                        <inf:maxLength/>
+                        <inf:callInfo>
+                            <inf:callName>Create</inf:callName>
+                            <inf:returned>Always</inf:returned>
+                        </inf:callInfo>
+                    </xsd:appinfo>
+                </xsd:annotation>
+            </xsd:element>
+        </xsd:sequence>
+    </xsd:complexType>
+    <xsd:complexType name="UpdateRequest">
+        <xsd:annotation>
+            <xsd:documentation/>
+            <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap"/>
+        </xsd:annotation>
+        <xsd:sequence>
+            <xsd:element name="id" type="xsd:int">
+                <xsd:annotation>
+                    <xsd:documentation>Entity ID</xsd:documentation>
+                    <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap">
+                        <inf:min/>
+                        <inf:max/>
+                        <inf:callInfo>
+                            <inf:callName>Update</inf:callName>
+                            <inf:requiredInput>Yes</inf:requiredInput>
+                        </inf:callInfo>
+                    </xsd:appinfo>
+                </xsd:annotation>
+            </xsd:element>
+        </xsd:sequence>
+    </xsd:complexType>
+    <xsd:complexType name="UpdateResponse">
+        <xsd:annotation>
+            <xsd:documentation>Response container for the Update call.</xsd:documentation>
+            <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap"/>
+        </xsd:annotation>
+        <xsd:sequence>
+            <xsd:element name="id" type="xsd:string">
+                <xsd:annotation>
+                    <xsd:documentation>Default label</xsd:documentation>
+                    <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap">
+                        <inf:maxLength/>
+                        <inf:callInfo>
+                            <inf:callName>Update</inf:callName>
+                            <inf:returned>Always</inf:returned>
+                        </inf:callInfo>
+                    </xsd:appinfo>
+                </xsd:annotation>
+            </xsd:element>
+        </xsd:sequence>
+    </xsd:complexType>
+    <xsd:complexType name="RemoveRequest">
+        <xsd:annotation>
+            <xsd:documentation/>
+            <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap"/>
+        </xsd:annotation>
+        <xsd:sequence>
+            <xsd:element name="id" type="xsd:int">
+                <xsd:annotation>
+                    <xsd:documentation>Entity ID</xsd:documentation>
+                    <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap">
+                        <inf:min/>
+                        <inf:max/>
+                        <inf:callInfo>
+                            <inf:callName>Remove</inf:callName>
+                            <inf:requiredInput>Yes</inf:requiredInput>
+                        </inf:callInfo>
+                    </xsd:appinfo>
+                </xsd:annotation>
+            </xsd:element>
+        </xsd:sequence>
+    </xsd:complexType>
+    <xsd:complexType name="RemoveResponse">
+        <xsd:annotation>
+            <xsd:documentation>Response container for the Remove call.</xsd:documentation>
+            <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap"/>
+        </xsd:annotation>
+        <xsd:sequence>
+            <xsd:element name="id" type="xsd:string">
+                <xsd:annotation>
+                    <xsd:documentation>Default label</xsd:documentation>
+                    <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap">
+                        <inf:maxLength/>
+                        <inf:callInfo>
+                            <inf:callName>Remove</inf:callName>
+                            <inf:returned>Always</inf:returned>
+                        </inf:callInfo>
+                    </xsd:appinfo>
+                </xsd:annotation>
+            </xsd:element>
+        </xsd:sequence>
+    </xsd:complexType>
+</xsd:schema>
diff --git a/dev/tests/api-functional/_files/Magento/TestModule2/etc/schema/SubsetRestV1.xsd b/dev/tests/api-functional/_files/Magento/TestModule2/etc/schema/SubsetRestV1.xsd
new file mode 100644
index 0000000000000000000000000000000000000000..3e7caaa8d480048a83ea35018572ff62bf034ca9
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule2/etc/schema/SubsetRestV1.xsd
@@ -0,0 +1,239 @@
+<!--
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+-->
+<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+    <xsd:complexType name="ItemRequest">
+        <xsd:annotation>
+            <xsd:documentation/>
+            <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap"/>
+        </xsd:annotation>
+        <xsd:sequence>
+            <xsd:element name="id" type="xsd:int">
+                <xsd:annotation>
+                    <xsd:documentation>Entity ID</xsd:documentation>
+                    <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap">
+                        <inf:min/>
+                        <inf:max/>
+                        <inf:callInfo>
+                            <inf:callName>Item</inf:callName>
+                            <inf:requiredInput>Yes</inf:requiredInput>
+                        </inf:callInfo>
+                    </xsd:appinfo>
+                </xsd:annotation>
+            </xsd:element>
+        </xsd:sequence>
+    </xsd:complexType>
+    <xsd:complexType name="ItemResponse">
+        <xsd:annotation>
+            <xsd:documentation>Response container for the Item call.</xsd:documentation>
+            <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap"/>
+        </xsd:annotation>
+        <xsd:sequence>
+            <xsd:element name="id" type="xsd:string">
+                <xsd:annotation>
+                    <xsd:documentation>Default label</xsd:documentation>
+                    <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap">
+                        <inf:maxLength/>
+                        <inf:callInfo>
+                            <inf:callName>Item</inf:callName>
+                            <inf:returned>Always</inf:returned>
+                        </inf:callInfo>
+                    </xsd:appinfo>
+                </xsd:annotation>
+            </xsd:element>
+        </xsd:sequence>
+    </xsd:complexType>
+
+    <xsd:complexType name="ItemsResponse">
+        <xsd:annotation>
+            <xsd:documentation>Response container for the Items call.</xsd:documentation>
+            <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap"/>
+        </xsd:annotation>
+        <xsd:sequence>
+            <xsd:element minOccurs="0" maxOccurs="unbounded" name="complexObjectArray"
+                         type="ItemsArray">
+                <xsd:annotation>
+                    <xsd:documentation>Default label</xsd:documentation>
+                    <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap">
+                        <inf:maxLength/>
+                        <inf:callInfo>
+                            <inf:callName>Items</inf:callName>
+                            <inf:returned>Always</inf:returned>
+                        </inf:callInfo>
+                    </xsd:appinfo>
+                </xsd:annotation>
+            </xsd:element>
+        </xsd:sequence>
+    </xsd:complexType>
+
+
+    <xsd:complexType name="ItemsArray">
+        <xsd:annotation>
+            <xsd:documentation>Response container for the Items call.</xsd:documentation>
+            <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap"/>
+        </xsd:annotation>
+        <xsd:sequence>
+            <xsd:element name="id" type="xsd:string">
+                <xsd:annotation>
+                    <xsd:documentation>Default label</xsd:documentation>
+                    <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap">
+                        <inf:maxLength/>
+                        <inf:callInfo>
+                            <inf:callName>Items</inf:callName>
+                            <inf:returned>Always</inf:returned>
+                        </inf:callInfo>
+                    </xsd:appinfo>
+                </xsd:annotation>
+            </xsd:element>
+            <xsd:element name="name"  type="xsd:string">
+                <xsd:annotation>
+                    <xsd:documentation>Default label</xsd:documentation>
+                    <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap">
+                        <inf:maxLength/>
+                        <inf:callInfo>
+                            <inf:callName>Item</inf:callName>
+                            <inf:returned>Always</inf:returned>
+                        </inf:callInfo>
+                    </xsd:appinfo>
+                </xsd:annotation>
+            </xsd:element>
+        </xsd:sequence>
+    </xsd:complexType>
+
+    <xsd:complexType name="CreateRequest">
+        <xsd:annotation>
+            <xsd:documentation/>
+            <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap"/>
+        </xsd:annotation>
+        <xsd:sequence>
+            <xsd:element name="name"  type="xsd:string">
+                <xsd:annotation>
+                    <xsd:documentation>Default label</xsd:documentation>
+                    <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap">
+                        <inf:maxLength/>
+                        <inf:callInfo>
+                            <inf:callName>Create</inf:callName>
+                            <inf:returned>Always</inf:returned>
+                        </inf:callInfo>
+                    </xsd:appinfo>
+                </xsd:annotation>
+            </xsd:element>
+        </xsd:sequence>
+    </xsd:complexType>
+    <xsd:complexType name="CreateResponse">
+        <xsd:annotation>
+            <xsd:documentation>Response container for the Create call.</xsd:documentation>
+            <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap"/>
+        </xsd:annotation>
+        <xsd:sequence>
+            <xsd:element name="id" type="xsd:string">
+                <xsd:annotation>
+                    <xsd:documentation>Default label</xsd:documentation>
+                    <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap">
+                        <inf:maxLength/>
+                        <inf:callInfo>
+                            <inf:callName>Create</inf:callName>
+                            <inf:returned>Always</inf:returned>
+                        </inf:callInfo>
+                    </xsd:appinfo>
+                </xsd:annotation>
+            </xsd:element>
+            <xsd:element name="name"  type="xsd:string">
+                <xsd:annotation>
+                    <xsd:documentation>Default label</xsd:documentation>
+                    <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap">
+                        <inf:maxLength/>
+                        <inf:callInfo>
+                            <inf:callName>Create</inf:callName>
+                            <inf:returned>Always</inf:returned>
+                        </inf:callInfo>
+                    </xsd:appinfo>
+                </xsd:annotation>
+            </xsd:element>
+        </xsd:sequence>
+    </xsd:complexType>
+    <xsd:complexType name="UpdateRequest">
+        <xsd:annotation>
+            <xsd:documentation/>
+            <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap"/>
+        </xsd:annotation>
+        <xsd:sequence>
+            <xsd:element name="id" type="xsd:int">
+                <xsd:annotation>
+                    <xsd:documentation>Entity ID</xsd:documentation>
+                    <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap">
+                        <inf:min/>
+                        <inf:max/>
+                        <inf:callInfo>
+                            <inf:callName>Update</inf:callName>
+                            <inf:requiredInput>Yes</inf:requiredInput>
+                        </inf:callInfo>
+                    </xsd:appinfo>
+                </xsd:annotation>
+            </xsd:element>
+        </xsd:sequence>
+    </xsd:complexType>
+    <xsd:complexType name="UpdateResponse">
+        <xsd:annotation>
+            <xsd:documentation>Response container for the Update call.</xsd:documentation>
+            <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap"/>
+        </xsd:annotation>
+        <xsd:sequence>
+            <xsd:element name="id" type="xsd:string">
+                <xsd:annotation>
+                    <xsd:documentation>Default label</xsd:documentation>
+                    <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap">
+                        <inf:maxLength/>
+                        <inf:callInfo>
+                            <inf:callName>Update</inf:callName>
+                            <inf:returned>Always</inf:returned>
+                        </inf:callInfo>
+                    </xsd:appinfo>
+                </xsd:annotation>
+            </xsd:element>
+        </xsd:sequence>
+    </xsd:complexType>
+    <xsd:complexType name="RemoveRequest">
+        <xsd:annotation>
+            <xsd:documentation/>
+            <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap"/>
+        </xsd:annotation>
+        <xsd:sequence>
+            <xsd:element name="id" type="xsd:int">
+                <xsd:annotation>
+                    <xsd:documentation>Entity ID</xsd:documentation>
+                    <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap">
+                        <inf:min/>
+                        <inf:max/>
+                        <inf:callInfo>
+                            <inf:callName>Remove</inf:callName>
+                            <inf:requiredInput>Yes</inf:requiredInput>
+                        </inf:callInfo>
+                    </xsd:appinfo>
+                </xsd:annotation>
+            </xsd:element>
+        </xsd:sequence>
+    </xsd:complexType>
+    <xsd:complexType name="RemoveResponse">
+        <xsd:annotation>
+            <xsd:documentation>Response container for the Remove call.</xsd:documentation>
+            <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap"/>
+        </xsd:annotation>
+        <xsd:sequence>
+            <xsd:element name="id" type="xsd:string">
+                <xsd:annotation>
+                    <xsd:documentation>Default label</xsd:documentation>
+                    <xsd:appinfo xmlns:inf="http://magento.ll/webapi/soap">
+                        <inf:maxLength/>
+                        <inf:callInfo>
+                            <inf:callName>Remove</inf:callName>
+                            <inf:returned>Always</inf:returned>
+                        </inf:callInfo>
+                    </xsd:appinfo>
+                </xsd:annotation>
+            </xsd:element>
+        </xsd:sequence>
+    </xsd:complexType>
+</xsd:schema>
diff --git a/dev/tests/api-functional/_files/Magento/TestModule2/etc/webapi.xml b/dev/tests/api-functional/_files/Magento/TestModule2/etc/webapi.xml
new file mode 100644
index 0000000000000000000000000000000000000000..fb4339826d5d1926d0947ab4ffa5b79556b763d3
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule2/etc/webapi.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0"?>
+<!--
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+-->
+<routes xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../app/code/Magento/Webapi/etc/webapi.xsd">
+    <route method="GET" url="/V1/testModule2SubsetRest/:id">
+        <service class="Magento\TestModule2\Service\V1\SubsetRestInterface" method="item" />
+        <resources>
+            <resource ref="Magento_TestModule2::resource1" />
+        </resources>
+    </route>
+    <route method="GET" url="/V1/testModule2SubsetRest">
+        <service class="Magento\TestModule2\Service\V1\SubsetRestInterface" method="items" />
+        <resources>
+            <resource ref="Magento_TestModule2::resource2" />
+            <resource ref="Magento_TestModule2::resource3" />
+        </resources>
+    </route>
+</routes>
diff --git a/dev/tests/api-functional/_files/Magento/TestModule3/Service/V1/Entity/Parameter.php b/dev/tests/api-functional/_files/Magento/TestModule3/Service/V1/Entity/Parameter.php
new file mode 100644
index 0000000000000000000000000000000000000000..a6f58178d0453ba3dab9ae658c8dbf3124b4d0d9
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule3/Service/V1/Entity/Parameter.php
@@ -0,0 +1,28 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestModule3\Service\V1\Entity;
+
+class Parameter extends \Magento\Framework\Api\AbstractExtensibleObject
+{
+    /**
+     * Get Name.
+     *
+     * @return string $name
+     */
+    public function getName()
+    {
+        return $this->_data['name'];
+    }
+
+    /**
+     * Get value.
+     *
+     * @return string $value
+     */
+    public function getValue()
+    {
+        return $this->_data['value'];
+    }
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModule3/Service/V1/Entity/ParameterBuilder.php b/dev/tests/api-functional/_files/Magento/TestModule3/Service/V1/Entity/ParameterBuilder.php
new file mode 100644
index 0000000000000000000000000000000000000000..31ec2076df461d2d6e2468b0b9a37449bda75b43
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule3/Service/V1/Entity/ParameterBuilder.php
@@ -0,0 +1,32 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestModule3\Service\V1\Entity;
+
+class ParameterBuilder extends \Magento\Framework\Api\ExtensibleObjectBuilder
+{
+    /**
+     * Set Name.
+     *
+     * @param string $name
+     * @return \Magento\TestModule3\Service\V1\Entity\ParameterBuilder
+     */
+    public function setName($name)
+    {
+        $this->data['name'] = $name;
+        return $this;
+    }
+
+    /**
+     * Set value.
+     *
+     * @param string $value
+     * @return \Magento\TestModule3\Service\V1\Entity\ParameterBuilder
+     */
+    public function setValue($value)
+    {
+        $this->data['value'] = $value;
+        return $this;
+    }
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModule3/Service/V1/Entity/WrappedErrorParameter.php b/dev/tests/api-functional/_files/Magento/TestModule3/Service/V1/Entity/WrappedErrorParameter.php
new file mode 100644
index 0000000000000000000000000000000000000000..775e04a2ce5ca2e3668069fbb4e7303b06a088a7
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule3/Service/V1/Entity/WrappedErrorParameter.php
@@ -0,0 +1,29 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+namespace Magento\TestModule3\Service\V1\Entity;
+
+class WrappedErrorParameter extends \Magento\Framework\Api\AbstractExtensibleObject
+{
+    /**
+     * Get field name.
+     *
+     * @return string $name
+     */
+    public function getFieldName()
+    {
+        return $this->_data['field_name'];
+    }
+
+    /**
+     * Get value.
+     *
+     * @return string $value
+     */
+    public function getValue()
+    {
+        return $this->_data['value'];
+    }
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModule3/Service/V1/Entity/WrappedErrorParameterBuilder.php b/dev/tests/api-functional/_files/Magento/TestModule3/Service/V1/Entity/WrappedErrorParameterBuilder.php
new file mode 100644
index 0000000000000000000000000000000000000000..d6b3f9de2aec33064eec74f16014f2b59afd0bb8
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule3/Service/V1/Entity/WrappedErrorParameterBuilder.php
@@ -0,0 +1,33 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+namespace Magento\TestModule3\Service\V1\Entity;
+
+class WrappedErrorParameterBuilder extends \Magento\Framework\Api\ExtensibleObjectBuilder
+{
+    /**
+     * Set field name.
+     *
+     * @param string $fieldName
+     * @return $this
+     */
+    public function setFieldName($fieldName)
+    {
+        $this->data['field_name'] = $fieldName;
+        return $this;
+    }
+
+    /**
+     * Set value.
+     *
+     * @param string $value
+     * @return $this
+     */
+    public function setValue($value)
+    {
+        $this->data['value'] = $value;
+        return $this;
+    }
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModule3/Service/V1/Error.php b/dev/tests/api-functional/_files/Magento/TestModule3/Service/V1/Error.php
new file mode 100644
index 0000000000000000000000000000000000000000..6410845b0516d3dde2647155a87b6137a7cfd75e
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule3/Service/V1/Error.php
@@ -0,0 +1,117 @@
+<?php
+/**
+ * Implementation of a test service for error handling testing
+ *
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestModule3\Service\V1;
+
+use Magento\Framework\Exception\AuthorizationException;
+use Magento\Framework\Exception\InputException;
+use Magento\Framework\Exception\LocalizedException;
+use Magento\Framework\Exception\NoSuchEntityException;
+use Magento\TestModule3\Service\V1\Entity\Parameter;
+use Magento\TestModule3\Service\V1\Entity\ParameterBuilder;
+
+class Error implements \Magento\TestModule3\Service\V1\ErrorInterface
+{
+    /**
+     * @var ParameterBuilder
+     */
+    protected $parameterBuilder;
+
+    /**
+     * @param ParameterBuilder $parameterBuilder
+     */
+    public function __construct(ParameterBuilder $parameterBuilder)
+    {
+        $this->parameterBuilder = $parameterBuilder;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function success()
+    {
+        return $this->parameterBuilder->setName('id')->setValue('a good id')->create();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function resourceNotFoundException()
+    {
+        throw new NoSuchEntityException('Resource with ID "%resource_id" not found.', ['resource_id' => 'resourceY']);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function serviceException()
+    {
+        throw new LocalizedException('Generic service exception %param', ['param' => 3456]);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function parameterizedServiceException($parameters)
+    {
+        $details = [];
+        foreach ($parameters as $parameter) {
+            $details[$parameter->getName()] = $parameter->getValue();
+        }
+        throw new LocalizedException('Parameterized service exception', $details);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function authorizationException()
+    {
+        throw new AuthorizationException('Consumer is not authorized to access %resources', [
+            'resources'   => 'resourceN'
+        ]);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function webapiException()
+    {
+        throw new \Magento\Webapi\Exception('Service not found', 5555, \Magento\Webapi\Exception::HTTP_NOT_FOUND);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function otherException()
+    {
+        throw new \Exception('Non service exception', 5678);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function returnIncompatibleDataType()
+    {
+        return "incompatibleDataType";
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function inputException($wrappedErrorParameters)
+    {
+        $exception = new InputException();
+        if ($wrappedErrorParameters) {
+            foreach ($wrappedErrorParameters as $error) {
+                $exception->addError(
+                    InputException::INVALID_FIELD_VALUE,
+                    ['fieldName' => $error->getFieldName(), 'value' => $error->getValue()]
+                );
+            }
+        }
+        throw $exception;
+    }
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModule3/Service/V1/ErrorInterface.php b/dev/tests/api-functional/_files/Magento/TestModule3/Service/V1/ErrorInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..59612a9755b750bcf8e3974b206e323e87be4bac
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule3/Service/V1/ErrorInterface.php
@@ -0,0 +1,59 @@
+<?php
+/**
+ * Interface for a test service for error handling testing
+ *
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestModule3\Service\V1;
+
+use Magento\TestModule3\Service\V1\Entity\Parameter;
+
+interface ErrorInterface
+{
+    /**
+     * @return \Magento\TestModule3\Service\V1\Entity\Parameter
+     */
+    public function success();
+
+    /**
+     * @return int Status
+     */
+    public function resourceNotFoundException();
+
+    /**
+     * @return int Status
+     */
+    public function serviceException();
+
+    /**
+     * @param \Magento\TestModule3\Service\V1\Entity\Parameter[] $parameters
+     * @return int Status
+     */
+    public function parameterizedServiceException($parameters);
+
+    /**
+     * @return int Status
+     */
+    public function authorizationException();
+
+    /**
+     * @return int Status
+     */
+    public function webapiException();
+
+    /**
+     * @return int Status
+     */
+    public function otherException();
+
+    /**
+     * @return int Status
+     */
+    public function returnIncompatibleDataType();
+
+    /**
+     * @param \Magento\TestModule3\Service\V1\Entity\WrappedErrorParameter[] $wrappedErrorParameters
+     * @return int Status
+     */
+    public function inputException($wrappedErrorParameters);
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModule3/etc/acl.xml b/dev/tests/api-functional/_files/Magento/TestModule3/etc/acl.xml
new file mode 100644
index 0000000000000000000000000000000000000000..556c098784384a38a6c7323e2b71731b959f407c
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule3/etc/acl.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0"?>
+<!--
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+-->
+<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../lib/internal/Magento/Framework/Acl/etc/acl.xsd">
+    <acl>
+        <resources>
+            <resource id="Magento_Adminhtml::admin">
+                <resource id="Magento_TestModule3::all" title="TestModule3" sortOrder="1">
+                    <resource id="Magento_TestModule3::resource1" title="Resource1" sortOrder="20"/>
+                </resource>
+            </resource>
+        </resources>
+    </acl>
+</config>
diff --git a/dev/tests/api-functional/_files/Magento/TestModule3/etc/di.xml b/dev/tests/api-functional/_files/Magento/TestModule3/etc/di.xml
new file mode 100644
index 0000000000000000000000000000000000000000..b26be009346343d0e43a5a7c29312d8e67e978d6
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule3/etc/di.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0"?>
+<!--
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+-->
+<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../lib/internal/Magento/Framework/ObjectManager/etc/config.xsd">
+    <preference for="Magento\TestModule3\Service\V1\ErrorInterface" type="Magento\TestModule3\Service\V1\Error" />
+</config>
diff --git a/dev/tests/api-functional/_files/Magento/TestModule3/etc/module.xml b/dev/tests/api-functional/_files/Magento/TestModule3/etc/module.xml
new file mode 100644
index 0000000000000000000000000000000000000000..42ae9667a277150322d4c2a793a87c2882edeeab
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule3/etc/module.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0"?>
+<!--
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+-->
+<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../lib/internal/Magento/Framework/Module/etc/module.xsd">
+    <module name="Magento_TestModule3" schema_version="1.0"/>
+</config>
diff --git a/dev/tests/api-functional/_files/Magento/TestModule3/etc/webapi.xml b/dev/tests/api-functional/_files/Magento/TestModule3/etc/webapi.xml
new file mode 100644
index 0000000000000000000000000000000000000000..ca7043e0c429cbcbf1b42a16e29bfdd4db6164eb
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule3/etc/webapi.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0"?>
+<!--
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+-->
+<routes xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../app/code/Magento/Webapi/etc/webapi.xsd">
+    <route method="GET" url="/V1/errortest/success">
+        <service class="Magento\TestModule3\Service\V1\ErrorInterface" method="success" />
+        <resources>
+            <resource ref="Magento_TestModule3::resource1" />
+        </resources>
+    </route>
+    <route method="GET" url="/V1/errortest/notfound">
+        <service class="Magento\TestModule3\Service\V1\ErrorInterface" method="resourceNotFoundException" />
+        <resources>
+            <resource ref="Magento_TestModule3::resource1" />
+        </resources>
+    </route>
+    <route method="GET" url="/V1/errortest/serviceexception">
+        <service class="Magento\TestModule3\Service\V1\ErrorInterface" method="serviceException" />
+        <resources>
+            <resource ref="Magento_TestModule3::resource1" />
+        </resources>
+    </route>
+    <route method="POST" url="/V1/errortest/parameterizedserviceexception">
+        <service class="Magento\TestModule3\Service\V1\ErrorInterface" method="parameterizedServiceException" />
+        <resources>
+            <resource ref="Magento_TestModule3::resource1" />
+        </resources>
+    </route>
+    <route method="GET" url="/V1/errortest/unauthorized">
+        <service class="Magento\TestModule3\Service\V1\ErrorInterface" method="authorizationException" />
+        <resources>
+            <resource ref="Magento_TestModule3::resource1" />
+            <resource ref="Magento_TestModule3::resource2" />
+        </resources>
+    </route>
+    <route method="GET" url="/V1/errortest/otherException">
+        <service class="Magento\TestModule3\Service\V1\ErrorInterface" method="otherException" />
+        <resources>
+            <resource ref="Magento_TestModule3::resource1" />
+        </resources>
+    </route>
+    <route method="GET" url="/V1/errortest/returnIncompatibleDataType">
+        <service class="Magento\TestModule3\Service\V1\ErrorInterface" method="returnIncompatibleDataType" />
+        <resources>
+            <resource ref="Magento_TestModule3::resource1" />
+        </resources>
+    </route>
+    <route method="GET" url="/V1/errortest/webapiException">
+        <service class="Magento\TestModule3\Service\V1\ErrorInterface" method="webapiException" />
+        <resources>
+            <resource ref="Magento_TestModule3::resource1" />
+        </resources>
+    </route>
+    <route method="POST" url="/V1/errortest/inputException">
+        <service class="Magento\TestModule3\Service\V1\ErrorInterface" method="inputException" />
+        <resources>
+            <resource ref="Magento_TestModule3::resource1" />
+        </resources>
+    </route>
+</routes>
diff --git a/dev/tests/api-functional/_files/Magento/TestModule4/Model/Resource/Item.php b/dev/tests/api-functional/_files/Magento/TestModule4/Model/Resource/Item.php
new file mode 100644
index 0000000000000000000000000000000000000000..346d39bdca4b16a57bc51aee9b23f99b67143b0f
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule4/Model/Resource/Item.php
@@ -0,0 +1,22 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+namespace Magento\TestModule4\Model\Resource;
+
+/**
+ * Sample resource model
+ */
+class Item extends \Magento\Framework\Model\Resource\Db\AbstractDb
+{
+    /**
+     * Initialize connection and define main table
+     *
+     * @return void
+     */
+    protected function _construct()
+    {
+        $this->_init('dummy_item', 'dummy_item_id');
+    }
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModule4/Service/V1/DataObjectService.php b/dev/tests/api-functional/_files/Magento/TestModule4/Service/V1/DataObjectService.php
new file mode 100644
index 0000000000000000000000000000000000000000..9c960939015e0c93a992ce14957aa39d8d965d46
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule4/Service/V1/DataObjectService.php
@@ -0,0 +1,69 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestModule4\Service\V1;
+
+use Magento\TestModule4\Service\V1\Entity\DataObjectRequest;
+use Magento\TestModule4\Service\V1\Entity\DataObjectResponseBuilder;
+use Magento\TestModule4\Service\V1\Entity\ExtensibleRequestInterface;
+use Magento\TestModule4\Service\V1\Entity\NestedDataObjectRequest;
+
+class DataObjectService implements \Magento\TestModule4\Service\V1\DataObjectServiceInterface
+{
+    /**
+     * @var DataObjectResponseBuilder
+     */
+    protected $responseBuilder;
+
+    /**
+     * @param DataObjectResponseBuilder $responseBuilder
+     */
+    public function __construct(DataObjectResponseBuilder $responseBuilder)
+    {
+        $this->responseBuilder = $responseBuilder;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getData($id)
+    {
+        return $this->responseBuilder->setEntityId($id)->setName("Test")->create();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function updateData($id, DataObjectRequest $request)
+    {
+        return $this->responseBuilder->setEntityId($id)->setName($request->getName())->create();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function nestedData($id, NestedDataObjectRequest $request)
+    {
+        return $this->responseBuilder->setEntityId($id)->setName($request->getDetails()->getName())->create();
+    }
+
+    /**
+     * Test return scalar value
+     *
+     * @param int $id
+     * @return int
+     */
+    public function scalarResponse($id)
+    {
+        return $id;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function extensibleDataObject($id, ExtensibleRequestInterface $request)
+    {
+        return $this->responseBuilder->setEntityId($id)->setName($request->getName())->create();
+    }
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModule4/Service/V1/DataObjectServiceInterface.php b/dev/tests/api-functional/_files/Magento/TestModule4/Service/V1/DataObjectServiceInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..f189b2c9ef42a3c4d2f478c44cf0aa06eadc2097
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule4/Service/V1/DataObjectServiceInterface.php
@@ -0,0 +1,46 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestModule4\Service\V1;
+
+use Magento\TestModule4\Service\V1\Entity\DataObjectRequest;
+use Magento\TestModule4\Service\V1\Entity\NestedDataObjectRequest;
+
+interface DataObjectServiceInterface
+{
+    /**
+     * @param int $id
+     * @return \Magento\TestModule4\Service\V1\Entity\DataObjectResponse
+     */
+    public function getData($id);
+
+    /**
+     * @param int $id
+     * @param \Magento\TestModule4\Service\V1\Entity\DataObjectRequest $request
+     * @return \Magento\TestModule4\Service\V1\Entity\DataObjectResponse
+     */
+    public function updateData($id, DataObjectRequest $request);
+
+    /**
+     * @param int $id
+     * @param \Magento\TestModule4\Service\V1\Entity\NestedDataObjectRequest $request
+     * @return \Magento\TestModule4\Service\V1\Entity\DataObjectResponse
+     */
+    public function nestedData($id, NestedDataObjectRequest $request);
+
+    /**
+     * Test return scalar value
+     *
+     * @param int $id
+     * @return int
+     */
+    public function scalarResponse($id);
+
+    /**
+     * @param int $id
+     * @param \Magento\TestModule4\Service\V1\Entity\ExtensibleRequestInterface $request
+     * @return \Magento\TestModule4\Service\V1\Entity\DataObjectResponse
+     */
+    public function extensibleDataObject($id, \Magento\TestModule4\Service\V1\Entity\ExtensibleRequestInterface $request);
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModule4/Service/V1/Entity/DataObjectRequest.php b/dev/tests/api-functional/_files/Magento/TestModule4/Service/V1/Entity/DataObjectRequest.php
new file mode 100644
index 0000000000000000000000000000000000000000..1c3f3dff0f4fb6890b5b2ce65032c8ef25fa2957
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule4/Service/V1/Entity/DataObjectRequest.php
@@ -0,0 +1,24 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestModule4\Service\V1\Entity;
+
+class DataObjectRequest extends \Magento\Framework\Api\AbstractExtensibleObject
+{
+    /**
+     * @return string
+     */
+    public function getName()
+    {
+        return $this->_get('name');
+    }
+
+    /**
+     * @return int|null
+     */
+    public function getEntityId()
+    {
+        return $this->_get('entity_id');
+    }
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModule4/Service/V1/Entity/DataObjectRequestBuilder.php b/dev/tests/api-functional/_files/Magento/TestModule4/Service/V1/Entity/DataObjectRequestBuilder.php
new file mode 100644
index 0000000000000000000000000000000000000000..d3d92a6d74a3c2baa2b386889aa69b913bd53678
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule4/Service/V1/Entity/DataObjectRequestBuilder.php
@@ -0,0 +1,26 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestModule4\Service\V1\Entity;
+
+class DataObjectRequestBuilder extends \Magento\Framework\Api\ExtensibleObjectBuilder
+{
+    /**
+     * @param string $name
+     * @return DataObjectRequest
+     */
+    public function setName($name)
+    {
+        return $this->_set('name', $name);
+    }
+
+    /**
+     * @param int $entityId
+     * @return DataObjectRequest
+     */
+    public function setEntityId($entityId)
+    {
+        return $this->_set('entity_id', $entityId);
+    }
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModule4/Service/V1/Entity/DataObjectResponse.php b/dev/tests/api-functional/_files/Magento/TestModule4/Service/V1/Entity/DataObjectResponse.php
new file mode 100644
index 0000000000000000000000000000000000000000..0086eda109521c4f4894204e5694e478ac8050d6
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule4/Service/V1/Entity/DataObjectResponse.php
@@ -0,0 +1,24 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestModule4\Service\V1\Entity;
+
+class DataObjectResponse extends \Magento\Framework\Api\AbstractExtensibleObject
+{
+    /**
+     * @return int
+     */
+    public function getEntityId()
+    {
+        return $this->_get('entity_id');
+    }
+
+    /**
+     * @return string
+     */
+    public function getName()
+    {
+        return $this->_get('name');
+    }
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModule4/Service/V1/Entity/DataObjectResponseBuilder.php b/dev/tests/api-functional/_files/Magento/TestModule4/Service/V1/Entity/DataObjectResponseBuilder.php
new file mode 100644
index 0000000000000000000000000000000000000000..288c72299852709e26e36906a75eda74e935634e
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule4/Service/V1/Entity/DataObjectResponseBuilder.php
@@ -0,0 +1,26 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestModule4\Service\V1\Entity;
+
+class DataObjectResponseBuilder extends \Magento\Framework\Api\ExtensibleObjectBuilder
+{
+    /**
+     * @param int $entityId
+     * @return DataObjectResponseBuilder
+     */
+    public function setEntityId($entityId)
+    {
+        return $this->_set('entity_id', $entityId);
+    }
+
+    /**
+     * @param string $name
+     * @return DataObjectResponseBuilder
+     */
+    public function setName($name)
+    {
+        return $this->_set('name', $name);
+    }
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModule4/Service/V1/Entity/ExtensibleRequest.php b/dev/tests/api-functional/_files/Magento/TestModule4/Service/V1/Entity/ExtensibleRequest.php
new file mode 100644
index 0000000000000000000000000000000000000000..41f78a2ed61cc9b620b2864cce32452651b1ab35
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule4/Service/V1/Entity/ExtensibleRequest.php
@@ -0,0 +1,14 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestModule4\Service\V1\Entity;
+
+class ExtensibleRequest extends \Magento\Framework\Model\AbstractExtensibleModel
+    implements ExtensibleRequestInterface
+{
+    public function getName()
+    {
+        return $this->getData("name");
+    }
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModule4/Service/V1/Entity/ExtensibleRequestInterface.php b/dev/tests/api-functional/_files/Magento/TestModule4/Service/V1/Entity/ExtensibleRequestInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..fd258c7af99a2d8922a5e2b92d6f50c8a7cfad11
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule4/Service/V1/Entity/ExtensibleRequestInterface.php
@@ -0,0 +1,18 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestModule4\Service\V1\Entity;
+
+interface ExtensibleRequestInterface extends \Magento\Framework\Api\ExtensibleDataInterface
+{
+    /**
+     * @return string
+     */
+    public function getName();
+
+    /**
+     * @return int|null
+     */
+    public function getEntityId();
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModule4/Service/V1/Entity/NestedDataObjectRequest.php b/dev/tests/api-functional/_files/Magento/TestModule4/Service/V1/Entity/NestedDataObjectRequest.php
new file mode 100644
index 0000000000000000000000000000000000000000..6c2775f006da8222eaee4035575308cd128275b7
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule4/Service/V1/Entity/NestedDataObjectRequest.php
@@ -0,0 +1,16 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestModule4\Service\V1\Entity;
+
+class NestedDataObjectRequest extends \Magento\Framework\Api\AbstractExtensibleObject
+{
+    /**
+     * @return \Magento\TestModule4\Service\V1\Entity\DataObjectRequest
+     */
+    public function getDetails()
+    {
+        return $this->_get('details');
+    }
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModule4/Service/V1/Entity/NestedDataObjectRequestBuilder.php b/dev/tests/api-functional/_files/Magento/TestModule4/Service/V1/Entity/NestedDataObjectRequestBuilder.php
new file mode 100644
index 0000000000000000000000000000000000000000..68e0123e75e279034a5dcdfbb1c126d048097c1d
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule4/Service/V1/Entity/NestedDataObjectRequestBuilder.php
@@ -0,0 +1,17 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestModule4\Service\V1\Entity;
+
+class NestedDataObjectRequestBuilder extends \Magento\Framework\Api\ExtensibleObjectBuilder
+{
+    /**
+     * @param \Magento\TestModule4\Service\V1\Entity\DataObjectRequest $details
+     * @return \Magento\TestModule4\Service\V1\Entity\DataObjectRequest
+     */
+    public function setDetails(DataObjectRequest $details)
+    {
+        return $this->_set('details', $details);
+    }
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModule4/etc/acl.xml b/dev/tests/api-functional/_files/Magento/TestModule4/etc/acl.xml
new file mode 100644
index 0000000000000000000000000000000000000000..cb35e81b7750b58814484a7c3edb60e747d2db75
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule4/etc/acl.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0"?>
+<!--
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+-->
+<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../lib/internal/Magento/Framework/Acl/etc/acl.xsd">
+    <acl>
+        <resources>
+            <resource id="Magento_Adminhtml::admin">
+                <resource id="Magento_TestModule4::all" title="TestModule4" sortOrder="1">
+                    <resource id="Magento_TestModule4::resource1" title="Resource1" sortOrder="20"/>
+                    <resource id="Magento_TestModule4::resource2" title="Resource2" sortOrder="10"/>
+                    <resource id="Magento_TestModule4::resource3" title="Resource3" sortOrder="30"/>
+                </resource>
+            </resource>
+        </resources>
+    </acl>
+</config>
diff --git a/dev/tests/api-functional/_files/Magento/TestModule4/etc/di.xml b/dev/tests/api-functional/_files/Magento/TestModule4/etc/di.xml
new file mode 100644
index 0000000000000000000000000000000000000000..d41a2955c3398e375393522fd659a7c9286ebb95
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule4/etc/di.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0"?>
+<!--
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+-->
+<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../lib/internal/Magento/Framework/ObjectManager/etc/config.xsd">
+    <preference for="Magento\TestModule4\Service\V1\DataObjectServiceInterface" type="Magento\TestModule4\Service\V1\DataObjectService" />
+    <preference for="Magento\TestModule4\Service\V1\Entity\ExtensibleRequestInterface" type="Magento\TestModule4\Service\V1\Entity\ExtensibleRequest" />
+    <type name="Magento\TestModule4\Service\V1\Entity\ExtensibleRequest">
+        <arguments>
+            <argument name="resource" xsi:type="object">Magento\TestModule4\Model\Resource\Item</argument>
+        </arguments>
+    </type>
+</config>
diff --git a/dev/tests/api-functional/_files/Magento/TestModule4/etc/module.xml b/dev/tests/api-functional/_files/Magento/TestModule4/etc/module.xml
new file mode 100644
index 0000000000000000000000000000000000000000..f433c75e68f9ab915eae3aeea9eef076849a9c03
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule4/etc/module.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0"?>
+<!--
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+-->
+<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../lib/internal/Magento/Framework/Module/etc/module.xsd">
+    <module name="Magento_TestModule4" schema_version="1.0"/>
+</config>
diff --git a/dev/tests/api-functional/_files/Magento/TestModule4/etc/webapi.xml b/dev/tests/api-functional/_files/Magento/TestModule4/etc/webapi.xml
new file mode 100644
index 0000000000000000000000000000000000000000..196aaf5ff2a06544f30372d3ccd7d597f051785a
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule4/etc/webapi.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0"?>
+<!--
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+-->
+<routes xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../app/code/Magento/Webapi/etc/webapi.xsd">
+    <route method="GET" url="/V1/testmodule4/:id">
+        <service class="Magento\TestModule4\Service\V1\DataObjectServiceInterface" method="getData" />
+        <resources>
+            <resource ref="Magento_TestModule4::resource1" />
+        </resources>
+    </route>
+    <route method="GET" url="/V1/testmodule4/scalar/:id">
+        <service class="Magento\TestModule4\Service\V1\DataObjectServiceInterface" method="scalarResponse" />
+        <resources>
+            <resource ref="Magento_TestModule4::resource1" />
+        </resources>
+    </route>
+    <route method="POST" url="/V1/testmodule4/:id">
+        <service class="Magento\TestModule4\Service\V1\DataObjectServiceInterface" method="updateData" />
+        <resources>
+            <resource ref="Magento_TestModule4::resource2" />
+        </resources>
+    </route>
+    <route method="POST" url="/V1/testmodule4/:id/nested">
+        <service class="Magento\TestModule4\Service\V1\DataObjectServiceInterface" method="nestedData" />
+        <resources>
+            <resource ref="Magento_TestModule4::resource3" />
+        </resources>
+    </route>
+    <route method="POST" url="/V1/testmodule4/extensibleDataObject/:id">
+        <service class="Magento\TestModule4\Service\V1\DataObjectServiceInterface" method="extensibleDataObject" />
+        <resources>
+            <resource ref="Magento_TestModule4::resource3" />
+        </resources>
+    </route>
+</routes>
diff --git a/dev/tests/api-functional/_files/Magento/TestModule5/Service/V1/AllSoapAndRest.php b/dev/tests/api-functional/_files/Magento/TestModule5/Service/V1/AllSoapAndRest.php
new file mode 100644
index 0000000000000000000000000000000000000000..6cf3942fe2d754f9119201e3e4d58503d13ff676
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule5/Service/V1/AllSoapAndRest.php
@@ -0,0 +1,73 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestModule5\Service\V1;
+
+use Magento\TestModule5\Service\V1\Entity\AllSoapAndRestBuilder;
+
+class AllSoapAndRest implements \Magento\TestModule5\Service\V1\AllSoapAndRestInterface
+{
+    /**
+     * @var AllSoapAndRestBuilder
+     */
+    protected $builder;
+
+    /**
+     * @param AllSoapAndRestBuilder $builder
+     */
+    public function __construct(AllSoapAndRestBuilder $builder)
+    {
+        $this->builder = $builder;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function item($entityId)
+    {
+        return $this->builder
+            ->setEntityId($entityId)
+            ->setName('testItemName')
+            ->setIsEnabled(true)
+            ->setHasOrders(true)
+            ->create();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function items()
+    {
+        $allSoapAndRest1 = $this->builder->setEntityId(1)->setName('testProduct1')->create();
+        $allSoapAndRest2 = $this->builder->setEntityId(2)->setName('testProduct2')->create();
+        return [$allSoapAndRest1, $allSoapAndRest2];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function create(\Magento\TestModule5\Service\V1\Entity\AllSoapAndRest $item)
+    {
+        return $this->builder->populate($item)->create();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function update(\Magento\TestModule5\Service\V1\Entity\AllSoapAndRest $entityItem)
+    {
+        return $entityItem;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function nestedUpdate(
+        $parentId,
+        $entityId,
+        \Magento\TestModule5\Service\V1\Entity\AllSoapAndRest $entityItem
+    ) {
+        return $entityItem;
+    }
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModule5/Service/V1/AllSoapAndRestInterface.php b/dev/tests/api-functional/_files/Magento/TestModule5/Service/V1/AllSoapAndRestInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..de1b4472479b98ef9b4dfbba4ac986709f8a0c2c
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule5/Service/V1/AllSoapAndRestInterface.php
@@ -0,0 +1,54 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestModule5\Service\V1;
+
+interface AllSoapAndRestInterface
+{
+    /**
+     * Retrieve an item.
+     *
+     * @param int $entityId
+     * @return \Magento\TestModule5\Service\V1\Entity\AllSoapAndRest
+     * @throws \Magento\Webapi\Exception
+     */
+    public function item($entityId);
+
+    /**
+     * Retrieve all items.
+     *
+     * @return \Magento\TestModule5\Service\V1\Entity\AllSoapAndRest[]
+     */
+    public function items();
+
+    /**
+     * Create a new item.
+     *
+     * @param \Magento\TestModule5\Service\V1\Entity\AllSoapAndRest $item
+     * @return \Magento\TestModule5\Service\V1\Entity\AllSoapAndRest
+     */
+    public function create(\Magento\TestModule5\Service\V1\Entity\AllSoapAndRest $item);
+
+    /**
+     * Update existing item.
+     *
+     * @param \Magento\TestModule5\Service\V1\Entity\AllSoapAndRest $entityItem
+     * @return \Magento\TestModule5\Service\V1\Entity\AllSoapAndRest
+     */
+    public function update(\Magento\TestModule5\Service\V1\Entity\AllSoapAndRest $entityItem);
+
+    /**
+     * Update existing item.
+     *
+     * @param string $parentId
+     * @param string $entityId
+     * @param \Magento\TestModule5\Service\V1\Entity\AllSoapAndRest $entityItem
+     * @return \Magento\TestModule5\Service\V1\Entity\AllSoapAndRest
+     */
+    public function nestedUpdate(
+        $parentId,
+        $entityId,
+        \Magento\TestModule5\Service\V1\Entity\AllSoapAndRest $entityItem
+    );
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModule5/Service/V1/Entity/AllSoapAndRest.php b/dev/tests/api-functional/_files/Magento/TestModule5/Service/V1/Entity/AllSoapAndRest.php
new file mode 100644
index 0000000000000000000000000000000000000000..19e78efe69463cccaf0f21a64c5db0ab57d336ac
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule5/Service/V1/Entity/AllSoapAndRest.php
@@ -0,0 +1,70 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestModule5\Service\V1\Entity;
+
+/**
+ * Some Data Object short description.
+ *
+ * Data Object long
+ * multi line description.
+ */
+class AllSoapAndRest extends \Magento\Framework\Api\AbstractExtensibleObject
+{
+    const ID = 'entity_id';
+    const NAME = 'name';
+    const ENABLED = 'enabled';
+    const HAS_ORDERS = 'orders';
+
+    /**
+     * Retrieve item ID.
+     *
+     * @return int Item ID
+     */
+    public function getEntityId()
+    {
+        return $this->_get(self::ID);
+    }
+
+    /**
+     * Retrieve item Name.
+     *
+     * @return string|null Item name
+     */
+    public function getName()
+    {
+        return $this->_get(self::NAME);
+    }
+
+    /**
+     * Check if entity is enabled
+     *
+     * @return bool
+     */
+    public function isEnabled()
+    {
+        return $this->_get(self::ENABLED);
+    }
+
+    /**
+     * Check if current entity has a property defined
+     *
+     * @return bool
+     */
+    public function hasOrders()
+    {
+        return $this->_get(self::HAS_ORDERS);
+    }
+
+    /**
+     * Method which will not be used when adding complex type field to WSDL.
+     *
+     * @param string $value
+     * @return string
+     */
+    public function getFieldExcludedFromWsdl($value)
+    {
+        return $value;
+    }
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModule5/Service/V1/Entity/AllSoapAndRestBuilder.php b/dev/tests/api-functional/_files/Magento/TestModule5/Service/V1/Entity/AllSoapAndRestBuilder.php
new file mode 100644
index 0000000000000000000000000000000000000000..70ed93fa515aa4a78d23a9697b719e63db533335
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule5/Service/V1/Entity/AllSoapAndRestBuilder.php
@@ -0,0 +1,56 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestModule5\Service\V1\Entity;
+
+use Magento\Framework\Api\AbstractSimpleObjectBuilder;
+
+/**
+ * Some Data Object short description.
+ *
+ * Data Object long
+ * multi line description.
+ */
+class AllSoapAndRestBuilder extends AbstractSimpleObjectBuilder
+{
+    /**
+     * @param int $id
+     * @return AllSoapAndRestBuilder
+     */
+    public function setEntityId($id)
+    {
+        return $this->_set(AllSoapAndRest::ID, $id);
+    }
+
+    /**
+     * @param string $name
+     * @return AllSoapAndRestBuilder
+     */
+    public function setName($name)
+    {
+        return $this->_set(AllSoapAndRest::NAME, $name);
+    }
+
+    /**
+     * Set flag if entity is enabled
+     *
+     * @param bool $isEnabled
+     * @return AllSoapAndRestBuilder
+     */
+    public function setIsEnabled($isEnabled)
+    {
+        return $this->_set(AllSoapAndRest::ENABLED, $isEnabled);
+    }
+
+    /**
+     * Set flag if entity has orders
+     *
+     * @param bool $hasOrders
+     * @return AllSoapAndRestBuilder
+     */
+    public function setHasOrders($hasOrders)
+    {
+        return $this->_set(AllSoapAndRest::HAS_ORDERS, $hasOrders);
+    }
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModule5/Service/V1/OverrideService.php b/dev/tests/api-functional/_files/Magento/TestModule5/Service/V1/OverrideService.php
new file mode 100644
index 0000000000000000000000000000000000000000..d5fb874d457bce5a61aac253687cee80e3099a91
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule5/Service/V1/OverrideService.php
@@ -0,0 +1,35 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+namespace Magento\TestModule5\Service\V1;
+
+use Magento\TestModule5\Service\V1\Entity\AllSoapAndRestBuilder;
+
+class OverrideService implements OverrideServiceInterface
+{
+    /**
+     * @var AllSoapAndRestBuilder
+     */
+    protected $builder;
+
+    /**
+     * @param AllSoapAndRestBuilder $builder
+     */
+    public function __construct(AllSoapAndRestBuilder $builder)
+    {
+        $this->builder = $builder;
+    }
+    /**
+     * {@inheritdoc}
+     */
+    public function scalarUpdate($entityId, $name, $hasOrders)
+    {
+        return $this->builder
+            ->setEntityId($entityId)
+            ->setName($name)
+            ->setHasOrders($hasOrders)
+            ->create();
+    }
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModule5/Service/V1/OverrideServiceInterface.php b/dev/tests/api-functional/_files/Magento/TestModule5/Service/V1/OverrideServiceInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..f8ce591980b5c2914d51e54f473882b6a589e404
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule5/Service/V1/OverrideServiceInterface.php
@@ -0,0 +1,19 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+namespace Magento\TestModule5\Service\V1;
+
+interface OverrideServiceInterface
+{
+    /**
+     * Update existing item.
+     *
+     * @param string $entityId
+     * @param string $name
+     * @param bool $orders
+     * @return \Magento\TestModule5\Service\V1\Entity\AllSoapAndRest
+     */
+    public function scalarUpdate($entityId, $name, $orders);
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModule5/Service/V2/AllSoapAndRest.php b/dev/tests/api-functional/_files/Magento/TestModule5/Service/V2/AllSoapAndRest.php
new file mode 100644
index 0000000000000000000000000000000000000000..e4b006e8feadf62720ab0a0b950980b12909e40a
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule5/Service/V2/AllSoapAndRest.php
@@ -0,0 +1,69 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestModule5\Service\V2;
+
+use Magento\TestModule5\Service\V2\Entity\AllSoapAndRest as AllSoapAndRestEntity;
+use Magento\TestModule5\Service\V2\Entity\AllSoapAndRestBuilder;
+
+class AllSoapAndRest implements AllSoapAndRestInterface
+{
+    /**
+     * @var AllSoapAndRestBuilder
+     */
+    protected $builder;
+
+    /**
+     * @param AllSoapAndRestBuilder $builder
+     */
+    public function __construct(AllSoapAndRestBuilder $builder)
+    {
+        $this->builder = $builder;
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function item($id)
+    {
+        return $this->builder->setPrice(1)->setId($id)->setName('testItemName')->create();
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function items()
+    {
+        $allSoapAndRest1 = $this->builder->setPrice(1)->setId(1)->setName('testProduct1')->create();
+        $allSoapAndRest2 = $this->builder->setPrice(1)->setId(2)->setName('testProduct2')->create();
+        return [$allSoapAndRest1, $allSoapAndRest2];
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function create(\Magento\TestModule5\Service\V2\Entity\AllSoapAndRest $item)
+    {
+        return $this->builder->populate($item)->create();
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function update(\Magento\TestModule5\Service\V2\Entity\AllSoapAndRest $item)
+    {
+        $item->setName('Updated' . $item->getName());
+        return $this->builder->populate($item)->create();
+    }
+
+    /**
+     * @param string $id
+     * @return AllSoapAndRestEntity
+     * @throws \Magento\Webapi\Exception
+     */
+    public function delete($id)
+    {
+        return $this->builder->setPrice(1)->setId($id)->setName('testItemName')->create();
+    }
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModule5/Service/V2/AllSoapAndRestInterface.php b/dev/tests/api-functional/_files/Magento/TestModule5/Service/V2/AllSoapAndRestInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..dce9a15f0f5dc720e97996e8c470f98e19bddf8b
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule5/Service/V2/AllSoapAndRestInterface.php
@@ -0,0 +1,48 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestModule5\Service\V2;
+
+interface AllSoapAndRestInterface
+{
+    /**
+     * Retrieve existing item.
+     *
+     * @param int $id
+     * @return \Magento\TestModule5\Service\V2\Entity\AllSoapAndRest
+     * @throws \Magento\Webapi\Exception
+     */
+    public function item($id);
+
+    /**
+     * Retrieve a list of all existing items.
+     *
+     * @return \Magento\TestModule5\Service\V2\Entity\AllSoapAndRest[]
+     */
+    public function items();
+
+    /**
+     * Add new item.
+     *
+     * @param \Magento\TestModule5\Service\V2\Entity\AllSoapAndRest $item
+     * @return \Magento\TestModule5\Service\V2\Entity\AllSoapAndRest
+     */
+    public function create(\Magento\TestModule5\Service\V2\Entity\AllSoapAndRest $item);
+
+    /**
+     * Update one item.
+     *
+     * @param \Magento\TestModule5\Service\V2\Entity\AllSoapAndRest $item
+     * @return \Magento\TestModule5\Service\V2\Entity\AllSoapAndRest
+     */
+    public function update(\Magento\TestModule5\Service\V2\Entity\AllSoapAndRest $item);
+
+    /**
+     * Delete existing item.
+     *
+     * @param string $id
+     * @return \Magento\TestModule5\Service\V2\Entity\AllSoapAndRest
+     */
+    public function delete($id);
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModule5/Service/V2/Entity/AllSoapAndRest.php b/dev/tests/api-functional/_files/Magento/TestModule5/Service/V2/Entity/AllSoapAndRest.php
new file mode 100644
index 0000000000000000000000000000000000000000..35e3674b679ab6623ed7f417738dd3010297b994
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule5/Service/V2/Entity/AllSoapAndRest.php
@@ -0,0 +1,18 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestModule5\Service\V2\Entity;
+
+class AllSoapAndRest extends \Magento\TestModule5\Service\V2\AllSoapAndRest
+{
+    const PRICE = 'price';
+
+    /**
+     * @return int
+     */
+    public function getPrice()
+    {
+        return $this->_get(self::PRICE);
+    }
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModule5/Service/V2/Entity/AllSoapAndRestBuilder.php b/dev/tests/api-functional/_files/Magento/TestModule5/Service/V2/Entity/AllSoapAndRestBuilder.php
new file mode 100644
index 0000000000000000000000000000000000000000..d68589484b322c64b7ddb3147e3519e8ab03646e
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule5/Service/V2/Entity/AllSoapAndRestBuilder.php
@@ -0,0 +1,21 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestModule5\Service\V2\Entity;
+
+use Magento\TestModule5\Service\V1\Entity;
+
+class AllSoapAndRestBuilder extends \Magento\TestModule5\Service\V1\Entity\AllSoapAndRestBuilder
+{
+    const PRICE = 'price';
+
+    /**
+     * @param int $price
+     * @return \Magento\TestModule5\Service\V2\Entity\AllSoapAndRestBuilder
+     */
+    public function setPrice($price)
+    {
+        return $this->_set(self::PRICE, $price);
+    }
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModule5/etc/acl.xml b/dev/tests/api-functional/_files/Magento/TestModule5/etc/acl.xml
new file mode 100644
index 0000000000000000000000000000000000000000..ebad51bd78d996e7fecd8ca070bacb434028a93b
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule5/etc/acl.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0"?>
+<!--
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+-->
+<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../lib/internal/Magento/Framework/Acl/etc/acl.xsd">
+    <acl>
+        <resources>
+            <resource id="Magento_Adminhtml::admin">
+                <resource id="Magento_TestModule5::all" title="TestModule5" sortOrder="1">
+                    <resource id="Magento_TestModule5::resource1" title="Resource1" sortOrder="20"/>
+                    <resource id="Magento_TestModule5::resource2" title="Resource2" sortOrder="10"/>
+                    <resource id="Magento_TestModule5::resource3" title="Resource3" sortOrder="30"/>
+                </resource>
+            </resource>
+        </resources>
+    </acl>
+</config>
diff --git a/dev/tests/api-functional/_files/Magento/TestModule5/etc/di.xml b/dev/tests/api-functional/_files/Magento/TestModule5/etc/di.xml
new file mode 100644
index 0000000000000000000000000000000000000000..13e121a2d7220be0c26b7785e033e496b0c47610
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule5/etc/di.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0"?>
+<!--
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+-->
+<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../lib/internal/Magento/Framework/ObjectManager/etc/config.xsd">
+    <preference for="Magento\TestModule5\Service\V1\AllSoapAndRestInterface" type="Magento\TestModule5\Service\V1\AllSoapAndRest" />
+    <preference for="Magento\TestModule5\Service\V2\AllSoapAndRestInterface" type="Magento\TestModule5\Service\V2\AllSoapAndRest" />
+    <preference for="Magento\TestModule5\Service\V1\OverrideServiceInterface" type="Magento\TestModule5\Service\V1\OverrideService" />
+</config>
diff --git a/dev/tests/api-functional/_files/Magento/TestModule5/etc/module.xml b/dev/tests/api-functional/_files/Magento/TestModule5/etc/module.xml
new file mode 100644
index 0000000000000000000000000000000000000000..801468fd880a1697f3d4644135cf8dea87da8568
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule5/etc/module.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0"?>
+<!--
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+-->
+<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../lib/internal/Magento/Framework/Module/etc/module.xsd">
+    <module name="Magento_TestModule5" schema_version="1.0"/>
+</config>
diff --git a/dev/tests/api-functional/_files/Magento/TestModule5/etc/webapi.xml b/dev/tests/api-functional/_files/Magento/TestModule5/etc/webapi.xml
new file mode 100644
index 0000000000000000000000000000000000000000..38cbd15afb17892b91493662cb015dce20c69937
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModule5/etc/webapi.xml
@@ -0,0 +1,78 @@
+<?xml version="1.0"?>
+<!--
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+-->
+<routes xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../app/code/Magento/Webapi/etc/webapi.xsd">
+    <route method="GET" url="/V1/TestModule5/:entityId">
+        <service class="Magento\TestModule5\Service\V1\AllSoapAndRestInterface" method="item" />
+        <resources>
+            <resource ref="Magento_TestModule5::resource1" />
+        </resources>
+    </route>
+    <route method="GET" url="/V1/TestModule5">
+        <service class="Magento\TestModule5\Service\V1\AllSoapAndRestInterface" method="items" />
+        <resources>
+            <resource ref="Magento_TestModule5::resource2" />
+        </resources>
+    </route>
+    <route method="POST" url="/V1/TestModule5">
+        <service class="Magento\TestModule5\Service\V1\AllSoapAndRestInterface" method="create" />
+        <resources>
+            <resource ref="Magento_TestModule5::resource3" />
+        </resources>
+    </route>
+    <route method="PUT" url="/V1/TestModule5/:entityId">
+        <service class="Magento\TestModule5\Service\V1\AllSoapAndRestInterface" method="update" />
+        <resources>
+            <resource ref="Magento_TestModule5::resource1" />
+            <resource ref="Magento_TestModule5::resource2" />
+        </resources>
+    </route>
+    <route method="PUT" url="/V1/TestModule5/:parentId/nestedResource/:entityId">
+        <service class="Magento\TestModule5\Service\V1\AllSoapAndRestInterface" method="nestedUpdate" />
+        <resources>
+            <resource ref="Magento_TestModule5::resource1" />
+            <resource ref="Magento_TestModule5::resource2" />
+        </resources>
+    </route>
+    <route method="PUT" url="/V1/TestModule5/OverrideService/:parentId/nestedResource/:entityId">
+        <service class="Magento\TestModule5\Service\V1\OverrideServiceInterface" method="scalarUpdate" />
+        <resources>
+            <resource ref="Magento_TestModule5::resource1" />
+            <resource ref="Magento_TestModule5::resource2" />
+        </resources>
+    </route>
+    <route method="GET" url="/V2/TestModule5/:id">
+        <service class="Magento\TestModule5\Service\V2\AllSoapAndRestInterface" method="item" />
+        <resources>
+            <resource ref="Magento_TestModule5::resource1" />
+        </resources>
+    </route>
+    <route method="GET" url="/V2/TestModule5">
+        <service class="Magento\TestModule5\Service\V2\AllSoapAndRestInterface" method="items" />
+        <resources>
+            <resource ref="Magento_TestModule5::resource2" />
+        </resources>
+    </route>
+    <route method="POST" url="/V2/TestModule5">
+        <service class="Magento\TestModule5\Service\V2\AllSoapAndRestInterface" method="create" />
+        <resources>
+            <resource ref="Magento_TestModule5::resource3" />
+        </resources>
+    </route>
+    <route method="PUT" url="/V2/TestModule5/:id">
+        <service class="Magento\TestModule5\Service\V2\AllSoapAndRestInterface" method="update" />
+        <resources>
+            <resource ref="Magento_TestModule5::resource1" />
+            <resource ref="Magento_TestModule5::resource2" />
+        </resources>
+    </route>
+    <route method="DELETE" url="/V2/TestModule5/:id">
+        <service class="Magento\TestModule5\Service\V2\AllSoapAndRestInterface" method="delete" />
+        <resources>
+            <resource ref="Magento_TestModule5::resource1" />
+        </resources>
+    </route>
+</routes>
diff --git a/dev/tests/api-functional/_files/Magento/TestModuleMSC/Api/AllSoapAndRestInterface.php b/dev/tests/api-functional/_files/Magento/TestModuleMSC/Api/AllSoapAndRestInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..baa4ab49f376be3b933997ec09a715be8a621992
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModuleMSC/Api/AllSoapAndRestInterface.php
@@ -0,0 +1,48 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestModuleMSC\Api;
+
+interface AllSoapAndRestInterface
+{
+    /**
+     * @param int $itemId
+     * @return \Magento\TestModuleMSC\Api\Data\ItemInterface
+     */
+    public function item($itemId);
+
+    /**
+     * @param string $name
+     * @return \Magento\TestModuleMSC\Api\Data\ItemInterface
+     */
+    public function create($name);
+
+    /**
+     * @param \Magento\TestModuleMSC\Api\Data\ItemInterface $entityItem
+     * @return \Magento\TestModuleMSC\Api\Data\ItemInterface
+     */
+    public function update(\Magento\TestModuleMSC\Api\Data\ItemInterface $entityItem);
+
+    /**
+     * @return \Magento\TestModuleMSC\Api\Data\ItemInterface[]
+     */
+    public function items();
+
+    /**
+     * @param string $name
+     * @return \Magento\TestModuleMSC\Api\Data\ItemInterface
+     */
+    public function testOptionalParam($name = null);
+
+    /**
+     * @param \Magento\TestModuleMSC\Api\Data\ItemInterface $entityItem
+     * @return \Magento\TestModuleMSC\Api\Data\ItemInterface
+     */
+    public function itemAnyType(\Magento\TestModuleMSC\Api\Data\ItemInterface $entityItem);
+
+    /**
+     * @return \Magento\TestModuleMSC\Api\Data\ItemInterface
+     */
+    public function getPreconfiguredItem();
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModuleMSC/Api/Data/CustomAttributeDataObjectInterface.php b/dev/tests/api-functional/_files/Magento/TestModuleMSC/Api/Data/CustomAttributeDataObjectInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..0710ee62a85158a9494336ba209504860e513281
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModuleMSC/Api/Data/CustomAttributeDataObjectInterface.php
@@ -0,0 +1,15 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestModuleMSC\Api\Data;
+
+interface CustomAttributeDataObjectInterface extends \Magento\Framework\Api\ExtensibleDataInterface
+{
+    const NAME = 'name';
+
+    /**
+     * @return string
+     */
+    public function getName();
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModuleMSC/Api/Data/CustomAttributeNestedDataObjectInterface.php b/dev/tests/api-functional/_files/Magento/TestModuleMSC/Api/Data/CustomAttributeNestedDataObjectInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..5c5b634bd15e9b82dd078217f044c06b355add1a
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModuleMSC/Api/Data/CustomAttributeNestedDataObjectInterface.php
@@ -0,0 +1,13 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestModuleMSC\Api\Data;
+
+interface CustomAttributeNestedDataObjectInterface extends \Magento\Framework\Api\ExtensibleDataInterface
+{
+    /**
+     * @return string
+     */
+    public function getName();
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModuleMSC/Api/Data/ItemInterface.php b/dev/tests/api-functional/_files/Magento/TestModuleMSC/Api/Data/ItemInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..65595bafe836436811fb65b4bf8c1d9b14d8fe2b
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModuleMSC/Api/Data/ItemInterface.php
@@ -0,0 +1,18 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestModuleMSC\Api\Data;
+
+interface ItemInterface extends \Magento\Framework\Api\ExtensibleDataInterface
+{
+    /**
+     * @return int
+     */
+    public function getItemId();
+
+    /**
+     * @return string
+     */
+    public function getName();
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModuleMSC/Model/AllSoapAndRest.php b/dev/tests/api-functional/_files/Magento/TestModuleMSC/Model/AllSoapAndRest.php
new file mode 100644
index 0000000000000000000000000000000000000000..cca810f6ca4d47b3e73bbae34e16b5d8c225a07a
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModuleMSC/Model/AllSoapAndRest.php
@@ -0,0 +1,107 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestModuleMSC\Model;
+
+use Magento\TestModuleMSC\Api\Data\CustomAttributeDataObjectDataBuilder;
+use Magento\TestModuleMSC\Api\Data\ItemDataBuilder;
+
+class AllSoapAndRest implements \Magento\TestModuleMSC\Api\AllSoapAndRestInterface
+{
+    /**
+     * @var ItemDataBuilder
+     */
+    protected $itemDataBuilder;
+
+    /**
+     * @var CustomAttributeDataObjectDataBuilder
+     */
+    protected $customAttributeDataObjectDataBuilder;
+
+    /**
+     * @param ItemDataBuilder $itemDataBuilder
+     * @param CustomAttributeDataObjectDataBuilder $customAttributeNestedDataObjectBuilder
+     */
+    public function __construct(
+        ItemDataBuilder $itemDataBuilder,
+        CustomAttributeDataObjectDataBuilder $customAttributeNestedDataObjectBuilder
+    ) {
+        $this->itemDataBuilder = $itemDataBuilder;
+        $this->customAttributeDataObjectDataBuilder = $customAttributeNestedDataObjectBuilder;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function item($itemId)
+    {
+        return $this->itemDataBuilder->setItemId($itemId)->setName('testProduct1')->create();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function items()
+    {
+        $result1 = $this->itemDataBuilder->setItemId(1)->setName('testProduct1')->create();
+        $result2 = $this->itemDataBuilder->setItemId(2)->setName('testProduct2')->create();
+
+        return [$result1, $result2];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function create($name)
+    {
+        return $this->itemDataBuilder->setItemId(rand())->setName($name)->create();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function update(\Magento\TestModuleMSC\Api\Data\ItemInterface $entityItem)
+    {
+        return $this->itemDataBuilder->setItemId($entityItem->getItemId())
+            ->setName('Updated' . $entityItem->getName())
+            ->create();
+    }
+
+    public function testOptionalParam($name = null)
+    {
+        if (is_null($name)) {
+            return $this->itemDataBuilder->setItemId(3)->setName('No Name')->create();
+        } else {
+            return $this->itemDataBuilder->setItemId(3)->setName($name)->create();
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function itemAnyType(\Magento\TestModuleMSC\Api\Data\ItemInterface $entityItem)
+    {
+        return $entityItem;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getPreconfiguredItem()
+    {
+        $customAttributeDataObject = $this->customAttributeDataObjectDataBuilder
+            ->setName('nameValue')
+            ->setCustomAttribute('custom_attribute_int', 1)
+            ->create();
+
+        $item = $this->itemDataBuilder
+            ->setItemId(1)
+            ->setName('testProductAnyType')
+            ->setCustomAttribute('custom_attribute_data_object', $customAttributeDataObject)
+            ->setCustomAttribute('custom_attribute_string', 'someStringValue')
+            ->create();
+
+        return $item;
+    }
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModuleMSC/Model/Data/CustomAttributeDataObject.php b/dev/tests/api-functional/_files/Magento/TestModuleMSC/Model/Data/CustomAttributeDataObject.php
new file mode 100644
index 0000000000000000000000000000000000000000..dc3a1c1a17ece2d251d28e1fb18984174786bd41
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModuleMSC/Model/Data/CustomAttributeDataObject.php
@@ -0,0 +1,19 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestModuleMSC\Model\Data;
+
+use Magento\TestModuleMSC\Api\Data\CustomAttributeDataObjectInterface;
+
+class CustomAttributeDataObject extends \Magento\Framework\Api\AbstractExtensibleObject
+    implements CustomAttributeDataObjectInterface
+{
+    /**
+     * @return string
+     */
+    public function getName()
+    {
+        return $this->_data['name'];
+    }
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModuleMSC/Model/Data/CustomAttributeNestedDataObject.php b/dev/tests/api-functional/_files/Magento/TestModuleMSC/Model/Data/CustomAttributeNestedDataObject.php
new file mode 100644
index 0000000000000000000000000000000000000000..9ea73d87acee50649757bb3622933b42aeefa1cd
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModuleMSC/Model/Data/CustomAttributeNestedDataObject.php
@@ -0,0 +1,19 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestModuleMSC\Model\Data;
+
+use Magento\TestModuleMSC\Api\Data\CustomAttributeNestedDataObjectInterface;
+
+class CustomAttributeNestedDataObject extends \Magento\Framework\Model\AbstractExtensibleModel
+    implements CustomAttributeNestedDataObjectInterface
+{
+    /**
+     * @return string
+     */
+    public function getName()
+    {
+        return $this->_data['name'];
+    }
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModuleMSC/Model/Data/Eav/AttributeMetadata.php b/dev/tests/api-functional/_files/Magento/TestModuleMSC/Model/Data/Eav/AttributeMetadata.php
new file mode 100644
index 0000000000000000000000000000000000000000..2977a6e50691abf1b2c8f4f9996d0e1c340eef98
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModuleMSC/Model/Data/Eav/AttributeMetadata.php
@@ -0,0 +1,42 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestModuleMSC\Model\Data\Eav;
+
+use Magento\Framework\Api\AbstractExtensibleObject;
+use Magento\Framework\Api\MetadataObjectInterface;
+
+/**
+ * Class AttributeMetadata
+ */
+class AttributeMetadata extends AbstractExtensibleObject implements MetadataObjectInterface
+{
+    /**#@+
+     * Constants used as keys into $_data
+     */
+    const ATTRIBUTE_ID = 'attribute_id';
+
+    const ATTRIBUTE_CODE = 'attribute_code';
+    /**#@-*/
+
+    /**
+     * Retrieve id of the attribute.
+     *
+     * @return string|null
+     */
+    public function getAttributeId()
+    {
+        return $this->_get(self::ATTRIBUTE_ID);
+    }
+
+    /**
+     * Retrieve code of the attribute.
+     *
+     * @return string|null
+     */
+    public function getAttributeCode()
+    {
+        return $this->_get(self::ATTRIBUTE_CODE);
+    }
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModuleMSC/Model/Data/Eav/AttributeMetadataBuilder.php b/dev/tests/api-functional/_files/Magento/TestModuleMSC/Model/Data/Eav/AttributeMetadataBuilder.php
new file mode 100644
index 0000000000000000000000000000000000000000..d037b299cd059cbf378598d17ecd0d31dd09c628
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModuleMSC/Model/Data/Eav/AttributeMetadataBuilder.php
@@ -0,0 +1,36 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestModuleMSC\Model\Data\Eav;
+
+use Magento\Framework\Api\AttributeMetadataBuilderInterface;
+use Magento\Framework\Api\ExtensibleObjectBuilder;
+
+/**
+ * Class AttributeMetadataBuilder
+ */
+class AttributeMetadataBuilder extends ExtensibleObjectBuilder implements AttributeMetadataBuilderInterface
+{
+    /**
+     * Set attribute id
+     *
+     * @param  int $attributeId
+     * @return $this
+     */
+    public function setAttributeId($attributeId)
+    {
+        return $this->_set(AttributeMetadata::ATTRIBUTE_ID, $attributeId);
+    }
+
+    /**
+     * Set attribute code
+     *
+     * @param  string $attributeCode
+     * @return $this
+     */
+    public function setAttributeCode($attributeCode)
+    {
+        return $this->_set(AttributeMetadata::ATTRIBUTE_CODE, $attributeCode);
+    }
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModuleMSC/Model/Data/Item.php b/dev/tests/api-functional/_files/Magento/TestModuleMSC/Model/Data/Item.php
new file mode 100644
index 0000000000000000000000000000000000000000..5593ffcffb412548e6b0796000e661fccd51c3b4
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModuleMSC/Model/Data/Item.php
@@ -0,0 +1,27 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestModuleMSC\Model\Data;
+
+use Magento\TestModuleMSC\Api\Data\ItemInterface;
+
+class Item extends \Magento\Framework\Model\AbstractExtensibleModel
+    implements ItemInterface
+{
+    /**
+     * @return int
+     */
+    public function getItemId()
+    {
+        return $this->_data['item_id'];
+    }
+
+    /**
+     * @return string
+     */
+    public function getName()
+    {
+        return $this->_data['name'];
+    }
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModuleMSC/Model/Resource/Item.php b/dev/tests/api-functional/_files/Magento/TestModuleMSC/Model/Resource/Item.php
new file mode 100644
index 0000000000000000000000000000000000000000..b1236934e35a504bbb3a07d021d65492693eb6a1
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModuleMSC/Model/Resource/Item.php
@@ -0,0 +1,22 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+namespace Magento\TestModuleMSC\Model\Resource;
+
+/**
+ * Sample resource model
+ */
+class Item extends \Magento\Framework\Model\Resource\Db\AbstractDb
+{
+    /**
+     * Initialize connection and define main table
+     *
+     * @return void
+     */
+    protected function _construct()
+    {
+        $this->_init('dummy_item', 'dummy_item_id');
+    }
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModuleMSC/etc/acl.xml b/dev/tests/api-functional/_files/Magento/TestModuleMSC/etc/acl.xml
new file mode 100644
index 0000000000000000000000000000000000000000..923334c57ddd2347ccc53c4cb50db3ab5c1b77da
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModuleMSC/etc/acl.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0"?>
+<!--
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+-->
+<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../lib/internal/Magento/Framework/Acl/etc/acl.xsd">
+    <acl>
+        <resources>
+            <resource id="Magento_Adminhtml::admin">
+                <resource id="Magento_TestModuleMSC::all" title="TestModuleMSC" sortOrder="1">
+                    <resource id="Magento_TestModuleMSC::resource1" title="Resource1" sortOrder="20"/>
+                    <resource id="Magento_TestModuleMSC::resource2" title="Resource2" sortOrder="10"/>
+                    <resource id="Magento_TestModuleMSC::resource3" title="Resource3" sortOrder="30"/>
+                </resource>
+            </resource>
+        </resources>
+    </acl>
+</config>
diff --git a/dev/tests/api-functional/_files/Magento/TestModuleMSC/etc/data_object.xml b/dev/tests/api-functional/_files/Magento/TestModuleMSC/etc/data_object.xml
new file mode 100644
index 0000000000000000000000000000000000000000..8fed3f6421cfb9030ebae2728c77e4bf1db5861e
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModuleMSC/etc/data_object.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0"?>
+<!--
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+-->
+<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../lib/internal/Magento/Framework/Api/etc/data_object.xsd">
+    <custom_attributes for="Magento\TestModuleMSC\Api\Data\ItemInterface">
+        <attribute code="custom_attribute_data_object" type="Magento\TestModuleMSC\Api\Data\CustomAttributeDataObjectInterface" />
+        <attribute code="custom_attribute_string" type="string" />
+    </custom_attributes>
+    <custom_attributes for="Magento\TestModuleMSC\Api\Data\CustomAttributeDataObjectInterface">
+        <attribute code="custom_attribute_nested" type="Magento\TestModuleMSC\Api\Data\CustomAttributeNestedDataObjectInterface" />
+        <attribute code="custom_attribute_int" type="int" />
+    </custom_attributes>
+</config>
diff --git a/dev/tests/api-functional/_files/Magento/TestModuleMSC/etc/di.xml b/dev/tests/api-functional/_files/Magento/TestModuleMSC/etc/di.xml
new file mode 100644
index 0000000000000000000000000000000000000000..62788df693dbe8215ad23ac2f8e698f0b3b6ef3a
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModuleMSC/etc/di.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0"?>
+<!--
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+-->
+<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../lib/internal/Magento/Framework/ObjectManager/etc/config.xsd">
+
+    <preference for="Magento\TestModuleMSC\Api\AllSoapAndRestInterface" type="Magento\TestModuleMSC\Model\AllSoapAndRest" />
+
+    <preference for="Magento\TestModuleMSC\Api\Data\ItemInterface" type="Magento\TestModuleMSC\Model\Data\Item" />
+    <preference for="Magento\TestModuleMSC\Api\Data\CustomAttributeDataObjectInterface" type="Magento\TestModuleMSC\Model\Data\CustomAttributeDataObject" />
+    <preference for="Magento\TestModuleMSC\Api\Data\CustomAttributeNestedDataObjectInterface" type="Magento\TestModuleMSC\Model\Data\CustomAttributeNestedDataObject" />
+
+    <virtualType name="Magento\TestModuleMSC\Service\Config\TestModuleMSCMetadataConfig" type="Magento\Framework\Api\Config\MetadataConfig">
+        <arguments>
+            <argument name="attributeMetadataBuilder" xsi:type="object">Magento\TestModuleMSC\Model\Data\Eav\AttributeMetadataBuilder</argument>
+            <argument name="dataObjectClassName" xsi:type="string">Magento\TestModuleMSC\Model\Data\Item</argument>
+        </arguments>
+    </virtualType>
+    <type name="Magento\TestModuleMSC\Model\Data\Item">
+        <arguments>
+            <argument name="resource" xsi:type="object">Magento\TestModuleMSC\Model\Resource\Item</argument>
+            <argument name="metadataService" xsi:type="object">Magento\TestModuleMSC\Service\Config\TestModuleMSCMetadataConfig</argument>
+        </arguments>
+    </type>
+    <type name="Magento\TestModuleMSC\Model\Data\CustomAttributeDataObject">
+        <arguments>
+            <argument name="resource" xsi:type="object">Magento\TestModuleMSC\Model\Resource\Item</argument>
+        </arguments>
+    </type>
+    <type name="Magento\TestModuleMSC\Model\Data\CustomAttributeNestedDataObject">
+        <arguments>
+            <argument name="resource" xsi:type="object">Magento\TestModuleMSC\Model\Resource\Item</argument>
+        </arguments>
+    </type>
+
+</config>
diff --git a/dev/tests/api-functional/_files/Magento/TestModuleMSC/etc/module.xml b/dev/tests/api-functional/_files/Magento/TestModuleMSC/etc/module.xml
new file mode 100644
index 0000000000000000000000000000000000000000..554ab6d9d9143f03d394987815140ff52f9c8e11
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModuleMSC/etc/module.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0"?>
+<!--
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+-->
+<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../lib/internal/Magento/Framework/Module/etc/module.xsd">
+    <module name="Magento_TestModuleMSC" schema_version="1.0"/>
+</config>
diff --git a/dev/tests/api-functional/_files/Magento/TestModuleMSC/etc/webapi.xml b/dev/tests/api-functional/_files/Magento/TestModuleMSC/etc/webapi.xml
new file mode 100644
index 0000000000000000000000000000000000000000..ed721a73e8e884195f5c4aaba36b6118c3b91089
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModuleMSC/etc/webapi.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0"?>
+<!--
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+-->
+<routes xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../app/code/Magento/Webapi/etc/webapi.xsd">
+
+    <route method="GET" url="/V1/testmoduleMSC/overwritten">
+        <service class="Magento\TestModuleMSC\Api\AllSoapAndRestInterface" method="item" />
+        <resources>
+            <resource ref="Magento_TestModuleMSC::resource1" />
+        </resources>
+        <data>
+            <parameter name="itemId" force="true">-55</parameter>
+        </data>
+    </route>
+
+    <route method="POST" url="/V1/testmoduleMSC/testOptionalParam">
+        <service class="Magento\TestModuleMSC\Api\AllSoapAndRestInterface" method="testOptionalParam" />
+        <resources>
+            <resource ref="Magento_TestModuleMSC::resource1" />
+        </resources>
+        <data>
+            <parameter name="name">Default Name</parameter>
+        </data>
+    </route>
+
+    <route method="GET" url="/V1/testmoduleMSC/:itemId">
+        <service class="Magento\TestModuleMSC\Api\AllSoapAndRestInterface" method="item" />
+        <resources>
+            <resource ref="Magento_TestModuleMSC::resource1" />
+        </resources>
+    </route>
+    <route method="GET" url="/V1/testmoduleMSC">
+        <service class="Magento\TestModuleMSC\Api\AllSoapAndRestInterface" method="items" />
+        <resources>
+            <resource ref="Magento_TestModuleMSC::resource2" />
+        </resources>
+    </route>
+    <route method="POST" url="/V1/testmoduleMSC">
+        <service class="Magento\TestModuleMSC\Api\AllSoapAndRestInterface" method="create" />
+        <resources>
+            <resource ref="Magento_TestModuleMSC::resource3" />
+        </resources>
+    </route>
+    <route method="PUT" url="/V1/testmoduleMSC/:itemId">
+        <service class="Magento\TestModuleMSC\Api\AllSoapAndRestInterface" method="update" />
+        <resources>
+            <resource ref="Magento_TestModuleMSC::resource1" />
+            <resource ref="Magento_TestModuleMSC::resource2" />
+        </resources>
+    </route>
+    <route method="POST" url="/V1/testmoduleMSC/itemAnyType">
+        <service class="Magento\TestModuleMSC\Api\AllSoapAndRestInterface" method="itemAnyType" />
+        <resources>
+            <resource ref="Magento_TestModuleMSC::resource1" />
+            <resource ref="Magento_TestModuleMSC::resource2" />
+        </resources>
+    </route>
+    <route method="GET" url="/V1/testmoduleMSC/itemPreconfigured">
+        <service class="Magento\TestModuleMSC\Api\AllSoapAndRestInterface" method="getPreconfiguredItem" />
+        <resources>
+            <resource ref="Magento_TestModuleMSC::resource1" />
+            <resource ref="Magento_TestModuleMSC::resource2" />
+        </resources>
+    </route>
+</routes>
diff --git a/dev/tests/api-functional/config/install-config-mysql.php.dist b/dev/tests/api-functional/config/install-config-mysql.php.dist
new file mode 100644
index 0000000000000000000000000000000000000000..85665d34d61615880847566e595d3dade4dafd3b
--- /dev/null
+++ b/dev/tests/api-functional/config/install-config-mysql.php.dist
@@ -0,0 +1,29 @@
+<?php
+/**
+ * Magento console installer options for Web API functional tests. Are used in functional tests bootstrap.
+ *
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+return [
+    'language'                     => 'en_US',
+    'timezone'                     => 'America/Los_Angeles',
+    'currency'                     => 'USD',
+    'db_host'                      => 'localhost',
+    'db_name'                      => 'magento_functional_tests',
+    'db_user'                      => 'root',
+    'db_pass'                      => '',
+    'backend_frontname'            => 'backend',
+    'base_url'                     => 'http://localhost/',
+    'use_secure'                   => '0',
+    'use_rewrites'                 => '0',
+    'admin_lastname'               => 'Admin',
+    'admin_firstname'              => 'Admin',
+    'admin_email'                  => 'admin@example.com',
+    'admin_username'               => 'admin',
+    'admin_password'               => '123123q',
+    'admin_use_security_key'       => '0',
+    /* PayPal has limitation for order number - 20 characters. 10 digits prefix + 8 digits number is good enough */
+    'sales_order_increment_prefix' => time(),
+    'session_save'                 => 'db',
+    'cleanup_database'             => true,
+];
diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/Annotation/ApiDataFixture.php b/dev/tests/api-functional/framework/Magento/TestFramework/Annotation/ApiDataFixture.php
new file mode 100644
index 0000000000000000000000000000000000000000..6bc41d3a3f41e0e0e3fa1f35b87357d224236a54
--- /dev/null
+++ b/dev/tests/api-functional/framework/Magento/TestFramework/Annotation/ApiDataFixture.php
@@ -0,0 +1,152 @@
+<?php
+/**
+ * Implementation of the magentoApiDataFixture DocBlock annotation.
+ *
+ * The difference of magentoApiDataFixture from magentoDataFixture is
+ * that no transactions should be used for API data fixtures.
+ * Otherwise fixture data will not be accessible to Web API functional tests.
+ *
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestFramework\Annotation;
+
+class ApiDataFixture
+{
+    /**
+     * @var string
+     */
+    protected $_fixtureBaseDir;
+
+    /**
+     * Fixtures that have been applied
+     *
+     * @var array
+     */
+    private $_appliedFixtures = [];
+
+    /**
+     * Constructor
+     *
+     * @param string $fixtureBaseDir
+     * @throws \Magento\Framework\Exception
+     */
+    public function __construct($fixtureBaseDir)
+    {
+        if (!is_dir($fixtureBaseDir)) {
+            throw new \Magento\Framework\Exception("Fixture base directory '{$fixtureBaseDir}' does not exist.");
+        }
+        $this->_fixtureBaseDir = realpath($fixtureBaseDir);
+    }
+
+    /**
+     * Handler for 'startTest' event
+     *
+     * @param \PHPUnit_Framework_TestCase $test
+     */
+    public function startTest(\PHPUnit_Framework_TestCase $test)
+    {
+        /** Apply method level fixtures if thy are available, apply class level fixtures otherwise */
+        $this->_applyFixtures($this->_getFixtures('method', $test) ?: $this->_getFixtures('class', $test));
+    }
+
+    /**
+     * Handler for 'endTest' event
+     */
+    public function endTest()
+    {
+        $this->_revertFixtures();
+    }
+
+    /**
+     * Retrieve fixtures from annotation
+     *
+     * @param string $scope 'class' or 'method'
+     * @param \PHPUnit_Framework_TestCase $test
+     * @return array
+     * @throws \Magento\Framework\Exception
+     */
+    protected function _getFixtures($scope, \PHPUnit_Framework_TestCase $test)
+    {
+        $annotations = $test->getAnnotations();
+        $result = [];
+        if (!empty($annotations[$scope]['magentoApiDataFixture'])) {
+            foreach ($annotations[$scope]['magentoApiDataFixture'] as $fixture) {
+                if (strpos($fixture, '\\') !== false) {
+                    // usage of a single directory separator symbol streamlines search across the source code
+                    throw new \Magento\Framework\Exception('Directory separator "\\" is prohibited in fixture declaration.');
+                }
+                $fixtureMethod = [get_class($test), $fixture];
+                if (is_callable($fixtureMethod)) {
+                    $result[] = $fixtureMethod;
+                } else {
+                    $result[] = $this->_fixtureBaseDir . '/' . $fixture;
+                }
+            }
+        }
+        return $result;
+    }
+
+    /**
+     * Execute single fixture script
+     *
+     * @param string|array $fixture
+     */
+    protected function _applyOneFixture($fixture)
+    {
+        try {
+            if (is_callable($fixture)) {
+                call_user_func($fixture);
+            } else {
+                require $fixture;
+            }
+        } catch (\Exception $e) {
+            echo 'Exception occurred when running the '
+            . (is_array($fixture) || is_scalar($fixture) ? json_encode($fixture) : 'callback')
+            . ' fixture: ', PHP_EOL, $e;
+        }
+        $this->_appliedFixtures[] = $fixture;
+    }
+
+    /**
+     * Execute fixture scripts if any
+     *
+     * @param array $fixtures
+     * @throws \Magento\Framework\Exception
+     */
+    protected function _applyFixtures(array $fixtures)
+    {
+        /* Execute fixture scripts */
+        foreach ($fixtures as $oneFixture) {
+            /* Skip already applied fixtures */
+            if (!in_array($oneFixture, $this->_appliedFixtures, true)) {
+                $this->_applyOneFixture($oneFixture);
+            }
+        }
+    }
+
+    /**
+     * Revert changes done by fixtures
+     */
+    protected function _revertFixtures()
+    {
+        foreach ($this->_appliedFixtures as $fixture) {
+            if (is_callable($fixture)) {
+                $fixture[1] .= 'Rollback';
+                if (is_callable($fixture)) {
+                    $this->_applyOneFixture($fixture);
+                }
+            } else {
+                $fileInfo = pathinfo($fixture);
+                $extension = '';
+                if (isset($fileInfo['extension'])) {
+                    $extension = '.' . $fileInfo['extension'];
+                }
+                $rollbackScript = $fileInfo['dirname'] . '/' . $fileInfo['filename'] . '_rollback' . $extension;
+                if (file_exists($rollbackScript)) {
+                    $this->_applyOneFixture($rollbackScript);
+                }
+            }
+        }
+        $this->_appliedFixtures = [];
+    }
+}
diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/Authentication/OauthHelper.php b/dev/tests/api-functional/framework/Magento/TestFramework/Authentication/OauthHelper.php
new file mode 100644
index 0000000000000000000000000000000000000000..05d33b6bb9fb29ef25a68f63d898f3e6ab30d786
--- /dev/null
+++ b/dev/tests/api-functional/framework/Magento/TestFramework/Authentication/OauthHelper.php
@@ -0,0 +1,200 @@
+<?php
+/**
+ * Helper class for generating OAuth related credentials
+ *
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestFramework\Authentication;
+
+use Magento\TestFramework\Authentication\Rest\OauthClient;
+use Magento\TestFramework\Helper\Bootstrap;
+use OAuth\Common\Consumer\Credentials;
+use Zend\Stdlib\Exception\LogicException;
+
+class OauthHelper
+{
+    /** @var array */
+    protected static $_apiCredentials;
+
+    /**
+     * Generate authentication credentials
+     * @param string $date consumer creation date
+     * @return array
+     * <pre>
+     * array (
+     *   'key' => 'ajdsjashgdkahsdlkjasldkjals', //consumer key
+     *   'secret' => 'alsjdlaskjdlaksjdlasjkdlas', //consumer secret
+     *   'verifier' => 'oiudioqueoiquweoiqwueoqwuii'
+     *   'consumer' => $consumer, // retrieved consumer Model
+     *   'token' => $token // retrieved token Model
+     *   );
+     * </pre>
+     */
+    public static function getConsumerCredentials($date = null)
+    {
+        $integration = self::_createIntegration('all');
+        $objectManager = Bootstrap::getObjectManager();
+        /** @var $oauthService \Magento\Integration\Service\V1\Oauth */
+        $oauthService = $objectManager->get('Magento\Integration\Service\V1\Oauth');
+        $consumer = $oauthService->loadConsumer($integration->getConsumerId());
+        $url = TESTS_BASE_URL;
+        $consumer->setCallbackUrl($url);
+        $consumer->setRejectedCallbackUrl($url);
+        if (!is_null($date)) {
+            $consumer->setCreatedAt($date);
+        }
+        $consumer->save();
+        $token = $objectManager->create('Magento\Integration\Model\Oauth\Token');
+        $verifier = $token->createVerifierToken($consumer->getId())->getVerifier();
+
+        return [
+            'key' => $consumer->getKey(),
+            'secret' => $consumer->getSecret(),
+            'verifier' => $verifier,
+            'consumer' => $consumer,
+            'token' => $token
+        ];
+    }
+
+    /**
+     * Create an access token to associated to a consumer to access APIs. No resources are available to this consumer.
+     *
+     * @return array comprising of token  key and secret
+     * <pre>
+     * array (
+     *   'key' => 'ajdsjashgdkahsdlkjasldkjals', //token key
+     *   'secret' => 'alsjdlaskjdlaksjdlasjkdlas', //token secret
+     *   'oauth_client' => $oauthClient // OauthClient instance used to fetch the access token
+     *   );
+     * </pre>
+     */
+    public static function getAccessToken()
+    {
+        $consumerCredentials = self::getConsumerCredentials();
+        $credentials = new Credentials($consumerCredentials['key'], $consumerCredentials['secret'], TESTS_BASE_URL);
+        $oAuthClient = new OauthClient($credentials);
+        $requestToken = $oAuthClient->requestRequestToken();
+        $accessToken = $oAuthClient->requestAccessToken(
+            $requestToken->getRequestToken(),
+            $consumerCredentials['verifier'],
+            $requestToken->getRequestTokenSecret()
+        );
+
+        /** TODO: Reconsider return format. It is not aligned with method name. */
+        return [
+            'key' => $accessToken->getAccessToken(),
+            'secret' => $accessToken->getAccessTokenSecret(),
+            'oauth_client' => $oAuthClient
+        ];
+    }
+
+    /**
+     * Create an access token, tied to integration which has permissions to all API resources in the system.
+     *
+     * @param array $resources list of resources to grant to the integration
+     * @return array
+     * <pre>
+     * array (
+     *   'key' => 'ajdsjashgdkahsdlkjasldkjals', //token key
+     *   'secret' => 'alsjdlaskjdlaksjdlasjkdlas', //token secret
+     *   'oauth_client' => $oauthClient // OauthClient instance used to fetch the access token
+     *   'integration' => $integration // Integration instance associated with access token
+     *   );
+     * </pre>
+     * @throws LogicException
+     */
+    public static function getApiAccessCredentials($resources = null)
+    {
+        if (!self::$_apiCredentials) {
+            $integration = self::_createIntegration($resources);
+            $objectManager = Bootstrap::getObjectManager();
+            /** @var \Magento\Integration\Service\V1\Oauth $oauthService */
+            $oauthService = $objectManager->get('Magento\Integration\Service\V1\Oauth');
+            $oauthService->createAccessToken($integration->getConsumerId());
+            $accessToken = $oauthService->getAccessToken($integration->getConsumerId());
+            if (!$accessToken) {
+                throw new LogicException('Access token was not created.');
+            }
+            $consumer = $oauthService->loadConsumer($integration->getConsumerId());
+            $credentials = new Credentials($consumer->getKey(), $consumer->getSecret(), TESTS_BASE_URL);
+            /** @var $oAuthClient OauthClient */
+            $oAuthClient = new OauthClient($credentials);
+
+            self::$_apiCredentials = [
+                'key' => $accessToken->getToken(),
+                'secret' => $accessToken->getSecret(),
+                'oauth_client' => $oAuthClient,
+                'integration' => $integration,
+            ];
+        }
+        return self::$_apiCredentials;
+    }
+
+    /**
+     * Forget API access credentials.
+     */
+    public static function clearApiAccessCredentials()
+    {
+        self::$_apiCredentials = false;
+    }
+
+    /**
+     * Remove fs element with nested elements.
+     *
+     * @param string $dir
+     * @param bool   $doSaveRoot
+     */
+    protected static function _rmRecursive($dir, $doSaveRoot = false)
+    {
+        if (is_dir($dir)) {
+            foreach (glob($dir . '/*') as $object) {
+                if (is_dir($object)) {
+                    self::_rmRecursive($object);
+                } else {
+                    unlink($object);
+                }
+            }
+            if (!$doSaveRoot) {
+                rmdir($dir);
+            }
+        } else {
+            unlink($dir);
+        }
+    }
+
+    /**
+     * Create integration instance.
+     *
+     * @param array $resources
+     * @return \Magento\Integration\Model\Integration
+     * @throws \Zend\Stdlib\Exception\LogicException
+     */
+    protected static function _createIntegration($resources)
+    {
+        $objectManager = Bootstrap::getObjectManager();
+        /** @var $integrationService \Magento\Integration\Service\V1\IntegrationInterface */
+        $integrationService = $objectManager->get('Magento\Integration\Service\V1\IntegrationInterface');
+
+        $params = ['name' => 'Integration' . microtime()];
+
+        if ($resources === null || $resources == 'all') {
+            $params['all_resources'] = true;
+        } else {
+            $params['resource'] = $resources;
+        }
+        $integration = $integrationService->create($params);
+        $integration->setStatus(\Magento\Integration\Model\Integration::STATUS_ACTIVE)->save();
+
+        /** Magento cache must be cleared to activate just created ACL role. */
+        $varPath = realpath('../../../var');
+        if (!$varPath) {
+            throw new LogicException("Magento cache cannot be cleared after new ACL role creation.");
+        } else {
+            $cachePath = $varPath . '/cache';
+            if (is_dir($cachePath)) {
+                self::_rmRecursive($cachePath, true);
+            }
+        }
+        return $integration;
+    }
+}
diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/Authentication/Rest/OauthClient.php b/dev/tests/api-functional/framework/Magento/TestFramework/Authentication/Rest/OauthClient.php
new file mode 100644
index 0000000000000000000000000000000000000000..021e836e0f37ccef541c018673ee37d4e3f9ace9
--- /dev/null
+++ b/dev/tests/api-functional/framework/Magento/TestFramework/Authentication/Rest/OauthClient.php
@@ -0,0 +1,271 @@
+<?php
+/**
+ * oAuth client for Magento REST API.
+ *
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestFramework\Authentication\Rest;
+
+use Magento\TestFramework\Helper\Bootstrap;
+use OAuth\Common\Consumer\Credentials;
+use OAuth\Common\Http\Client\ClientInterface;
+use OAuth\Common\Http\Exception\TokenResponseException;
+use OAuth\Common\Http\Uri\Uri;
+use OAuth\Common\Http\Uri\UriInterface;
+use OAuth\Common\Storage\TokenStorageInterface;
+use OAuth\OAuth1\Service\AbstractService;
+use OAuth\OAuth1\Signature\SignatureInterface;
+use OAuth\OAuth1\Token\StdOAuth1Token;
+use OAuth\OAuth1\Token\TokenInterface;
+
+class OauthClient extends AbstractService
+{
+    /** @var string|null */
+    protected $_oauthVerifier = null;
+
+    public function __construct(
+        Credentials $credentials,
+        ClientInterface $httpClient = null,
+        TokenStorageInterface $storage = null,
+        SignatureInterface $signature = null,
+        UriInterface $baseApiUri = null
+    ) {
+        if (!isset($httpClient)) {
+            $httpClient = new \OAuth\Common\Http\Client\StreamClient();
+        }
+        if (!isset($storage)) {
+            $storage = new \OAuth\Common\Storage\Session();
+        }
+        if (!isset($signature)) {
+            $signature = new \Magento\TestFramework\Authentication\Rest\OauthClient\Signature($credentials);
+        }
+        parent::__construct($credentials, $httpClient, $storage, $signature, $baseApiUri);
+    }
+
+    /**
+     * @return UriInterface
+     */
+    public function getRequestTokenEndpoint()
+    {
+        return new Uri(TESTS_BASE_URL . '/oauth/token/request');
+    }
+
+    /**
+     * Returns the authorization API endpoint.
+     *
+     * @return UriInterface
+     */
+    public function getAuthorizationEndpoint()
+    {
+        throw new \OAuth\Common\Exception\Exception(
+            'Magento REST API is 2-legged. Current operation is not available.'
+        );
+    }
+
+    /**
+     * Returns the access token API endpoint.
+     *
+     * @return UriInterface
+     */
+    public function getAccessTokenEndpoint()
+    {
+        return new Uri(TESTS_BASE_URL . '/oauth/token/access');
+    }
+
+    /**
+     * Returns the TestModule1 Rest API endpoint.
+     *
+     * @return UriInterface
+     */
+    public function getTestApiEndpoint()
+    {
+        $defaultStoreCode = Bootstrap::getObjectManager()->get('Magento\Store\Model\StoreManagerInterface')
+            ->getStore()->getCode();
+        return new Uri(TESTS_BASE_URL . '/rest/' . $defaultStoreCode . '/V1/testmodule1');
+    }
+
+    /**
+     * Parses the access token response and returns a TokenInterface.
+     *
+     * @return TokenInterface
+     * @param string $responseBody
+     */
+    protected function parseAccessTokenResponse($responseBody)
+    {
+        return $this->_parseToken($responseBody);
+    }
+
+    /**
+     * Parses the request token response and returns a TokenInterface.
+     *
+     * @return TokenInterface
+     * @param string $responseBody
+     * @throws TokenResponseException
+     */
+    protected function parseRequestTokenResponse($responseBody)
+    {
+        $data = $this->_parseResponseBody($responseBody);
+        if (isset($data['oauth_verifier'])) {
+            $this->_oauthVerifier = $data['oauth_verifier'];
+        }
+        return $this->_parseToken($responseBody);
+    }
+
+    /**
+     * Parse response body and create oAuth token object based on parameters provided.
+     *
+     * @param string $responseBody
+     * @return StdOAuth1Token
+     * @throws TokenResponseException
+     */
+    protected function _parseToken($responseBody)
+    {
+        $data = $this->_parseResponseBody($responseBody);
+        $token = new StdOAuth1Token();
+        $token->setRequestToken($data['oauth_token']);
+        $token->setRequestTokenSecret($data['oauth_token_secret']);
+        $token->setAccessToken($data['oauth_token']);
+        $token->setAccessTokenSecret($data['oauth_token_secret']);
+        $token->setEndOfLife(StdOAuth1Token::EOL_NEVER_EXPIRES);
+        unset($data['oauth_token'], $data['oauth_token_secret']);
+        $token->setExtraParams($data);
+        return $token;
+    }
+
+    /**
+     * Parse response body and return data in array.
+     *
+     * @param string $responseBody
+     * @return array
+     * @throws \OAuth\Common\Http\Exception\TokenResponseException
+     */
+    protected function _parseResponseBody($responseBody)
+    {
+        if (!is_string($responseBody)) {
+            throw new TokenResponseException("Response body is expected to be a string.");
+        }
+        parse_str($responseBody, $data);
+        if (null === $data || !is_array($data)) {
+            throw new TokenResponseException('Unable to parse response.');
+        } elseif (isset($data['error'])) {
+            throw new TokenResponseException("Error occurred: '{$data['error']}'");
+        }
+        return $data;
+    }
+
+    /**
+     * Retrieve oAuth verifier that was obtained during request token request.
+     *
+     * @return string
+     * @throws \OAuth\Common\Http\Exception\TokenResponseException
+     */
+    public function getOauthVerifier()
+    {
+        if (!isset($this->_oauthVerifier) || isEmpty($this->_oauthVerifier)) {
+            throw new TokenResponseException("oAuth verifier must be obtained during request token request.");
+        }
+        return $this->_oauthVerifier;
+    }
+
+    /**
+     * @override to fix since parent implementation from lib not sending the oauth_verifier when requesting access token
+     * Builds the authorization header for an authenticated API request
+     * @param string $method
+     * @param UriInterface $uri the uri the request is headed
+     * @param \OAuth\OAuth1\Token\TokenInterface $token
+     * @param $bodyParams array
+     * @return string
+     */
+    protected function buildAuthorizationHeaderForAPIRequest(
+        $method,
+        UriInterface $uri,
+        TokenInterface $token,
+        $bodyParams = null
+    ) {
+        $this->signature->setTokenSecret($token->getAccessTokenSecret());
+        $parameters = $this->getBasicAuthorizationHeaderInfo();
+        if (isset($parameters['oauth_callback'])) {
+            unset($parameters['oauth_callback']);
+        }
+
+        $parameters = array_merge($parameters, ['oauth_token' => $token->getAccessToken()]);
+        $parameters = array_merge($parameters, $bodyParams);
+        $parameters['oauth_signature'] = $this->signature->getSignature($uri, $parameters, $method);
+
+        $authorizationHeader = 'OAuth ';
+        $delimiter = '';
+
+        foreach ($parameters as $key => $value) {
+            $authorizationHeader .= $delimiter . rawurlencode($key) . '="' . rawurlencode($value) . '"';
+            $delimiter = ', ';
+        }
+
+        return $authorizationHeader;
+    }
+
+    /**
+     * Builds the oAuth authorization header for an authenticated API request
+     *
+     * @param UriInterface $uri the uri the request is headed
+     * @param \OAuth\OAuth1\Token\TokenInterface $token
+     * @param string $tokenSecret used to verify the passed token
+     * @param array $bodyParams
+     * @param string $method HTTP method to use
+     * @return array
+     */
+    public function buildOauthAuthorizationHeader($uri, $token, $tokenSecret, $bodyParams, $method = 'GET')
+    {
+        $uri = new Uri($uri);
+        $tokenObj = new StdOAuth1Token();
+        $tokenObj->setAccessToken($token);
+        $tokenObj->setAccessTokenSecret($tokenSecret);
+        $tokenObj->setEndOfLife(StdOAuth1Token::EOL_NEVER_EXPIRES);
+        return [
+            'Authorization: ' . $this->buildAuthorizationHeaderForAPIRequest($method, $uri, $tokenObj, $bodyParams)
+        ];
+    }
+
+    /**
+     * Builds the bearer token authorization header
+     *
+     * @param string $token
+     * @return array
+     */
+    public function buildBearerTokenAuthorizationHeader($token)
+    {
+        return [
+            'Authorization: Bearer ' . $token
+        ];
+    }
+
+    /**
+     * Validates a Test REST api call access using oauth access token
+     *
+     * @param TokenInterface $token The access token.
+     * @param string $method HTTP method.
+     * @return array
+     * @throws TokenResponseException
+     */
+    public function validateAccessToken($token, $method = 'GET')
+    {
+        //Need to add Accept header else Magento errors out with 503
+        $extraAuthenticationHeaders = ['Accept' => 'application/json'];
+
+        $this->signature->setTokenSecret($token->getAccessTokenSecret());
+
+        $authorizationHeader = [
+            'Authorization' => $this->buildAuthorizationHeaderForAPIRequest(
+                $method,
+                $this->getTestApiEndpoint(),
+                $token,
+                []
+            ),
+        ];
+
+        $headers = array_merge($authorizationHeader, $extraAuthenticationHeaders);
+
+        $responseBody = $this->httpClient->retrieveResponse($this->getTestApiEndpoint(), [], $headers, $method);
+
+        return json_decode($responseBody);
+    }
+}
diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/Authentication/Rest/OauthClient/Signature.php b/dev/tests/api-functional/framework/Magento/TestFramework/Authentication/Rest/OauthClient/Signature.php
new file mode 100644
index 0000000000000000000000000000000000000000..2a2a6c73692bf1c529cd05d23295022085778a32
--- /dev/null
+++ b/dev/tests/api-functional/framework/Magento/TestFramework/Authentication/Rest/OauthClient/Signature.php
@@ -0,0 +1,53 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+namespace Magento\TestFramework\Authentication\Rest\OauthClient;
+
+use OAuth\Common\Http\Uri\UriInterface;
+
+/**
+ * Signature class for Magento REST API.
+ */
+class Signature extends \OAuth\OAuth1\Signature\Signature
+{
+    /**
+     * {@inheritdoc}
+     *
+     * In addition to the original method, allows array parameters for filters.
+     */
+    public function getSignature(UriInterface $uri, array $params, $method = 'POST')
+    {
+        $queryStringData = !$uri->getQuery() ? [] : array_reduce(
+            explode('&', $uri->getQuery()),
+            function ($carry, $item) {
+                list($key, $value) = explode('=', $item, 2);
+                $carry[rawurldecode($key)] = rawurldecode($value);
+                return $carry;
+            },
+            []
+        );
+
+        foreach (array_merge($queryStringData, $params) as $key => $value) {
+            $signatureData[rawurlencode($key)] = rawurlencode($value);
+        }
+
+        ksort($signatureData);
+
+        // determine base uri
+        $baseUri = $uri->getScheme() . '://' . $uri->getRawAuthority();
+
+        if ('/' == $uri->getPath()) {
+            $baseUri .= $uri->hasExplicitTrailingHostSlash() ? '/' : '';
+        } else {
+            $baseUri .= $uri->getPath();
+        }
+
+        $baseString = strtoupper($method) . '&';
+        $baseString .= rawurlencode($baseUri) . '&';
+        $baseString .= rawurlencode($this->buildSignatureDataString($signatureData));
+
+        return base64_encode($this->hash($baseString));
+    }
+}
diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/Bootstrap/WebapiDocBlock.php b/dev/tests/api-functional/framework/Magento/TestFramework/Bootstrap/WebapiDocBlock.php
new file mode 100644
index 0000000000000000000000000000000000000000..a764f9988864f3053ae6890605d4f54452a78b24
--- /dev/null
+++ b/dev/tests/api-functional/framework/Magento/TestFramework/Bootstrap/WebapiDocBlock.php
@@ -0,0 +1,23 @@
+<?php
+/**
+ * Bootstrap of the custom Web API DocBlock annotations.
+ *
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestFramework\Bootstrap;
+
+class WebapiDocBlock extends \Magento\TestFramework\Bootstrap\DocBlock
+{
+    /**
+     * Get list of subscribers. In addition, register <b>magentoApiDataFixture</b> annotation processing.
+     *
+     * @param \Magento\TestFramework\Application $application
+     * @return array
+     */
+    protected function _getSubscribers(\Magento\TestFramework\Application $application)
+    {
+        $subscribers = parent::_getSubscribers($application);
+        array_unshift($subscribers, new \Magento\TestFramework\Annotation\ApiDataFixture($this->_fixturesBaseDir));
+        return $subscribers;
+    }
+}
diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/Helper/Customer.php b/dev/tests/api-functional/framework/Magento/TestFramework/Helper/Customer.php
new file mode 100644
index 0000000000000000000000000000000000000000..77033575557430fc8b296893bc099b1b41d3152c
--- /dev/null
+++ b/dev/tests/api-functional/framework/Magento/TestFramework/Helper/Customer.php
@@ -0,0 +1,170 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestFramework\Helper;
+
+use Magento\Customer\Api\Data\AddressDataBuilder;
+use Magento\Customer\Api\Data\CustomerDataBuilder;
+use Magento\Customer\Api\Data\CustomerInterface;
+use Magento\Customer\Model\Data\Customer as CustomerData;
+use Magento\Framework\Reflection\DataObjectProcessor;
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+class Customer extends WebapiAbstract
+{
+    const RESOURCE_PATH = '/V1/customers';
+    const SERVICE_NAME = 'customerAccountManagementV1';
+    const SERVICE_VERSION = 'V1';
+
+    const CONFIRMATION = 'a4fg7h893e39d';
+    const CREATED_AT = '2013-11-05';
+    const CREATED_IN = 'default';
+    const STORE_NAME = 'Store Name';
+    const DOB = '1970-01-01';
+    const GENDER = 'Male';
+    const GROUP_ID = 1;
+    const MIDDLENAME = 'A';
+    const PREFIX = 'Mr.';
+    const STORE_ID = 1;
+    const SUFFIX = 'Esq.';
+    const TAXVAT = '12';
+    const WEBSITE_ID = 1;
+
+    /** Sample values for testing */
+    const FIRSTNAME = 'Jane';
+    const LASTNAME = 'Doe';
+    const PASSWORD = 'test@123';
+
+    const ADDRESS_CITY1 = 'CityM';
+    const ADDRESS_CITY2 = 'CityX';
+    const ADDRESS_REGION_CODE1 = 'AL';
+    const ADDRESS_REGION_CODE2 = 'AL';
+
+    /** @var AddressDataBuilder */
+    private $addressBuilder;
+
+    /** @var CustomerDataBuilder */
+    private $customerBuilder;
+
+    /** @var DataObjectProcessor */
+    private $dataObjectProcessor;
+
+    public function __construct($name = null, array $data = [], $dataName = '')
+    {
+        parent::__construct($name, $data, $dataName);
+
+        $this->addressBuilder = Bootstrap::getObjectManager()->create(
+            'Magento\Customer\Api\Data\AddressDataBuilder'
+        );
+
+        $this->customerBuilder = Bootstrap::getObjectManager()->create(
+            'Magento\Customer\Api\Data\CustomerDataBuilder'
+        );
+
+        $this->dataObjectProcessor = Bootstrap::getObjectManager()->create(
+            'Magento\Framework\Reflection\DataObjectProcessor'
+        );
+    }
+
+    public function createSampleCustomer()
+    {
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH,
+                'httpMethod' => RestConfig::HTTP_METHOD_POST,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'CreateAccount',
+            ],
+        ];
+        $customerDataArray = $this->dataObjectProcessor->buildOutputDataArray(
+            $this->createSampleCustomerDataObject(),
+            '\Magento\Customer\Api\Data\CustomerInterface'
+        );
+        $requestData = ['customer' => $customerDataArray, 'password' => self::PASSWORD];
+        $customerData = $this->_webApiCall($serviceInfo, $requestData);
+        return $customerData;
+    }
+
+    /**
+     * Create customer using setters.
+     *
+     * @return CustomerInterface
+     */
+    public function createSampleCustomerDataObject()
+    {
+        $this->addressBuilder
+            ->setCountryId('US')
+            ->setDefaultBilling(true)
+            ->setDefaultShipping(true)
+            ->setPostcode('75477')
+            ->setRegion(
+                Bootstrap::getObjectManager()->create('Magento\Customer\Api\Data\RegionDataBuilder')
+                    ->setRegionCode(self::ADDRESS_REGION_CODE1)
+                    ->setRegion('Alabama')
+                    ->setRegionId(1)
+                    ->create()
+            )
+            ->setStreet(['Green str, 67'])
+            ->setTelephone('3468676')
+            ->setCity(self::ADDRESS_CITY1)
+            ->setFirstname('John')
+            ->setLastname('Smith');
+        $address1 = $this->dataObjectProcessor->buildOutputDataArray(
+            $this->addressBuilder->create(),
+            'Magento\Customer\Api\Data\AddressInterface'
+        );
+
+        $this->addressBuilder
+            ->setCountryId('US')
+            ->setDefaultBilling(false)
+            ->setDefaultShipping(false)
+            ->setPostcode('47676')
+            ->setRegion(
+                Bootstrap::getObjectManager()->create('Magento\Customer\Api\Data\RegionDataBuilder')
+                    ->setRegionCode(self::ADDRESS_REGION_CODE2)
+                    ->setRegion('Alabama')
+                    ->setRegionId(1)
+                    ->create()
+            )
+            ->setStreet(['Black str, 48', 'Building D'])
+            ->setCity(self::ADDRESS_CITY2)
+            ->setTelephone('3234676')
+            ->setFirstname('John')
+            ->setLastname('Smith');
+        $address2 = $this->dataObjectProcessor->buildOutputDataArray(
+            $this->addressBuilder->create(),
+            'Magento\Customer\Api\Data\AddressInterface'
+        );
+
+        $customerData = [
+            CustomerData::FIRSTNAME => self::FIRSTNAME,
+            CustomerData::LASTNAME => self::LASTNAME,
+            CustomerData::EMAIL => 'janedoe' . uniqid() . '@example.com',
+            CustomerData::CONFIRMATION => self::CONFIRMATION,
+            CustomerData::CREATED_AT => self::CREATED_AT,
+            CustomerData::CREATED_IN => self::STORE_NAME,
+            CustomerData::DOB => self::DOB,
+            CustomerData::GENDER => self::GENDER,
+            CustomerData::GROUP_ID => self::GROUP_ID,
+            CustomerData::MIDDLENAME => self::MIDDLENAME,
+            CustomerData::PREFIX => self::PREFIX,
+            CustomerData::STORE_ID => self::STORE_ID,
+            CustomerData::SUFFIX => self::SUFFIX,
+            CustomerData::TAXVAT => self::TAXVAT,
+            CustomerData::WEBSITE_ID => self::WEBSITE_ID,
+            CustomerData::KEY_ADDRESSES => [$address1, $address2],
+            'custom_attributes' => [
+                [
+                    'attribute_code' => 'disable_auto_group_change',
+                    'value' => '0',
+                ],
+            ],
+        ];
+        return $this->customerBuilder->populateWithArray($customerData)->create();
+    }
+}
diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Rest.php b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Rest.php
new file mode 100644
index 0000000000000000000000000000000000000000..91a12efba587b2d5a22bfccee576f1a875af0c3a
--- /dev/null
+++ b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Rest.php
@@ -0,0 +1,145 @@
+<?php
+/**
+ * Test client for REST API testing.
+ *
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+namespace Magento\TestFramework\TestCase\Webapi\Adapter;
+
+use Magento\TestFramework\Helper\Bootstrap;
+use Magento\Webapi\Model\Rest\Config;
+
+class Rest implements \Magento\TestFramework\TestCase\Webapi\AdapterInterface
+{
+    /** @var \Magento\Webapi\Model\Config */
+    protected $_config;
+
+    /** @var \Magento\Integration\Model\Oauth\Consumer */
+    protected static $_consumer;
+
+    /** @var \Magento\Integration\Model\Oauth\Token */
+    protected static $_token;
+
+    /** @var string */
+    protected static $_consumerKey;
+
+    /** @var string */
+    protected static $_consumerSecret;
+
+    /** @var string */
+    protected static $_verifier;
+
+    /** @var \Magento\TestFramework\TestCase\Webapi\Adapter\Rest\CurlClient */
+    protected $curlClient;
+
+    /** @var \Magento\TestFramework\TestCase\Webapi\Adapter\Rest\DocumentationGenerator */
+    protected $documentationGenerator;
+
+    /** @var string */
+    protected $defaultStoreCode;
+
+    /**
+     * Initialize dependencies.
+     */
+    public function __construct()
+    {
+        /** @var $objectManager \Magento\TestFramework\ObjectManager */
+        $objectManager = Bootstrap::getObjectManager();
+        $this->_config = $objectManager->get('Magento\Webapi\Model\Config');
+        $this->curlClient = $objectManager->get('Magento\TestFramework\TestCase\Webapi\Adapter\Rest\CurlClient');
+        $this->documentationGenerator = $objectManager->get(
+            'Magento\TestFramework\TestCase\Webapi\Adapter\Rest\DocumentationGenerator'
+        );
+        $this->defaultStoreCode = Bootstrap::getObjectManager()
+            ->get('Magento\Store\Model\StoreManagerInterface')
+            ->getStore()
+            ->getCode();
+    }
+
+    /**
+     * {@inheritdoc}
+     * @throws \Exception
+     */
+    public function call($serviceInfo, $arguments = [])
+    {
+        $resourcePath = '/' . $this->defaultStoreCode . $this->_getRestResourcePath($serviceInfo);
+        $httpMethod = $this->_getRestHttpMethod($serviceInfo);
+        //Get a valid token
+        $accessCredentials = \Magento\TestFramework\Authentication\OauthHelper::getApiAccessCredentials();
+        /** @var $oAuthClient \Magento\TestFramework\Authentication\Rest\OauthClient */
+        $oAuthClient = $accessCredentials['oauth_client'];
+        $urlFormEncoded = false;
+        // we're always using JSON
+        $authHeader = [];
+        $restServiceInfo = $serviceInfo['rest'];
+        if (array_key_exists('token', $restServiceInfo)) {
+            $authHeader = $oAuthClient->buildBearerTokenAuthorizationHeader($restServiceInfo['token']);
+        } else {
+            $authHeader = $oAuthClient->buildOauthAuthorizationHeader(
+                $this->curlClient->constructResourceUrl($resourcePath),
+                $accessCredentials['key'],
+                $accessCredentials['secret'],
+                ($httpMethod == 'PUT' || $httpMethod == 'POST') && $urlFormEncoded ? $arguments : [],
+                $httpMethod
+            );
+        }
+        $authHeader = array_merge($authHeader, ['Accept: application/json', 'Content-Type: application/json']);
+        switch ($httpMethod) {
+            case Config::HTTP_METHOD_GET:
+                $response = $this->curlClient->get($resourcePath, [], $authHeader);
+                break;
+            case Config::HTTP_METHOD_POST:
+                $response = $this->curlClient->post($resourcePath, $arguments, $authHeader);
+                break;
+            case Config::HTTP_METHOD_PUT:
+                $response = $this->curlClient->put($resourcePath, $arguments, $authHeader);
+                break;
+            case Config::HTTP_METHOD_DELETE:
+                $response = $this->curlClient->delete($resourcePath, $authHeader);
+                break;
+            default:
+                throw new \LogicException("HTTP method '{$httpMethod}' is not supported.");
+        }
+        if (defined('GENERATE_REST_DOCUMENTATION') && GENERATE_REST_DOCUMENTATION) {
+            $this->documentationGenerator->generateDocumentation($httpMethod, $resourcePath, $arguments, $response);
+        }
+        return $response;
+    }
+
+    /**
+     * Retrieve REST endpoint from $serviceInfo array and return it to the caller.
+     *
+     * @param array $serviceInfo
+     * @return string
+     * @throws \Exception
+     */
+    protected function _getRestResourcePath($serviceInfo)
+    {
+        if (isset($serviceInfo['rest']['resourcePath'])) {
+            $resourcePath = $serviceInfo['rest']['resourcePath'];
+        }
+        if (!isset($resourcePath)) {
+            throw new \Exception("REST endpoint cannot be identified.");
+        }
+        return $resourcePath;
+    }
+
+    /**
+     * Retrieve HTTP method to be used in REST request.
+     *
+     * @param array $serviceInfo
+     * @return string
+     * @throws \Exception
+     */
+    protected function _getRestHttpMethod($serviceInfo)
+    {
+        if (isset($serviceInfo['rest']['httpMethod'])) {
+            $httpMethod = $serviceInfo['rest']['httpMethod'];
+        }
+        if (!isset($httpMethod)) {
+            throw new \Exception("REST HTTP method cannot be identified.");
+        }
+        return $httpMethod;
+    }
+}
diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Rest/CurlClient.php b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Rest/CurlClient.php
new file mode 100644
index 0000000000000000000000000000000000000000..a907a4ca8b55e8f788f7be7da3d61eb1e6bba71b
--- /dev/null
+++ b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Rest/CurlClient.php
@@ -0,0 +1,308 @@
+<?php
+/**
+ * Client for invoking REST API
+ *
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestFramework\TestCase\Webapi\Adapter\Rest;
+
+class CurlClient
+{
+    const EMPTY_REQUEST_BODY = 'Empty body';
+    /**
+     * @var string REST URL base path
+     */
+    protected $restBasePath = '/rest/';
+
+    /**
+     * @var array Response array
+     */
+    protected $responseArray;
+
+    /**
+     * @var array JSON Error code to error message mapping
+     */
+    protected $_jsonErrorMessages = [
+        JSON_ERROR_DEPTH => 'Maximum depth exceeded',
+        JSON_ERROR_STATE_MISMATCH => 'State mismatch',
+        JSON_ERROR_CTRL_CHAR => 'Unexpected control character found',
+        JSON_ERROR_SYNTAX => 'Syntax error, invalid JSON',
+    ];
+
+    /**
+     * Perform HTTP GET request
+     *
+     * @param string $resourcePath Resource URL like /V1/Resource1/123
+     * @param array $data
+     * @param array $headers
+     * @return mixed
+     */
+    public function get($resourcePath, $data = [], $headers = [])
+    {
+        $url = $this->constructResourceUrl($resourcePath);
+        if (!empty($data)) {
+            $url .= '?' . http_build_query($data);
+        }
+
+        $curlOpts = [];
+        $curlOpts[CURLOPT_CUSTOMREQUEST] = \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_GET;
+        $resp = $this->_invokeApi($url, $curlOpts, $headers);
+        $respArray = $this->_jsonDecode($resp["body"]);
+        return $respArray;
+    }
+
+    /**
+     * Perform HTTP POST request
+     *
+     * @param string $resourcePath Resource URL like /V1/Resource1/123
+     * @param array $data
+     * @param array $headers
+     * @return mixed
+     */
+    public function post($resourcePath, $data, $headers = [])
+    {
+        return $this->_postOrPut($resourcePath, $data, false, $headers);
+    }
+
+    /**
+     * Perform HTTP PUT request
+     *
+     * @param string $resourcePath Resource URL like /V1/Resource1/123
+     * @param array $data
+     * @param array $headers
+     * @return mixed
+     */
+    public function put($resourcePath, $data, $headers = [])
+    {
+        return $this->_postOrPut($resourcePath, $data, true, $headers);
+    }
+
+    /**
+     * Perform HTTP DELETE request
+     *
+     * @param string $resourcePath Resource URL like /V1/Resource1/123
+     * @param array $headers
+     * @return mixed
+     */
+    public function delete($resourcePath, $headers = [])
+    {
+        $url = $this->constructResourceUrl($resourcePath);
+
+        $curlOpts = [];
+        $curlOpts[CURLOPT_CUSTOMREQUEST] = \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_DELETE;
+
+        $resp = $this->_invokeApi($url, $curlOpts, $headers);
+        $respArray = $this->_jsonDecode($resp["body"]);
+
+        return $respArray;
+    }
+
+    /**
+     * Perform HTTP POST or PUT request
+     *
+     * @param string $resourcePath Resource URL like /V1/Resource1/123
+     * @param array $data
+     * @param boolean $put Set true to post data as HTTP PUT operation (update). If this value is set to false,
+     *        HTTP POST (create) will be used
+     * @param array $headers
+     * @return mixed
+     */
+    protected function _postOrPut($resourcePath, $data, $put = false, $headers = [])
+    {
+        $url = $this->constructResourceUrl($resourcePath);
+
+        if (in_array("Content-Type: application/json", $headers)) {
+            // json encode data
+            if ($data != self::EMPTY_REQUEST_BODY) {
+                $data = $this->_jsonEncode($data);
+            } else {
+                $data = '';
+            }
+        }
+
+        $curlOpts = [];
+        if ($put) {
+            $curlOpts[CURLOPT_CUSTOMREQUEST] = \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_PUT;
+        } else {
+            $curlOpts[CURLOPT_CUSTOMREQUEST] = \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_POST;
+        }
+        $headers[] = 'Content-Length: ' . strlen($data);
+        $curlOpts[CURLOPT_POSTFIELDS] = $data;
+
+        $this->responseArray = $this->_invokeApi($url, $curlOpts, $headers);
+        $respBodyArray = $this->_jsonDecode($this->responseArray["body"]);
+
+        return $respBodyArray;
+    }
+
+    /**
+     * Set Rest base path if available
+     *
+     * @param string $restBasePath
+     *
+     * @return void
+     */
+    public function setRestBasePath($restBasePath)
+    {
+        $this->restBasePath = $restBasePath;
+    }
+
+    /**
+     * Get current response array
+     *
+     * @return array
+     */
+    public function getCurrentResponseArray()
+    {
+        return $this->responseArray;
+    }
+
+    /**
+     * @param string $resourcePath Resource URL like /V1/Resource1/123
+     * @return string resource URL
+     * @throws \Exception
+     */
+    public function constructResourceUrl($resourcePath)
+    {
+        return rtrim(TESTS_BASE_URL, '/') . $this->restBasePath . ltrim($resourcePath, '/');
+    }
+
+    /**
+     * Makes the REST api call using passed $curl object
+     *
+     * @param string $url
+     * @param array $additionalCurlOpts cURL Options
+     * @param array $headers
+     * @return array
+     * @throws \Exception
+     */
+    protected function _invokeApi($url, $additionalCurlOpts, $headers = [])
+    {
+        // initialize cURL
+        $curl = curl_init($url);
+        if ($curl === false) {
+            throw new \Exception("Error Initializing cURL for baseUrl: " . $url);
+        }
+
+        // get cURL options
+        $curlOpts = $this->_getCurlOptions($additionalCurlOpts, $headers);
+
+        // add CURL opts
+        foreach ($curlOpts as $opt => $val) {
+            curl_setopt($curl, $opt, $val);
+        }
+
+        $response = curl_exec($curl);
+        if ($response === false) {
+            throw new \Exception(curl_error($curl));
+        }
+
+        $resp = [];
+        $headerSize = curl_getinfo($curl, CURLINFO_HEADER_SIZE);
+        $resp["header"] = substr($response, 0, $headerSize);
+        $resp["body"] = substr($response, $headerSize);
+
+        $resp["meta"] = curl_getinfo($curl);
+        if ($resp["meta"] === false) {
+            throw new \Exception(curl_error($curl));
+        }
+
+        curl_close($curl);
+
+        $meta = $resp["meta"];
+        if ($meta && $meta['http_code'] >= 400) {
+            throw new \Exception($resp["body"], $meta['http_code']);
+        }
+
+        return $resp;
+    }
+
+    /**
+     * Constructs and returns a curl options array
+     *
+     * @param array $customCurlOpts Additional / overridden cURL options
+     * @param array $headers
+     * @return array
+     */
+    protected function _getCurlOptions($customCurlOpts = [], $headers = [])
+    {
+        // default curl options
+        $curlOpts = [
+            CURLOPT_RETURNTRANSFER => true, // return result instead of echoing
+            CURLOPT_SSL_VERIFYPEER => false, // stop cURL from verifying the peer's certificate
+            CURLOPT_FOLLOWLOCATION => false, // follow redirects, Location: headers
+            CURLOPT_MAXREDIRS => 10, // but don't redirect more than 10 times
+            CURLOPT_HTTPHEADER => [],
+            CURLOPT_HEADER => 1,
+        ];
+
+        // merge headers
+        $headers = array_merge($curlOpts[CURLOPT_HTTPHEADER], $headers);
+        if (TESTS_XDEBUG_ENABLED) {
+            $headers[] = 'Cookie: XDEBUG_SESSION=' . TESTS_XDEBUG_SESSION;
+        }
+        $curlOpts[CURLOPT_HTTPHEADER] = $headers;
+
+        // merge custom Curl Options & return
+        foreach ($customCurlOpts as $opt => $val) {
+            $curlOpts[$opt] = $val;
+        }
+
+        return $curlOpts;
+    }
+
+    /**
+     * JSON encode with error checking
+     *
+     * @param mixed $data
+     * @return string
+     * @throws \Exception
+     */
+    protected function _jsonEncode($data)
+    {
+        $ret = json_encode($data);
+        $this->_checkJsonError($data);
+
+        // return the json String
+        return $ret;
+    }
+
+    /**
+     * Decode a JSON string with error checking
+     *
+     * @param string $data
+     * @param bool $asArray
+     * @throws \Exception
+     * @return mixed
+     */
+    protected function _jsonDecode($data, $asArray = true)
+    {
+        $ret = json_decode($data, $asArray);
+        $this->_checkJsonError($data);
+
+        // return the array
+        return $ret;
+    }
+
+    /**
+     * Checks for JSON error in the latest encoding / decoding and throws an exception in case of error
+     *
+     * @throws \Exception
+     */
+    protected function _checkJsonError()
+    {
+        $jsonError = json_last_error();
+        if ($jsonError !== JSON_ERROR_NONE) {
+            // find appropriate error message
+            $message = 'Unknown JSON Error';
+            if (isset($this->_jsonErrorMessages[$jsonError])) {
+                $message = $this->_jsonErrorMessages[$jsonError];
+            }
+
+            throw new \Exception(
+                'JSON Encoding / Decoding error: ' . $message . var_export(func_get_arg(0), true),
+                $jsonError
+            );
+        }
+    }
+}
diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Rest/DocumentationGenerator.php b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Rest/DocumentationGenerator.php
new file mode 100644
index 0000000000000000000000000000000000000000..27cce9d7d6203ab6e45b1e4c7d19d1e49afaeeb5
--- /dev/null
+++ b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Rest/DocumentationGenerator.php
@@ -0,0 +1,238 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+namespace Magento\TestFramework\TestCase\Webapi\Adapter\Rest;
+
+/**
+ * Generator for documentation
+ *
+ */
+class DocumentationGenerator
+{
+    /**
+     * Generate documentation based on request-response data during REST requests.
+     *
+     * @param string $httpMethod
+     * @param string $resourcePath
+     * @param array $arguments
+     * @param array $response
+     */
+    public function generateDocumentation($httpMethod, $resourcePath, $arguments, $response)
+    {
+        $content = $this->generateHtmlContent($httpMethod, $resourcePath, $arguments, $response);
+        $filePath = $this->generateFileName($resourcePath);
+        if (is_null($filePath)) {
+            return;
+        }
+        if (!is_writable(dirname($filePath))) {
+            throw new \RuntimeException('Cannot write to documentation directory.');
+        } elseif (file_exists($filePath)) {
+            $fileContent = file_get_contents($filePath);
+            $endHtml = $this->generateHtmlFooter();
+            $fileContent = str_replace($endHtml, '', $fileContent);
+            $content = "{$fileContent}{$content}";
+            unlink($filePath);
+            file_put_contents($filePath, $content, FILE_APPEND);
+        } else {
+            file_put_contents($filePath, $content, FILE_APPEND);
+        }
+    }
+
+    /**
+     * Prepare HTML for the generated documentation.
+     *
+     * @param string $httpMethod
+     * @param string $resourcePath
+     * @param array $arguments
+     * @param array $response
+     * @return string
+     */
+    protected function generateHtmlContent($httpMethod, $resourcePath, $arguments, $response)
+    {
+        if (empty($arguments)) {
+            $arguments = 'This call does not accept a request body.';
+            $requestParametersHtml = '';
+        } else {
+            $requestParameters = $this->retrieveParametersAsHtml($arguments);
+            $arguments = json_encode($arguments, JSON_PRETTY_PRINT);
+            $requestParametersHtml = <<<HTML
+            <table class="docutils field-list" frame="void" rules="none"  width="400">
+                <colgroup>
+                    <col width="35%" class="field-name">
+                    <col  width="65%" class="field-body">
+                </colgroup>
+                <tbody valign="top">
+                <tr class="field-odd field">
+                    <th class="field-name">Request parameters:</th>
+                    <td class="field-body">
+                        <ul class="first last simple">
+                            {$requestParameters}
+                        </ul>
+                    </td>
+                </tr>
+                </tbody>
+            </table>
+HTML;
+        }
+        if (is_array($response)) {
+            $responseArrayKeys = array_keys($response);
+            $responseParameters = "Parameters should be specified manually.";
+            foreach ($responseArrayKeys as $key) {
+                if (!is_int($key)) {
+                    $responseParameters = '';
+                    break;
+                }
+            }
+        }
+        if (empty($responseParameters)) {
+            $responseParameters = $this->retrieveParametersAsHtml($response);
+        }
+        $response = json_encode($response, JSON_PRETTY_PRINT);
+        $responseParametersHtml = <<<HTML
+        <table class="docutils field-list" frame="void" rules="none"  width="400">
+            <colgroup>
+                <col width="35%" class="field-name">
+                <col  width="65%" class="field-body">
+            </colgroup>
+            <tbody valign="top">
+            <tr class="field-odd field">
+                <th class="field-name">Response attributes:</th>
+                <td class="field-body">
+                    <ul class="first last simple">
+                        {$responseParameters}
+                    </ul>
+                </td>
+            </tr>
+            </tbody>
+        </table>
+HTML;
+        $resourcePath = urldecode($resourcePath);
+        $resource = str_replace('/', '-', preg_replace('#/\w*/V\d+/(.*)#', '${1}', $resourcePath));
+        $lowerCaseResource = strtolower($resource);
+        $lowerCaseMethod = strtolower($httpMethod);
+        $beginningHtml = <<<HTML
+<div class="col-xs-9" role="main">
+    <div class="bs-docs-section">
+HTML;
+        $headingHtml = <<<HTML
+        <h2 class="api2" id="$lowerCaseResource">$resource</h2>
+        <h3 class="api3" id="$lowerCaseMethod-$lowerCaseResource">$httpMethod $resourcePath</h3>
+        <h4 class="api4">Request</h4>
+HTML;
+        $responseHtml = <<<HTML
+        <h4 class="api4" id=”$lowerCaseResource-response>Response</h4>
+HTML;
+        $requestResponseParametersHtml = <<<HTML
+        <h3 class="api3" id="$lowerCaseResource-parameters">Request and response parameters</h3>
+HTML;
+        $endHtml = $this->generateHtmlFooter();
+        $content = "{$beginningHtml}{$headingHtml}<pre>{$arguments}</pre>{$responseHtml}<pre>{$response}"
+            . "</pre>{$requestResponseParametersHtml}{$requestParametersHtml}{$responseParametersHtml}{$endHtml}";
+        return $content;
+    }
+
+    /**
+     * Generate the end html text;
+     *
+     * @return string
+     */
+    protected function generateHtmlFooter()
+    {
+        $endHtml = <<<HTML
+        <h3 class="api3" id="products-responses">Response codes</h3>
+        <table class="docutils field-list" frame="void" rules="none" width="400">
+            <colgroup>
+                <col  width="35%" class="field-name">
+                <col  width="65%" class="field-body">
+            </colgroup>
+            <tbody valign="top">
+            <tr class="field-odd field">
+                <th class="field-name">Normal response codes:</th>
+                <td class="field-body">
+                    <ul class="first last simple">
+                        <li><strong>SUCCESS_CODE</strong> - SUCCESS_DESCRIPTION</li>
+                    </ul>
+                </td>
+            </tr>
+            </tbody>
+        </table>
+        <table class="docutils field-list" frame="void" rules="none" width="400">
+            <colgroup>
+                <col  width="35%" class="field-name">
+                <col  width="65%" class="field-body">
+            </colgroup>
+            <tbody valign="top">
+            <tr class="field-odd field">
+                <th class="field-name">Error response codes:</th>
+                <td class="field-body">
+                    <ul class="first last simple">
+                        <li><strong>ERROR_CODE</strong> - ERROR_DESCRIPTION</li>
+                    </ul>
+                </td>
+            </tr>
+            </tbody>
+        </table>
+    </div>
+</div>
+HTML;
+        return $endHtml;
+    }
+
+    /**
+     * Generate a name of file
+     *
+     * @return string|null
+     * @throws \RuntimeException
+     */
+    protected function generateFileName()
+    {
+        $varDir = realpath(__DIR__ . '/../../../../../../..') . '/var';
+        $documentationDir = $varDir . '/log/rest-documentation/';
+        $debugBackTrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
+        $pathToFile = $documentationDir;
+        $fileName = null;
+        foreach ($debugBackTrace as $traceItem) {
+            /** Test invocation trace item is the only item which has 3 elements, other trace items have 5 elements */
+            if (count($traceItem) == 3) {
+                /** Remove 'test' prefix from method name, e.g. testCreate => create */
+                $fileName = lcfirst(substr($traceItem['function'], 4));
+                /** Remove 'Test' suffix from test class name */
+                $pathToFile .= str_replace('\\', '/', substr($traceItem['class'], 0, -4)) . '/';
+                break;
+            }
+        }
+        if (!file_exists($pathToFile)) {
+            if (!mkdir($pathToFile, 0755, true)) {
+                throw new \RuntimeException('Unable to create missing directory for REST documentation generation');
+            }
+        }
+        if (!is_null($fileName)) {
+            $filePath = $pathToFile . $fileName . '.html';
+            return $filePath;
+        }
+        return null;
+    }
+
+    /**
+     * Retrieve parameters of response/request
+     *
+     * @param array|string $parameters
+     * @return string
+     */
+    protected function retrieveParametersAsHtml($parameters)
+    {
+        $parametersAsHtml = '';
+        if (is_array($parameters)) {
+            foreach (array_keys($parameters) as $parameter) {
+                $parametersAsHtml = $parametersAsHtml . '<li><strong>' . $parameter .
+                    '</strong> (<em>Change type manually!</em>) TBD.</li>';
+            }
+        } else {
+            $parametersAsHtml = '<li><strong>' . 'scalar_value' .
+                '</strong> (<em>Change type manually!</em>) TBD.</li>';
+        }
+        return $parametersAsHtml;
+    }
+}
diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Soap.php b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Soap.php
new file mode 100644
index 0000000000000000000000000000000000000000..73f6d8a6582c314bf4e7447452e89e8f77d97be9
--- /dev/null
+++ b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Soap.php
@@ -0,0 +1,238 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestFramework\TestCase\Webapi\Adapter;
+
+use Magento\Framework\Api\SimpleDataObjectConverter;
+use Magento\TestFramework\Helper\Bootstrap;
+use Magento\Webapi\Controller\Soap\Request\Handler as SoapHandler;
+
+/**
+ * Test client for SOAP API testing.
+ */
+class Soap implements \Magento\TestFramework\TestCase\Webapi\AdapterInterface
+{
+    const WSDL_BASE_PATH = '/soap';
+
+    /**
+     * SOAP client initialized with different WSDLs.
+     *
+     * @var \Zend\Soap\Client[]
+     */
+    protected $_soapClients = [];
+
+    /**
+     * @var \Magento\Webapi\Model\Soap\Config
+     */
+    protected $_soapConfig;
+
+    /**
+     * @var \Magento\Webapi\Helper\Data
+     */
+    protected $_helper;
+
+    /**
+     * @var SimpleDataObjectConverter
+     */
+    protected $_converter;
+
+    /**
+     * Initialize dependencies.
+     */
+    public function __construct()
+    {
+        /** @var $objectManager \Magento\TestFramework\ObjectManager */
+        $objectManager = Bootstrap::getObjectManager();
+        $this->_soapConfig = $objectManager->get('Magento\Webapi\Model\Soap\Config');
+        $this->_helper = $objectManager->get('Magento\Webapi\Helper\Data');
+        $this->_converter = $objectManager->get('Magento\Framework\Api\SimpleDataObjectConverter');
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function call($serviceInfo, $arguments = [])
+    {
+        $soapOperation = $this->_getSoapOperation($serviceInfo);
+        $arguments = $this->_converter->convertKeysToCamelCase($arguments);
+        $soapResponse = $this->_getSoapClient($serviceInfo)->$soapOperation($arguments);
+        //Convert to snake case for tests to use same assertion data for both SOAP and REST tests
+        $result = (is_array($soapResponse) || is_object($soapResponse))
+            ? $this->toSnakeCase($this->_converter->convertStdObjectToArray($soapResponse, true))
+            : $soapResponse;
+        /** Remove result wrappers */
+        $result = isset($result[SoapHandler::RESULT_NODE_NAME]) ? $result[SoapHandler::RESULT_NODE_NAME] : $result;
+        return $result;
+    }
+
+    /**
+     * Get proper SOAP client instance that is initialized with with WSDL corresponding to requested service interface.
+     *
+     * @param string $serviceInfo PHP service interface name, should include version if present
+     * @return \Zend\Soap\Client
+     */
+    protected function _getSoapClient($serviceInfo)
+    {
+        $wsdlUrl = $this->generateWsdlUrl(
+            [$this->_getSoapServiceName($serviceInfo) . $this->_getSoapServiceVersion($serviceInfo)]
+        );
+        /** Check if there is SOAP client initialized with requested WSDL available */
+        if (!isset($this->_soapClients[$wsdlUrl])) {
+            $token = isset($serviceInfo['soap']['token']) ? $serviceInfo['soap']['token'] : null;
+            $this->_soapClients[$wsdlUrl] = $this->instantiateSoapClient($wsdlUrl, $token);
+        }
+        return $this->_soapClients[$wsdlUrl];
+    }
+
+    /**
+     * Create SOAP client instance and initialize it with provided WSDL URL.
+     *
+     * @param string $wsdlUrl
+     * @param string $token Authentication token
+     * @return \Zend\Soap\Client
+     */
+    public function instantiateSoapClient($wsdlUrl, $token = null)
+    {
+        $accessCredentials = $token
+            ? $token
+            : \Magento\TestFramework\Authentication\OauthHelper::getApiAccessCredentials()['key'];
+        $opts = ['http' => ['header' => "Authorization: Bearer " . $accessCredentials]];
+        $context = stream_context_create($opts);
+        $soapClient = new \Zend\Soap\Client($wsdlUrl);
+        $soapClient->setSoapVersion(SOAP_1_2);
+        $soapClient->setStreamContext($context);
+        if (TESTS_XDEBUG_ENABLED) {
+            $soapClient->setCookie('XDEBUG_SESSION', 1);
+        }
+        return $soapClient;
+    }
+
+    /**
+     * Generate WSDL URL.
+     *
+     * @param array $services e.g.<pre>
+     * array(
+     *     'catalogProductV1',
+     *     'customerV2'
+     * );</pre>
+     * @return string
+     */
+    public function generateWsdlUrl($services)
+    {
+        /** Sort list of services to avoid having different WSDL URLs for the identical lists of services. */
+        //TODO: This may change since same resource of multiple versions may be allowed after namespace changes
+        ksort($services);
+        /** @var \Magento\Store\Model\StoreManagerInterface $storeManager */
+        $storeManager = Bootstrap::getObjectManager()->get('Magento\Store\Model\StoreManagerInterface');
+        $storeCode = $storeManager->getStore()->getCode();
+        /** TESTS_BASE_URL is initialized in PHPUnit configuration */
+        $wsdlUrl = rtrim(TESTS_BASE_URL, '/') . self::WSDL_BASE_PATH . '/' . $storeCode . '?wsdl=1&services=';
+        $wsdlResourceArray = [];
+        foreach ($services as $serviceName) {
+            $wsdlResourceArray[] = $serviceName;
+        }
+        return $wsdlUrl . implode(",", $wsdlResourceArray);
+    }
+
+    /**
+     * Retrieve SOAP operation name from available service info.
+     *
+     * @param array $serviceInfo
+     * @return string
+     * @throws \LogicException
+     */
+    protected function _getSoapOperation($serviceInfo)
+    {
+        if (isset($serviceInfo['soap']['operation'])) {
+            $soapOperation = $serviceInfo['soap']['operation'];
+        } elseif (isset($serviceInfo['serviceInterface']) && isset($serviceInfo['method'])) {
+            $soapOperation = $this->_soapConfig->getSoapOperation(
+                $serviceInfo['serviceInterface'],
+                $serviceInfo['method']
+            );
+        } else {
+            throw new \LogicException("SOAP operation cannot be identified.");
+        }
+        return $soapOperation;
+    }
+
+    /**
+     * Retrieve service version from available service info.
+     *
+     * @param array $serviceInfo
+     * @return string
+     * @throws \LogicException
+     */
+    protected function _getSoapServiceVersion($serviceInfo)
+    {
+        if (isset($serviceInfo['soap']['operation'])) {
+            /*
+                TODO: Need to rework this to remove version call for serviceInfo array with 'operation' key
+                since version will be part of the service name
+            */
+            return '';
+        } elseif (isset($serviceInfo['serviceInterface'])) {
+            preg_match(
+                \Magento\Webapi\Model\Config::SERVICE_CLASS_PATTERN,
+                $serviceInfo['serviceInterface'],
+                $matches
+            );
+            if (isset($matches[3])) {
+                $version = $matches[3];
+            } else {
+                //TODO: Need to add this temporary version until version is added back for new MSC based services
+                $version = 1;
+                //throw new \LogicException("Service interface name is invalid.");
+            }
+        } else {
+            throw new \LogicException("Service version cannot be identified.");
+        }
+        /** Normalize version */
+        $version = 'V' . ltrim($version, 'vV');
+        return $version;
+    }
+
+    /**
+     * Retrieve service name from available service info.
+     *
+     * @param array $serviceInfo
+     * @return string
+     * @throws \LogicException
+     */
+    protected function _getSoapServiceName($serviceInfo)
+    {
+        if (isset($serviceInfo['soap']['service'])) {
+            $serviceName = $serviceInfo['soap']['service'];
+        } elseif (isset($serviceInfo['serviceInterface'])) {
+            $serviceName = $this->_helper->getServiceName($serviceInfo['serviceInterface'], false);
+        } else {
+            throw new \LogicException("Service name cannot be identified.");
+        }
+        return $serviceName;
+    }
+
+    /**
+     * Recursively transform array keys from camelCase to snake_case.
+     *
+     * Utility method for converting SOAP responses. Webapi framework's SOAP processing outputs
+     * snake case Data Object properties(ex. item_id) as camel case(itemId) to adhere to the WSDL.
+     * This method allows tests to use the same data for asserting both SOAP and REST responses.
+     *
+     * @param array $objectData An array of data.
+     * @return array The array with all camelCase keys converted to snake_case.
+     */
+    protected function toSnakeCase(array $objectData)
+    {
+        $data = [];
+        foreach ($objectData as $key => $value) {
+            $key = strtolower(preg_replace("/(?<=\\w)(?=[A-Z])/", "_$1", $key));
+            if (is_array($value)) {
+                $data[$key] = $this->toSnakeCase($value);
+            } else {
+                $data[$key] = $value;
+            }
+        }
+        return $data;
+    }
+}
diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/AdapterInterface.php b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/AdapterInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..e34d3cb702d6ae71e46ac6778660ec42a3f75d8f
--- /dev/null
+++ b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/AdapterInterface.php
@@ -0,0 +1,37 @@
+<?php
+/**
+ * API tests adapter interface.
+ *
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestFramework\TestCase\Webapi;
+
+interface AdapterInterface
+{
+    /**
+     * Perform call to the specified service method.
+     *
+     * @param array $serviceInfo <pre>
+     * array(
+     *     'rest' => array(
+     *         'resourcePath' => $resourcePath, // e.g. /products/:id
+     *         'httpMethod' => $httpMethod,     // e.g. GET
+     *         'token' => '21hasbtlaqy8t3mj73kjh71cxxkqj4aq'    // optional : for token based Authentication. Will
+     *                                                             override default Oauth based authentication provided
+     *                                                             by test framework
+     *     ),
+     *     'soap' => array(
+     *         'service' => $soapService,    // soap service name with Version suffix e.g. catalogProductV1, customerV2
+     *         'operation' => $operation     // soap operation name e.g. catalogProductCreate
+     *     ),
+     *     OR
+     *     'serviceInterface' => $phpServiceInterfaceName, // e.g. \Magento\Catalog\Api\ProductInterface
+     *     'method' => $serviceMethodName                  // e.g. create
+     *     'entityId' => $entityId                         // is used in REST route placeholder (if applicable)
+     * );
+     * </pre>
+     * @param array $arguments
+     * @return array|string|int|float|bool
+     */
+    public function call($serviceInfo, $arguments = []);
+}
diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Curl.php b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Curl.php
new file mode 100644
index 0000000000000000000000000000000000000000..655f31579c729e40b5922e5f39f87ae66c4beff7
--- /dev/null
+++ b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Curl.php
@@ -0,0 +1,93 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+namespace Magento\TestFramework\TestCase\Webapi;
+
+/**
+ * A Curl client that can be called independently, outside of REST controller.
+ *
+ * Used by CookieManager tests.
+ */
+class Curl extends Adapter\Rest\CurlClient
+{
+    const COOKIE_HEADER = 'Set-Cookie: ';
+
+    /**
+     * @param string $resourcePath Resource URL like /V1/Resource1/123
+     * @return string resource URL
+     * @throws \Exception
+     */
+    public function constructResourceUrl($resourcePath)
+    {
+        return rtrim(TESTS_BASE_URL, '/') . ltrim($resourcePath, '/');
+    }
+
+    /**
+     * Perform HTTP GET request
+     *
+     * @param string $resourcePath Resource URL like /V1/Resource1/123
+     * @param array $data
+     * @param array $headers
+     * @return array
+     */
+    public function get($resourcePath, $data = [], $headers = [])
+    {
+        $url = $this->constructResourceUrl($resourcePath);
+        if (!empty($data)) {
+            $url .= '?' . http_build_query($data);
+        }
+
+        $curlOpts = [];
+        $curlOpts[CURLOPT_CUSTOMREQUEST] = \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_GET;
+        $curlOpts[CURLOPT_SSLVERSION] = 3;
+        $response = $this->_invokeApi($url, $curlOpts, $headers);
+        $response['cookies'] = $this->cookieParse($response['header']);
+        return $response;
+    }
+
+    /**
+     * Takes a string in the form of an HTTP header block, returns cookie data.
+     *
+     * Return array is in the form of:
+     *  [
+     *      [
+     *          'name' = <cookie_name>,
+     *          'value' = <cookie_value>,
+     *          <cookie_metadata_name> => <cookie_metadata_value> || 'true'
+     *      ],
+     *  ]
+     *
+     * @param $headerBlock
+     * @return array
+     */
+    private function cookieParse($headerBlock)
+    {
+        $header = explode("\r\n", $headerBlock);
+        $cookies = [];
+        foreach ($header as $line) {
+            $line = trim($line);
+            if (substr($line, 0, strlen(self::COOKIE_HEADER)) == self::COOKIE_HEADER) {
+                $line = trim(substr($line, strlen(self::COOKIE_HEADER)));
+                $cookieData = [];
+                // Check if cookie contains attributes
+                if (strpos($line, ';') === false) {
+                    // no attributes, just name and value
+                    list($cookieData['name'], $cookieData['value']) = explode('=', $line);
+                } else {
+                    // has attributes, must parse them out and loop through
+                    list($nvPair, $cookieMetadata) = explode(';', $line, 2);
+                    list($cookieData['name'], $cookieData['value']) = explode('=', $nvPair);
+                    $rawCookieData = explode(';', $cookieMetadata);
+                    foreach ($rawCookieData as $keyValuePairs) {
+                        list($key, $value) = array_merge(explode('=', $keyValuePairs), ['true']);
+                        $cookieData[strtolower(trim($key))] = trim($value);
+                    }
+                }
+                $cookies[] = $cookieData;
+            }
+        }
+        return $cookies;
+    }
+}
diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/WebapiAbstract.php b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/WebapiAbstract.php
new file mode 100644
index 0000000000000000000000000000000000000000..d02247b75a0838f352d1f3844dff87ab36cf475e
--- /dev/null
+++ b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/WebapiAbstract.php
@@ -0,0 +1,665 @@
+<?php
+/**
+ * Generic test case for Web API functional tests.
+ *
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestFramework\TestCase;
+
+use Magento\Framework\App\Filesystem\DirectoryList;
+use Magento\Framework\Filesystem;
+use Magento\Webapi\Model\Soap\Fault;
+
+abstract class WebapiAbstract extends \PHPUnit_Framework_TestCase
+{
+    /** TODO: Reconsider implementation of fixture-management methods after implementing several tests */
+    /**#@+
+     * Auto tear down options in setFixture
+     */
+    const AUTO_TEAR_DOWN_DISABLED = 0;
+    const AUTO_TEAR_DOWN_AFTER_METHOD = 1;
+    const AUTO_TEAR_DOWN_AFTER_CLASS = 2;
+    /**#@-*/
+
+    /**#@+
+     * Web API adapters that are used to perform actual calls.
+     */
+    const ADAPTER_SOAP = 'soap';
+    const ADAPTER_REST = 'rest';
+    /**#@-*/
+
+    /**
+     * Application cache model.
+     *
+     * @var \Magento\Framework\App\Cache
+     */
+    protected $_appCache;
+
+    /**
+     * The list of models to be deleted automatically in tearDown().
+     *
+     * @var array
+     */
+    protected $_modelsToDelete = [];
+
+    /**
+     * Namespace for fixtures is different for each test case.
+     *
+     * @var string
+     */
+    protected static $_fixturesNamespace;
+
+    /**
+     * The list of registered fixtures.
+     *
+     * @var array
+     */
+    protected static $_fixtures = [];
+
+    /**
+     * Fixtures to be deleted in tearDown().
+     *
+     * @var array
+     */
+    protected static $_methodLevelFixtures = [];
+
+    /**
+     * Fixtures to be deleted in tearDownAfterClass().
+     *
+     * @var array
+     */
+    protected static $_classLevelFixtures = [];
+
+    /**
+     * Original Magento config values.
+     *
+     * @var array
+     */
+    protected $_origConfigValues = [];
+
+    /**
+     * The list of instantiated Web API adapters.
+     *
+     * @var \Magento\TestFramework\TestCase\Webapi\AdapterInterface[]
+     */
+    protected $_webApiAdapters;
+
+    /**
+     * The list of available Web API adapters.
+     *
+     * @var array
+     */
+    protected $_webApiAdaptersMap = [
+        self::ADAPTER_SOAP => 'Magento\TestFramework\TestCase\Webapi\Adapter\Soap',
+        self::ADAPTER_REST => 'Magento\TestFramework\TestCase\Webapi\Adapter\Rest',
+    ];
+
+    /**
+     * Initialize fixture namespaces.
+     */
+    public static function setUpBeforeClass()
+    {
+        parent::setUpBeforeClass();
+        self::_setFixtureNamespace();
+    }
+
+    /**
+     * Run garbage collector for cleaning memory
+     *
+     * @return void
+     */
+    public static function tearDownAfterClass()
+    {
+        //clear garbage in memory
+        if (version_compare(PHP_VERSION, '5.3', '>=')) {
+            gc_collect_cycles();
+        }
+
+        $fixtureNamespace = self::_getFixtureNamespace();
+        if (isset(self::$_classLevelFixtures[$fixtureNamespace])
+            && count(self::$_classLevelFixtures[$fixtureNamespace])
+        ) {
+            self::_deleteFixtures(self::$_classLevelFixtures[$fixtureNamespace]);
+        }
+
+        //ever disable secure area on class down
+        self::_enableSecureArea(false);
+        self::_unsetFixtureNamespace();
+        parent::tearDownAfterClass();
+    }
+
+    /**
+     * Call safe delete for models which added to delete list
+     * Restore config values changed during the test
+     *
+     * @return void
+     */
+    protected function tearDown()
+    {
+        $fixtureNamespace = self::_getFixtureNamespace();
+        if (isset(self::$_methodLevelFixtures[$fixtureNamespace])
+            && count(self::$_methodLevelFixtures[$fixtureNamespace])
+        ) {
+            self::_deleteFixtures(self::$_methodLevelFixtures[$fixtureNamespace]);
+        }
+        $this->_callModelsDelete();
+        $this->_restoreAppConfig();
+        parent::tearDown();
+    }
+
+    /**
+     * Perform Web API call to the system under test.
+     *
+     * @see \Magento\TestFramework\TestCase\Webapi\AdapterInterface::call()
+     * @param array $serviceInfo
+     * @param array $arguments
+     * @param string|null $webApiAdapterCode
+     * @return array|int|string|float|bool Web API call results
+     */
+    protected function _webApiCall($serviceInfo, $arguments = [], $webApiAdapterCode = null)
+    {
+        if (is_null($webApiAdapterCode)) {
+            /** Default adapter code is defined in PHPUnit configuration */
+            $webApiAdapterCode = strtolower(TESTS_WEB_API_ADAPTER);
+        }
+        return $this->_getWebApiAdapter($webApiAdapterCode)->call($serviceInfo, $arguments);
+    }
+
+    /**
+     * Mark test to be executed for SOAP adapter only.
+     */
+    protected function _markTestAsSoapOnly($message = null)
+    {
+        if (TESTS_WEB_API_ADAPTER != self::ADAPTER_SOAP) {
+            $this->markTestSkipped($message ? $message : "The test is intended to be executed for SOAP adapter only.");
+        }
+    }
+
+    /**
+     * Mark test to be executed for REST adapter only.
+     */
+    protected function _markTestAsRestOnly($message = null)
+    {
+        if (TESTS_WEB_API_ADAPTER != self::ADAPTER_REST) {
+            $this->markTestSkipped($message ? $message : "The test is intended to be executed for REST adapter only.");
+        }
+    }
+
+    /**
+     * Set fixture to registry
+     *
+     * @param string $key
+     * @param mixed $fixture
+     * @param int $tearDown
+     * @return void
+     */
+    public static function setFixture($key, $fixture, $tearDown = self::AUTO_TEAR_DOWN_AFTER_METHOD)
+    {
+        $fixturesNamespace = self::_getFixtureNamespace();
+        if (!isset(self::$_fixtures[$fixturesNamespace])) {
+            self::$_fixtures[$fixturesNamespace] = [];
+        }
+        self::$_fixtures[$fixturesNamespace][$key] = $fixture;
+        if ($tearDown == self::AUTO_TEAR_DOWN_AFTER_METHOD) {
+            if (!isset(self::$_methodLevelFixtures[$fixturesNamespace])) {
+                self::$_methodLevelFixtures[$fixturesNamespace] = [];
+            }
+            self::$_methodLevelFixtures[$fixturesNamespace][] = $key;
+        } else {
+            if ($tearDown == self::AUTO_TEAR_DOWN_AFTER_CLASS) {
+                if (!isset(self::$_classLevelFixtures[$fixturesNamespace])) {
+                    self::$_classLevelFixtures[$fixturesNamespace] = [];
+                }
+                self::$_classLevelFixtures[$fixturesNamespace][] = $key;
+            }
+        }
+    }
+
+    /**
+     * Get fixture by key
+     *
+     * @param string $key
+     * @return mixed
+     */
+    public static function getFixture($key)
+    {
+        $fixturesNamespace = self::_getFixtureNamespace();
+        if (array_key_exists($key, self::$_fixtures[$fixturesNamespace])) {
+            return self::$_fixtures[$fixturesNamespace][$key];
+        }
+        return null;
+    }
+
+    /**
+     * Call safe delete for model
+     *
+     * @param \Magento\Framework\Model\AbstractModel $model
+     * @param bool $secure
+     * @return \Magento\TestFramework\TestCase\WebapiAbstract
+     */
+    public static function callModelDelete($model, $secure = false)
+    {
+        if ($model instanceof \Magento\Framework\Model\AbstractModel && $model->getId()) {
+            if ($secure) {
+                self::_enableSecureArea();
+            }
+            $model->delete();
+            if ($secure) {
+                self::_enableSecureArea(false);
+            }
+        }
+    }
+
+    /**
+     * Call safe delete for model
+     *
+     * @param \Magento\Framework\Model\AbstractModel $model
+     * @param bool $secure
+     * @return \Magento\TestFramework\TestCase\WebapiAbstract
+     */
+    public function addModelToDelete($model, $secure = false)
+    {
+        $this->_modelsToDelete[] = ['model' => $model, 'secure' => $secure];
+        return $this;
+    }
+
+    /**
+     * Get Web API adapter (create if requested one does not exist).
+     *
+     * @param string $webApiAdapterCode
+     * @return \Magento\TestFramework\TestCase\Webapi\AdapterInterface
+     * @throws \LogicException When requested Web API adapter is not declared
+     */
+    protected function _getWebApiAdapter($webApiAdapterCode)
+    {
+        if (!isset($this->_webApiAdapters[$webApiAdapterCode])) {
+            if (!isset($this->_webApiAdaptersMap[$webApiAdapterCode])) {
+                throw new \LogicException(
+                    sprintf('Declaration of the requested Web API adapter "%s" was not found.', $webApiAdapterCode)
+                );
+            }
+            $this->_webApiAdapters[$webApiAdapterCode] = new $this->_webApiAdaptersMap[$webApiAdapterCode]();
+        }
+        return $this->_webApiAdapters[$webApiAdapterCode];
+    }
+
+    /**
+     * Set fixtures namespace
+     *
+     * @throws \RuntimeException
+     */
+    protected static function _setFixtureNamespace()
+    {
+        if (!is_null(self::$_fixturesNamespace)) {
+            throw new \RuntimeException('Fixture namespace is already set.');
+        }
+        self::$_fixturesNamespace = uniqid();
+    }
+
+    /**
+     * Unset fixtures namespace
+     */
+    protected static function _unsetFixtureNamespace()
+    {
+        $fixturesNamespace = self::_getFixtureNamespace();
+        unset(self::$_fixtures[$fixturesNamespace]);
+        self::$_fixturesNamespace = null;
+    }
+
+    /**
+     * Get fixtures namespace
+     *
+     * @throws \RuntimeException
+     * @return string
+     */
+    protected static function _getFixtureNamespace()
+    {
+        $fixtureNamespace = self::$_fixturesNamespace;
+        if (is_null($fixtureNamespace)) {
+            throw new \RuntimeException('Fixture namespace must be set.');
+        }
+        return $fixtureNamespace;
+    }
+
+    /**
+     * Enable secure/admin area
+     *
+     * @param bool $flag
+     * @return void
+     */
+    protected static function _enableSecureArea($flag = true)
+    {
+        /** @var $objectManager \Magento\TestFramework\ObjectManager */
+        $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+
+        $objectManager->get('Magento\Framework\Registry')->unregister('isSecureArea');
+        if ($flag) {
+            $objectManager->get('Magento\Framework\Registry')->register('isSecureArea', $flag);
+        }
+    }
+
+    /**
+     * Call delete models from list
+     *
+     * @return \Magento\TestFramework\TestCase\WebapiAbstract
+     */
+    protected function _callModelsDelete()
+    {
+        if ($this->_modelsToDelete) {
+            foreach ($this->_modelsToDelete as $key => $modelData) {
+                /** @var $model \Magento\Framework\Model\AbstractModel */
+                $model = $modelData['model'];
+                $this->callModelDelete($model, $modelData['secure']);
+                unset($this->_modelsToDelete[$key]);
+            }
+        }
+        return $this;
+    }
+
+    /**
+     * Check if all error messages are expected ones
+     *
+     * @param array $expectedMessages
+     * @param array $receivedMessages
+     */
+    protected function _assertMessagesEqual($expectedMessages, $receivedMessages)
+    {
+        foreach ($receivedMessages as $message) {
+            $this->assertContains($message, $expectedMessages, "Unexpected message: '{$message}'");
+        }
+        $expectedErrorsCount = count($expectedMessages);
+        $this->assertCount($expectedErrorsCount, $receivedMessages, 'Invalid messages quantity received');
+    }
+
+    /**
+     * Delete array of fixtures
+     *
+     * @param array $fixtures
+     */
+    protected static function _deleteFixtures($fixtures)
+    {
+        foreach ($fixtures as $fixture) {
+            self::deleteFixture($fixture, true);
+        }
+    }
+
+    /**
+     * Delete fixture by key
+     *
+     * @param string $key
+     * @param bool $secure
+     * @return void
+     */
+    public static function deleteFixture($key, $secure = false)
+    {
+        $fixturesNamespace = self::_getFixtureNamespace();
+        if (array_key_exists($key, self::$_fixtures[$fixturesNamespace])) {
+            self::callModelDelete(self::$_fixtures[$fixturesNamespace][$key], $secure);
+            unset(self::$_fixtures[$fixturesNamespace][$key]);
+        }
+    }
+
+    /** TODO: Remove methods below if not used, otherwise fix them (after having some tests implemented)*/
+
+    /**
+     * Get application cache model
+     *
+     * @return \Magento\Framework\App\Cache
+     */
+    protected function _getAppCache()
+    {
+        if (null === $this->_appCache) {
+            //set application path
+            $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+            /** @var \Magento\Framework\App\Config\ScopeConfigInterface $config */
+            $config = $objectManager->get('Magento\Framework\App\Config\ScopeConfigInterface');
+            $options = $config->getOptions();
+            $currentCacheDir = $options->getCacheDir();
+            $currentEtcDir = $options->getEtcDir();
+            /** @var Filesystem $filesystem */
+            $filesystem = $objectManager->get('Magento\Framework\Filesystem');
+            $options->setCacheDir($filesystem->getDirectoryRead(DirectoryList::CACHE)->getAbsolutePath());
+            $options->setEtcDir($filesystem->getDirectoryRead(DirectoryList::CONFIG)->getAbsolutePath());
+
+            $this->_appCache = $objectManager->get('Magento\Framework\App\Cache');
+
+            //revert paths options
+            $options->setCacheDir($currentCacheDir);
+            $options->setEtcDir($currentEtcDir);
+        }
+        return $this->_appCache;
+    }
+
+    /**
+     * Clean config cache of application
+     *
+     * @return bool
+     */
+    protected function _cleanAppConfigCache()
+    {
+        return $this->_getAppCache()->clean(\Magento\Framework\App\Config::CACHE_TAG);
+    }
+
+    /**
+     * Update application config data
+     *
+     * @param string $path              Config path with the form "section/group/node"
+     * @param string|int|null $value    Value of config item
+     * @param bool $cleanAppCache       If TRUE application cache will be refreshed
+     * @param bool $updateLocalConfig   If TRUE local config object will be updated too
+     * @param bool $restore             If TRUE config value will be restored after test run
+     * @return \Magento\TestFramework\TestCase\WebapiAbstract
+     * @throws \RuntimeException
+     */
+    protected function _updateAppConfig(
+        $path,
+        $value,
+        $cleanAppCache = true,
+        $updateLocalConfig = false,
+        $restore = false
+    ) {
+        list($section, $group, $node) = explode('/', $path);
+
+        if (!$section || !$group || !$node) {
+            throw new \RuntimeException(
+                sprintf('Config path must have view as "section/group/node" but now it "%s"', $path)
+            );
+        }
+
+        $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+        /** @var $config \Magento\Backend\Model\Config */
+        $config = $objectManager->create('Magento\Backend\Model\Config');
+        $data[$group]['fields'][$node]['value'] = $value;
+        $config->setSection($section)->setGroups($data)->save();
+
+        if ($restore && !isset($this->_origConfigValues[$path])) {
+            $this->_origConfigValues[$path] = (string)$objectManager->get(
+                'Magento\Framework\App\Config\ScopeConfigInterface'
+            )->getNode(
+                $path,
+                'default'
+            );
+        }
+
+        //refresh local cache
+        if ($cleanAppCache) {
+            if ($updateLocalConfig) {
+                $objectManager->get('Magento\Framework\App\Config\ReinitableConfigInterface')->reinit();
+                $objectManager->get('Magento\Store\Model\StoreManagerInterface')->reinitStores();
+            }
+
+            if (!$this->_cleanAppConfigCache()) {
+                throw new \RuntimeException('Application configuration cache cannot be cleaned.');
+            }
+        }
+
+        return $this;
+    }
+
+    /**
+     * Restore config values changed during tests
+     */
+    protected function _restoreAppConfig()
+    {
+        foreach ($this->_origConfigValues as $configPath => $origValue) {
+            $this->_updateAppConfig($configPath, $origValue, true, true);
+        }
+    }
+
+    /**
+     * @param \Exception $e
+     * @return array
+     * <pre> ex.
+     * 'message' => "No such entity with %fieldName1 = %value1, %fieldName2 = %value2"
+     * 'parameters' => [
+     *      "fieldName1" => "email",
+     *      "value1" => "dummy@example.com",
+     *      "fieldName2" => "websiteId",
+     *      "value2" => 0
+     * ]
+     *
+     * </pre>
+     */
+    public function processRestExceptionResult(\Exception $e)
+    {
+        $error = json_decode($e->getMessage(), true);
+        //Remove line breaks and replace with space
+        $error['message'] = trim(preg_replace('/\s+/', ' ', $error['message']));
+        // remove trace and type, will only be present if server is in dev mode
+        unset($error['trace']);
+        unset($error['type']);
+        return $error;
+    }
+
+    /**
+     * Verify that SOAP fault contains necessary information.
+     *
+     * @param \SoapFault $soapFault
+     * @param string $expectedMessage
+     * @param string $expectedFaultCode
+     * @param array $expectedErrorParams
+     * @param array $expectedWrappedErrors
+     * @param string $traceString
+     */
+    protected function checkSoapFault(
+        $soapFault,
+        $expectedMessage,
+        $expectedFaultCode,
+        $expectedErrorParams = [],
+        $expectedWrappedErrors = [],
+        $traceString = null
+    ) {
+        $this->assertContains($expectedMessage, $soapFault->getMessage(), "Fault message is invalid.");
+
+        $errorDetailsNode = 'GenericFault';
+        $errorDetails = isset($soapFault->detail->$errorDetailsNode) ? $soapFault->detail->$errorDetailsNode : null;
+        if (!empty($expectedErrorParams) || !empty($expectedWrappedErrors)) {
+            /** Check SOAP fault details */
+            $this->assertNotNull($errorDetails, "Details must be present.");
+            $this->_checkFaultParams($expectedErrorParams, $errorDetails);
+            $this->_checkWrappedErrors($expectedWrappedErrors, $errorDetails);
+        }
+
+        if ($traceString) {
+            /** Check error trace */
+            $traceNode = Fault::NODE_DETAIL_TRACE;
+            $mode = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get('Magento\Framework\App\State')
+                ->getMode();
+            if ($mode == \Magento\Framework\App\State::MODE_DEVELOPER) {
+                /** Developer mode changes tested behavior and it cannot properly be tested for now */
+                $this->assertContains(
+                    $traceString,
+                    $errorDetails->$traceNode,
+                    'Trace Information is incorrect.'
+                );
+            } else {
+                $this->assertNull($errorDetails, "Details are not expected.");
+            }
+        }
+
+        /** Check SOAP fault code */
+        $this->assertNotNull($soapFault->faultcode, "Fault code must not be empty.");
+        $this->assertEquals($expectedFaultCode, $soapFault->faultcode, "Fault code is invalid.");
+    }
+
+    /**
+     * Check additional error parameters.
+     *
+     * @param array $expectedErrorParams
+     * @param \stdClass $errorDetails
+     */
+    protected function _checkFaultParams($expectedErrorParams, $errorDetails)
+    {
+        $paramsNode = Fault::NODE_DETAIL_PARAMETERS;
+        if ($expectedErrorParams) {
+            $paramNode = Fault::NODE_DETAIL_PARAMETER;
+            $paramKey = Fault::NODE_DETAIL_PARAMETER_KEY;
+            $paramValue = Fault::NODE_DETAIL_PARAMETER_VALUE;
+            $actualParams = [];
+            if (isset($errorDetails->$paramsNode->$paramNode)) {
+                if (is_array($errorDetails->$paramsNode->$paramNode)) {
+                    foreach ($errorDetails->$paramsNode->$paramNode as $param) {
+                        $actualParams[$param->$paramKey] = $param->$paramValue;
+                    }
+                } else {
+                    $param = $errorDetails->$paramsNode->$paramNode;
+                    $actualParams[$param->$paramKey] = $param->$paramValue;
+                }
+            }
+            $this->assertEquals(
+                $expectedErrorParams,
+                $actualParams,
+                "Parameters in fault details are invalid."
+            );
+        } else {
+            $this->assertFalse(isset($errorDetails->$paramsNode), "Parameters are not expected in fault details.");
+        }
+    }
+
+    /**
+     * Check additional wrapped errors.
+     *
+     * @param array $expectedWrappedErrors
+     * @param \stdClass $errorDetails
+     */
+    protected function _checkWrappedErrors($expectedWrappedErrors, $errorDetails)
+    {
+        $wrappedErrorsNode = Fault::NODE_DETAIL_WRAPPED_ERRORS;
+        if ($expectedWrappedErrors) {
+            $wrappedErrorNode = Fault::NODE_DETAIL_WRAPPED_ERROR;
+            $wrappedErrorNodeFieldName = 'fieldName';
+            $wrappedErrorNodeValue = Fault::NODE_DETAIL_WRAPPED_ERROR_VALUE;
+            $actualWrappedErrors = [];
+            if (isset($errorDetails->$wrappedErrorsNode->$wrappedErrorNode)) {
+                if (is_array($errorDetails->$wrappedErrorsNode->$wrappedErrorNode)) {
+                    foreach ($errorDetails->$wrappedErrorsNode->$wrappedErrorNode as $error) {
+                        $actualParameters = [];
+                        foreach ($error->parameters->parameter as $parameter) {
+                            $actualParameters[$parameter->key] = $parameter->value;
+                        }
+                        $actualWrappedErrors[] = [
+                            'message' => $error->message,
+                            'params' => $actualParameters,
+                        ];
+                    }
+                } else {
+                    $error = $errorDetails->$wrappedErrorsNode->$wrappedErrorNode;
+                    $actualWrappedErrors[] = [
+                        "fieldName" => $error->$wrappedErrorNodeFieldName,
+                        "value" => $error->$wrappedErrorNodeValue,
+                    ];
+                }
+            }
+            $this->assertEquals(
+                $expectedWrappedErrors,
+                $actualWrappedErrors,
+                "Wrapped errors in fault details are invalid."
+            );
+        } else {
+            $this->assertFalse(
+                isset($errorDetails->$wrappedErrorsNode),
+                "Wrapped errors are not expected in fault details."
+            );
+        }
+    }
+}
diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/WebApiApplication.php b/dev/tests/api-functional/framework/Magento/TestFramework/WebApiApplication.php
new file mode 100644
index 0000000000000000000000000000000000000000..055fa381ae23bb427e1d4c9512158396ce874a32
--- /dev/null
+++ b/dev/tests/api-functional/framework/Magento/TestFramework/WebApiApplication.php
@@ -0,0 +1,62 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\TestFramework;
+
+
+/**
+ * Provides access to the application for the tests
+ *
+ * Allows installation and uninstallation
+ */
+class WebApiApplication extends Application
+{
+    /**
+     * {@inheritdoc}
+     */
+    public function run()
+    {
+        throw new \Exception(
+            "Can't start application: purpose of Web API Application is to use classes and models from the application"
+            . " and don't run it"
+        );
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function install()
+    {
+        $installOptions = $this->getInstallConfig();
+
+        /* Install application */
+        if ($installOptions) {
+            $installCmd = 'php -f ' . BP . '/setup/index.php install';
+            $installArgs = [];
+            foreach ($installOptions as $optionName => $optionValue) {
+                if (is_bool($optionValue)) {
+                    if (true === $optionValue) {
+                        $installCmd .= " --$optionName";
+                    }
+                    continue;
+                }
+                if (!empty($optionValue)) {
+                    $installCmd .= " --$optionName=%s";
+                    $installArgs[] = $optionValue;
+                }
+            }
+            $this->_shell->execute($installCmd, $installArgs);
+        }
+    }
+
+    /**
+     * Use the application as is
+     *
+     * {@inheritdoc}
+     */
+    protected function getCustomDirs()
+    {
+        return [];
+    }
+}
diff --git a/dev/tests/api-functional/framework/autoload.php b/dev/tests/api-functional/framework/autoload.php
new file mode 100644
index 0000000000000000000000000000000000000000..231e39080884504cd02a427a6333e875d0dae087
--- /dev/null
+++ b/dev/tests/api-functional/framework/autoload.php
@@ -0,0 +1,14 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+require_once __DIR__ . '/../../../../app/autoload.php';
+
+$testsBaseDir = dirname(__DIR__);
+$integrationTestsDir = realpath("{$testsBaseDir}/../integration/");
+
+$autoloadWrapper = \Magento\Framework\Autoload\AutoloaderRegistry::getAutoloader();
+$autoloadWrapper->addPsr4('Magento\\TestFramework\\', "{$testsBaseDir}/framework/Magento/TestFramework/");
+$autoloadWrapper->addPsr4('Magento\\TestFramework\\', "{$integrationTestsDir}/framework/Magento/TestFramework/");
+$autoloadWrapper->addPsr4('Magento\\', "{$testsBaseDir}/testsuite/Magento/");
diff --git a/dev/tests/api-functional/framework/bootstrap.php b/dev/tests/api-functional/framework/bootstrap.php
new file mode 100644
index 0000000000000000000000000000000000000000..ae037a554b21cd9e7529fd5bb320d5ea087b6580
--- /dev/null
+++ b/dev/tests/api-functional/framework/bootstrap.php
@@ -0,0 +1,77 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+use Magento\Framework\App\Filesystem\DirectoryList;
+use Magento\Framework\Autoload\AutoloaderRegistry;
+
+require_once __DIR__ . '/../../../../app/bootstrap.php';
+require_once __DIR__ . '/autoload.php';
+
+$testsBaseDir = dirname(__DIR__);
+$integrationTestsDir = realpath("{$testsBaseDir}/../integration");
+
+$logWriter = new \Zend_Log_Writer_Stream('php://output');
+$logWriter->setFormatter(new \Zend_Log_Formatter_Simple('%message%' . PHP_EOL));
+$logger = new \Zend_Log($logWriter);
+
+/** Copy test modules to app/code/Magento to make them visible for Magento instance */
+$pathToCommittedTestModules = __DIR__ . '/../_files/Magento';
+$pathToInstalledMagentoInstanceModules = __DIR__ . '/../../../../app/code/Magento';
+$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($pathToCommittedTestModules));
+/** @var SplFileInfo $file */
+foreach ($iterator as $file) {
+    if (!$file->isDir()) {
+        $source = $file->getPathname();
+        $relativePath = substr($source, strlen($pathToCommittedTestModules));
+        $destination = $pathToInstalledMagentoInstanceModules . $relativePath;
+        $targetDir = dirname($destination);
+        if (!is_dir($targetDir)) {
+            mkdir($targetDir, 0755, true);
+        }
+        copy($source, $destination);
+    }
+}
+unset($iterator, $file);
+
+/* Bootstrap the application */
+$settings = new \Magento\TestFramework\Bootstrap\Settings($testsBaseDir, get_defined_constants());
+$shell = new \Magento\Framework\Shell(new \Magento\Framework\Shell\CommandRenderer(), $logger);
+
+$installConfigFile = $settings->getAsConfigFile('TESTS_INSTALL_CONFIG_FILE');
+if (!file_exists($installConfigFile)) {
+    $installConfigFile = $installConfigFile . '.dist';
+}
+$dirList = new \Magento\Framework\App\Filesystem\DirectoryList(BP);
+$application =  new \Magento\TestFramework\WebApiApplication(
+    $shell,
+    $dirList->getPath(DirectoryList::VAR_DIR),
+    $installConfigFile,
+    BP . '/app/etc/',
+    $settings->get('TESTS_MAGENTO_MODE'),
+    AutoloaderRegistry::getAutoloader()
+);
+
+if (defined('TESTS_MAGENTO_INSTALLATION') && TESTS_MAGENTO_INSTALLATION === 'enabled') {
+    if (defined('TESTS_CLEANUP') && TESTS_CLEANUP === 'enabled') {
+        $application->cleanup();
+    }
+    $application->install();
+}
+
+$bootstrap = new \Magento\TestFramework\Bootstrap(
+    $settings,
+    new \Magento\TestFramework\Bootstrap\Environment(),
+    new \Magento\TestFramework\Bootstrap\WebapiDocBlock("{$integrationTestsDir}/testsuite"),
+    new \Magento\TestFramework\Bootstrap\Profiler(new \Magento\Framework\Profiler\Driver\Standard()),
+    $shell,
+    $application,
+    new \Magento\TestFramework\Bootstrap\MemoryFactory($shell)
+);
+$bootstrap->runBootstrap();
+$application->initialize();
+
+\Magento\TestFramework\Helper\Bootstrap::setInstance(new \Magento\TestFramework\Helper\Bootstrap($bootstrap));
+\Magento\Framework\Test\Utility\Files::setInstance(new \Magento\Framework\Test\Utility\Files(BP));
+unset($bootstrap, $application, $settings, $shell);
diff --git a/dev/tests/api-functional/phpunit.xml.dist b/dev/tests/api-functional/phpunit.xml.dist
new file mode 100644
index 0000000000000000000000000000000000000000..b885beab08ed0fbbbaf06705775f250543ef32d6
--- /dev/null
+++ b/dev/tests/api-functional/phpunit.xml.dist
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/**
+ * PHPUnit configuration for Web API functional tests.
+ *
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+-->
+<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd"
+         colors="true"
+         bootstrap="./framework/bootstrap.php"
+>
+    <!-- Test suites definition -->
+    <testsuites>
+        <testsuite name="Magento Web API Functional Tests">
+            <directory suffix="Test.php">testsuite</directory>
+        </testsuite>
+    </testsuites>
+
+    <!-- Code coverage filters -->
+    <filter>
+        <whitelist>
+            <!-- All CE modules -->
+            <directory suffix=".php">../../app/code/Magento</directory>
+            <exclude>
+                <!-- Excluding installation and upgrade scripts -->
+                <directory>../../app/code/Magento/*/sql</directory>
+                <!-- Excluding data installation and upgrade scripts -->
+                <directory>../../app/code/Magento/*/data</directory>
+            </exclude>
+        </whitelist>
+    </filter>
+
+    <!-- PHP INI settings and constants definition -->
+    <php>
+        <includePath>./testsuite</includePath>
+        <const name="TESTS_INSTALL_CONFIG_FILE" value="config/install-config-mysql.php"/>
+        <!-- WebSerivice Type. Possible values: soap, rest -->
+        <const name="TESTS_WEB_API_ADAPTER" value="rest"/>
+        <!-- Webserver URL -->
+        <const name="TESTS_BASE_URL" value="http://magento.url"/>
+        <!-- Webserver API user -->
+        <const name="TESTS_WEBSERVICE_USER" value="admin"/>
+        <!-- Webserver API key -->
+        <const name="TESTS_WEBSERVICE_APIKEY" value="123123q"/>
+        <!-- Define if debugger should be started using XDEBUG_SESSION cookie -->
+        <const name="TESTS_XDEBUG_ENABLED" value="false"/>
+        <!-- Define XDEBUG_SESSION cookie value-->
+        <const name="TESTS_XDEBUG_SESSION" value="phpstorm" />
+        <!--Generate documentation from REST tests and put it into var/log/rest-documentation directory-->
+        <const name="GENERATE_REST_DOCUMENTATION" value="false" />
+
+        <ini name="date.timezone" value="America/Los_Angeles"/>
+        <ini name="soap.wsdl_cache_enabled" value="0" />
+
+        <!-- Semicolon-separated 'glob' patterns, that match global XML configuration files -->
+        <const name="TESTS_GLOBAL_CONFIG_DIR" value="../../../app/etc"/>
+        <!-- Whether to cleanup the application before running tests or not -->
+        <const name="TESTS_CLEANUP" value="enabled"/>
+        <!--Defines if Magento should be installed before tests execution-->
+        <const name="TESTS_MAGENTO_INSTALLATION" value="disabled"/>
+        <!-- Magento mode for tests execution. Possible values are "default", "developer" and "production". -->
+        <const name="TESTS_MAGENTO_MODE" value="default"/>
+    </php>
+
+    <!-- Test listeners -->
+    <listeners>
+        <listener class="Magento\TestFramework\Event\PhpUnit"/>
+    </listeners>
+</phpunit>
diff --git a/dev/tests/api-functional/testsuite/Magento/Bundle/Api/ProductLinkManagementTest.php b/dev/tests/api-functional/testsuite/Magento/Bundle/Api/ProductLinkManagementTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..078d61a66d8e91ed7b55c2c8414e5942c4b26f03
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Bundle/Api/ProductLinkManagementTest.php
@@ -0,0 +1,163 @@
+<?php
+/**
+ *
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Bundle\Api;
+
+use Magento\TestFramework\Helper\Bootstrap;
+use Magento\Webapi\Model\Rest\Config;
+
+class ProductLinkManagementTest extends \Magento\TestFramework\TestCase\WebapiAbstract
+{
+    const SERVICE_NAME = 'bundleProductLinkManagementV1';
+    const SERVICE_VERSION = 'V1';
+    const RESOURCE_PATH = '/V1/bundle-products';
+
+    /**
+     * @magentoApiDataFixture Magento/Bundle/_files/product.php
+     */
+    public function testGetChildren()
+    {
+        $productSku = 'bundle-product';
+        $expected = [
+            [
+                'sku' => 'simple',
+                'position' => 0,
+                'qty' => 1,
+            ],
+        ];
+
+        $result = $this->getChildren($productSku);
+
+        $this->assertArrayHasKey(0, $result);
+        $this->assertArrayHasKey('option_id', $result[0]);
+        $this->assertArrayHasKey('is_default', $result[0]);
+        $this->assertArrayHasKey('is_defined', $result[0]);
+        $this->assertArrayHasKey('price', $result[0]);
+        $this->assertArrayHasKey('price_type', $result[0]);
+
+        unset($result[0]['option_id'], $result[0]['is_default'], $result[0]['is_defined']);
+        unset($result[0]['price'], $result[0]['price_type']);
+
+        ksort($result[0]);
+        ksort($expected[0]);
+        $this->assertEquals($expected, $result);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Bundle/_files/product.php
+     */
+    public function testRemoveChild()
+    {
+        $productSku = 'bundle-product';
+        $childSku = 'simple';
+        $optionIds = $this->getProductOptions(3);
+        $optionId = array_shift($optionIds);
+        $this->assertTrue($this->removeChild($productSku, $optionId, $childSku));
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Bundle/_files/product.php
+     * @magentoApiDataFixture Magento/Catalog/_files/product_virtual.php
+     */
+    public function testAddChild()
+    {
+        $productSku = 'bundle-product';
+        $children = $this->getChildren($productSku);
+
+        $optionId = $children[0]['option_id'];
+
+        $linkedProduct = [
+            'sku' => 'virtual-product',
+            'option_id' => $optionId,
+            'position' => '1',
+            'is_default' => 1,
+            'priceType' => 2,
+            'price' => 151.34,
+            'qty' => 8,
+            'can_change_quantity' => 1,
+        ];
+
+        $childId = $this->addChild($productSku, $optionId, $linkedProduct);
+        $this->assertGreaterThan(0, $childId);
+    }
+
+    /**
+     * @param string $productSku
+     * @param int $optionId
+     * @param array $linkedProduct
+     * @return string
+     */
+    private function addChild($productSku, $optionId, $linkedProduct)
+    {
+        $resourcePath = self::RESOURCE_PATH . '/:productSku/links/:optionId';
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => str_replace(
+                    [':productSku', ':optionId'],
+                    [$productSku, $optionId],
+                    $resourcePath
+                ),
+                'httpMethod' => Config::HTTP_METHOD_POST,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'AddChildByProductSku',
+            ],
+        ];
+        return $this->_webApiCall(
+            $serviceInfo,
+            ['productSku' => $productSku, 'optionId' => $optionId, 'linkedProduct' => $linkedProduct]
+        );
+    }
+
+    protected function getProductOptions($productId)
+    {
+        /** @var \Magento\Catalog\Model\Product $product */
+        $product = Bootstrap::getObjectManager()->get('Magento\Catalog\Model\Product');
+        $product->load($productId);
+        /** @var  \Magento\Bundle\Model\Product\Type $type */
+        $type = Bootstrap::getObjectManager()->get('Magento\Bundle\Model\Product\Type');
+        return $type->getOptionsIds($product);
+    }
+
+    protected function removeChild($productSku, $optionId, $childSku)
+    {
+        $resourcePath = self::RESOURCE_PATH . '/%s/option/%s/child/%s';
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => sprintf($resourcePath, $productSku, $optionId, $childSku),
+                'httpMethod' => Config::HTTP_METHOD_DELETE,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'removeChild',
+            ],
+        ];
+        $requestData = ['productSku' => $productSku, 'optionId' => $optionId, 'childSku' => $childSku];
+        return $this->_webApiCall($serviceInfo, $requestData);
+    }
+
+    /**
+     * @param string $productSku
+     * @return string
+     */
+    protected function getChildren($productSku)
+    {
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/' . $productSku . '/children',
+                'httpMethod' => Config::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'getChildren',
+            ],
+        ];
+        return $this->_webApiCall($serviceInfo, ['productId' => $productSku]);
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Bundle/Api/ProductOptionRepositoryTest.php b/dev/tests/api-functional/testsuite/Magento/Bundle/Api/ProductOptionRepositoryTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..2b35e1c4ff5be9dab24c6cc24b26eac6fad91cbd
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Bundle/Api/ProductOptionRepositoryTest.php
@@ -0,0 +1,267 @@
+<?php
+/**
+ *
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Bundle\Api;
+
+use Magento\Webapi\Model\Rest\Config;
+
+class ProductOptionRepositoryTest extends \Magento\TestFramework\TestCase\WebapiAbstract
+{
+    const SERVICE_NAME = 'bundleProductOptionRepositoryV1';
+    const SERVICE_VERSION = 'V1';
+    const RESOURCE_PATH = '/V1/bundle-products/:productSku/option';
+
+    /**
+     * @magentoApiDataFixture Magento/Bundle/_files/product.php
+     */
+    public function testGet()
+    {
+        $productSku = 'bundle-product';
+        $expected = [
+            'required' => true,
+            'position' => 0,
+            'type' => 'select',
+            'title' => 'Bundle Product Items',
+            'sku' => $productSku,
+            'product_links' => [
+                [
+                    'sku' => 'simple',
+                    'qty' => 1,
+                    'position' => 0,
+                    'is_defined' => true,
+                    'is_default' => false,
+                    'price' => null,
+                    'price_type' => null,
+                ],
+            ],
+        ];
+        $optionId = $this->getList($productSku)[0]['option_id'];
+        $result = $this->get($productSku, $optionId);
+
+        $this->assertArrayHasKey('option_id', $result);
+        $expected['product_links'][0]['option_id'] = $result['option_id'];
+        unset($result['option_id']);
+
+        ksort($expected);
+        ksort($result);
+        $this->assertEquals($expected, $result);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Bundle/_files/product.php
+     */
+    public function testGetList()
+    {
+        $productSku = 'bundle-product';
+        $expected = [
+            [
+                'required' => true,
+                'position' => 0,
+                'type' => 'select',
+                'title' => 'Bundle Product Items',
+                'sku' => $productSku,
+                'product_links' => [
+                    [
+                        'sku' => 'simple',
+                        'qty' => 1,
+                        'position' => 0,
+                        'is_defined' => true,
+                        'is_default' => false,
+                        'price' => null,
+                        'price_type' => null,
+                    ],
+                ],
+            ],
+        ];
+        $result = $this->getList($productSku);
+
+        $this->assertArrayHasKey(0, $result);
+        $this->assertArrayHasKey('option_id', $result[0]);
+        $expected[0]['product_links'][0]['option_id'] = $result[0]['option_id'];
+        unset($result[0]['option_id']);
+
+        ksort($expected[0]);
+        ksort($result[0]);
+        $this->assertEquals($expected, $result);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Bundle/_files/product.php
+     * @expectedException \Magento\Framework\Exception\NoSuchEntityException
+     */
+    public function testRemove()
+    {
+        $productSku = 'bundle-product';
+
+        $optionId = $this->getList($productSku)[0]['option_id'];
+        $result = $this->remove($productSku, $optionId);
+
+        $this->assertTrue($result);
+
+        try {
+            $this->get($productSku, $optionId);
+        } catch (\Exception $e) {
+            throw new \Magento\Framework\Exception\NoSuchEntityException();
+        }
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Bundle/_files/product.php
+     */
+    public function testAdd()
+    {
+        $productSku = 'bundle-product';
+        $request = [
+            'required' => true,
+            'position' => 0,
+            'type' => 'select',
+            'title' => 'test product',
+            'product_links' => [],
+            'sku' => $productSku,
+        ];
+
+        $optionId = $this->add($request);
+        $this->assertGreaterThan(0, $optionId);
+        $result = $this->get($productSku, $optionId);
+
+        $this->assertArrayHasKey('option_id', $result);
+        $this->assertArrayHasKey('sku', $result);
+        unset($result['option_id']);
+
+        ksort($result);
+        ksort($request);
+        $this->assertEquals($request, $result);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Bundle/_files/product.php
+     */
+    public function testUpdate()
+    {
+        $productSku = 'bundle-product';
+        $request = [
+            'title' => 'someTitle',
+            'sku' => $productSku,
+        ];
+
+        $optionId = $this->getList($productSku)[0]['option_id'];
+        $result = $this->update($optionId, $request);
+
+        $this->assertEquals($result, $optionId);
+
+        $result = $this->get($productSku, $optionId);
+
+        $this->assertCount(7, $result);
+        $this->assertArrayHasKey('title', $result);
+        $this->assertEquals($request['title'], $result['title']);
+    }
+
+    /**
+     * @param int $optionId
+     * @param array $option
+     * @return string
+     */
+    protected function update($optionId, $option)
+    {
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/bundle-products/option/' . $optionId,
+                'httpMethod' => Config::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => 'bundleProductOptionManagementV1',
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => 'bundleProductOptionManagementV1Save',
+            ],
+        ];
+
+        if (TESTS_WEB_API_ADAPTER == self::ADAPTER_SOAP) {
+            $option['optionId'] = $optionId;
+        }
+        return $this->_webApiCall($serviceInfo, ['option' => $option]);
+    }
+
+    /**
+     * @param array $option
+     * @return string
+     */
+    protected function add($option)
+    {
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/bundle-products/option/add',
+                'httpMethod' => Config::HTTP_METHOD_POST,
+            ],
+            'soap' => [
+                'service' => 'bundleProductOptionManagementV1',
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => 'bundleProductOptionManagementV1Save',
+            ],
+        ];
+        return $this->_webApiCall($serviceInfo, ['option' => $option]);
+    }
+
+    /**
+     * @param string $productSku
+     * @param int $optionId
+     * @return string
+     */
+    protected function remove($productSku, $optionId)
+    {
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => str_replace(':productSku', $productSku, self::RESOURCE_PATH) . '/' . $optionId,
+                'httpMethod' => Config::HTTP_METHOD_DELETE,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'DeleteById',
+            ],
+        ];
+        return $this->_webApiCall($serviceInfo, ['productSku' => $productSku, 'optionId' => $optionId]);
+    }
+
+    /**
+     * @param string $productSku
+     * @return string
+     */
+    protected function getList($productSku)
+    {
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => str_replace(':productSku', $productSku, self::RESOURCE_PATH) . '/all',
+                'httpMethod' => Config::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'GetList',
+            ],
+        ];
+        return $this->_webApiCall($serviceInfo, ['productSku' => $productSku]);
+    }
+
+    /**
+     * @param string $productSku
+     * @param int $optionId
+     * @return string
+     */
+    protected function get($productSku, $optionId)
+    {
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => str_replace(':productSku', $productSku, self::RESOURCE_PATH) . '/' . $optionId,
+                'httpMethod' => Config::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Get',
+            ],
+        ];
+        return $this->_webApiCall($serviceInfo, ['productSku' => $productSku, 'optionId' => $optionId]);
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Bundle/Api/ProductOptionTypeListTest.php b/dev/tests/api-functional/testsuite/Magento/Bundle/Api/ProductOptionTypeListTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..4d4be9a704ce9e18608f3476b7cafb1b6f7afd80
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Bundle/Api/ProductOptionTypeListTest.php
@@ -0,0 +1,47 @@
+<?php
+/**
+ *
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Bundle\Api;
+
+use Magento\Webapi\Model\Rest\Config;
+
+class ProductOptionTypeListTest extends \Magento\TestFramework\TestCase\WebapiAbstract
+{
+    const SERVICE_READ_NAME = 'bundleProductOptionTypeListV1';
+    const SERVICE_VERSION = 'V1';
+    const RESOURCE_PATH = '/V1/bundle-products/option/types';
+
+    public function testGetTypes()
+    {
+        $expected = [
+            ['label' => 'Drop-down', 'code' => 'select'],
+            ['label' => 'Radio Buttons', 'code' => 'radio'],
+            ['label' => 'Checkbox', 'code' => 'checkbox'],
+            ['label' => 'Multiple Select', 'code' => 'multi'],
+        ];
+        $result = $this->getTypes();
+
+        $this->assertEquals($expected, $result);
+    }
+
+    /**
+     * @return string
+     */
+    protected function getTypes()
+    {
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH,
+                'httpMethod' => Config::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_READ_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_READ_NAME . 'getItems',
+            ],
+        ];
+        return $this->_webApiCall($serviceInfo);
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Bundle/Api/ProductServiceTest.php b/dev/tests/api-functional/testsuite/Magento/Bundle/Api/ProductServiceTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..68db7d521095ae798280dfd5ede1e4dd5b095059
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Bundle/Api/ProductServiceTest.php
@@ -0,0 +1,170 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+namespace Magento\Bundle\Api;
+
+use Magento\Catalog\Api\Data\ProductInterface;
+use Magento\Framework\Api\AbstractExtensibleObject;
+use Magento\TestFramework\Helper\Bootstrap;
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+/**
+ * Class ProductServiceTest for testing Bundle Product API
+ */
+class ProductServiceTest extends WebapiAbstract
+{
+    const SERVICE_NAME = 'catalogProductRepositoryV1';
+    const SERVICE_VERSION = 'V1';
+    const RESOURCE_PATH = '/V1/products';
+
+    /**
+     * @var \Magento\Catalog\Model\Resource\Product\Collection
+     */
+    protected $productCollection;
+
+    /**
+     * Execute per test initialization
+     */
+    public function setUp()
+    {
+        $objectManager = Bootstrap::getObjectManager();
+        $this->productCollection = $objectManager->get('Magento\Catalog\Model\Resource\Product\Collection');
+    }
+
+    /**
+     * Execute per test cleanup
+     */
+    public function tearDown()
+    {
+        /** @var \Magento\Framework\Registry $registry */
+        $registry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get('Magento\Framework\Registry');
+
+        $registry->unregister('isSecureArea');
+        $registry->register('isSecureArea', true);
+
+        $this->productCollection->addFieldToFilter(
+            'sku',
+            ['in' => ['sku-test-product-bundle']]
+        )->delete();
+        unset($this->productCollection);
+
+        $registry->unregister('isSecureArea');
+        $registry->register('isSecureArea', false);
+        parent::tearDown();
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/products_new.php
+     */
+    public function testCreateBundle()
+    {
+        if (TESTS_WEB_API_ADAPTER == self::ADAPTER_SOAP) {
+            $this->markTestIncomplete('MAGETWO-31016: incompatible with ZF 1.12.9');
+        }
+        $bundleProductOptions = [
+            "attribute_code" => "bundle_product_options",
+            "value" => [
+                [
+                    "title" => "test option",
+                    "type" => "checkbox",
+                    "required" => 1,
+                    "product_links" => [
+                        [
+                            "sku" => 'simple',
+                            "qty" => 1,
+                        ],
+                    ],
+                ],
+            ],
+        ];
+
+        $uniqueId = 'sku-test-product-bundle';
+        $product = [
+            "sku" => $uniqueId,
+            "name" => $uniqueId,
+            "type_id" => "bundle",
+            "price" => 50,
+            'attribute_set_id' => 4,
+            "custom_attributes" => [
+                "price_type" => [
+                    'attribute_code' => 'price_type',
+                    'value' => \Magento\Bundle\Model\Product\Price::PRICE_TYPE_DYNAMIC
+                ],
+                "bundle_product_options" => $bundleProductOptions,
+                "price_view" => [
+                    "attribute_code" => "price_view",
+                    "value" => "test",
+                ],
+            ],
+        ];
+
+        $response = $this->createProduct($product);
+
+        $this->assertEquals($uniqueId, $response[ProductInterface::SKU]);
+        $this->assertEquals(
+            $bundleProductOptions,
+            $response[AbstractExtensibleObject::CUSTOM_ATTRIBUTES_KEY]["bundle_product_options"]
+        );
+
+        $response = $this->getProduct($uniqueId);
+        $foundBundleProductOptions = false;
+        foreach ($response[AbstractExtensibleObject::CUSTOM_ATTRIBUTES_KEY] as $customAttribute) {
+            if ($customAttribute["attribute_code"] === 'bundle_product_options') {
+                $this->assertEquals('simple', $customAttribute["value"][0]["product_links"][0]["sku"]);
+                $foundBundleProductOptions = true;
+            }
+        }
+        $this->assertTrue($foundBundleProductOptions);
+    }
+
+    /**
+     * Get product
+     *
+     * @param string $productSku
+     * @return array the product data
+     */
+    protected function getProduct($productSku)
+    {
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/' . $productSku,
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Get',
+            ],
+        ];
+
+        $response = (TESTS_WEB_API_ADAPTER == self::ADAPTER_SOAP) ?
+            $this->_webApiCall($serviceInfo, ['productSku' => $productSku]) : $this->_webApiCall($serviceInfo);
+
+        return $response;
+    }
+
+    /**
+     * Create product
+     *
+     * @param array $product
+     * @return array the created product data
+     */
+    protected function createProduct($product)
+    {
+        $serviceInfo = [
+            'rest' => ['resourcePath' => self::RESOURCE_PATH, 'httpMethod' => RestConfig::HTTP_METHOD_POST],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Save',
+            ],
+        ];
+        $requestData = ['product' => $product];
+        $response = $this->_webApiCall($serviceInfo, $requestData);
+        $product[ProductInterface::SKU] = $response[ProductInterface::SKU];
+        return $product;
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/AttributeSetManagementTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/AttributeSetManagementTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..34dd1076b8843813b129b1e22f93027eec34b1f2
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/AttributeSetManagementTest.php
@@ -0,0 +1,221 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Catalog\Api;
+
+use Magento\TestFramework\Helper\Bootstrap;
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Exception as HTTPExceptionCodes;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+class AttributeSetManagementTest extends WebapiAbstract
+{
+    /**
+     * @var array
+     */
+    private $createServiceInfo;
+
+    protected function setUp()
+    {
+        $this->createServiceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/products/attribute-sets',
+                'httpMethod' => RestConfig::HTTP_METHOD_POST,
+            ],
+            'soap' => [
+                'service' => 'catalogAttributeSetManagementV1',
+                'serviceVersion' => 'V1',
+                'operation' => 'catalogAttributeSetManagementV1Create',
+            ],
+        ];
+    }
+
+    public function testCreate()
+    {
+        $entityTypeCode = 'catalog_product';
+        $entityType = $this->getEntityTypeByCode($entityTypeCode);
+        $attributeSetName = 'new_attribute_set';
+
+        $arguments = [
+            'attributeSet' => [
+                'attribute_set_name' => $attributeSetName,
+                'sort_order' => 500,
+            ],
+            'skeletonId' => $entityType->getDefaultAttributeSetId(),
+        ];
+        $result = $this->_webApiCall($this->createServiceInfo, $arguments);
+        $this->assertNotNull($result);
+        $attributeSet = $this->getAttributeSetByName($attributeSetName);
+        $this->assertNotNull($attributeSet);
+        $this->assertEquals($attributeSet->getId(), $result['attribute_set_id']);
+        $this->assertEquals($attributeSet->getAttributeSetName(), $result['attribute_set_name']);
+        $this->assertEquals($attributeSet->getEntityTypeId(), $result['entity_type_id']);
+        $this->assertEquals($attributeSet->getEntityTypeId(), $entityType->getId());
+        $this->assertEquals($attributeSet->getSortOrder(), $result['sort_order']);
+        $this->assertEquals($attributeSet->getSortOrder(), 500);
+
+        // Clean up database
+        $attributeSet->delete();
+    }
+
+    /**
+     * @expectedException \Exception
+     * @expectedExceptionMessage Invalid value
+     */
+    public function testCreateThrowsExceptionIfGivenAttributeSetAlreadyHasId()
+    {
+        $entityTypeCode = 'catalog_product';
+        $entityType = $this->getEntityTypeByCode($entityTypeCode);
+        $attributeSetName = 'new_attribute_set';
+
+        $arguments = [
+            'attributeSet' => [
+                'attribute_set_id' => 1,
+                'attribute_set_name' => $attributeSetName,
+                'sort_order' => 100,
+            ],
+            'skeletonId' => $entityType->getDefaultAttributeSetId(),
+        ];
+        $this->_webApiCall($this->createServiceInfo, $arguments);
+    }
+
+    /**
+     * @expectedException \Exception
+     * @expectedExceptionMessage Can not create attribute set based on not existing attribute set
+     */
+    public function testCreateThrowsExceptionIfGivenSkeletonIdIsInvalid()
+    {
+        $attributeSetName = 'new_attribute_set';
+        $arguments = [
+            'attributeSet' => [
+                'attribute_set_name' => $attributeSetName,
+                'sort_order' => 200,
+            ],
+            'skeletonId' => 0,
+        ];
+        $this->_webApiCall($this->createServiceInfo, $arguments);
+    }
+
+    /**
+     * @expectedException \Exception
+     * @expectedExceptionMessage Can not create attribute set based on non product attribute set.
+     */
+    public function testCreateThrowsExceptionIfGivenSkeletonIdHasWrongEntityType()
+    {
+        $attributeSetName = 'new_attribute_set';
+        $arguments = [
+            'attributeSet' => [
+                'attribute_set_name' => $attributeSetName,
+                'sort_order' => 200,
+            ],
+            'skeletonId' => 7,
+        ];
+        $this->_webApiCall($this->createServiceInfo, $arguments);
+    }
+
+    /**
+     * @expectedException \Exception
+     * @expectedExceptionMessage Can not create attribute set based on not existing attribute set
+     */
+    public function testCreateThrowsExceptionIfGivenSkeletonAttributeSetDoesNotExist()
+    {
+        $attributeSetName = 'new_attribute_set';
+        $arguments = [
+            'attributeSet' => [
+                'attribute_set_name' => $attributeSetName,
+                'sort_order' => 300,
+            ],
+            'skeletonId' => 9999,
+        ];
+        $this->_webApiCall($this->createServiceInfo, $arguments);
+    }
+
+    /**
+     * @expectedException \Exception
+     * @expectedExceptionMessage Attribute set name is empty.
+     */
+    public function testCreateThrowsExceptionIfAttributeSetNameIsEmpty()
+    {
+        $entityTypeCode = 'catalog_product';
+        $entityType = $this->getEntityTypeByCode($entityTypeCode);
+        $attributeSetName = '';
+
+        $arguments = [
+            'attributeSet' => [
+                'attribute_set_name' => $attributeSetName,
+                'sort_order' => 500,
+            ],
+            'skeletonId' => $entityType->getDefaultAttributeSetId(),
+        ];
+        $this->_webApiCall($this->createServiceInfo, $arguments);
+    }
+
+    public function testCreateThrowsExceptionIfAttributeSetWithGivenNameAlreadyExists()
+    {
+        $entityTypeCode = 'catalog_product';
+        $entityType = $this->getEntityTypeByCode($entityTypeCode);
+        $attributeSetName = 'Default';
+        $expectedMessage = 'An attribute set with the "Default" name already exists.';
+
+        $arguments = [
+            'attributeSet' => [
+                'attribute_set_name' => $attributeSetName,
+                'sort_order' => 550,
+            ],
+            'skeletonId' => $entityType->getDefaultAttributeSetId(),
+        ];
+
+        try {
+            $this->_webApiCall($this->createServiceInfo, $arguments);
+            $this->fail("Expected exception");
+        } catch (\SoapFault $e) {
+            $this->assertContains(
+                $expectedMessage,
+                $e->getMessage(),
+                "SoapFault does not contain expected message."
+            );
+        } catch (\Exception $e) {
+            $errorObj = $this->processRestExceptionResult($e);
+            $this->assertEquals(
+                $expectedMessage,
+                $errorObj['message']
+            );
+            $this->assertEquals(HTTPExceptionCodes::HTTP_BAD_REQUEST, $e->getCode());
+        }
+    }
+
+    /**
+     * Retrieve attribute set based on given name.
+     * This utility methods assumes that there is only one attribute set with given name,
+     *
+     * @param string $attributeSetName
+     * @return \Magento\Eav\Model\Entity\Attribute\Set|null
+     */
+    protected function getAttributeSetByName($attributeSetName)
+    {
+        $objectManager = Bootstrap::getObjectManager();
+        /** @var \Magento\Eav\Model\Entity\Attribute\Set $attributeSet */
+        $attributeSet = $objectManager->create('Magento\Eav\Model\Entity\Attribute\Set')
+            ->load($attributeSetName, 'attribute_set_name');
+        if ($attributeSet->getId() === null) {
+            return null;
+        }
+        return $attributeSet;
+    }
+
+    /**
+     * Retrieve entity type based on given code.
+     *
+     * @param string $entityTypeCode
+     * @return \Magento\Eav\Model\Entity\Type|null
+     */
+    protected function getEntityTypeByCode($entityTypeCode)
+    {
+        $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+        /** @var \Magento\Eav\Model\Entity\Type $entityType */
+        $entityType = $objectManager->create('Magento\Eav\Model\Config')
+            ->getEntityType($entityTypeCode);
+        return $entityType;
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/AttributeSetRepositoryTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/AttributeSetRepositoryTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..7b2bb1d5b2e74b2b9f3bc0cb3efd387dc4731fdf
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/AttributeSetRepositoryTest.php
@@ -0,0 +1,228 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Catalog\Api;
+
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+class AttributeSetRepositoryTest extends WebapiAbstract
+{
+    /**
+     * @magentoApiDataFixture Magento/Eav/_files/empty_attribute_set.php
+     */
+    public function testGet()
+    {
+        $attributeSetName = 'empty_attribute_set';
+        $attributeSet = $this->getAttributeSetByName($attributeSetName);
+        $attributeSetId = $attributeSet->getId();
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/products/attribute-sets/' . $attributeSetId,
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => 'catalogAttributeSetRepositoryV1',
+                'serviceVersion' => 'V1',
+                'operation' => 'catalogAttributeSetRepositoryV1Get',
+            ],
+        ];
+        $arguments = [
+            'attributeSetId' => $attributeSetId,
+        ];
+        $result = $this->_webApiCall($serviceInfo, $arguments);
+        $this->assertNotNull($result);
+        $this->assertEquals($attributeSet->getId(), $result['attribute_set_id']);
+        $this->assertEquals($attributeSet->getAttributeSetName(), $result['attribute_set_name']);
+        $this->assertEquals($attributeSet->getEntityTypeId(), $result['entity_type_id']);
+        $this->assertEquals($attributeSet->getSortOrder(), $result['sort_order']);
+    }
+
+    /**
+     * @expectedException \Exception
+     */
+    public function testGetThrowsExceptionIfRequestedAttributeSetDoesNotExist()
+    {
+        $attributeSetId = 9999;
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/products/attribute-sets/' . $attributeSetId,
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => 'catalogAttributeSetRepositoryV1',
+                'serviceVersion' => 'V1',
+                'operation' => 'catalogAttributeSetRepositoryV1Get',
+            ],
+        ];
+        $arguments = [
+            'attributeSetId' => $attributeSetId,
+        ];
+        $this->_webApiCall($serviceInfo, $arguments);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Eav/_files/empty_attribute_set.php
+     */
+    public function testSave()
+    {
+        $attributeSetName = 'empty_attribute_set';
+        $attributeSet = $this->getAttributeSetByName($attributeSetName);
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/products/attribute-sets/' . $attributeSet->getId(),
+                'httpMethod' => RestConfig::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => 'catalogAttributeSetRepositoryV1',
+                'serviceVersion' => 'V1',
+                'operation' => 'catalogAttributeSetRepositoryV1Save',
+            ],
+        ];
+
+        $updatedSortOrder = $attributeSet->getSortOrder() + 200;
+
+        $arguments = [
+            'attributeSet' => [
+                'attribute_set_id' => $attributeSet->getId(),
+                // name is the same, because it is used by fixture rollback script
+                'attribute_set_name' => $attributeSet->getAttributeSetName(),
+                'entity_type_id' => $attributeSet->getEntityTypeId(),
+                'sort_order' => $updatedSortOrder,
+            ],
+        ];
+        $result = $this->_webApiCall($serviceInfo, $arguments);
+        $this->assertNotNull($result);
+        // Reload attribute set data
+        $attributeSet = $this->getAttributeSetByName($attributeSetName);
+        $this->assertEquals($attributeSet->getAttributeSetId(), $result['attribute_set_id']);
+        $this->assertEquals($attributeSet->getAttributeSetName(), $result['attribute_set_name']);
+        $this->assertEquals($attributeSet->getEntityTypeId(), $result['entity_type_id']);
+        $this->assertEquals($updatedSortOrder, $result['sort_order']);
+        $this->assertEquals($attributeSet->getSortOrder(), $result['sort_order']);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Eav/_files/empty_attribute_set.php
+     */
+    public function testDeleteById()
+    {
+        $attributeSetName = 'empty_attribute_set';
+        $attributeSet = $this->getAttributeSetByName($attributeSetName);
+        $attributeSetId = $attributeSet->getId();
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/products/attribute-sets/' . $attributeSetId,
+                'httpMethod' => RestConfig::HTTP_METHOD_DELETE,
+            ],
+            'soap' => [
+                'service' => 'catalogAttributeSetRepositoryV1',
+                'serviceVersion' => 'V1',
+                'operation' => 'catalogAttributeSetRepositoryV1DeleteById',
+            ],
+        ];
+
+        $arguments = [
+            'attributeSetId' => $attributeSetId,
+        ];
+        $this->assertTrue($this->_webApiCall($serviceInfo, $arguments));
+        $this->assertNull($this->getAttributeSetByName($attributeSetName));
+    }
+
+    /**
+     * @expectedException \Exception
+     */
+    public function testDeleteByIdThrowsExceptionIfRequestedAttributeSetDoesNotExist()
+    {
+        $attributeSetId = 9999;
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/products/attribute-sets/' . $attributeSetId,
+                'httpMethod' => RestConfig::HTTP_METHOD_DELETE,
+            ],
+            'soap' => [
+                'service' => 'catalogAttributeSetRepositoryV1',
+                'serviceVersion' => 'V1',
+                'operation' => 'catalogAttributeSetRepositoryV1DeleteById',
+            ],
+        ];
+
+        $arguments = [
+            'attributeSetId' => $attributeSetId,
+        ];
+        $this->_webApiCall($serviceInfo, $arguments);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Eav/_files/empty_attribute_set.php
+     */
+    public function testGetList()
+    {
+        $searchCriteria = [
+            'searchCriteria' => [
+                'filter_groups' => [
+                    [
+                        'filters' => [
+                            [
+                                'field' => 'entity_type_code',
+                                'value' => 'catalog_product',
+                                'condition_type' => 'eq',
+                            ],
+                        ],
+                    ],
+                ],
+                'current_page' => 1,
+                'page_size' => 2,
+            ],
+        ];
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/products/attribute-sets/sets/list',
+                'httpMethod' => RestConfig::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => 'catalogAttributeSetRepositoryV1',
+                'serviceVersion' => 'V1',
+                'operation' => 'catalogAttributeSetRepositoryV1GetList',
+            ],
+        ];
+
+        $response = $this->_webApiCall($serviceInfo, $searchCriteria);
+
+        $this->assertArrayHasKey('search_criteria', $response);
+        $this->assertArrayHasKey('total_count', $response);
+        $this->assertArrayHasKey('items', $response);
+
+        $this->assertEquals($searchCriteria['searchCriteria'], $response['search_criteria']);
+        $this->assertTrue($response['total_count'] > 0);
+        $this->assertTrue(count($response['items']) > 0);
+
+        $this->assertNotNull($response['items'][0]['attribute_set_id']);
+        $this->assertNotNull($response['items'][0]['attribute_set_name']);
+    }
+
+    /**
+     * Retrieve attribute set based on given name.
+     * This utility methods assumes that there is only one attribute set with given name,
+     *
+     * @param string $attributeSetName
+     * @return \Magento\Eav\Model\Entity\Attribute\Set|null
+     */
+    protected function getAttributeSetByName($attributeSetName)
+    {
+        $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+        /** @var \Magento\Eav\Model\Entity\Attribute\Set $attributeSet */
+        $attributeSet = $objectManager->create('Magento\Eav\Model\Entity\Attribute\Set')
+            ->load($attributeSetName, 'attribute_set_name');
+        if ($attributeSet->getId() === null) {
+            return null;
+        }
+        return $attributeSet;
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/CategoryAttributeOptionManagementInterfaceTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/CategoryAttributeOptionManagementInterfaceTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..d7540f1b3a348f62e6a78ffb45241aeedb536e3b
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/CategoryAttributeOptionManagementInterfaceTest.php
@@ -0,0 +1,47 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Catalog\Api;
+
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+class CategoryAttributeOptionManagementInterfaceTest extends WebapiAbstract
+{
+    const SERVICE_NAME = 'catalogCategoryAttributeOptionManagementV1';
+    const SERVICE_VERSION = 'V1';
+    const RESOURCE_PATH = '/V1/categories/attributes';
+
+    public function testGetItems()
+    {
+        $testAttributeCode = 'include_in_menu';
+        $expectedOptions = [
+            [
+                    'label' => 'Yes',
+                    'value' => '1',
+            ],
+            [
+                    'label' => 'No',
+                    'value' => '0',
+            ],
+        ];
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/' . $testAttributeCode . '/options',
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'getItems',
+            ],
+        ];
+
+        $response = $this->_webApiCall($serviceInfo, ['attributeCode' => $testAttributeCode]);
+
+        $this->assertTrue(is_array($response));
+        $this->assertEquals($expectedOptions, $response);
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/CategoryAttributeRepositoryTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/CategoryAttributeRepositoryTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..a206507971116cb7118c717cd822923e29e2c058
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/CategoryAttributeRepositoryTest.php
@@ -0,0 +1,96 @@
+<?php
+/**
+ *
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Catalog\Api;
+
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+class CategoryAttributeRepositoryTest extends \Magento\TestFramework\TestCase\WebapiAbstract
+{
+    const SERVICE_NAME = 'catalogCategoryAttributeRepositoryV1';
+    const SERVICE_VERSION = 'V1';
+    const RESOURCE_PATH = '/V1/categories/attributes';
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/category_attribute.php
+     */
+    public function testGet()
+    {
+        $attributeCode = 'test_attribute_code_666';
+        $attribute = $this->getAttribute($attributeCode);
+
+        $this->assertTrue(is_array($attribute));
+        $this->assertArrayHasKey('attribute_id', $attribute);
+        $this->assertArrayHasKey('attribute_code', $attribute);
+        $this->assertEquals($attributeCode, $attribute['attribute_code']);
+    }
+
+    public function testGetList()
+    {
+        $searchCriteria = [
+            'searchCriteria' => [
+                'filter_groups' => [
+                    [
+                        'filters' => [
+                            [
+                                'field' => 'frontend_input',
+                                'value' => 'text',
+                                'condition_type' => 'eq',
+                            ],
+                        ],
+                    ],
+                ],
+                'current_page' => 1,
+                'page_size' => 2,
+            ],
+            'entityTypeCode' => \Magento\Catalog\Api\Data\CategoryAttributeInterface::ENTITY_TYPE_CODE,
+        ];
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/',
+                'httpMethod' => RestConfig::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'GetList',
+            ],
+        ];
+
+        $response = $this->_webApiCall($serviceInfo, $searchCriteria);
+
+        $this->assertArrayHasKey('search_criteria', $response);
+        $this->assertArrayHasKey('total_count', $response);
+        $this->assertArrayHasKey('items', $response);
+
+        $this->assertEquals($searchCriteria['searchCriteria'], $response['search_criteria']);
+        $this->assertTrue($response['total_count'] > 0);
+        $this->assertTrue(count($response['items']) > 0);
+
+        $this->assertNotNull($response['items'][0]['default_frontend_label']);
+        $this->assertNotNull($response['items'][0]['attribute_id']);
+    }
+
+    /**
+     * @param $attributeCode
+     * @return array|bool|float|int|string
+     */
+    protected function getAttribute($attributeCode)
+    {
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/' . $attributeCode,
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Get',
+            ],
+        ];
+        return $this->_webApiCall($serviceInfo, ['attributeCode' => $attributeCode]);
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/CategoryLinkManagementTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/CategoryLinkManagementTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..fc64e880b646e0110d17ff9a46808fe372206385
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/CategoryLinkManagementTest.php
@@ -0,0 +1,65 @@
+<?php
+/**
+ *
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Catalog\Api;
+
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config;
+
+class CategoryLinkManagementTest extends WebapiAbstract
+{
+    const SERVICE_WRITE_NAME = 'catalogCategoryLinkManagementV1';
+    const SERVICE_VERSION = 'V1';
+    const RESOURCE_PATH_SUFFIX = '/V1/categories';
+    const RESOURCE_PATH_PREFIX = 'products';
+
+    private $modelId = 333;
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/category_product.php
+     */
+    public function testAssignedProducts()
+    {
+        $expected = [
+            [
+                'sku' => 'simple333',
+                'position' => '1',
+                'category_id' => '333',
+            ],
+        ];
+        $result = $this->getAssignedProducts($this->modelId);
+
+        $this->assertEquals($expected, $result);
+    }
+
+    public function testInfoNoSuchEntityException()
+    {
+        try {
+            $this->getAssignedProducts(-1);
+        } catch (\Exception $e) {
+            $this->assertContains('No such entity with %fieldName = %fieldValue', $e->getMessage());
+        }
+    }
+
+    /**
+     * @param int $id category id
+     * @return string
+     */
+    protected function getAssignedProducts($id)
+    {
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH_SUFFIX . '/' . $id . '/' . self::RESOURCE_PATH_PREFIX,
+                'httpMethod' => Config::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_WRITE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_WRITE_NAME . 'GetAssignedProducts',
+            ],
+        ];
+        return $this->_webApiCall($serviceInfo, ['categoryId' => $id]);
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/CategoryLinkRepositoryTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/CategoryLinkRepositoryTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..99ecffa03a07c598cb5f94e11fcd00b0fafb5b9f
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/CategoryLinkRepositoryTest.php
@@ -0,0 +1,149 @@
+<?php
+/**
+ *
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Catalog\Api;
+
+use Magento\TestFramework\Helper\Bootstrap;
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config;
+
+class CategoryLinkRepositoryTest extends WebapiAbstract
+{
+    const SERVICE_WRITE_NAME = 'catalogCategoryLinkRepositoryV1';
+    const SERVICE_VERSION = 'V1';
+    const RESOURCE_PATH_SUFFIX = '/V1/categories';
+    const RESOURCE_PATH_PREFIX = 'products';
+
+    private $categoryId = 333;
+
+    /**
+     * @dataProvider saveDataProvider
+     * @magentoApiDataFixture Magento/Catalog/_files/products_in_category.php
+     * @param int $productId
+     * @param string[] $productLink
+     * @param int $productPosition
+     */
+    public function testSave($productLink, $productId, $productPosition = 0)
+    {
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH_SUFFIX
+                    . '/' . $this->categoryId . '/' . self::RESOURCE_PATH_PREFIX,
+                'httpMethod' => Config::HTTP_METHOD_POST,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_WRITE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_WRITE_NAME . 'Save',
+            ],
+        ];
+        $result = $this->_webApiCall($serviceInfo, ['productLink' => $productLink]);
+        $this->assertTrue($result);
+        $this->assertTrue($this->isProductInCategory($this->categoryId, $productId, $productPosition));
+    }
+
+    public function saveDataProvider()
+    {
+        return [
+            [
+                ['sku' => 'simple_with_cross', 'position' => 7, 'category_id' => $this->categoryId],
+                334,
+                7,
+            ],
+            [
+                ['sku' => 'simple_with_cross', 'category_id' => $this->categoryId],
+                334,
+                0
+            ],
+        ];
+    }
+
+    /**
+     * @dataProvider updateProductProvider
+     * @magentoApiDataFixture Magento/Catalog/_files/products_in_category.php
+     * @param int $productId
+     * @param string[] $productLink
+     * @param int $productPosition
+     */
+    public function testUpdateProduct($productLink, $productId, $productPosition = 0)
+    {
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH_SUFFIX
+                    . '/' . $this->categoryId . '/' . self::RESOURCE_PATH_PREFIX,
+                'httpMethod' => Config::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_WRITE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_WRITE_NAME . 'Save',
+            ],
+        ];
+        $result = $this->_webApiCall($serviceInfo, ['productLink' => $productLink]);
+        $this->assertTrue($result);
+        $this->assertFalse($this->isProductInCategory($this->categoryId, $productId, $productPosition));
+    }
+
+    public function updateProductProvider()
+    {
+        return [
+            [
+                ['sku' => 'simple_with_cross', 'position' => 7, 'categoryId' => $this->categoryId],
+                333,
+                4,
+            ],
+            [
+                ['sku' => 'simple_with_cross', 'categoryId' => $this->categoryId],
+                333,
+                0
+            ],
+        ];
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/products_in_category.php
+     */
+    public function testDelete()
+    {
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH_SUFFIX . '/' . $this->categoryId .
+                    '/' . self::RESOURCE_PATH_PREFIX . '/simple',
+                'httpMethod' => Config::HTTP_METHOD_DELETE,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_WRITE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_WRITE_NAME . 'DeleteByIds',
+            ],
+        ];
+        $result = $this->_webApiCall(
+            $serviceInfo,
+            ['productSku' => 'simple', 'categoryId' => $this->categoryId]
+        );
+        $this->assertTrue($result);
+        $this->assertFalse($this->isProductInCategory($this->categoryId, 333, 10));
+    }
+
+    /**
+     * @param int $categoryId
+     * @param int $productId
+     * @param int $productPosition
+     * @return bool
+     */
+    private function isProductInCategory($categoryId, $productId, $productPosition)
+    {
+        /** @var \Magento\Catalog\Api\CategoryRepositoryInterface $categoryLoader */
+        $categoryLoader = Bootstrap::getObjectManager()->create('Magento\Catalog\Api\CategoryRepositoryInterface');
+        $category = $categoryLoader->get($categoryId);
+        $productsPosition = $category->getProductsPosition();
+
+        if (isset($productsPosition[$productId]) && $productsPosition[$productId] == $productPosition) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/CategoryManagementTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/CategoryManagementTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..eaa58bae0e501b3fcaed3a062d3dbed85361c48b
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/CategoryManagementTest.php
@@ -0,0 +1,94 @@
+<?php
+/**
+ *
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Catalog\Api;
+
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config;
+use Magento\TestFramework\Helper\Bootstrap;
+use Magento\TestFramework\ObjectManager;
+
+class CategoryManagementTest extends WebapiAbstract
+{
+    const RESOURCE_PATH = '/V1/categories';
+
+    const SERVICE_NAME = 'catalogCategoryManagementV1';
+
+    /**
+     * @dataProvider treeDataProvider
+     * @magentoApiDataFixture Magento/Catalog/_files/category_tree.php
+     */
+    public function testTree($rootCategoryId, $depth, $expectedLevel, $expectedId)
+    {
+        $requestData = ['rootCategoryId' => $rootCategoryId, 'depth' => $depth];
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '?' . http_build_query($requestData),
+                'httpMethod' => Config::HTTP_METHOD_GET
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => 'V1',
+                'operation' => self::SERVICE_NAME . 'GetTree'
+            ]
+        ];
+        $result = $this->_webApiCall($serviceInfo, $requestData);
+
+        for($i = 0; $i < $expectedLevel; $i++) {
+            $result = $result['children_data'][0];
+        }
+        $this->assertEquals($expectedId, $result['id']);
+        $this->assertEmpty($result['children_data']);
+    }
+
+    public function treeDataProvider()
+    {
+        return [
+            [2, 100, 3, 402],
+            [2, null, 3, 402],
+            [400, 1, 1, 401],
+            [401, 0, 0, 401],
+        ];
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/category_tree.php
+     * @dataProvider updateMoveDataProvider
+     */
+    public function testUpdateMove($categoryId, $parentId, $afterId, $expectedPosition)
+    {
+        $expectedPath = '1/2/400/' . $categoryId;
+        $categoryData = ['categoryId' => $categoryId, 'parentId' => $parentId, 'afterId' => $afterId];
+        $serviceInfo =
+            [
+                'rest' => [
+                    'resourcePath' => self::RESOURCE_PATH . '/' . $categoryId . '/move',
+                    'httpMethod' => Config::HTTP_METHOD_PUT
+                ],
+                'soap' => [
+                    'service' => self::SERVICE_NAME,
+                    'serviceVersion' => 'V1',
+                    'operation' => self::SERVICE_NAME . 'Move'
+                ]
+            ];
+        $this->assertTrue($this->_webApiCall($serviceInfo, $categoryData));
+        /** @var \Magento\Catalog\Model\Category $model */
+        $readService = Bootstrap::getObjectManager()->create('Magento\Catalog\Api\CategoryRepositoryInterface');
+        $model = $readService->get($categoryId);
+        $this->assertEquals($expectedPath, $model->getPath());
+        $this->assertEquals($expectedPosition, $model->getPosition());
+        $this->assertEquals($parentId, $model->getParentId());
+    }
+
+    public function updateMoveDataProvider()
+    {
+        return [
+            [402, 400, null, 2],
+            [402, 400, 401, 2],
+            [402, 400, 999, 2],
+            [402, 400, 0, 1]
+        ];
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/CategoryRepositoryTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/CategoryRepositoryTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..a6a2a5d9efe59c57b0ff284aea06e64b04dd07ce
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/CategoryRepositoryTest.php
@@ -0,0 +1,253 @@
+<?php
+/**
+ *
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Catalog\Api;
+
+use Magento\TestFramework\Helper\Bootstrap;
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config;
+
+class CategoryRepositoryTest extends WebapiAbstract
+{
+    const RESOURCE_PATH = '/V1/categories';
+    const SERVICE_NAME = 'catalogCategoryRepositoryV1';
+
+    private $modelId = 333;
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/category_backend.php
+     */
+    public function testGet()
+    {
+        $expected = [
+            'parent_id' => 2,
+            'path' => '1/2/3',
+            'position' => 1,
+            'level' => 2,
+            'available_sort_by' => ['position', 'name'],
+            'include_in_menu' => true,
+            'name' => 'Category 1',
+            'id' => 333,
+            'is_active' => true,
+        ];
+
+        $result = $this->getInfoCategory($this->modelId);
+
+        $this->assertArrayHasKey('created_at', $result);
+        $this->assertArrayHasKey('updated_at', $result);
+        $this->assertArrayHasKey('children', $result);
+        unset($result['created_at'], $result['updated_at'], $result['children']);
+        ksort($expected);
+        ksort($result);
+        $this->assertEquals($expected, $result);
+    }
+
+    public function testInfoNoSuchEntityException()
+    {
+        try {
+            $this->getInfoCategory(-1);
+        } catch (\Exception $e) {
+            $this->assertContains('No such entity with %fieldName = %fieldValue', $e->getMessage());
+        }
+    }
+
+    /**
+     * @param int $id
+     * @return string
+     */
+    protected function getInfoCategory($id)
+    {
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/' . $id,
+                'httpMethod' => Config::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => 'V1',
+                'operation' => self::SERVICE_NAME . 'Get',
+            ],
+        ];
+        return $this->_webApiCall($serviceInfo, ['categoryId' => $id]);
+    }
+    /**
+     * @return array
+     */
+    public function categoryCreationProvider()
+    {
+        return [
+            [
+                $this->getSimpleCategoryData(
+                    [
+                        'name' => 'Test Category Name',
+                    ]
+                ),
+            ]
+        ];
+    }
+
+    /**
+     * Test for create category process
+     *
+     * @magentoApiDataFixture Magento/Catalog/Model/Category/_files/service_category_create.php
+     * @dataProvider categoryCreationProvider
+     */
+    public function testCreate($category)
+    {
+        $category = $this->createCategory($category);
+        $this->assertGreaterThan(0, $category['id']);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/category.php
+     */
+    public function testDelete()
+    {
+        $this->assertTrue($this->deleteCategory($this->modelId));
+    }
+
+    public function testDeleteNoSuchEntityException()
+    {
+        try {
+            $this->deleteCategory(-1);
+        } catch (\Exception $e) {
+            $this->assertContains('No such entity with %fieldName = %fieldValue', $e->getMessage());
+        }
+    }
+
+    /**
+     * @dataProvider deleteSystemOrRootDataProvider
+     * @expectedException \Exception
+     */
+    public function testDeleteSystemOrRoot()
+    {
+        $this->deleteCategory($this->modelId);
+    }
+
+    public function deleteSystemOrRootDataProvider()
+    {
+        return [
+            [\Magento\Catalog\Model\Category::TREE_ROOT_ID],
+            [2] //Default root category
+        ];
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/category.php
+     */
+    public function testUpdate()
+    {
+        $categoryId = 333;
+        $categoryData = [
+            'name' => "Update Category Test",
+            'custom_attributes' => [
+                [
+                    'attribute_code' => 'description',
+                    'value' => "Update Category Description Test",
+                ],
+            ],
+        ];
+        $result = $this->updateCategory($categoryId, $categoryData);
+        $this->assertEquals($categoryId, $result['id']);
+        /** @var \Magento\Catalog\Model\Category $model */
+        $model = Bootstrap::getObjectManager()->get('Magento\Catalog\Model\Category');
+        $category = $model->load($categoryId);
+        $this->assertEquals("Update Category Test", $category->getName());
+        $this->assertEquals("Update Category Description Test", $category->getDescription());
+    }
+
+    protected function getSimpleCategoryData($categoryData = [])
+    {
+        return [
+            'path' => '2',
+            'parent_id' => '2',
+            'name' => isset($categoryData['name'])
+                ? $categoryData['name'] : uniqid('Category-', true),
+            'is_active' => '1',
+            'custom_attributes' => [
+                ['attribute_code' => 'url_key', 'value' => ''],
+                ['attribute_code' => 'description', 'value' => 'Custom description'],
+                ['attribute_code' => 'meta_title', 'value' => ''],
+                ['attribute_code' => 'meta_keywords', 'value' => ''],
+                ['attribute_code' => 'meta_description', 'value' => ''],
+                ['attribute_code' => 'include_in_menu', 'value' => '1'],
+                ['attribute_code' => 'display_mode', 'value' => 'PRODUCTS'],
+                ['attribute_code' => 'landing_page', 'value' => ''],
+                ['attribute_code' => 'is_anchor', 'value' => '0'],
+                ['attribute_code' => 'custom_use_parent_settings', 'value' => '0'],
+                ['attribute_code' => 'custom_apply_to_products', 'value' => '0'],
+                ['attribute_code' => 'custom_design', 'value' => ''],
+                ['attribute_code' => 'custom_design_from', 'value' => ''],
+                ['attribute_code' => 'custom_design_to', 'value' => ''],
+                ['attribute_code' => 'page_layout', 'value' => ''],
+            ]
+        ];
+    }
+
+    /**
+     * Create category process
+     *
+     * @param  $category
+     * @return int
+     */
+    protected function createCategory($category)
+    {
+        $serviceInfo = [
+            'rest' => ['resourcePath' => self::RESOURCE_PATH, 'httpMethod' => Config::HTTP_METHOD_POST],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => 'V1',
+                'operation' => self::SERVICE_NAME . 'Save',
+            ],
+        ];
+        $requestData = ['category' => $category];
+        return $this->_webApiCall($serviceInfo, $requestData);
+    }
+
+    /**
+     * @param int $id
+     * @return bool
+     * @throws \Exception
+     */
+    protected function deleteCategory($id)
+    {
+        $serviceInfo =
+            [
+                'rest' => [
+                    'resourcePath' => self::RESOURCE_PATH . '/' . $id,
+                    'httpMethod' => Config::HTTP_METHOD_DELETE,
+                ],
+                'soap' => [
+                    'service' => self::SERVICE_NAME,
+                    'serviceVersion' => 'V1',
+                    'operation' => self::SERVICE_NAME . 'DeleteByIdentifier',
+                ],
+            ];
+        return $this->_webApiCall($serviceInfo, ['categoryId' => $id]);
+    }
+
+    protected function updateCategory($id, $data)
+    {
+        $serviceInfo =
+            [
+                'rest' => [
+                    'resourcePath' => self::RESOURCE_PATH . '/' . $id,
+                    'httpMethod' => Config::HTTP_METHOD_PUT,
+                ],
+                'soap' => [
+                    'service' => self::SERVICE_NAME,
+                    'serviceVersion' => 'V1',
+                    'operation' => self::SERVICE_NAME . 'Save',
+                ],
+            ];
+
+        if (TESTS_WEB_API_ADAPTER == self::ADAPTER_SOAP) {
+            $data['id'] = $id;
+            return $this->_webApiCall($serviceInfo, ['id' => $id, 'category' => $data]);
+        } else {
+            return $this->_webApiCall($serviceInfo, ['category' => $data]);
+        }
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductAttributeGroupRepositoryTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductAttributeGroupRepositoryTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..daf34f578a9bd3947ed0622d0790eb2a8e2023a0
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductAttributeGroupRepositoryTest.php
@@ -0,0 +1,191 @@
+<?php
+/**
+ *
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Catalog\Api;
+
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+class ProductAttributeGroupRepositoryTest extends \Magento\TestFramework\TestCase\WebapiAbstract
+{
+    const SERVICE_NAME = 'catalogProductAttributeGroupRepositoryV1';
+    const SERVICE_VERSION = 'V1';
+    const RESOURCE_PATH = '/V1/products/attribute-sets';
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/empty_attribute_group.php
+     */
+    public function testCreateGroup()
+    {
+        $attributeSetId = 1;
+        $groupData = $this->createGroupData($attributeSetId);
+        $groupData['attribute_group_name'] = 'empty_attribute_group_updated';
+
+        $result = $this->createGroup($attributeSetId, $groupData);
+        $this->assertArrayHasKey('attribute_group_id', $result);
+        $this->assertNotNull($result['attribute_group_id']);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/empty_attribute_group.php
+     */
+    public function testDeleteGroup()
+    {
+        $group = $this->getGroupByName('empty_attribute_group');
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . "/groups/" . $group->getId(),
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_DELETE,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'DeleteById',
+            ],
+        ];
+        $this->assertTrue($this->_webApiCall($serviceInfo, ['groupId' => $group->getId()]));
+    }
+
+    /**
+     * @expectedException \Exception
+     */
+    public function testCreateGroupWithAttributeSetThatDoesNotExist()
+    {
+        $attributeSetId = -1;
+        $this->createGroup($attributeSetId);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/empty_attribute_group.php
+     */
+    public function testUpdateGroup()
+    {
+        $attributeSetId = 1;
+        $group = $this->getGroupByName('empty_attribute_group');
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/' . $attributeSetId . '/groups',
+                'httpMethod' => RestConfig::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Save',
+            ],
+        ];
+
+        $newGroupData = $this->createGroupData($attributeSetId);
+        $newGroupData['attribute_group_name'] = 'empty_attribute_group_updated';
+        $newGroupData['attribute_group_id'] = $group->getId();
+
+        $result = $this->_webApiCall($serviceInfo, ['group' => $newGroupData]);
+
+        $this->assertArrayHasKey('attribute_group_id', $result);
+        $this->assertEquals($group->getId(), $result['attribute_group_id']);
+        $this->assertArrayHasKey('attribute_group_name', $result);
+        $this->assertEquals($newGroupData['attribute_group_name'], $result['attribute_group_name']);
+    }
+
+    public function testGetList()
+    {
+        $searchCriteria = [
+            'searchCriteria' => [
+                'filter_groups' => [
+                    [
+                        'filters' => [
+                            [
+                                'field' => 'attribute_set_id',
+                                'value' => 1,
+                                'condition_type' => 'eq',
+                            ],
+                        ],
+                    ],
+                ],
+                'current_page' => 1,
+                'page_size' => 2,
+            ],
+        ];
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . "/groups/list",
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'GetList',
+            ],
+        ];
+
+        $response = $this->_webApiCall($serviceInfo, $searchCriteria);
+
+        $this->assertArrayHasKey('search_criteria', $response);
+        $this->assertArrayHasKey('total_count', $response);
+        $this->assertArrayHasKey('items', $response);
+
+        $this->assertEquals($searchCriteria['searchCriteria'], $response['search_criteria']);
+        $this->assertTrue($response['total_count'] > 0);
+        $this->assertTrue(count($response['items']) > 0);
+
+        $this->assertNotNull($response['items'][0]['attribute_group_name']);
+        $this->assertNotNull($response['items'][0]['attribute_group_id']);
+    }
+
+    /**
+     * @param $attributeSetId
+     * @return array|bool|float|int|string
+     */
+    protected function createGroup($attributeSetId, $groupData = null)
+    {
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/groups',
+                'httpMethod' => RestConfig::HTTP_METHOD_POST,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Save',
+            ],
+        ];
+        return $this->_webApiCall(
+            $serviceInfo,
+            ['group' => $groupData ? $groupData : $this->createGroupData($attributeSetId)]
+        );
+    }
+
+    /**
+     * @param $attributeSetId
+     * @return array
+     */
+    protected function createGroupData($attributeSetId)
+    {
+        return [
+            'attribute_group_name' => 'empty_attribute_group',
+            'attribute_set_id' => $attributeSetId
+        ];
+    }
+
+    /**
+     * Retrieve attribute group based on given name.
+     * This utility methods assumes that there is only one attribute group with given name,
+     *
+     * @param string $groupName
+     * @return \Magento\Eav\Model\Entity\Attribute\Group|null
+     */
+    protected function getGroupByName($groupName)
+    {
+        $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+        /** @var \Magento\Eav\Model\Entity\Attribute\Group */
+        $attributeGroup = $objectManager->create('Magento\Eav\Model\Entity\Attribute\Group')
+            ->load($groupName, 'attribute_group_name');
+        if ($attributeGroup->getId() === null) {
+            return null;
+        }
+        return $attributeGroup;
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductAttributeManagementTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductAttributeManagementTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..14f8614829e033dfd41bd61ee6ba9cd815608979
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductAttributeManagementTest.php
@@ -0,0 +1,184 @@
+<?php
+/**
+ *
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Catalog\Api;
+
+use Magento\TestFramework\Helper\Bootstrap;
+use Magento\Webapi\Exception as HTTPExceptionCodes;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+class ProductAttributeManagementTest extends \Magento\TestFramework\TestCase\WebapiAbstract
+{
+    const SERVICE_NAME = 'catalogProductAttributeManagementV1';
+    const SERVICE_VERSION = 'V1';
+    const RESOURCE_PATH = '/V1/products/attribute-sets';
+
+    public function testGetAttributes()
+    {
+        $attributeSetId = \Magento\Catalog\Api\Data\ProductAttributeInterface::DEFAULT_ATTRIBUTE_SET_ID;
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/' . $attributeSetId . '/attributes',
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'GetAttributes',
+            ],
+        ];
+        $attributes = $this->_webApiCall($serviceInfo, ['attributeSetId' => $attributeSetId]);
+
+        $this->assertTrue(count($attributes) > 0);
+        $this->assertArrayHasKey('attribute_code', $attributes[0]);
+        $this->assertArrayHasKey('attribute_id', $attributes[0]);
+        $this->assertArrayHasKey('default_frontend_label', $attributes[0]);
+        $this->assertNotNull($attributes[0]['attribute_code']);
+        $this->assertNotNull($attributes[0]['attribute_id']);
+        $this->assertNotNull($attributes[0]['default_frontend_label']);
+    }
+
+    public function testAssignAttribute()
+    {
+        $this->assertNotNull(
+            $this->_webApiCall(
+                $this->getAssignServiceInfo(),
+                $this->getAttributeData()
+            )
+        );
+    }
+
+    public function testAssignAttributeWrongAttributeSet()
+    {
+        $payload = $this->getAttributeData();
+        $payload['attributeSetId'] = -1;
+
+        $expectedMessage = 'AttributeSet with id "' . $payload['attributeSetId'] . '" does not exist.';
+
+        try {
+            $this->_webApiCall($this->getAssignServiceInfo(), $payload);
+            $this->fail("Expected exception");
+        } catch (\SoapFault $e) {
+            $this->assertContains(
+                $expectedMessage,
+                $e->getMessage(),
+                "SoapFault does not contain expected message."
+            );
+        } catch (\Exception $e) {
+            $errorObj = $this->processRestExceptionResult($e);
+            $this->assertEquals($expectedMessage, $errorObj['message']);
+            $this->assertEquals(HTTPExceptionCodes::HTTP_NOT_FOUND, $e->getCode());
+        }
+    }
+
+    public function testAssignAttributeWrongAttributeGroup()
+    {
+        $payload = $this->getAttributeData();
+        $payload['attributeGroupId'] = -1;
+        $expectedMessage = 'Group with id "' . $payload['attributeGroupId'] . '" does not exist.';
+
+        try {
+            $this->_webApiCall($this->getAssignServiceInfo(), $payload);
+            $this->fail("Expected exception");
+        } catch (\SoapFault $e) {
+            $this->assertContains(
+                $expectedMessage,
+                $e->getMessage(),
+                "SoapFault does not contain expected message."
+            );
+        } catch (\Exception $e) {
+            $errorObj = $this->processRestExceptionResult($e);
+            $this->assertEquals($expectedMessage, $errorObj['message']);
+            $this->assertEquals(HTTPExceptionCodes::HTTP_NOT_FOUND, $e->getCode());
+        }
+    }
+
+    public function testAssignAttributeWrongAttribute()
+    {
+        $payload = $this->getAttributeData();
+        $payload['attributeCode'] = 'badCode';
+        $expectedMessage = 'Attribute with attributeCode "' . $payload['attributeCode'] . '" does not exist.';
+
+        try {
+            $this->_webApiCall($this->getAssignServiceInfo(), $payload);
+            $this->fail("Expected exception");
+        } catch (\SoapFault $e) {
+            $this->assertContains(
+                $expectedMessage,
+                $e->getMessage(),
+                "SoapFault does not contain expected message."
+            );
+        } catch (\Exception $e) {
+            $errorObj = $this->processRestExceptionResult($e);
+            $this->assertEquals($expectedMessage, $errorObj['message']);
+            $this->assertEquals(HTTPExceptionCodes::HTTP_NOT_FOUND, $e->getCode());
+        }
+    }
+
+    public function testUnassignAttribute()
+    {
+        $payload = $this->getAttributeData();
+
+        //Assign attribute to attribute set
+        /** @var \Magento\Eav\Model\AttributeManagement $attributeManagement */
+        $attributeManagement = Bootstrap::getObjectManager()->get('Magento\Eav\Model\AttributeManagement');
+        $attributeManagement->assign(
+            \Magento\Catalog\Api\Data\ProductAttributeInterface::ENTITY_TYPE_CODE,
+            $payload['attributeSetId'],
+            $payload['attributeGroupId'],
+            $payload['attributeCode'],
+            $payload['sortOrder']
+        );
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH .
+                    '/' . $payload['attributeSetId'] .
+                    '/attributes/' .
+                    $payload['attributeCode'],
+                'httpMethod' => RestConfig::HTTP_METHOD_DELETE,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Unassign',
+            ],
+        ];
+        $this->assertTrue($this->_webApiCall(
+                $serviceInfo,
+                [
+                    'attributeSetId' => $payload['attributeSetId'],
+                    'attributeCode' => $payload['attributeCode']
+                ]
+            )
+        );
+    }
+
+    protected function getAttributeData()
+    {
+        return [
+            'attributeSetId' => \Magento\Catalog\Api\Data\ProductAttributeInterface::DEFAULT_ATTRIBUTE_SET_ID,
+            'attributeGroupId' => 8,
+            'attributeCode' => 'cost',
+            'sortOrder' => 3
+        ];
+    }
+
+    protected function getAssignServiceInfo()
+    {
+        return [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/attributes',
+                'httpMethod' => RestConfig::HTTP_METHOD_POST,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Assign',
+            ],
+        ];
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductAttributeMediaGalleryManagementInterfaceTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductAttributeMediaGalleryManagementInterfaceTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..cb480d3702a2130ac68a57566b7938f3fe0ba482
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductAttributeMediaGalleryManagementInterfaceTest.php
@@ -0,0 +1,647 @@
+<?php
+/**
+ *
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Catalog\Api;
+
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+use Magento\TestFramework\Helper\Bootstrap;
+
+class ProductAttributeMediaGalleryManagementInterfaceTest extends \Magento\TestFramework\TestCase\WebapiAbstract
+{
+    /**
+     * Default create service request information (product with SKU 'simple' is used)
+     *
+     * @var array
+     */
+    protected $createServiceInfo;
+
+    /**
+     * Default update service request information (product with SKU 'simple' is used)
+     *
+     * @var array
+     */
+    protected $updateServiceInfo;
+
+    /**
+     * Default delete service request information (product with SKU 'simple' is used)
+     *
+     * @var array
+     */
+    protected $deleteServiceInfo;
+
+    /**
+     * @var string
+     */
+    protected $testImagePath;
+
+    protected function setUp()
+    {
+        $this->createServiceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/products/simple/media',
+                'httpMethod' => RestConfig::HTTP_METHOD_POST,
+            ],
+            'soap' => [
+                'service' => 'catalogProductAttributeMediaGalleryManagementV1',
+                'serviceVersion' => 'V1',
+                'operation' => 'catalogProductAttributeMediaGalleryManagementV1Create',
+            ],
+        ];
+        $this->updateServiceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/products/simple/media',
+                'httpMethod' => RestConfig::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => 'catalogProductAttributeMediaGalleryManagementV1',
+                'serviceVersion' => 'V1',
+                'operation' => 'catalogProductAttributeMediaGalleryManagementV1Update',
+            ],
+        ];
+        $this->deleteServiceInfo = [
+            'rest' => [
+                'httpMethod' => RestConfig::HTTP_METHOD_DELETE,
+            ],
+            'soap' => [
+                'service' => 'catalogProductAttributeMediaGalleryManagementV1',
+                'serviceVersion' => 'V1',
+                'operation' => 'catalogProductAttributeMediaGalleryManagementV1Remove',
+            ],
+        ];
+        $this->testImagePath = __DIR__ . DIRECTORY_SEPARATOR . '_files' . DIRECTORY_SEPARATOR . 'test_image.jpg';
+    }
+
+    /**
+     * Retrieve product that was updated by test
+     *
+     * @return \Magento\Catalog\Model\Product
+     */
+    protected function getTargetSimpleProduct()
+    {
+        $objectManager = Bootstrap::getObjectManager();
+        return $objectManager->get('Magento\Catalog\Model\ProductFactory')->create()->load(1);
+    }
+
+    /**
+     * Retrieve target product image ID
+     *
+     * Target product must have single image if this function is used
+     *
+     * @return int
+     */
+    protected function getTargetGalleryEntryId()
+    {
+        $mediaGallery = $this->getTargetSimpleProduct()->getData('media_gallery');
+        return (int)$mediaGallery['images'][0]['value_id'];
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+     */
+    public function testCreate()
+    {
+        $requestData = [
+            'productSku' => 'simple',
+            'entry' => [
+                'id' => null,
+                'label' => 'Image Text',
+                'position' => 1,
+                'types' => ['image'],
+                'is_disabled' => false,
+            ],
+            'entryContent' => [
+                'entry_data' => base64_encode(file_get_contents($this->testImagePath)),
+                'mime_type' => 'image/jpeg',
+                'name' => 'test_image',
+            ],
+            // Store ID is not provided so the default one must be used
+        ];
+
+        $actualResult = $this->_webApiCall($this->createServiceInfo, $requestData);
+        $targetProduct = $this->getTargetSimpleProduct();
+        $mediaGallery = $targetProduct->getData('media_gallery');
+
+        $this->assertCount(1, $mediaGallery['images']);
+        $updatedImage = $mediaGallery['images'][0];
+        $this->assertEquals($actualResult, $updatedImage['value_id']);
+        $this->assertEquals('Image Text', $updatedImage['label']);
+        $this->assertEquals(1, $updatedImage['position']);
+        $this->assertEquals(0, $updatedImage['disabled']);
+        $this->assertEquals('Image Text', $updatedImage['label_default']);
+        $this->assertEquals(1, $updatedImage['position_default']);
+        $this->assertEquals(0, $updatedImage['disabled_default']);
+        $this->assertStringStartsWith('/t/e/test_image', $updatedImage['file']);
+        $this->assertEquals($updatedImage['file'], $targetProduct->getData('image'));
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+     */
+    public function testCreateWithNotDefaultStoreId()
+    {
+        $requestData = [
+            'productSku' => 'simple',
+            'entry' => [
+                'id' => null,
+                'label' => 'Image Text',
+                'position' => 1,
+                'types' => ['image'],
+                'is_disabled' => false,
+            ],
+            'entryContent' => [
+                'entry_data' => base64_encode(file_get_contents($this->testImagePath)),
+                'mime_type' => 'image/jpeg',
+                'name' => 'test_image',
+            ],
+            'storeId' => 1,
+        ];
+
+        $actualResult = $this->_webApiCall($this->createServiceInfo, $requestData);
+        $targetProduct = $this->getTargetSimpleProduct();
+        $mediaGallery = $targetProduct->getData('media_gallery');
+        $this->assertCount(1, $mediaGallery['images']);
+        $updatedImage = $mediaGallery['images'][0];
+        // Values for not default store view were provided
+        $this->assertEquals('Image Text', $updatedImage['label']);
+        $this->assertEquals($actualResult, $updatedImage['value_id']);
+        $this->assertEquals(1, $updatedImage['position']);
+        $this->assertEquals(0, $updatedImage['disabled']);
+        $this->assertStringStartsWith('/t/e/test_image', $updatedImage['file']);
+        $this->assertEquals($updatedImage['file'], $targetProduct->getData('image'));
+        // No values for default store view were provided
+        $this->assertNull($updatedImage['label_default']);
+        $this->assertNull($updatedImage['position_default']);
+        $this->assertNull($updatedImage['disabled_default']);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/product_with_image.php
+     */
+    public function testUpdate()
+    {
+        $requestData = [
+            'productSku' => 'simple',
+            'entry' => [
+                'id' => $this->getTargetGalleryEntryId(),
+                'label' => 'Updated Image Text',
+                'position' => 10,
+                'types' => ['thumbnail'],
+                'is_disabled' => true,
+            ],
+            // Store ID is not provided so the default one must be used
+        ];
+
+        $this->updateServiceInfo['rest']['resourcePath'] = $this->updateServiceInfo['rest']['resourcePath']
+            . '/' . $this->getTargetGalleryEntryId();
+
+        $this->assertTrue($this->_webApiCall($this->updateServiceInfo, $requestData));
+
+        $targetProduct = $this->getTargetSimpleProduct();
+        $this->assertEquals('/m/a/magento_image.jpg', $targetProduct->getData('thumbnail'));
+        $this->assertNull($targetProduct->getData('image'));
+        $this->assertNull($targetProduct->getData('small_image'));
+        $mediaGallery = $targetProduct->getData('media_gallery');
+        $this->assertCount(1, $mediaGallery['images']);
+        $updatedImage = $mediaGallery['images'][0];
+        $this->assertEquals('Updated Image Text', $updatedImage['label']);
+        $this->assertEquals('/m/a/magento_image.jpg', $updatedImage['file']);
+        $this->assertEquals(10, $updatedImage['position']);
+        $this->assertEquals(1, $updatedImage['disabled']);
+        $this->assertEquals('Updated Image Text', $updatedImage['label_default']);
+        $this->assertEquals(10, $updatedImage['position_default']);
+        $this->assertEquals(1, $updatedImage['disabled_default']);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/product_with_image.php
+     */
+    public function testUpdateWithNotDefaultStoreId()
+    {
+        $requestData = [
+            'productSku' => 'simple',
+            'entry' => [
+                'id' => $this->getTargetGalleryEntryId(),
+                'label' => 'Updated Image Text',
+                'position' => 10,
+                'types' => ['thumbnail'],
+                'is_disabled' => true,
+            ],
+            'storeId' => 1,
+        ];
+
+        $this->updateServiceInfo['rest']['resourcePath'] = $this->updateServiceInfo['rest']['resourcePath']
+            . '/' . $this->getTargetGalleryEntryId();
+
+        $this->assertTrue($this->_webApiCall($this->updateServiceInfo, $requestData));
+
+        $targetProduct = $this->getTargetSimpleProduct();
+        $this->assertEquals('/m/a/magento_image.jpg', $targetProduct->getData('thumbnail'));
+        $this->assertNull($targetProduct->getData('image'));
+        $this->assertNull($targetProduct->getData('small_image'));
+        $mediaGallery = $targetProduct->getData('media_gallery');
+        $this->assertCount(1, $mediaGallery['images']);
+        $updatedImage = $mediaGallery['images'][0];
+        // Not default store view values were updated
+        $this->assertEquals('Updated Image Text', $updatedImage['label']);
+        $this->assertEquals('/m/a/magento_image.jpg', $updatedImage['file']);
+        $this->assertEquals(10, $updatedImage['position']);
+        $this->assertEquals(1, $updatedImage['disabled']);
+        // Default store view values were not updated
+        $this->assertEquals('Image Alt Text', $updatedImage['label_default']);
+        $this->assertEquals(1, $updatedImage['position_default']);
+        $this->assertEquals(0, $updatedImage['disabled_default']);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/product_with_image.php
+     */
+    public function testDelete()
+    {
+        $entryId = $this->getTargetGalleryEntryId();
+        $this->deleteServiceInfo['rest']['resourcePath'] = "/V1/products/simple/media/{$entryId}";
+        $requestData = [
+            'productSku' => 'simple',
+            'entryId' => $this->getTargetGalleryEntryId(),
+        ];
+
+        $this->assertTrue($this->_webApiCall($this->deleteServiceInfo, $requestData));
+        $targetProduct = $this->getTargetSimpleProduct();
+        $mediaGallery = $targetProduct->getData('media_gallery');
+        $this->assertCount(0, $mediaGallery['images']);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+     * @expectedException \Exception
+     * @expectedExceptionMessage There is no store with provided ID.
+     */
+    public function testCreateThrowsExceptionIfThereIsNoStoreWithProvidedStoreId()
+    {
+        $requestData = [
+            'productSku' => 'simple',
+            'entry' => [
+                'id' => null,
+                'label' => 'Image Text',
+                'position' => 1,
+                'types' => ['image'],
+                'is_disabled' => false,
+            ],
+            'storeId' => 9999, // target store view does not exist
+            'entryContent' => [
+                'entry_data' => base64_encode(file_get_contents($this->testImagePath)),
+                'mime_type' => 'image/jpeg',
+                'name' => 'test_image',
+            ],
+        ];
+
+        $this->_webApiCall($this->createServiceInfo, $requestData);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+     * @expectedException \Exception
+     * @expectedExceptionMessage The image content must be valid base64 encoded data.
+     */
+    public function testCreateThrowsExceptionIfProvidedContentIsNotBase64Encoded()
+    {
+        $encodedContent = 'not_a_base64_encoded_content';
+        $requestData = [
+            'productSku' => 'simple',
+            'entry' => [
+                'id' => null,
+                'label' => 'Image Text',
+                'position' => 1,
+                'is_disabled' => false,
+                'types' => ['image'],
+            ],
+            'entryContent' => [
+                'entry_data' => $encodedContent,
+                'mime_type' => 'image/jpeg',
+                'name' => 'test_image',
+            ],
+            'storeId' => 0,
+        ];
+
+        $this->_webApiCall($this->createServiceInfo, $requestData);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+     * @expectedException \Exception
+     * @expectedExceptionMessage The image content must be valid base64 encoded data.
+     */
+    public function testCreateThrowsExceptionIfProvidedContentIsNotAnImage()
+    {
+        $encodedContent = base64_encode('not_an_image');
+        $requestData = [
+            'productSku' => 'simple',
+            'entry' => [
+                'id' => null,
+                'is_disabled' => false,
+                'label' => 'Image Text',
+                'position' => 1,
+                'types' => ['image'],
+            ],
+            'entryContent' => [
+                'entry_data' => $encodedContent,
+                'mime_type' => 'image/jpeg',
+                'name' => 'test_image',
+            ],
+            'storeId' => 0,
+        ];
+
+        $this->_webApiCall($this->createServiceInfo, $requestData);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+     * @expectedException \Exception
+     * @expectedExceptionMessage The image MIME type is not valid or not supported.
+     */
+    public function testCreateThrowsExceptionIfProvidedImageHasWrongMimeType()
+    {
+        $encodedContent = base64_encode(file_get_contents($this->testImagePath));
+        $requestData = [
+            'entry' => [
+                'id' => null,
+                'label' => 'Image Text',
+                'position' => 1,
+                'types' => ['image'],
+                'is_disabled' => false,
+            ],
+            'productSku' => 'simple',
+            'entryContent' => [
+                'entry_data' => $encodedContent,
+                'mime_type' => 'wrong_mime_type',
+                'name' => 'test_image',
+            ],
+            'storeId' => 0,
+        ];
+
+        $this->_webApiCall($this->createServiceInfo, $requestData);
+    }
+
+    /**
+     * @expectedException \Exception
+     * @expectedExceptionMessage Requested product doesn't exist
+     */
+    public function testCreateThrowsExceptionIfTargetProductDoesNotExist()
+    {
+        $this->createServiceInfo['rest']['resourcePath'] = '/V1/products/wrong_product_sku/media';
+        $requestData = [
+            'productSku' => 'wrong_product_sku',
+            'entry' => [
+                'id' => null,
+                'position' => 1,
+                'label' => 'Image Text',
+                'types' => ['image'],
+                'is_disabled' => false,
+            ],
+            'entryContent' => [
+                'entry_data' => base64_encode(file_get_contents($this->testImagePath)),
+                'mime_type' => 'image/jpeg',
+                'name' => 'test_image',
+            ],
+            'storeId' => 0,
+        ];
+
+        $this->_webApiCall($this->createServiceInfo, $requestData);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+     * @expectedException \Exception
+     * @expectedExceptionMessage Provided image name contains forbidden characters.
+     */
+    public function testCreateThrowsExceptionIfProvidedImageNameContainsForbiddenCharacters()
+    {
+        $requestData = [
+            'productSku' => 'simple',
+            'entry' => [
+                'id' => null,
+                'label' => 'Image Text',
+                'position' => 1,
+                'types' => ['image'],
+                'is_disabled' => false,
+            ],
+            'entryContent' => [
+                'entry_data' => base64_encode(file_get_contents($this->testImagePath)),
+                'mime_type' => 'image/jpeg',
+                'name' => 'test/\\{}|:"<>', // Cannot contain \ / : * ? " < > |
+            ],
+            'storeId' => 0,
+        ];
+
+        $this->_webApiCall($this->createServiceInfo, $requestData);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/product_with_image.php
+     * @expectedException \Exception
+     * @expectedExceptionMessage There is no store with provided ID.
+     */
+    public function testUpdateIfThereIsNoStoreWithProvidedStoreId()
+    {
+        $requestData = [
+            'productSku' => 'simple',
+            'entry' => [
+                'id' => $this->getTargetGalleryEntryId(),
+                'label' => 'Updated Image Text',
+                'position' => 10,
+                'types' => ['thumbnail'],
+                'is_disabled' => true,
+            ],
+            'storeId' => 9999, // target store view does not exist
+        ];
+
+        $this->updateServiceInfo['rest']['resourcePath'] = $this->updateServiceInfo['rest']['resourcePath']
+            . '/' . $this->getTargetGalleryEntryId();
+
+        $this->_webApiCall($this->updateServiceInfo, $requestData);
+    }
+
+    /**
+     * @expectedException \Exception
+     * @expectedExceptionMessage Requested product doesn't exist
+     */
+    public function testUpdateThrowsExceptionIfTargetProductDoesNotExist()
+    {
+        $this->updateServiceInfo['rest']['resourcePath'] = '/V1/products/wrong_product_sku/media'
+            . '/' . $this->getTargetGalleryEntryId();
+        $requestData = [
+            'productSku' => 'wrong_product_sku',
+            'entry' => [
+                'id' => 9999,
+                'label' => 'Updated Image Text',
+                'position' => 1,
+                'types' => ['thumbnail'],
+                'is_disabled' => true,
+            ],
+            'storeId' => 0,
+        ];
+
+        $this->_webApiCall($this->updateServiceInfo, $requestData);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/product_with_image.php
+     * @expectedException \Exception
+     * @expectedExceptionMessage There is no image with provided ID.
+     */
+    public function testUpdateThrowsExceptionIfThereIsNoImageWithGivenId()
+    {
+        $requestData = [
+            'productSku' => 'simple',
+            'entry' => [
+                'id' => 9999,
+                'label' => 'Updated Image Text',
+                'position' => 1,
+                'types' => ['thumbnail'],
+                'is_disabled' => true,
+            ],
+            'storeId' => 0,
+        ];
+
+        $this->updateServiceInfo['rest']['resourcePath'] = $this->updateServiceInfo['rest']['resourcePath']
+            . '/' . $this->getTargetGalleryEntryId();
+
+        $this->_webApiCall($this->updateServiceInfo, $requestData);
+    }
+
+    /**
+     * @expectedException \Exception
+     * @expectedExceptionMessage Requested product doesn't exist
+     */
+    public function testDeleteThrowsExceptionIfTargetProductDoesNotExist()
+    {
+        $this->deleteServiceInfo['rest']['resourcePath'] = '/V1/products/wrong_product_sku/media/9999';
+        $requestData = [
+            'productSku' => 'wrong_product_sku',
+            'entryId' => 9999,
+        ];
+
+        $this->_webApiCall($this->deleteServiceInfo, $requestData);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/product_with_image.php
+     * @expectedException \Exception
+     * @expectedExceptionMessage There is no image with provided ID.
+     */
+    public function testDeleteThrowsExceptionIfThereIsNoImageWithGivenId()
+    {
+        $this->deleteServiceInfo['rest']['resourcePath'] = '/V1/products/simple/media/9999';
+        $requestData = [
+            'productSku' => 'simple',
+            'entryId' => 9999,
+        ];
+
+        $this->_webApiCall($this->deleteServiceInfo, $requestData);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/product_with_image.php
+     */
+    public function testGet()
+    {
+        $productSku = 'simple';
+
+        $objectManager = \Magento\TestFramework\ObjectManager::getInstance();
+        /** @var \Magento\Catalog\Model\ProductRepository $repository */
+        $repository = $objectManager->create('Magento\Catalog\Model\ProductRepository');
+        $product = $repository->get($productSku);
+        $image = current($product->getMediaGallery('images'));
+        $imageId = $image['value_id'];
+
+        $expected = [
+            'label' => $image['label'],
+            'position' => $image['position'],
+            'is_disabled' => (bool)$image['disabled'],
+            'file' => $image['file'],
+            'types' => ['image', 'small_image', 'thumbnail'],
+        ];
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/products/' . $productSku . '/media/' . $imageId,
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => 'catalogProductAttributeMediaGalleryManagementV1',
+                'serviceVersion' => 'V1',
+                'operation' => 'catalogProductAttributeMediaGalleryManagementV1Get',
+            ],
+        ];
+        $requestData = [
+            'productSku' => $productSku,
+            'imageId' => $imageId,
+        ];
+        $data = $this->_webApiCall($serviceInfo, $requestData);
+        $actual = (array) $data;
+        $this->assertEquals($expected['label'], $actual['label']);
+        $this->assertEquals($expected['position'], $actual['position']);
+        $this->assertEquals($expected['file'], $actual['file']);
+        $this->assertEquals($expected['types'], $actual['types']);
+        $this->assertEquals($expected['is_disabled'], (bool)$actual['is_disabled']);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/product_with_image.php
+     */
+    public function testGetList()
+    {
+        $productSku = 'simple'; //from fixture
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/products/' . urlencode($productSku) . '/media',
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => 'catalogProductAttributeMediaGalleryManagementV1',
+                'serviceVersion' => 'V1',
+                'operation' => 'catalogProductAttributeMediaGalleryManagementV1GetList',
+            ],
+        ];
+
+        $requestData = [
+            'productSku' => $productSku,
+        ];
+        $imageList = $this->_webApiCall($serviceInfo, $requestData);
+
+        $image = reset($imageList);
+        $this->assertEquals('/m/a/magento_image.jpg', $image['file']);
+        $this->assertNotEmpty($image['types']);
+        $imageTypes = $image['types'];
+        $this->assertContains('image', $imageTypes);
+        $this->assertContains('small_image', $imageTypes);
+        $this->assertContains('thumbnail', $imageTypes);
+    }
+
+    public function testGetListForAbsentSku()
+    {
+        $productSku = 'absent_sku_' . time();
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/products/' . urlencode($productSku) . '/media',
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => 'catalogProductAttributeMediaGalleryManagementV1',
+                'serviceVersion' => 'V1',
+                'operation' => 'catalogProductAttributeMediaGalleryManagementV1GetList',
+            ],
+        ];
+
+        $requestData = [
+            'productSku' => $productSku,
+        ];
+        if (TESTS_WEB_API_ADAPTER == self::ADAPTER_SOAP) {
+            $this->setExpectedException('SoapFault', 'Requested product doesn\'t exist');
+        } else {
+            $this->setExpectedException('Exception', '', 404);
+        }
+        $this->_webApiCall($serviceInfo, $requestData);
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductAttributeOptionManagementInterfaceTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductAttributeOptionManagementInterfaceTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..1b687f2a6f4fe15216c03ebd355a75feb756243f
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductAttributeOptionManagementInterfaceTest.php
@@ -0,0 +1,152 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Catalog\Api;
+
+use Magento\Eav\Api\Data\AttributeOptionInterface;
+use Magento\Eav\Api\Data\AttributeOptionLabelInterface;
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+class ProductAttributeOptionManagementInterfaceTest extends WebapiAbstract
+{
+    const SERVICE_NAME = 'catalogProductAttributeOptionManagementV1';
+    const SERVICE_VERSION = 'V1';
+    const RESOURCE_PATH = '/V1/products/attributes';
+
+    public function testGetItems()
+    {
+        $testAttributeCode = 'quantity_and_stock_status';
+        $expectedOptions = [
+            [
+                AttributeOptionInterface::VALUE => '1',
+                AttributeOptionInterface::LABEL => 'In Stock',
+            ],
+            [
+                AttributeOptionInterface::VALUE => '0',
+                AttributeOptionInterface::LABEL => 'Out of Stock',
+            ],
+        ];
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/' . $testAttributeCode . '/options',
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'getItems',
+            ],
+        ];
+
+        $response = $this->_webApiCall($serviceInfo, ['attributeCode' => $testAttributeCode]);
+
+        $this->assertTrue(is_array($response));
+        $this->assertEquals($expectedOptions, $response);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/Model/Product/Attribute/_files/select_attribute.php
+     */
+    public function testAdd()
+    {
+        $testAttributeCode = 'select_attribute';
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/' . $testAttributeCode . '/options',
+                'httpMethod' => RestConfig::HTTP_METHOD_POST,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'add',
+            ],
+        ];
+
+        $optionData = [
+            AttributeOptionInterface::LABEL => 'new color',
+            AttributeOptionInterface::VALUE => 'grey',
+            AttributeOptionInterface::SORT_ORDER => 100,
+            AttributeOptionInterface::IS_DEFAULT => true,
+            AttributeOptionInterface::STORE_LABELS => [
+                [
+                    AttributeOptionLabelInterface::LABEL => 'DE label',
+                    AttributeOptionLabelInterface::STORE_ID => 1,
+                ],
+            ],
+        ];
+
+        $response = $this->_webApiCall(
+            $serviceInfo,
+            [
+                'attributeCode' => $testAttributeCode,
+                'option' => $optionData,
+            ]
+        );
+
+        $this->assertTrue($response);
+        $updatedData = $this->getAttributeOptions($testAttributeCode);
+        $lastOption = array_pop($updatedData);
+        $this->assertEquals(
+            $optionData[AttributeOptionInterface::STORE_LABELS][0][AttributeOptionLabelInterface::LABEL],
+            $lastOption['label']
+        );
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/Model/Product/Attribute/_files/select_attribute.php
+     */
+    public function testDelete()
+    {
+        $attributeCode = 'select_attribute';
+        //get option Id
+        $optionList = $this->getAttributeOptions($attributeCode);
+        $this->assertGreaterThan(0, count($optionList));
+        $lastOption = array_pop($optionList);
+        $this->assertNotEmpty($lastOption['value']);
+        $optionId = $lastOption['value'];
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/' . $attributeCode . '/options/' . $optionId,
+                'httpMethod' => RestConfig::HTTP_METHOD_DELETE,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'delete',
+            ],
+        ];
+        $this->assertTrue($this->_webApiCall(
+            $serviceInfo,
+            [
+                'attributeCode' => $attributeCode,
+                'optionId' => $optionId,
+            ]
+        ));
+        $updatedOptions = $this->getAttributeOptions($attributeCode);
+        $this->assertEquals($optionList, $updatedOptions);
+    }
+
+    /**
+     * @param $testAttributeCode
+     * @return array|bool|float|int|string
+     */
+    private function getAttributeOptions($testAttributeCode)
+    {
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/' . $testAttributeCode . '/options',
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'getItems',
+            ],
+        ];
+        return $this->_webApiCall($serviceInfo, ['attributeCode' => $testAttributeCode]);
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductAttributeRepositoryTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductAttributeRepositoryTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..20742ba9ee0e64cc8972160e1c4fb4c109355f0a
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductAttributeRepositoryTest.php
@@ -0,0 +1,266 @@
+<?php
+/**
+ *
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Catalog\Api;
+
+use Magento\Webapi\Exception as HTTPExceptionCodes;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+class ProductAttributeRepositoryTest extends \Magento\TestFramework\TestCase\WebapiAbstract
+{
+    const SERVICE_NAME = 'catalogProductAttributeRepositoryV1';
+    const SERVICE_VERSION = 'V1';
+    const RESOURCE_PATH = '/V1/products/attributes';
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/product_attribute.php
+     */
+    public function testGet()
+    {
+        $attributeCode = 'test_attribute_code_333';
+        $attribute = $this->getAttribute($attributeCode);
+
+        $this->assertTrue(is_array($attribute));
+        $this->assertArrayHasKey('attribute_id', $attribute);
+        $this->assertArrayHasKey('attribute_code', $attribute);
+        $this->assertEquals($attributeCode, $attribute['attribute_code']);
+    }
+
+    public function testGetList()
+    {
+        $searchCriteria = [
+            'searchCriteria' => [
+                'filter_groups' => [
+                    [
+                        'filters' => [
+                            [
+                                'field' => 'frontend_input',
+                                'value' => 'textarea',
+                                'condition_type' => 'eq',
+                            ],
+                        ],
+                    ],
+                ],
+                'current_page' => 1,
+                'page_size' => 2,
+            ],
+            'entityTypeCode' => \Magento\Catalog\Api\Data\ProductAttributeInterface::ENTITY_TYPE_CODE,
+        ];
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/',
+                'httpMethod' => RestConfig::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'GetList',
+            ],
+        ];
+        $response = $this->_webApiCall($serviceInfo, $searchCriteria);
+
+        $this->assertArrayHasKey('search_criteria', $response);
+        $this->assertArrayHasKey('total_count', $response);
+        $this->assertArrayHasKey('items', $response);
+
+        $this->assertEquals($searchCriteria['searchCriteria'], $response['search_criteria']);
+        $this->assertTrue($response['total_count'] > 0);
+        $this->assertTrue(count($response['items']) > 0);
+
+        $this->assertNotNull($response['items'][0]['default_frontend_label']);
+        $this->assertNotNull($response['items'][0]['attribute_id']);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/Model/Product/Attribute/_files/create_attribute_service.php
+     */
+    public function testCreate()
+    {
+        $attributeCode = 'label_attr_code3df4tr3';
+        $attribute = $this->createAttribute($attributeCode);
+        $this->assertArrayHasKey('attribute_id', $attribute);
+        $this->assertEquals($attributeCode, $attribute['attribute_code']);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/product_attribute.php
+     */
+    public function testCreateWithExceptionIfAttributeAlreadyExists()
+    {
+        $attributeCode = 'test_attribute_code_333';
+        try {
+            $this->createAttribute($attributeCode);
+            $this->fail("Expected exception");
+        } catch (\SoapFault $e) {
+            //Expects soap exception
+        } catch (\Exception $e) {
+            $this->assertEquals(HTTPExceptionCodes::HTTP_INTERNAL_ERROR, $e->getCode());
+        }
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/product_attribute.php
+     */
+    public function testUpdate()
+    {
+        $attributeCode = 'test_attribute_code_333';
+        $attribute = $this->getAttribute($attributeCode);
+
+        $attributeData = [
+            'attribute' => [
+                'attribute_code' => $attributeCode,
+                'frontend_labels' => [
+                    ['store_id' => 0, 'label' => 'front_lbl_new'],
+                ],
+                'default_value' => 'default value new',
+                'is_required' => false,
+                'frontend_input' => 'text',
+            ],
+        ];
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/' . $attribute['attribute_id'],
+                'httpMethod' => RestConfig::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Save',
+            ],
+        ];
+
+        if (TESTS_WEB_API_ADAPTER == self::ADAPTER_SOAP) {
+            $attributeData['attribute']['attributeId'] = $attribute['attribute_id'];
+        }
+        $result = $this->_webApiCall($serviceInfo, $attributeData);
+
+        $this->assertEquals($attribute['attribute_id'], $result['attribute_id']);
+        $this->assertEquals($attributeCode, $result['attribute_code']);
+        $this->assertEquals('default value new', $result['default_value']);
+        $this->assertEquals('front_lbl_new', $result['default_frontend_label']);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/product_attribute.php
+     */
+    public function testDeleteById()
+    {
+        $attributeCode = 'test_attribute_code_333';
+        $this->assertTrue($this->deleteAttribute($attributeCode));
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/product_attribute.php
+     */
+    public function testDeleteNoSuchEntityException()
+    {
+        $attributeCode = 'some_test_code';
+        $expectedMessage = 'Attribute with attributeCode "' . $attributeCode . '" does not exist.';
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/' . $attributeCode,
+                'httpMethod' => RestConfig::HTTP_METHOD_DELETE,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'deleteById',
+            ],
+        ];
+
+        try {
+            $this->_webApiCall($serviceInfo, ['attributeCode' => $attributeCode]);
+            $this->fail("Expected exception");
+        } catch (\SoapFault $e) {
+            $this->assertContains(
+                $expectedMessage,
+                $e->getMessage(),
+                "SoapFault does not contain expected message."
+            );
+        } catch (\Exception $e) {
+            $errorObj = $this->processRestExceptionResult($e);
+            $this->assertEquals($expectedMessage, $errorObj['message']);
+            $this->assertEquals(HTTPExceptionCodes::HTTP_NOT_FOUND, $e->getCode());
+        }
+    }
+
+    /**
+     * @param $attributeCode
+     * @return array|bool|float|int|string
+     */
+    protected function createAttribute($attributeCode)
+    {
+        $attributeData = [
+            'attribute' => [
+                'attribute_code' => $attributeCode,
+                'frontend_labels' => [
+                    ['store_id' => 0, 'label' => 'front_lbl'],
+                ],
+                'default_value' => 'default value',
+                'frontend_input' => 'textarea',
+                'is_required' => true,
+            ],
+        ];
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/',
+                'httpMethod' => RestConfig::HTTP_METHOD_POST,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Save',
+            ],
+        ];
+        return $this->_webApiCall($serviceInfo, $attributeData);
+    }
+
+    /**
+     * @param $attributeCode
+     * @return array|bool|float|int|string
+     */
+    protected function getAttribute($attributeCode)
+    {
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/' . $attributeCode,
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Get',
+            ],
+        ];
+
+        return $this->_webApiCall($serviceInfo, ['attributeCode' => $attributeCode]);
+    }
+
+    /**
+     * Delete attribute by code
+     *
+     * @param $attributeCode
+     * @return array|bool|float|int|string
+     */
+    protected function deleteAttribute($attributeCode)
+    {
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/' . $attributeCode,
+                'httpMethod' => RestConfig::HTTP_METHOD_DELETE,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'deleteById',
+            ],
+        ];
+        return $this->_webApiCall($serviceInfo, ['attributeCode' => $attributeCode]);
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductAttributeTypesListTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductAttributeTypesListTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..5967b8503bfcd635db17e0725b60dcef321e33f4
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductAttributeTypesListTest.php
@@ -0,0 +1,37 @@
+<?php
+/**
+ *
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Catalog\Api;
+
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+class ProductAttributeTypesListTest extends \Magento\TestFramework\TestCase\WebapiAbstract
+{
+    const SERVICE_NAME = 'catalogProductAttributeTypesListV1';
+    const SERVICE_VERSION = 'V1';
+    const RESOURCE_PATH = '/V1/products/attributes';
+
+    public function testGetItems()
+    {
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/types',
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'GetItems',
+            ],
+        ];
+        $types = $this->_webApiCall($serviceInfo);
+
+        $this->assertTrue(count($types) > 0);
+        $this->assertArrayHasKey('value', $types[0]);
+        $this->assertArrayHasKey('label', $types[0]);
+        $this->assertNotNull($types[0]['value']);
+        $this->assertNotNull($types[0]['label']);
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductCustomOptionRepositoryTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductCustomOptionRepositoryTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..1995c194bd5b81d0fa45d0c3027c04628e228854
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductCustomOptionRepositoryTest.php
@@ -0,0 +1,367 @@
+<?php
+/**
+ *
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+namespace Magento\Catalog\Api;
+
+use Magento\TestFramework\Helper\Bootstrap;
+use Magento\TestFramework\TestCase\WebapiAbstract;
+
+class ProductCustomOptionRepositoryTest extends WebapiAbstract
+{
+    /**
+     * @var \Magento\Framework\ObjectManagerInterface
+     */
+    protected $objectManager;
+
+    const SERVICE_NAME = 'catalogProductCustomOptionRepositoryV1';
+
+    /**
+     * @var \Magento\Catalog\Model\ProductFactory
+     */
+    protected $productFactory;
+
+    protected function setUp()
+    {
+        $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+        $this->productFactory = $this->objectManager->get('Magento\Catalog\Model\ProductFactory');
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/product_with_options.php
+     * @magentoAppIsolation enabled
+     */
+    public function testRemove()
+    {
+        $sku = 'simple';
+        /** @var  \Magento\Catalog\Model\Product $product */
+        $product = $this->objectManager->create('Magento\Catalog\Model\Product');
+        $product->load(1);
+        $customOptions = $product->getOptions();
+        $optionId = array_pop($customOptions)->getId();
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => "/V1/products/$sku/options/$optionId",
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_DELETE,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => 'V1',
+                'operation' => self::SERVICE_NAME . 'DeleteByIdentifier',
+            ],
+        ];
+        $this->assertTrue($this->_webApiCall($serviceInfo, ['productSku' => $sku, 'optionId' => $optionId]));
+        /** @var  \Magento\Catalog\Model\Product $product */
+        $product = $this->objectManager->create('Magento\Catalog\Model\Product');
+        $product->load(1);
+        $this->assertNull($product->getOptionById($optionId));
+        $this->assertEquals(9, count($product->getOptions()));
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/product_with_options.php
+     * @magentoAppIsolation enabled
+     */
+    public function testGet()
+    {
+        $productSku = 'simple';
+        /** @var \Magento\Catalog\Api\ProductCustomOptionRepositoryInterface $service */
+        $service = Bootstrap::getObjectManager()
+            ->get('Magento\Catalog\Api\ProductCustomOptionRepositoryInterface');
+        $options = $service->getList('simple');
+        $option = current($options);
+        $optionId = $option->getOptionId();
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/products/' . $productSku . "/options/" . $optionId,
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => 'V1',
+                'operation' => self::SERVICE_NAME . 'Get',
+            ],
+        ];
+        $option = $this->_webApiCall($serviceInfo, ['productSku' => $productSku, 'optionId' => $optionId]);
+        unset($option['product_sku']);
+        unset($option['option_id']);
+        $excepted = include '_files/product_options.php';
+        $this->assertEquals($excepted[0], $option);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/product_with_options.php
+     * @magentoAppIsolation enabled
+     */
+    public function testGetList()
+    {
+        $productSku = 'simple';
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/products/' . $productSku . "/options",
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => 'V1',
+                'operation' => self::SERVICE_NAME . 'GetList',
+            ],
+        ];
+        $options = $this->_webApiCall($serviceInfo, ['productSku' => $productSku]);
+
+        /** Unset dynamic data */
+        foreach ($options as $key => $value) {
+            unset($options[$key]['product_sku']);
+            unset($options[$key]['option_id']);
+            if (!empty($options[$key]['values'])) {
+                foreach ($options[$key]['values'] as $newKey => $valueData) {
+                    unset($options[$key]['values'][$newKey]['option_type_id']);
+                }
+            }
+        }
+
+        $excepted = include '_files/product_options.php';
+        $this->assertEquals(count($excepted), count($options));
+
+        //in order to make assertion result readable we need to check each element separately
+        foreach ($excepted as $index => $value) {
+            $this->assertEquals($value, $options[$index]);
+        }
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/product_without_options.php
+     * @magentoAppIsolation enabled
+     * @dataProvider optionDataProvider
+     */
+    public function testSave($optionData)
+    {
+        $productSku = 'simple';
+
+        $optionDataPost = $optionData;
+        $optionDataPost['product_sku'] = $productSku;
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/products/options',
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_POST,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => 'V1',
+                'operation' => self::SERVICE_NAME . 'Save',
+            ],
+        ];
+
+        $result = $this->_webApiCall($serviceInfo, ['option' => $optionDataPost]);
+        unset($result['product_sku']);
+        unset($result['option_id']);
+        if (!empty($result['values'])) {
+            foreach ($result['values'] as $key => $value) {
+                unset($result['values'][$key]['option_type_id']);
+            }
+        }
+        $this->assertEquals($optionData, $result);
+    }
+
+    public function optionDataProvider()
+    {
+        $fixtureOptions = [];
+        $fixture = include '_files/product_options.php';
+        foreach ($fixture as $item) {
+            $fixtureOptions[$item['type']] = [
+                'optionData' => $item,
+            ];
+        };
+
+        return $fixtureOptions;
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/product_without_options.php
+     * @magentoAppIsolation enabled
+     * @dataProvider optionNegativeDataProvider
+     */
+    public function testAddNegative($optionData)
+    {
+        $productSku = 'simple';
+        $optionDataPost = $optionData;
+        $optionDataPost['product_sku'] = $productSku;
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => "/V1/products/options",
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_POST,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => 'V1',
+                'operation' => self::SERVICE_NAME . 'Save',
+            ],
+        ];
+
+        if (TESTS_WEB_API_ADAPTER == self::ADAPTER_SOAP) {
+            $this->setExpectedException('SoapFault', 'Could not save product option');
+        } else {
+            $this->setExpectedException('Exception', '', 400);
+        }
+        $this->_webApiCall($serviceInfo, ['option' => $optionDataPost]);
+    }
+
+    public function optionNegativeDataProvider()
+    {
+        $fixtureOptions = [];
+        $fixture = include '_files/product_options_negative.php';
+        foreach ($fixture as $key => $item) {
+            $fixtureOptions[$key] = [
+                'optionData' => $item,
+            ];
+        };
+
+        return $fixtureOptions;
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/product_with_options.php
+     * @magentoAppIsolation enabled
+     */
+    public function testUpdate()
+    {
+        $productSku = 'simple';
+        /** @var \Magento\Catalog\Model\ProductRepository $optionReadService */
+        $productRepository = $this->objectManager->create(
+            'Magento\Catalog\Model\ProductRepository'
+        );
+
+        $options = $productRepository->get($productSku, true)->getOptions();
+        $option = array_shift($options);
+        $optionId = $option->getOptionId();
+        $optionDataPost = [
+            'product_sku' => $productSku,
+            'title' => $option->getTitle() . "_updated",
+            'type' => $option->getType(),
+            'sort_order' => $option->getSortOrder(),
+            'is_require' => $option->getIsRequire(),
+            'price' => $option->getPrice(),
+            'price_type' => $option->getPriceType(),
+            'sku' => $option->getSku(),
+            'max_characters' => 500,
+        ];
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/products/options/' . $optionId,
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => 'V1',
+                'operation' => self::SERVICE_NAME . 'Save',
+            ],
+        ];
+        if (TESTS_WEB_API_ADAPTER == self::ADAPTER_SOAP) {
+            $optionDataPost['option_id'] = $optionId;
+            $updatedOption = $this->_webApiCall(
+                $serviceInfo, [ 'id' => $optionId, 'option' => $optionDataPost]
+            );
+        } else {
+            $updatedOption = $this->_webApiCall(
+                $serviceInfo, ['option' => $optionDataPost]
+            );
+        }
+
+        unset($updatedOption['values']);
+        $optionDataPost['option_id'] = $option->getOptionId();
+        $this->assertEquals($optionDataPost, $updatedOption);
+    }
+
+    /**
+     * @param string $optionType
+     *
+     * @magentoApiDataFixture Magento/Catalog/_files/product_with_options.php
+     * @magentoAppIsolation enabled
+     * @dataProvider validOptionDataProvider
+     */
+    public function testUpdateOptionAddingNewValue($optionType)
+    {
+        $productId = 1;
+        $fixtureOption = null;
+        $valueData = [
+            'price' => 100500,
+            'price_type' => 'fixed',
+            'sku' => 'new option sku ' . $optionType,
+            'title' => 'New Option Title',
+            'sort_order' => 100,
+        ];
+
+        $product = $this->productFactory->create();
+        $product->load($productId);
+
+        /**@var $option \Magento\Catalog\Model\Product\Option */
+        foreach ($product->getOptions() as $option) {
+            if ($option->getType() == $optionType) {
+                $fixtureOption = $option;
+                break;
+            }
+        }
+
+        $values = [];
+        foreach ($option->getValues() as $key => $value) {
+            $values[] = [
+                'price' => $value->getPrice(),
+                'price_type' => $value->getPriceType(),
+                'sku' => $value->getSku(),
+                'title' => $value->getTitle(),
+                'sort_order' => $value->getSortOrder(),
+        ];
+        }
+        $values[] = $valueData;
+        $data = [
+            'product_sku' => $option->getProductSku(),
+            'title' => $option->getTitle(),
+            'type' => $option->getType(),
+            'is_require' => $option->getIsRequire(),
+            'sort_order' => $option->getSortOrder(),
+            'values' => $values,
+        ];
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/products/options/' . $fixtureOption->getId(),
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => 'V1',
+                'operation' => self::SERVICE_NAME . 'Save',
+            ],
+        ];
+        if (TESTS_WEB_API_ADAPTER == self::ADAPTER_SOAP) {
+            $data['option_id'] = $fixtureOption->getId();
+            $valueObject = $this->_webApiCall(
+                $serviceInfo, [ 'option_id' => $fixtureOption->getId(), 'option' => $data]
+            );
+        } else {
+            $valueObject = $this->_webApiCall(
+                $serviceInfo, ['option' => $data]
+            );
+        }
+
+        $values = end($valueObject['values']);
+        $this->assertEquals($valueData['price'], $values['price']);
+        $this->assertEquals($valueData['price_type'], $values['price_type']);
+        $this->assertEquals($valueData['sku'], $values['sku']);
+        $this->assertEquals('New Option Title', $values['title']);
+        $this->assertEquals(100, $values['sort_order']);
+    }
+
+    public function validOptionDataProvider()
+    {
+        return [
+            'drop_down' => ['drop_down'],
+            'checkbox' => ['checkbox'],
+            'radio' => ['radio'],
+            'multiple' => ['multiple']
+        ];
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductCustomOptionTypeListTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductCustomOptionTypeListTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..eb0f890231e58871102aa684a33ccb699d8902b1
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductCustomOptionTypeListTest.php
@@ -0,0 +1,42 @@
+<?php
+/**
+ *
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+namespace Magento\Catalog\Api;
+
+use Magento\TestFramework\TestCase\WebapiAbstract;
+
+class ProductCustomOptionTypeListTest extends WebapiAbstract
+{
+    const RESOURCE_PATH = '/V1/products/options/';
+
+    const SERVICE_NAME = 'catalogProductCustomOptionTypeListV1';
+
+    /**
+     * @magentoAppIsolation enabled
+     */
+    public function testGetTypes()
+    {
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . "types",
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => 'V1',
+                'operation' => self::SERVICE_NAME . 'GetItems',
+            ],
+        ];
+        $types = $this->_webApiCall($serviceInfo);
+        $excepted = [
+            'label' => __('Drop-down'),
+            'code' => 'drop_down',
+            'group' => __('Select'),
+        ];
+        $this->assertGreaterThanOrEqual(10, count($types));
+        $this->assertContains($excepted, $types);
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductGroupPriceManagementTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductGroupPriceManagementTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..035931302519d5baa2b598bb2865008828b1e045
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductGroupPriceManagementTest.php
@@ -0,0 +1,118 @@
+<?php
+/**
+ *
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+namespace Magento\Catalog\Api;
+
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+class ProductGroupPriceManagementTest extends WebapiAbstract
+{
+    const SERVICE_NAME = 'catalogProductGroupPriceManagementV1';
+    const SERVICE_VERSION = 'V1';
+    const RESOURCE_PATH = '/V1/products/';
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/product_group_prices.php
+     */
+    public function testGetList()
+    {
+        $productSku = 'simple_with_group_price';
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . $productSku . '/group-prices',
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'GetList',
+            ],
+        ];
+        $groupPriceList = $this->_webApiCall($serviceInfo, ['productSku' => $productSku]);
+        $this->assertCount(2, $groupPriceList);
+        $this->assertEquals(9, $groupPriceList[0]['value']);
+        $this->assertEquals(7, $groupPriceList[1]['value']);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/product_group_prices.php
+     */
+    public function testDelete()
+    {
+        $productSku = 'simple_with_group_price';
+        $customerGroupId = \Magento\Customer\Model\GroupManagement::NOT_LOGGED_IN_ID;
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . $productSku . "/group-prices/" . $customerGroupId,
+                'httpMethod' => RestConfig::HTTP_METHOD_DELETE,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Remove',
+            ],
+        ];
+        $requestData = ['productSku' => $productSku, 'customerGroupId' => $customerGroupId];
+        $this->assertTrue($this->_webApiCall($serviceInfo, $requestData));
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+     */
+    public function testAdd()
+    {
+        $productSku = 'simple';
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/products/' . $productSku . '/group-prices/1/price/10',
+                'httpMethod' => RestConfig::HTTP_METHOD_POST,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Add',
+            ],
+        ];
+        $this->_webApiCall($serviceInfo, ['productSku' => $productSku, 'customerGroupId' => 1, 'price' => 10]);
+        $objectManager = \Magento\TestFramework\ObjectManager::getInstance();
+        /** @var \Magento\Catalog\Api\ProductGroupPriceManagementInterface $service */
+        $service = $objectManager->get('Magento\Catalog\Api\ProductGroupPriceManagementInterface');
+        $prices = $service->getList($productSku);
+        $this->assertCount(1, $prices);
+        $this->assertEquals(10, $prices[0]->getValue());
+        $this->assertEquals(1, $prices[0]->getCustomerGroupId());
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+     * @magentoApiDataFixture Magento/Store/_files/website.php
+     */
+    public function testAddForDifferentWebsite()
+    {
+        $productSku = 'simple';
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/products/' . $productSku . '/group-prices/1/price/10',
+                'httpMethod' => RestConfig::HTTP_METHOD_POST,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Add',
+            ],
+
+        ];
+        $this->_webApiCall($serviceInfo, ['productSku' => $productSku, 'customerGroupId' => 1, 'price' => 10]);
+        $objectManager = \Magento\TestFramework\ObjectManager::getInstance();
+        /** @var \Magento\Catalog\Api\ProductGroupPriceManagementInterface $service */
+        $service = $objectManager->get('Magento\Catalog\Api\ProductGroupPriceManagementInterface');
+        $prices = $service->getList($productSku);
+        $this->assertCount(1, $prices);
+        $this->assertEquals(10, $prices[0]->getValue());
+        $this->assertEquals(1, $prices[0]->getCustomerGroupId());
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductLinkManagementInterfaceTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductLinkManagementInterfaceTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..67df2361c2c7ae928c9ba2f80e83f2d17abec267
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductLinkManagementInterfaceTest.php
@@ -0,0 +1,143 @@
+<?php
+/**
+ *
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Catalog\Api;
+
+use Magento\TestFramework\Helper\Bootstrap;
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+class ProductLinkManagementInterfaceTest extends WebapiAbstract
+{
+    const SERVICE_NAME = 'catalogProductLinkManagementV1';
+    const SERVICE_VERSION = 'V1';
+    const RESOURCE_PATH = '/V1/products/';
+
+    /**
+     * @var \Magento\Framework\ObjectManager
+     */
+    protected $objectManager;
+
+    protected function setUp()
+    {
+        $this->objectManager = Bootstrap::getObjectManager();
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/products_crosssell.php
+     */
+    public function testGetLinkedProductsCrossSell()
+    {
+        $productSku = 'simple_with_cross';
+        $linkType = 'crosssell';
+
+        $this->assertLinkedProducts($productSku, $linkType);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/products_related.php
+     */
+    public function testGetLinkedProductsRelated()
+    {
+        $productSku = 'simple_with_cross';
+        $linkType = 'related';
+
+        $this->assertLinkedProducts($productSku, $linkType);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/products_upsell.php
+     */
+    public function testGetLinkedProductsUpSell()
+    {
+        $productSku = 'simple_with_upsell';
+        $linkType = 'upsell';
+
+        $this->assertLinkedProducts($productSku, $linkType);
+    }
+
+    /**
+     * @param string $productSku
+     * @param int $linkType
+     */
+    protected function assertLinkedProducts($productSku, $linkType)
+    {
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . $productSku . '/links/' . $linkType,
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'GetLinkedItemsByType',
+            ],
+        ];
+
+        $actual = $this->_webApiCall($serviceInfo, ['productSku' => $productSku, 'type' => $linkType]);
+
+        $this->assertEquals('simple', $actual[0]['linked_product_type']);
+        $this->assertEquals('simple', $actual[0]['linked_product_sku']);
+        $this->assertEquals(1, $actual[0]['position']);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+     * @magentoApiDataFixture Magento/Catalog/_files/product_virtual.php
+     */
+    public function testAssign()
+    {
+        $linkType = 'related';
+        $productSku = 'simple';
+        $linkData = [
+            'linked_product_type' => 'virtual',
+            'linked_product_sku' => 'virtual-product',
+            'position' => 100,
+            'product_sku' => 'simple',
+            'link_type' => 'related',
+        ];
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . $productSku . '/links/' . $linkType,
+                'httpMethod' => RestConfig::HTTP_METHOD_POST,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'SetProductLinks',
+            ],
+        ];
+
+        $arguments = [
+            'productSku' => $productSku,
+            'items' => [$linkData],
+            'type' => $linkType,
+        ];
+
+        $this->_webApiCall($serviceInfo, $arguments);
+        $actual = $this->getLinkedProducts($productSku, 'related');
+        array_walk($actual, function (&$item) {
+            $item = $item->__toArray();
+        });
+        $this->assertEquals([$linkData], $actual);
+    }
+
+    /**
+     * Get list of linked products
+     *
+     * @param string $sku
+     * @param string $linkType
+     * @return \Magento\Catalog\Api\Data\ProductLinkInterface[]
+     */
+    protected function getLinkedProducts($sku, $linkType)
+    {
+        /** @var \Magento\Catalog\Model\ProductLink\Management $linkManagement */
+        $linkManagement = $this->objectManager->get('Magento\Catalog\Api\ProductLinkManagementInterface');
+        $linkedProducts = $linkManagement->getLinkedItemsByType($sku, $linkType);
+
+        return $linkedProducts;
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductLinkRepositoryInterfaceTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductLinkRepositoryInterfaceTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..ff04d9044a11382fb656ef82e2d8f3a476f147ff
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductLinkRepositoryInterfaceTest.php
@@ -0,0 +1,103 @@
+<?php
+/**
+ *
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Catalog\Api;
+
+use Magento\TestFramework\Helper\Bootstrap;
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+class ProductLinkRepositoryInterfaceTest extends WebapiAbstract
+{
+    const SERVICE_NAME = 'catalogProductLinkRepositoryV1';
+    const SERVICE_VERSION = 'V1';
+    const RESOURCE_PATH = '/V1/products/';
+
+    /**
+     * @var \Magento\Framework\ObjectManager
+     */
+    protected $objectManager;
+
+    protected function setUp()
+    {
+        $this->objectManager = Bootstrap::getObjectManager();
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/products_related_multiple.php
+     * @magentoAppIsolation enabled
+     */
+    public function testDelete()
+    {
+        $productSku = 'simple_with_cross';
+        $linkedSku = 'simple';
+        $linkType = 'related';
+        $this->_webApiCall(
+            [
+                'rest' => [
+                    'resourcePath' => self::RESOURCE_PATH . $productSku . '/links/' . $linkType . '/' . $linkedSku,
+                    'httpMethod' => RestConfig::HTTP_METHOD_DELETE,
+                ],
+                'soap' => [
+                    'service' => self::SERVICE_NAME,
+                    'serviceVersion' => self::SERVICE_VERSION,
+                    'operation' => self::SERVICE_NAME . 'DeleteById',
+                ],
+            ],
+            [
+                'productSku' => $productSku,
+                'type' => $linkType,
+                'linkedProductSku' => $linkedSku
+            ]
+        );
+        /** @var \Magento\Catalog\Model\ProductLink\Management $linkManagement */
+        $linkManagement = $this->objectManager->create('Magento\Catalog\Api\ProductLinkManagementInterface');
+        $linkedProducts = $linkManagement->getLinkedItemsByType($productSku, $linkType);
+        $this->assertCount(1, $linkedProducts);
+        /** @var \Magento\Catalog\Api\Data\ProductLinkInterface $product */
+        $product = current($linkedProducts);
+        $this->assertEquals($product->getLinkedProductSku(), 'simple_with_cross_two');
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/products_related.php
+     */
+    public function testSave()
+    {
+        $productSku = 'simple_with_cross';
+        $linkType = 'related';
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . $productSku . '/links/' . $linkType,
+                'httpMethod' => RestConfig::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Save',
+            ],
+        ];
+
+        $this->_webApiCall(
+            $serviceInfo,
+            [
+                'entity' => [
+                    'product_sku' => 'simple_with_cross',
+                    'link_type' => 'related',
+                    'linked_product_sku' => 'simple',
+                    'linked_product_type' => 'simple',
+                    'position' => 1000,
+                ]
+            ]
+        );
+
+        /** @var \Magento\Catalog\Model\ProductLink\Management $linkManagement */
+        $linkManagement = $this->objectManager->get('Magento\Catalog\Api\ProductLinkManagementInterface');
+        $actual = $linkManagement->getLinkedItemsByType($productSku, $linkType);
+        $this->assertCount(1, $actual, 'Invalid actual linked products count');
+        $this->assertEquals(1000, $actual[0]->getPosition(), 'Product position is not updated');
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductLinkTypeListTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductLinkTypeListTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..3fb46da2b49e8c635499136c87d24a159b4557f0
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductLinkTypeListTest.php
@@ -0,0 +1,53 @@
+<?php
+/**
+ *
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Catalog\Api;
+
+use Magento\Catalog\Model\Product\Link;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+class ProductLinkTypeListTest extends \Magento\TestFramework\TestCase\WebapiAbstract
+{
+    const SERVICE_NAME = 'catalogProductLinkTypeListV1';
+    const SERVICE_VERSION = 'V1';
+    const RESOURCE_PATH = '/V1/products/';
+
+    public function testGetItems()
+    {
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . 'links/types',
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'GetItems',
+            ],
+        ];
+        $actual = $this->_webApiCall($serviceInfo);
+        $expectedItems = ['name' => 'related', 'code' => Link::LINK_TYPE_RELATED];
+        $this->assertContains($expectedItems, $actual);
+    }
+
+    public function testGetItemAttributes()
+    {
+        $linkType = 'related';
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . 'links/' . $linkType . '/attributes',
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'GetItemAttributes',
+            ],
+        ];
+        $actual = $this->_webApiCall($serviceInfo, ['type' => $linkType]);
+        $expected = [['code' => 'position', 'type' => 'int']];
+        $this->assertEquals($expected, $actual);
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductMediaAttributeManagementTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductMediaAttributeManagementTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..8939bf5c362a322708c760a29f64d055e686c8f0
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductMediaAttributeManagementTest.php
@@ -0,0 +1,60 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Catalog\Api;
+
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+class ProductMediaAttributeManagementTest extends WebapiAbstract
+{
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/attribute_set_with_image_attribute.php
+     */
+    public function testGetList()
+    {
+        $attributeSetName = 'attribute_set_with_media_attribute';
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/products/media/types/' . $attributeSetName,
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => 'catalogProductMediaAttributeManagementV1',
+                'serviceVersion' => 'V1',
+                'operation' => 'catalogProductMediaAttributeManagementV1GetList',
+            ],
+        ];
+
+        $requestData = [
+            'attributeSetName' => $attributeSetName,
+        ];
+
+        $mediaAttributes = $this->_webApiCall($serviceInfo, $requestData);
+
+        $this->assertNotEmpty($mediaAttributes);
+        $attribute = $this->getAttributeByCode($mediaAttributes,  'funny_image');
+        $this->assertNotNull($attribute);
+        $this->assertEquals('Funny image', $attribute['default_frontend_label']);
+        $this->assertEquals(1, $attribute['is_user_defined']);
+    }
+
+    /**
+     * Retrieve attribute based on given attribute code
+     *
+     * @param array $attributeList
+     * @param string $attributeCode
+     * @return array|null
+     */
+    protected function getAttributeByCode($attributeList, $attributeCode)
+    {
+        foreach ($attributeList as $attribute) {
+            if ($attributeCode == $attribute['attribute_code']) {
+                return $attribute;
+            }
+        }
+
+        return null;
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRepositoryInterfaceTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRepositoryInterfaceTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..7b9e84a65f4d1adc2bf76578c9dad4d3e77d70b4
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRepositoryInterfaceTest.php
@@ -0,0 +1,279 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Catalog\Api;
+
+use Magento\Catalog\Api\Data\ProductInterface;
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Exception as HTTPExceptionCodes;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+class ProductRepositoryInterfaceTest extends WebapiAbstract
+{
+    const SERVICE_NAME = 'catalogProductRepositoryV1';
+    const SERVICE_VERSION = 'V1';
+    const RESOURCE_PATH = '/V1/products';
+
+    private $productData = [
+        [
+            ProductInterface::SKU => 'simple',
+            ProductInterface::NAME => 'Simple Related Product',
+            ProductInterface::TYPE_ID => 'simple',
+            ProductInterface::PRICE => 10,
+        ],
+        [
+            ProductInterface::SKU => 'simple_with_cross',
+            ProductInterface::NAME => 'Simple Product With Related Product',
+            ProductInterface::TYPE_ID => 'simple',
+            ProductInterface::PRICE => 10
+        ],
+    ];
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/products_related.php
+     */
+    public function testGet()
+    {
+        $productData = $this->productData[0];
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/' . $productData[ProductInterface::SKU],
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Get',
+            ],
+        ];
+
+        $response = $this->_webApiCall($serviceInfo, ['productSku' => $productData[ProductInterface::SKU]]);
+        foreach ([ProductInterface::SKU, ProductInterface::NAME, ProductInterface::PRICE] as $key) {
+            $this->assertEquals($productData[$key], $response[$key]);
+        }
+    }
+
+    public function testGetNoSuchEntityException()
+    {
+        $invalidSku = '(nonExistingSku)';
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/' . $invalidSku,
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Get',
+            ],
+        ];
+
+        $expectedMessage = 'Requested product doesn\'t exist';
+
+        try {
+            $this->_webApiCall($serviceInfo, ['productSku' => $invalidSku]);
+            $this->fail("Expected throwing exception");
+        } catch (\SoapFault $e) {
+            $this->assertContains(
+                $expectedMessage,
+                $e->getMessage(),
+                "SoapFault does not contain expected message."
+            );
+        } catch (\Exception $e) {
+            $errorObj = $this->processRestExceptionResult($e);
+            $this->assertEquals($expectedMessage, $errorObj['message']);
+            $this->assertEquals(HTTPExceptionCodes::HTTP_NOT_FOUND, $e->getCode());
+        }
+    }
+
+    /**
+     * @return array
+     */
+    public function productCreationProvider()
+    {
+        $productBuilder = function ($data) {
+            return array_replace_recursive(
+                $this->getSimpleProductData(),
+                $data
+            );
+        };
+        return [
+            [$productBuilder([ProductInterface::TYPE_ID => 'simple', ProductInterface::SKU => 'psku-test-1'])],
+            [$productBuilder([ProductInterface::TYPE_ID => 'virtual', ProductInterface::SKU => 'psku-test-2'])],
+        ];
+    }
+
+    /**
+     * @dataProvider productCreationProvider
+     */
+    public function testCreate($product)
+    {
+        $response = $this->saveProduct($product);
+        $this->assertArrayHasKey(ProductInterface::SKU, $response);
+        $this->deleteProduct($product[ProductInterface::SKU]);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+     */
+    public function testUpdate()
+    {
+        $productData = [
+            ProductInterface::NAME => 'Very Simple Product', //new name
+            ProductInterface::SKU => 'simple', //sku from fixture
+        ];
+        $product = $this->getSimpleProductData($productData);
+        if (TESTS_WEB_API_ADAPTER == self::ADAPTER_REST) {
+            $product[ProductInterface::SKU] = null;
+        }
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/' . $productData[ProductInterface::SKU],
+                'httpMethod' => RestConfig::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Save',
+            ],
+        ];
+        $requestData = ['product' => $product];
+        $response =  $this->_webApiCall($serviceInfo, $requestData);
+
+        $this->assertArrayHasKey(ProductInterface::SKU, $response);
+        $this->assertArrayHasKey(ProductInterface::NAME, $response);
+        $this->assertEquals($productData[ProductInterface::NAME], $response[ProductInterface::NAME]);
+        $this->assertEquals($productData[ProductInterface::SKU], $response[ProductInterface::SKU]);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+     */
+    public function testDelete()
+    {
+        $response = $this->deleteProduct('simple');
+        $this->assertTrue($response);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+     */
+    public function testGetList()
+    {
+        $searchCriteria = [
+            'searchCriteria' => [
+                'filter_groups' => [
+                    [
+                        'filters' => [
+                            [
+                                'field' => 'sku',
+                                'value' => 'simple',
+                                'condition_type' => 'eq',
+                            ],
+                        ],
+                    ],
+                ],
+                'current_page' => 1,
+                'page_size' => 2,
+            ],
+        ];
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/',
+                'httpMethod' => RestConfig::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'GetList',
+            ],
+        ];
+
+        $response = $this->_webApiCall($serviceInfo, $searchCriteria);
+
+        $this->assertArrayHasKey('search_criteria', $response);
+        $this->assertArrayHasKey('total_count', $response);
+        $this->assertArrayHasKey('items', $response);
+
+        $this->assertEquals($searchCriteria['searchCriteria'], $response['search_criteria']);
+        $this->assertTrue($response['total_count'] > 0);
+        $this->assertTrue(count($response['items']) > 0);
+
+        $this->assertNotNull($response['items'][0]['sku']);
+        $this->assertEquals('simple', $response['items'][0]['sku']);
+    }
+
+    /**
+     * Get Simple Product Data
+     *
+     * @param array $productData
+     * @return array
+     */
+    protected function getSimpleProductData($productData = [])
+    {
+        return [
+            ProductInterface::SKU => isset($productData[ProductInterface::SKU])
+                ? $productData[ProductInterface::SKU] : uniqid('sku-', true),
+            ProductInterface::NAME => isset($productData[ProductInterface::NAME])
+                ? $productData[ProductInterface::NAME] : uniqid('sku-', true),
+            ProductInterface::VISIBILITY => 4,
+            ProductInterface::TYPE_ID => 'simple',
+            ProductInterface::PRICE => 3.62,
+            ProductInterface::STATUS => 1,
+            ProductInterface::TYPE_ID => 'simple',
+            ProductInterface::ATTRIBUTE_SET_ID => 4,
+            'custom_attributes' => [
+                ['attribute_code' => 'cost', 'value' => ''],
+                ['attribute_code' => 'description', 'value' => 'Description'],
+            ]
+        ];
+    }
+
+    /**
+     * @param $product
+     * @return mixed
+     */
+    protected function saveProduct($product)
+    {
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH,
+                'httpMethod' => RestConfig::HTTP_METHOD_POST,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Save',
+            ],
+        ];
+        $requestData = ['product' => $product];
+        return $this->_webApiCall($serviceInfo, $requestData);
+    }
+
+    /**
+     * Delete Product
+     *
+     * @param string $sku
+     * @return boolean
+     */
+    protected function deleteProduct($sku)
+    {
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/' . $sku,
+                'httpMethod' => RestConfig::HTTP_METHOD_DELETE,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'DeleteById',
+            ],
+        ];
+
+        return (TESTS_WEB_API_ADAPTER == self::ADAPTER_SOAP) ?
+            $this->_webApiCall($serviceInfo, ['productSku' => $sku]) : $this->_webApiCall($serviceInfo);
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductTierPriceManagementTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductTierPriceManagementTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..04f3d9c3d447f46be7318e207586eedf1134edef
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductTierPriceManagementTest.php
@@ -0,0 +1,205 @@
+<?php
+/**
+ *
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+namespace Magento\Catalog\Api;
+
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+class ProductTierPriceManagementTest extends WebapiAbstract
+{
+    const SERVICE_NAME = 'catalogProductTierPriceManagementV1';
+    const SERVICE_VERSION = 'V1';
+    const RESOURCE_PATH = '/V1/products/';
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+     * @dataProvider getListDataProvider
+     */
+    public function testGetList($customerGroupId, $count, $value, $qty)
+    {
+        $productSku = 'simple';
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . $productSku . '/group-prices/' . $customerGroupId . '/tiers',
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'GetList',
+            ],
+        ];
+
+        $groupPriceList = $this->_webApiCall(
+                $serviceInfo,
+                ['productSku' => $productSku, 'customerGroupId' => $customerGroupId]
+            );
+
+        $this->assertCount($count, $groupPriceList);
+        if ($count) {
+            $this->assertEquals($value, $groupPriceList[0]['value']);
+            $this->assertEquals($qty, $groupPriceList[0]['qty']);
+        }
+    }
+
+    public function getListDataProvider()
+    {
+        return [
+            [0, 1, 5, 3],
+            [1, 0, null, null],
+            ['all', 2, 8, 2],
+        ];
+    }
+
+    /**
+     * @param string|int $customerGroupId
+     * @param int $qty
+     * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+     * @dataProvider deleteDataProvider
+     */
+    public function testDelete($customerGroupId, $qty)
+    {
+        $productSku = 'simple';
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' =>   self::RESOURCE_PATH
+                    . $productSku . "/group-prices/" . $customerGroupId . "/tiers/" . $qty,
+                'httpMethod' => RestConfig::HTTP_METHOD_DELETE,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Remove',
+            ],
+        ];
+        $requestData = ['productSku' => $productSku, 'customerGroupId' => $customerGroupId, 'qty' => $qty];
+        $this->assertTrue($this->_webApiCall($serviceInfo, $requestData));
+    }
+
+    public function deleteDataProvider()
+    {
+        return [
+            'delete_tier_price_for_specific_customer_group' => [0, 3],
+            'delete_tier_price_for_all_customer_group' => ['all', 5]
+        ];
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+     * @magentoAppIsolation enabled
+     */
+    public function testAdd()
+    {
+        $productSku = 'simple';
+        $customerGroupId = 1;
+        $qty = 50;
+        $price = 10;
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/products/' . $productSku
+                    . '/group-prices/' . $customerGroupId . '/tiers/' . $qty . '/price/' . $price,
+                'httpMethod' => RestConfig::HTTP_METHOD_POST,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Add',
+            ],
+        ];
+
+        $requestData = [
+            'productSku' => $productSku,
+            'customerGroupId' => $customerGroupId,
+            'qty' => $qty,
+            'price' => $price,
+        ];
+        $this->_webApiCall($serviceInfo, $requestData);
+        $objectManager = \Magento\TestFramework\ObjectManager::getInstance();
+        /** @var \Magento\Catalog\Api\ProductTierPriceManagementInterface $service */
+        $service = $objectManager->get('Magento\Catalog\Api\ProductTierPriceManagementInterface');
+        $prices = $service->getList($productSku, 1);
+        $this->assertCount(1, $prices);
+        $this->assertEquals(10, $prices[0]->getValue());
+        $this->assertEquals(50, $prices[0]->getQty());
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+     * @magentoAppIsolation enabled
+     */
+    public function testAddWithAllCustomerGrouped()
+    {
+        $productSku = 'simple';
+        $customerGroupId = 'all';
+        $qty = 50;
+        $price = 20;
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/products/' . $productSku
+                    . '/group-prices/' . $customerGroupId . '/tiers/' . $qty . '/price/' . $price,
+                'httpMethod' => RestConfig::HTTP_METHOD_POST,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Add',
+            ],
+        ];
+        $requestData = [
+            'productSku' => $productSku,
+            'customerGroupId' => $customerGroupId,
+            'qty' => $qty,
+            'price' => $price,
+        ];
+        $this->_webApiCall($serviceInfo, $requestData);
+        $objectManager = \Magento\TestFramework\ObjectManager::getInstance();
+        /** @var \Magento\Catalog\Api\ProductTierPriceManagementInterface $service */
+        $service = $objectManager->get('Magento\Catalog\Api\ProductTierPriceManagementInterface');
+        $prices = $service->getList($productSku, 'all');
+        $this->assertCount(3, $prices);
+        $this->assertEquals(20, (int)$prices[2]->getValue());
+        $this->assertEquals(50, (int)$prices[2]->getQty());
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+     * @magentoAppIsolation enabled
+     */
+    public function testUpdateWithAllGroups()
+    {
+        $productSku = 'simple';
+        $customerGroupId = 'all';
+        $qty = 2;
+        $price = 20;
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/products/' . $productSku
+                    . '/group-prices/' . $customerGroupId . '/tiers/' . $qty . '/price/' . $price,
+                'httpMethod' => RestConfig::HTTP_METHOD_POST,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Add',
+            ],
+        ];
+        $requestData = [
+            'productSku' => $productSku,
+            'customerGroupId' => $customerGroupId,
+            'qty' => $qty,
+            'price' => $price,
+        ];
+        $this->_webApiCall($serviceInfo, $requestData);
+        $objectManager = \Magento\TestFramework\ObjectManager::getInstance();
+        /** @var \Magento\Catalog\Api\ProductTierPriceManagementInterface $service */
+        $service = $objectManager->get('Magento\Catalog\Api\ProductTierPriceManagementInterface');
+        $prices = $service->getList($productSku, 'all');
+        $this->assertCount(2, $prices);
+        $this->assertEquals(20, (int)$prices[0]->getValue());
+        $this->assertEquals(2, (int)$prices[0]->getQty());
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductTypeListTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductTypeListTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..da15ba53f9221093f39b9154879fe9f8bddc1f9c
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductTypeListTest.php
@@ -0,0 +1,59 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Catalog\Api;
+
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+class ProductTypeListTest extends WebapiAbstract
+{
+    const SERVICE_NAME = 'catalogProductTypeListV1';
+    const SERVICE_VERSION = 'V1';
+    const RESOURCE_PATH = '/V1/products/';
+
+    public function testGetProductTypes()
+    {
+        $expectedProductTypes = [
+            [
+                'name' => 'simple',
+                'label' => 'Simple Product',
+            ],
+            [
+                'name' => 'virtual',
+                'label' => 'Virtual Product',
+            ],
+            [
+                'name' => 'downloadable',
+                'label' => 'Downloadable Product',
+            ],
+            [
+                'name' => 'bundle',
+                'label' => 'Bundle Product',
+            ],
+            [
+                'name' => 'configurable',
+                'label' => 'Configurable Product',
+            ],
+        ];
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/products/types',
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'GetProductTypes',
+            ],
+        ];
+
+        $productTypes = $this->_webApiCall($serviceInfo);
+
+        foreach ($expectedProductTypes as $expectedProductType) {
+            $this->assertContains($expectedProductType, $productTypes);
+        }
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/_files/product_options.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/_files/product_options.php
new file mode 100644
index 0000000000000000000000000000000000000000..a302a11ae9388df415cb27d9f667edc997149ab0
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/_files/product_options.php
@@ -0,0 +1,162 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+return [
+    [
+        'title' => 'test_option_code_1',
+        'type' => 'field',
+        'sort_order' => 1,
+        'is_require' => 1,
+        'price' => 10,
+        'price_type' => 'fixed',
+        'sku' => 'sku1',
+        'max_characters' => 10,
+        'values' => [],
+    ],
+    [
+        'title' => 'area option',
+        'type' => 'area',
+        'sort_order' => 2,
+        'is_require' => 0,
+        'price' => 20,
+        'price_type' => 'percent',
+        'sku' => 'sku2',
+        'max_characters' => 20,
+        'values' => []
+    ],
+    [
+        'title' => 'file option',
+        'type' => 'file',
+        'sort_order' => 3,
+        'is_require' => 1,
+        'price' => 30,
+        'price_type' => 'percent',
+        'sku' => 'sku3',
+        'file_extension' => 'jpg, png, gif',
+        'image_size_x' => 10,
+        'image_size_y' => 20,
+        'values' => []
+    ],
+    [
+        'title' => 'drop_down option',
+        'type' => 'drop_down',
+        'sort_order' => 4,
+        'is_require' => 1,
+        'values' => [
+            [
+                'title' => 'drop_down option 1',
+                'sort_order' => 1,
+                'price' => 10,
+                'price_type' => 'fixed',
+                'sku' => 'drop_down option 1 sku',
+
+            ],
+            [
+                'title' => 'drop_down option 2',
+                'sort_order' => 2,
+                'price' => 20,
+                'price_type' => 'fixed',
+                'sku' => 'drop_down option 2 sku'
+            ],
+        ],
+    ],
+    [
+        'title' => 'radio option',
+        'type' => 'radio',
+        'sort_order' => 5,
+        'is_require' => 1,
+        'values' => [
+            [
+                'title' => 'radio option 1',
+                'sort_order' => 1,
+                'price' => 10,
+                'price_type' => 'fixed',
+                'sku' => 'radio option 1 sku',
+            ],
+            [
+                'title' => 'radio option 2',
+                'sort_order' => 2,
+                'price' => 20,
+                'price_type' => 'fixed',
+                'sku' => 'radio option 2 sku',
+            ],
+        ],
+    ],
+    [
+        'title' => 'checkbox option',
+        'type' => 'checkbox',
+        'sort_order' => 6,
+        'is_require' => 1,
+        'values' => [
+            [
+                'title' => 'checkbox option 1',
+                'sort_order' => 1,
+                'price' => 10,
+                'price_type' => 'fixed',
+                'sku' => 'checkbox option 1 sku',
+            ],
+            [
+                'title' => 'checkbox option 2',
+                'sort_order' => 2,
+                'price' => 20,
+                'price_type' => 'fixed',
+                'sku' => 'checkbox option 2 sku'
+            ],
+        ],
+    ],
+    [
+        'title' => 'multiple option',
+        'type' => 'multiple',
+        'sort_order' => 7,
+        'is_require' => 1,
+        'values' => [
+            [
+                'title' => 'multiple option 1',
+                'sort_order' => 1,
+                'price' => 10,
+                'price_type' => 'fixed',
+                'sku' => 'multiple option 1 sku',
+            ],
+            [
+                'title' => 'multiple option 2',
+                'sort_order' => 2,
+                'price' => 20,
+                'price_type' => 'fixed',
+                'sku' => 'multiple option 2 sku'
+            ],
+        ],
+    ],
+    [
+        'title' => 'date option',
+        'type' => 'date',
+        'is_require' => 1,
+        'sort_order' => 8,
+        'price' => 80.0,
+        'price_type' => 'fixed',
+        'sku' => 'date option sku',
+        'values' => []
+
+    ],
+    [
+        'title' => 'date_time option',
+        'type' => 'date_time',
+        'is_require' => 1,
+        'sort_order' => 9,
+        'price' => 90.0,
+        'price_type' => 'fixed',
+        'sku' => 'date_time option sku',
+        'values' => []
+    ],
+    [
+        'title' => 'time option',
+        'type' => 'time',
+        'is_require' => 1,
+        'sort_order' => 10,
+        'price' => 100.0,
+        'price_type' => 'fixed',
+        'sku' => 'time option sku',
+        'values' => []
+    ],
+];
diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/_files/product_options_negative.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/_files/product_options_negative.php
new file mode 100644
index 0000000000000000000000000000000000000000..24fb82474ec0acce36ff5e886f657bb29730a276
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/_files/product_options_negative.php
@@ -0,0 +1,91 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+return [
+    'empty_required_field' => [
+        'title' => '',
+        'type' => 'field',
+        'sort_order' => 1,
+        'is_require' => 1,
+        'price' => 10,
+        'price_type' => 'fixed',
+        'sku' => 'sku1',
+        'max_characters' => 10,
+    ],
+    'negative_price' => [
+        'title' => 'area option',
+        'type' => 'area',
+        'sort_order' => 2,
+        'is_require' => 0,
+        'price' => -20,
+        'price_type' => 'percent',
+        'sku' => 'sku2',
+        'max_characters' => 20,
+
+    ],
+    'negative_value_of_image_size' => [
+        'title' => 'file option',
+        'type' => 'file',
+        'sort_order' => 3,
+        'is_require' => 1,
+        'price' => 30,
+        'price_type' => 'percent',
+        'sku' => 'sku3',
+        'file_extension' => 'jpg',
+        'image_size_x' => -10,
+        'image_size_y' => -20,
+    ],
+    'option_with_type_select_without_options' => [
+        'title' => 'drop_down option',
+        'type' => 'drop_down',
+        'sort_order' => 4,
+        'is_require' => 1,
+    ],
+    'title_is_empty' => [
+        'title' => 'radio option',
+        'type' => 'radio',
+        'sort_order' => 5,
+        'is_require' => 1,
+        'values' => [
+            [
+                'price' => 10,
+                'price_type' => 'fixed',
+                'sku' => 'radio option 1 sku',
+                'title' => '',
+                'sort_order' => 1,
+            ],
+        ],
+    ],
+    'option_with_non_existing_price_type' => [
+        'title' => 'checkbox option',
+        'type' => 'checkbox',
+        'sort_order' => 6,
+        'is_require' => 1,
+        'values' => [
+            [
+                'price' => 10,
+                'price_type' => 'fixed_one',
+                'sku' => 'checkbox option 1 sku',
+                'title' => 'checkbox option 1',
+                'sort_order' => 1,
+            ],
+        ],
+    ],
+    'option_with_non_existing_option_type' => [
+        'title' => 'multiple option',
+        'type' => 'multiple_some_value',
+        'sort_order' => 7,
+        'is_require' => 1,
+        'values' => [
+            [
+                'price' => 10,
+                'price_type' => 'fixed',
+                'sku' => 'multiple option 1 sku',
+                'title' => 'multiple option 1',
+                'sort_order' => 1,
+            ],
+        ],
+    ],
+];
diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/_files/test_image.jpg b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/_files/test_image.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..ad6b747f73dd9d20f73b8fc780e74d1ff7952762
Binary files /dev/null and b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/_files/test_image.jpg differ
diff --git a/dev/tests/api-functional/testsuite/Magento/CatalogInventory/Api/LowStockItemsTest.php b/dev/tests/api-functional/testsuite/Magento/CatalogInventory/Api/LowStockItemsTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..67c42fe79b822a94d69b605b2a3bb5496bd9a0a4
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/CatalogInventory/Api/LowStockItemsTest.php
@@ -0,0 +1,79 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\CatalogInventory\Api;
+
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+/**
+ * Class LowStockItemsTest
+ */
+class LowStockItemsTest extends WebapiAbstract
+{
+    const SERVICE_VERSION = 'V1';
+    const RESOURCE_PATH = '/V1/stockItem/lowStock/';
+
+    /**
+     * @param float $qty
+     * @param int $currentPage
+     * @param int $pageSize
+     * @param array $result
+     * @magentoApiDataFixture Magento/Catalog/_files/multiple_products.php
+     * @dataProvider getLowStockItemsDataProvider
+     */
+    public function testGetLowStockItems($qty, $currentPage, $pageSize, $result)
+    {
+        $requestData = ['websiteId' => 1, 'qty' => $qty, 'pageSize' => $pageSize, 'currentPage' => $currentPage];
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '?' . http_build_query($requestData),
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => 'catalogInventoryStockRegistryV1',
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => 'catalogInventoryStockRegistryV1GetLowStockItems',
+            ],
+        ];
+        $output = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertArrayHasKey('items', $output);
+    }
+
+    /**
+     * @return array
+     */
+    public function getLowStockItemsDataProvider()
+    {
+        return [
+            [
+                100,
+                1,
+                10,
+                [
+                    'search_criteria' => ['current_page' => 1, 'page_size' => 10, 'qty' => 100],
+                    'total_count' => 2,
+                    'items' => [
+                        [
+                            'product_id' => 10,
+                            'website_id' => 1,
+                            'stock_id' => 1,
+                            'qty' => 100,
+                            'stock_status' => null,
+                            'stock_item' => null,
+                        ],
+                        [
+                            'product_id' => 12,
+                            'website_id' => 1,
+                            'stock_id' => 1,
+                            'qty' => 140,
+                            'stock_status' => null,
+                            'stock_item' => null
+                        ],
+                    ]
+                ],
+            ],
+        ];
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/CatalogInventory/Api/StockItemTest.php b/dev/tests/api-functional/testsuite/Magento/CatalogInventory/Api/StockItemTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..bab5b01aa08d8c107023d11b69fe9d6a89037d42
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/CatalogInventory/Api/StockItemTest.php
@@ -0,0 +1,222 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\CatalogInventory\Api;
+
+use Magento\TestFramework\Helper\Bootstrap;
+use Magento\TestFramework\TestCase\WebapiAbstract;
+
+/**
+ * Class StockItemTest
+ */
+class StockItemTest extends WebapiAbstract
+{
+    /**
+     * Service name
+     */
+    const SERVICE_NAME = 'catalogInventoryStockItemApi';
+
+    /**
+     * Service version
+     */
+    const SERVICE_VERSION = 'V1';
+
+    /**
+     * Resource path
+     */
+    const RESOURCE_PATH = '/V1/stockItem';
+
+    /** @var \Magento\Catalog\Model\Resource\Product\Collection */
+    protected $productCollection;
+
+    /** @var \Magento\Framework\ObjectManagerInterface */
+    protected $objectManager;
+
+    /**
+     * Execute per test initialization
+     */
+    public function setUp()
+    {
+        $this->objectManager = Bootstrap::getObjectManager();
+        $this->productCollection = $this->objectManager->get('Magento\Catalog\Model\Resource\Product\Collection');
+    }
+
+    /**
+     * Execute per test cleanup
+     */
+    public function tearDown()
+    {
+        /** @var \Magento\Framework\Registry $registry */
+        $registry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get('Magento\Framework\Registry');
+
+        $registry->unregister('isSecureArea');
+        $registry->register('isSecureArea', true);
+
+        $this->productCollection->addFieldToFilter('entity_id', ['in' => [10, 11, 12]])->delete();
+        unset($this->productCollection);
+
+        $registry->unregister('isSecureArea');
+        $registry->register('isSecureArea', false);
+    }
+
+    /**
+     * @param array $result
+     * @return array
+     */
+    protected function getStockItemBySku($result)
+    {
+        $productSku = 'simple1';
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . "/$productSku",
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => 'catalogInventoryStockRegistryV1',
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => 'catalogInventoryStockRegistryV1GetStockItemBySku',
+            ],
+        ];
+        $arguments = ['productSku' => $productSku];
+        $apiResult = $this->_webApiCall($serviceInfo, $arguments);
+        $result['item_id'] = $apiResult['item_id'];
+        $this->assertEquals($result, $apiResult, 'The stock data does not match.');
+        return $apiResult;
+    }
+
+    /**
+     * @param array $newData
+     * @param array $expectedResult
+     * @param array $fixtureData
+     * @magentoApiDataFixture Magento/Catalog/_files/multiple_products.php
+     * @dataProvider saveStockItemBySkuWithWrongInputDataProvider
+     */
+    public function testStockItemPUTWithWrongInput($newData, $expectedResult, $fixtureData)
+    {
+        $stockItemOld = $this->getStockItemBySku($fixtureData);
+        $productSku = 'simple1';
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . "/$productSku",
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => 'catalogInventoryStockRegistryV1',
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => 'catalogInventoryStockRegistryV1UpdateStockItemBySku',
+            ],
+        ];
+
+        $stockItemDetailsDo = $this->objectManager->get(
+            'Magento\CatalogInventory\Api\Data\StockItemInterfaceBuilder'
+        )->populateWithArray($newData)->create();
+        $arguments = ['productSku' => $productSku, 'stockItem' => $stockItemDetailsDo->getData()];
+        $this->assertEquals($stockItemOld['item_id'], $this->_webApiCall($serviceInfo, $arguments));
+
+        $stockItemFactory = $this->objectManager->get('Magento\CatalogInventory\Api\Data\StockItemInterfaceFactory');
+        $stockItem = $stockItemFactory->create();
+        $stockItemResource = $this->objectManager->get('Magento\CatalogInventory\Model\Resource\Stock\Item');
+        $stockItemResource->loadByProductId($stockItem, $stockItemOld['product_id'], $stockItemOld['website_id']);
+        $expectedResult['item_id'] = $stockItem->getItemId();
+        $this->assertEquals($expectedResult, $stockItem->getData());
+    }
+
+    /**
+     * @return array
+     */
+    public function saveStockItemBySkuWithWrongInputDataProvider()
+    {
+        return [
+            [
+                [
+                    'item_id' => 222,
+                    'product_id' => 222,
+                    'stock_id' => 1,
+                    'website_id' => 1,
+                    'qty' => '111.0000',
+                    'min_qty' => '0.0000',
+                    'use_config_min_qty' => 1,
+                    'is_qty_decimal' => 0,
+                    'backorders' => 0,
+                    'use_config_backorders' => 1,
+                    'min_sale_qty' => '1.0000',
+                    'use_config_min_sale_qty' => 1,
+                    'max_sale_qty' => '0.0000',
+                    'use_config_max_sale_qty' => 1,
+                    'is_in_stock' => 1,
+                    'low_stock_date' => '',
+                    'notify_stock_qty' => NULL,
+                    'use_config_notify_stock_qty' => 1,
+                    'manage_stock' => 0,
+                    'use_config_manage_stock' => 1,
+                    'stock_status_changed_auto' => 0,
+                    'use_config_qty_increments' => 1,
+                    'qty_increments' => '0.0000',
+                    'use_config_enable_qty_inc' => 1,
+                    'enable_qty_increments' => 0,
+                    'is_decimal_divided' => 0,
+                    'show_default_notification_message' => false,
+                ],
+                [
+                    'item_id' => '1',
+                    'product_id' => '10',
+                    'stock_id' => '1',
+                    'qty' => '111.0000',
+                    'min_qty' => '0.0000',
+                    'use_config_min_qty' => '1',
+                    'is_qty_decimal' => '0',
+                    'backorders' => '0',
+                    'use_config_backorders' => '1',
+                    'min_sale_qty' => '1.0000',
+                    'use_config_min_sale_qty' => '1',
+                    'max_sale_qty' => '0.0000',
+                    'use_config_max_sale_qty' => '1',
+                    'is_in_stock' => '1',
+                    'low_stock_date' => NULL,
+                    'notify_stock_qty' => NULL,
+                    'use_config_notify_stock_qty' => '1',
+                    'manage_stock' => '0',
+                    'use_config_manage_stock' => '1',
+                    'stock_status_changed_auto' => '0',
+                    'use_config_qty_increments' => '1',
+                    'qty_increments' => '0.0000',
+                    'use_config_enable_qty_inc' => '1',
+                    'enable_qty_increments' => '0',
+                    'is_decimal_divided' => '0',
+                    'website_id' => '1',
+                    'type_id' => 'simple',
+                ],
+                [
+                    'item_id' => 1,
+                    'product_id' => 10,
+                    'website_id' => 1,
+                    'stock_id' => 1,
+                    'qty' => 100,
+                    'is_in_stock' => 1,
+                    'is_qty_decimal' => '',
+                    'show_default_notification_message' => '',
+                    'use_config_min_qty' => 1,
+                    'min_qty' => 0,
+                    'use_config_min_sale_qty' => 1,
+                    'min_sale_qty' => 1,
+                    'use_config_max_sale_qty' => 1,
+                    'max_sale_qty' => 10000,
+                    'use_config_backorders' => 1,
+                    'backorders' => 0,
+                    'use_config_notify_stock_qty' => 1,
+                    'notify_stock_qty' => 1,
+                    'use_config_qty_increments' => 1,
+                    'qty_increments' => 0,
+                    'use_config_enable_qty_inc' => 1,
+                    'enable_qty_increments' => '',
+                    'use_config_manage_stock' => 1,
+                    'manage_stock' => 1,
+                    'low_stock_date' => 0,
+                    'is_decimal_divided' => '',
+                    'stock_status_changed_auto' => 0
+                ],
+            ],
+        ];
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/CatalogInventory/Api/StockStatusTest.php b/dev/tests/api-functional/testsuite/Magento/CatalogInventory/Api/StockStatusTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..1ab00d4b085b5313427d6828afdbc5863317d092
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/CatalogInventory/Api/StockStatusTest.php
@@ -0,0 +1,48 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\CatalogInventory\Api;
+
+use Magento\TestFramework\Helper\Bootstrap;
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+/**
+ * Class StockStatusTest
+ */
+class StockStatusTest extends WebapiAbstract
+{
+    const SERVICE_VERSION = 'V1';
+    const RESOURCE_PATH = '/V1/stockStatus';
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+     */
+    public function testGetProductStockStatus()
+    {
+        $productSku = 'simple';
+        $objectManager = Bootstrap::getObjectManager();
+
+        /** @var \Magento\Catalog\Model\Product $product */
+        $product = $objectManager->get('Magento\Catalog\Model\Product')->load(1);
+        $expectedData = $product->getQuantityAndStockStatus();
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . "/$productSku",
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => 'catalogInventoryStockRegistryV1',
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => 'catalogInventoryStockRegistryV1GetStockStatusBySku',
+            ],
+        ];
+
+        $requestData = ['productSku' => $productSku];
+        $actualData = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertArrayHasKey('stock_item', $actualData);
+        $this->assertEquals($expectedData['is_in_stock'], $actualData['stock_item']['is_in_stock']);
+        $this->assertEquals($expectedData['qty'], $actualData['stock_item']['qty']);
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Checkout/Service/V1/Address/Billing/ReadServiceTest.php b/dev/tests/api-functional/testsuite/Magento/Checkout/Service/V1/Address/Billing/ReadServiceTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..961d7caf314fc7b61cd83da7cc945bbbe7dc006f
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Checkout/Service/V1/Address/Billing/ReadServiceTest.php
@@ -0,0 +1,85 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+namespace Magento\Checkout\Service\V1\Address\Billing;
+
+use Magento\Checkout\Service\V1\Data\Cart\Address;
+use Magento\Checkout\Service\V1\Data\Cart\Address\Region;
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+class ReadServiceTest extends WebapiAbstract
+{
+    const SERVICE_VERSION = 'V1';
+    const SERVICE_NAME = 'checkoutAddressBillingReadServiceV1';
+    const RESOURCE_PATH = '/V1/carts/';
+
+    /**
+     * @var \Magento\TestFramework\ObjectManager
+     */
+    protected $objectManager;
+
+    protected function setUp()
+    {
+        $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php
+     */
+    public function testGetAddress()
+    {
+        $quote = $this->objectManager->create('Magento\Sales\Model\Quote');
+        $quote->load('test_order_1', 'reserved_order_id');
+
+        /** @var \Magento\Sales\Model\Quote\Address $address */
+        $address = $quote->getBillingAddress();
+
+        $data = [
+            Address::KEY_COUNTRY_ID => $address->getCountryId(),
+            Address::KEY_ID => (int)$address->getId(),
+            Address::KEY_CUSTOMER_ID => $address->getCustomerId(),
+            Address::KEY_REGION => [
+                Region::REGION => $address->getRegion(),
+                Region::REGION_ID => $address->getRegionId(),
+                Region::REGION_CODE => $address->getRegionCode(),
+            ],
+            Address::KEY_STREET => $address->getStreet(),
+            Address::KEY_COMPANY => $address->getCompany(),
+            Address::KEY_TELEPHONE => $address->getTelephone(),
+            Address::KEY_POSTCODE => $address->getPostcode(),
+            Address::KEY_CITY => $address->getCity(),
+            Address::KEY_FIRSTNAME => $address->getFirstname(),
+            Address::KEY_LASTNAME => $address->getLastname(),
+            Address::KEY_EMAIL => $address->getEmail(),
+            Address::CUSTOM_ATTRIBUTES_KEY => [['attribute_code' => 'disable_auto_group_change', 'value' => null]],
+        ];
+
+        $cartId = $quote->getId();
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . $cartId . '/billing-address',
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'GetAddress',
+            ],
+        ];
+
+        if (TESTS_WEB_API_ADAPTER == self::ADAPTER_SOAP) {
+            unset($data[Address::KEY_PREFIX]);
+            unset($data[Address::KEY_SUFFIX]);
+            unset($data[Address::KEY_MIDDLENAME]);
+            unset($data[Address::KEY_FAX]);
+            unset($data[Address::KEY_VAT_ID]);
+        }
+
+        $requestData = ["cartId" => $cartId];
+        $this->assertEquals($data, $this->_webApiCall($serviceInfo, $requestData));
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Checkout/Service/V1/Address/Billing/WriteServiceTest.php b/dev/tests/api-functional/testsuite/Magento/Checkout/Service/V1/Address/Billing/WriteServiceTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..d69ab59b5d99cb1dee9fee7af0b62886a67d741b
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Checkout/Service/V1/Address/Billing/WriteServiceTest.php
@@ -0,0 +1,97 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+namespace Magento\Checkout\Service\V1\Address\Billing;
+
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+class WriteServiceTest extends WebapiAbstract
+{
+    const SERVICE_VERSION = 'V1';
+    const SERVICE_NAME = 'checkoutAddressBillingWriteServiceV1';
+    const RESOURCE_PATH = '/V1/carts/';
+
+    /**
+     * @var \Magento\TestFramework\ObjectManager
+     */
+    protected $objectManager;
+
+    /**
+     * @var \Magento\Checkout\Service\V1\Data\Cart\AddressBuilder
+     */
+    protected $builder;
+
+    protected function setUp()
+    {
+        $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+        $this->builder = $this->objectManager->create('Magento\Checkout\Service\V1\Data\Cart\AddressBuilder');
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php
+     */
+    public function testSetAddress()
+    {
+        /** @var \Magento\Sales\Model\Quote $quote */
+        $quote = $this->objectManager->create('Magento\Sales\Model\Quote');
+        $quote->load('test_order_1', 'reserved_order_id');
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . $quote->getId() . '/billing-address',
+                'httpMethod' => RestConfig::HTTP_METHOD_POST,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'SetAddress',
+            ],
+        ];
+
+        $addressData = [
+            'firstname' => 'John',
+            'lastname' => 'Smith',
+            'email' => 'cat@dog.com',
+            'company' => 'eBay Inc',
+            'street' => ['Typical Street', 'Tiny House 18'],
+            'city' => 'Big City',
+            'region' => [
+                'region_id' => 12,
+                'region' => 'California',
+                'region_code' => 'CA',
+            ],
+            'postcode' => '0985432',
+            'country_id' => 'US',
+            'telephone' => '88776655',
+            'fax' => '44332255',
+        ];
+        $requestData = [
+            "cartId" => $quote->getId(),
+            'addressData' => $addressData,
+        ];
+
+        $addressId = $this->_webApiCall($serviceInfo, $requestData);
+
+        //reset $quote to reload data
+        $quote = $this->objectManager->create('Magento\Sales\Model\Quote');
+        $quote->load('test_order_1', 'reserved_order_id');
+        $savedData  = $quote->getBillingAddress()->getData();
+        $this->assertEquals($addressId, $savedData['address_id']);
+        //custom checks for street, region and address_type
+        foreach ($addressData['street'] as $streetLine) {
+            $this->assertContains($streetLine, $quote->getBillingAddress()->getStreet());
+        }
+        unset($addressData['street']);
+        $this->assertEquals($addressData['region']['region_id'], $savedData['region_id']);
+        $this->assertEquals($addressData['region']['region'], $savedData['region']);
+        unset($addressData['region']);
+        $this->assertEquals('billing', $savedData['address_type']);
+        //check the rest of fields
+        foreach ($addressData as $key => $value) {
+            $this->assertEquals($value, $savedData[$key]);
+        }
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Checkout/Service/V1/Address/Shipping/ReadServiceTest.php b/dev/tests/api-functional/testsuite/Magento/Checkout/Service/V1/Address/Shipping/ReadServiceTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..da2908227abafe78327683472aa43680bfb6e955
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Checkout/Service/V1/Address/Shipping/ReadServiceTest.php
@@ -0,0 +1,111 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+namespace Magento\Checkout\Service\V1\Address\Shipping;
+
+use Magento\Checkout\Service\V1\Data\Cart\Address;
+use Magento\Checkout\Service\V1\Data\Cart\Address\Region;
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+class ReadServiceTest extends WebapiAbstract
+{
+    const SERVICE_VERSION = 'V1';
+    const SERVICE_NAME = 'checkoutAddressShippingReadServiceV1';
+    const RESOURCE_PATH = '/V1/carts/';
+
+    /**
+     * @var \Magento\TestFramework\ObjectManager
+     */
+    protected $objectManager;
+
+    protected function setUp()
+    {
+        $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php
+     */
+    public function testGetAddress()
+    {
+        $quote = $this->objectManager->create('Magento\Sales\Model\Quote');
+        $quote->load('test_order_1', 'reserved_order_id');
+
+        /** @var \Magento\Sales\Model\Quote\Address  $address */
+        $address = $quote->getShippingAddress();
+
+        $data = [
+            Address::KEY_COUNTRY_ID => $address->getCountryId(),
+            Address::KEY_ID => (int)$address->getId(),
+            Address::KEY_CUSTOMER_ID => $address->getCustomerId(),
+            Address::KEY_REGION => [
+                Region::REGION => $address->getRegion(),
+                Region::REGION_ID => $address->getRegionId(),
+                Region::REGION_CODE => $address->getRegionCode(),
+            ],
+            Address::KEY_STREET => $address->getStreet(),
+            Address::KEY_COMPANY => $address->getCompany(),
+            Address::KEY_TELEPHONE => $address->getTelephone(),
+            Address::KEY_POSTCODE => $address->getPostcode(),
+            Address::KEY_CITY => $address->getCity(),
+            Address::KEY_FIRSTNAME => $address->getFirstname(),
+            Address::KEY_LASTNAME => $address->getLastname(),
+            Address::KEY_EMAIL => $address->getEmail(),
+            Address::CUSTOM_ATTRIBUTES_KEY => [['attribute_code' => 'disable_auto_group_change', 'value' => null]],
+        ];
+
+        $cartId = $quote->getId();
+
+        $serviceInfo = $this->getServiceInfo();
+        $serviceInfo['rest']['resourcePath'] = str_replace('{cart_id}', $cartId, $serviceInfo['rest']['resourcePath']);
+
+        if (TESTS_WEB_API_ADAPTER == self::ADAPTER_SOAP) {
+            unset($data[Address::KEY_PREFIX]);
+            unset($data[Address::KEY_SUFFIX]);
+            unset($data[Address::KEY_MIDDLENAME]);
+            unset($data[Address::KEY_FAX]);
+            unset($data[Address::KEY_VAT_ID]);
+        }
+
+        $requestData = ["cartId" => $cartId];
+        $this->assertEquals($data, $this->_webApiCall($serviceInfo, $requestData));
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Checkout/_files/quote_with_virtual_product_and_address.php
+     *
+     * @expectedException \Exception
+     * @expectedExceptionMessage Cart contains virtual product(s) only. Shipping address is not applicable
+     */
+    public function testGetAddressOfQuoteWithVirtualProduct()
+    {
+        $quote = $this->objectManager->create('Magento\Sales\Model\Quote');
+        $cartId = $quote->load('test_order_with_virtual_product', 'reserved_order_id')->getId();
+
+        $serviceInfo = $this->getServiceInfo();
+        $serviceInfo['rest']['resourcePath'] = str_replace('{cart_id}', $cartId, $serviceInfo['rest']['resourcePath']);
+
+        $this->_webApiCall($serviceInfo, ["cartId" => $cartId]);
+    }
+
+    /**
+     * @return array
+     */
+    protected function getServiceInfo()
+    {
+        return $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '{cart_id}/shipping-address',
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'GetAddress',
+            ],
+        ];
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Checkout/Service/V1/Address/Shipping/WriteServiceTest.php b/dev/tests/api-functional/testsuite/Magento/Checkout/Service/V1/Address/Shipping/WriteServiceTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..1a8e322508d99d34ad92bdf17d759094fe96af86
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Checkout/Service/V1/Address/Shipping/WriteServiceTest.php
@@ -0,0 +1,147 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+namespace Magento\Checkout\Service\V1\Address\Shipping;
+
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+class WriteServiceTest extends WebapiAbstract
+{
+    const SERVICE_VERSION = 'V1';
+    const SERVICE_NAME = 'checkoutAddressShippingWriteServiceV1';
+    const RESOURCE_PATH = '/V1/carts/';
+
+    /**
+     * @var \Magento\TestFramework\ObjectManager
+     */
+    protected $objectManager;
+
+    /**
+     * @var \Magento\Checkout\Service\V1\Data\Cart\AddressBuilder
+     */
+    protected $builder;
+
+    protected function setUp()
+    {
+        $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+        $this->builder = $this->objectManager->create('Magento\Checkout\Service\V1\Data\Cart\AddressBuilder');
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php
+     */
+    public function testSetAddress()
+    {
+        /** @var \Magento\Sales\Model\Quote $quote */
+        $quote = $this->objectManager->create('Magento\Sales\Model\Quote');
+        $quote->load('test_order_1', 'reserved_order_id');
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . $quote->getId() . '/shipping-address',
+                'httpMethod' => RestConfig::HTTP_METHOD_POST,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'SetAddress',
+            ],
+        ];
+
+        $addressData = [
+            'firstname' => 'John',
+            'lastname' => 'Smith',
+            'email' => 'cat@dog.com',
+            'company' => 'eBay Inc',
+            'street' => ['Typical Street', 'Tiny House 18'],
+            'city' => 'Big City',
+            'region' => [
+                'region_id' => 12,
+                'region' => 'California',
+                'region_code' => 'CA',
+            ],
+            'postcode' => '0985432',
+            'country_id' => 'US',
+            'telephone' => '88776655',
+            'fax' => '44332255',
+        ];
+        $requestData = [
+            "cartId" => $quote->getId(),
+            'addressData' => $addressData,
+        ];
+
+        $addressId = $this->_webApiCall($serviceInfo, $requestData);
+
+        //reset $quote to reload data
+        $quote = $this->objectManager->create('Magento\Sales\Model\Quote');
+        $quote->load('test_order_1', 'reserved_order_id');
+        $savedData  = $quote->getShippingAddress()->getData();
+        $this->assertEquals($addressId, $savedData['address_id']);
+        $this->assertEquals(0, $savedData['same_as_billing']);
+        //custom checks for street, region and address_type
+        $this->assertEquals($addressData['street'], $quote->getShippingAddress()->getStreet());
+        unset($addressData['street']);
+        $this->assertEquals($addressData['region']['region_id'], $savedData['region_id']);
+        $this->assertEquals($addressData['region']['region'], $savedData['region']);
+        unset($addressData['region']);
+        $this->assertEquals('shipping', $savedData['address_type']);
+        //check the rest of fields
+        foreach ($addressData as $key => $value) {
+            $this->assertEquals($value, $savedData[$key]);
+        }
+    }
+
+    /**
+     * Set address to quote with virtual products only
+     *
+     * @expectedException \Exception
+     * @expectedExceptionMessage Cart contains virtual product(s) only. Shipping address is not applicable
+     *
+     * @magentoApiDataFixture Magento/Checkout/_files/quote_with_virtual_product_and_address.php
+     */
+    public function testSetAddressForVirtualQuote()
+    {
+        /** @var \Magento\Sales\Model\Quote $quote */
+        $quote = $this->objectManager->create('Magento\Sales\Model\Quote');
+        $quote->load('test_order_with_virtual_product', 'reserved_order_id');
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . $quote->getId() . '/shipping-address',
+                'httpMethod' => RestConfig::HTTP_METHOD_POST,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'SetAddress',
+            ],
+        ];
+
+        $addressData = [
+            'firstname' => 'John',
+            'lastname' => 'Smith',
+            'email' => 'cat@dog.com',
+            'company' => 'eBay Inc',
+            'street' => ['Typical Street', 'Tiny House 18'],
+            'city' => 'Big City',
+            'region' => [
+                'region_id' => 12,
+                'region' => 'California',
+                'region_code' => 'CA',
+            ],
+            'postcode' => '0985432',
+            'country_id' => 'US',
+            'telephone' => '88776655',
+            'fax' => '44332255',
+        ];
+        $requestData = [
+            "cartId" => $quote->getId(),
+            'addressData' => $addressData,
+        ];
+
+        $this->_webApiCall($serviceInfo, $requestData);
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Checkout/Service/V1/Cart/ReadServiceTest.php b/dev/tests/api-functional/testsuite/Magento/Checkout/Service/V1/Cart/ReadServiceTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..2d2f8063b359c9c231a6ebdff9149f469ce46183
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Checkout/Service/V1/Cart/ReadServiceTest.php
@@ -0,0 +1,297 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Checkout\Service\V1\Cart;
+
+use Magento\Checkout\Service\V1\Data\Cart;
+use Magento\Framework\Api\FilterBuilder;
+use Magento\Framework\Api\SearchCriteria;
+use Magento\Framework\Api\SearchCriteriaBuilder;
+use Magento\TestFramework\ObjectManager;
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+class ReadServiceTest extends WebapiAbstract
+{
+    /**
+     * @var ObjectManager
+     */
+    private $objectManager;
+
+    /**
+     * @var SearchCriteriaBuilder
+     */
+    private $searchBuilder;
+
+    /**
+     * @var SortOrderBuilder
+     */
+    private $sortOrderBuilder;
+
+    /**
+     * @var FilterBuilder
+     */
+    private $filterBuilder;
+
+    protected function setUp()
+    {
+        $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+        $this->filterBuilder = $this->objectManager->create(
+            'Magento\Framework\Api\FilterBuilder'
+        );
+        $this->sortOrderBuilder = $this->objectManager->create(
+            'Magento\Framework\Api\SortOrderBuilder'
+        );
+        $this->searchBuilder = $this->objectManager->create(
+            'Magento\Framework\Api\SearchCriteriaBuilder'
+        );
+    }
+
+    protected function tearDown()
+    {
+        try {
+            $cart = $this->getCart('test01');
+            $cart->delete();
+        } catch (\InvalidArgumentException $e) {
+            // Do nothing if cart fixture was not used
+        }
+        parent::tearDown();
+    }
+
+    /**
+     * Retrieve quote by given reserved order ID
+     *
+     * @param string $reservedOrderId
+     * @return \Magento\Sales\Model\Quote
+     * @throws \InvalidArgumentException
+     */
+    protected function getCart($reservedOrderId)
+    {
+        /** @var $cart \Magento\Sales\Model\Quote */
+        $cart = $this->objectManager->get('Magento\Sales\Model\Quote');
+        $cart->load($reservedOrderId, 'reserved_order_id');
+        if (!$cart->getId()) {
+            throw new \InvalidArgumentException('There is no quote with provided reserved order ID.');
+        }
+        return $cart;
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Sales/_files/quote.php
+     */
+    public function testGetCart()
+    {
+        $cart = $this->getCart('test01');
+        $cartId = $cart->getId();
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/carts/' . $cartId,
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => 'checkoutCartReadServiceV1',
+                'serviceVersion' => 'V1',
+                'operation' => 'checkoutCartReadServiceV1GetCart',
+            ],
+        ];
+
+        $requestData = ['cartId' => $cartId];
+        $cartData = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertEquals($cart->getId(), $cartData['id']);
+        $this->assertEquals($cart->getCreatedAt(), $cartData['created_at']);
+        $this->assertEquals($cart->getUpdatedAt(), $cartData['updated_at']);
+        $this->assertEquals($cart->getStoreId(), $cartData['store_id']);
+        $this->assertEquals($cart->getIsActive(), $cartData['is_active']);
+        $this->assertEquals($cart->getIsVirtual(), $cartData['is_virtual']);
+        $this->assertEquals($cart->getOrigOrderId(), $cartData['orig_order_id']);
+        $this->assertEquals($cart->getItemsCount(), $cartData['items_count']);
+        $this->assertEquals($cart->getItemsQty(), $cartData['items_qty']);
+
+        $this->assertContains('customer', $cartData);
+        $this->assertEquals(1, $cartData['customer']['is_guest']);
+        $this->assertContains('totals', $cartData);
+        $this->assertEquals($cart->getSubtotal(), $cartData['totals']['subtotal']);
+        $this->assertEquals($cart->getGrandTotal(), $cartData['totals']['grand_total']);
+        $this->assertContains('currency', $cartData);
+        $this->assertEquals($cart->getGlobalCurrencyCode(), $cartData['currency']['global_currency_code']);
+        $this->assertEquals($cart->getBaseCurrencyCode(), $cartData['currency']['base_currency_code']);
+        $this->assertEquals($cart->getQuoteCurrencyCode(), $cartData['currency']['quote_currency_code']);
+        $this->assertEquals($cart->getStoreCurrencyCode(), $cartData['currency']['store_currency_code']);
+        $this->assertEquals($cart->getBaseToGlobalRate(), $cartData['currency']['base_to_global_rate']);
+        $this->assertEquals($cart->getBaseToQuoteRate(), $cartData['currency']['base_to_quote_rate']);
+        $this->assertEquals($cart->getStoreToBaseRate(), $cartData['currency']['store_to_base_rate']);
+        $this->assertEquals($cart->getStoreToQuoteRate(), $cartData['currency']['store_to_quote_rate']);
+    }
+
+    /**
+     * @expectedException \Exception
+     * @expectedExceptionMessage No such entity with
+     */
+    public function testGetCartThrowsExceptionIfThereIsNoCartWithProvidedId()
+    {
+        $cartId = 9999;
+
+        $serviceInfo = [
+            'soap' => [
+                'service' => 'checkoutCartReadServiceV1',
+                'serviceVersion' => 'V1',
+                'operation' => 'checkoutCartReadServiceV1GetCart',
+            ],
+            'rest' => [
+                'resourcePath' => '/V1/carts/' . $cartId,
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+        ];
+
+        $requestData = ['cartId' => $cartId];
+        $this->_webApiCall($serviceInfo, $requestData);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Sales/_files/quote_with_customer.php
+     */
+    public function testGetCartForCustomer()
+    {
+        $cart = $this->getCart('test01');
+        $customerId = $cart->getCustomer()->getId();
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/customer/' . $customerId . '/cart',
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => 'checkoutCartReadServiceV1',
+                'serviceVersion' => 'V1',
+                'operation' => 'checkoutCartReadServiceV1GetCartForCustomer',
+            ],
+        ];
+
+        $requestData = ['customerId' => $customerId];
+        $cartData = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertEquals($cart->getId(), $cartData['id']);
+        $this->assertEquals($cart->getCreatedAt(), $cartData['created_at']);
+        $this->assertEquals($cart->getUpdatedAt(), $cartData['updated_at']);
+        $this->assertEquals($cart->getStoreId(), $cartData['store_id']);
+        $this->assertEquals($cart->getIsActive(), $cartData['is_active']);
+        $this->assertEquals($cart->getIsVirtual(), $cartData['is_virtual']);
+        $this->assertEquals($cart->getOrigOrderId(), $cartData['orig_order_id']);
+        $this->assertEquals($cart->getItemsCount(), $cartData['items_count']);
+        $this->assertEquals($cart->getItemsQty(), $cartData['items_qty']);
+
+        $this->assertContains('customer', $cartData);
+        $this->assertEquals(0, $cartData['customer']['is_guest']);
+        $this->assertContains('totals', $cartData);
+        $this->assertEquals($cart->getSubtotal(), $cartData['totals']['subtotal']);
+        $this->assertEquals($cart->getGrandTotal(), $cartData['totals']['grand_total']);
+        $this->assertContains('currency', $cartData);
+        $this->assertEquals($cart->getGlobalCurrencyCode(), $cartData['currency']['global_currency_code']);
+        $this->assertEquals($cart->getBaseCurrencyCode(), $cartData['currency']['base_currency_code']);
+        $this->assertEquals($cart->getQuoteCurrencyCode(), $cartData['currency']['quote_currency_code']);
+        $this->assertEquals($cart->getStoreCurrencyCode(), $cartData['currency']['store_currency_code']);
+        $this->assertEquals($cart->getBaseToGlobalRate(), $cartData['currency']['base_to_global_rate']);
+        $this->assertEquals($cart->getBaseToQuoteRate(), $cartData['currency']['base_to_quote_rate']);
+        $this->assertEquals($cart->getStoreToBaseRate(), $cartData['currency']['store_to_base_rate']);
+        $this->assertEquals($cart->getStoreToQuoteRate(), $cartData['currency']['store_to_quote_rate']);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Sales/_files/quote.php
+     */
+    public function testGetCartList()
+    {
+        $cart = $this->getCart('test01');
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/carts',
+                'httpMethod' => RestConfig::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => 'checkoutCartReadServiceV1',
+                'serviceVersion' => 'V1',
+                'operation' => 'checkoutCartReadServiceV1GetCartList',
+            ],
+        ];
+
+        // The following two filters are used as alternatives. The target cart does not match the first one.
+        $grandTotalFilter = $this->filterBuilder->setField('grand_total')
+            ->setConditionType('gteq')
+            ->setValue(15)
+            ->create();
+        $subtotalFilter = $this->filterBuilder->setField('subtotal')
+            ->setConditionType('eq')
+            ->setValue($cart->getSubtotal())
+            ->create();
+
+        $yesterdayDate = (new \DateTime())->sub(new \DateInterval('P1D'))->format('Y-m-d');
+        $tomorrowDate = (new \DateTime())->add(new \DateInterval('P1D'))->format('Y-m-d');
+        $minCreatedAtFilter = $this->filterBuilder->setField(Cart::CREATED_AT)
+            ->setConditionType('gteq')
+            ->setValue($yesterdayDate)
+            ->create();
+        $maxCreatedAtFilter = $this->filterBuilder->setField(Cart::CREATED_AT)
+            ->setConditionType('lteq')
+            ->setValue($tomorrowDate)
+            ->create();
+
+        $this->searchBuilder->addFilter([$grandTotalFilter, $subtotalFilter]);
+        $this->searchBuilder->addFilter([$minCreatedAtFilter]);
+        $this->searchBuilder->addFilter([$maxCreatedAtFilter]);
+        /** @var SortOrder $sortOrder */
+        $sortOrder = $this->sortOrderBuilder->setField('subtotal')->setDirection(SearchCriteria::SORT_ASC)->create();
+        $this->searchBuilder->setSortOrders([$sortOrder]);
+        $searchCriteria = $this->searchBuilder->create()->__toArray();
+
+        $requestData = ['searchCriteria' => $searchCriteria];
+        $searchResult = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertArrayHasKey('total_count', $searchResult);
+        $this->assertEquals(1, $searchResult['total_count']);
+        $this->assertArrayHasKey('items', $searchResult);
+        $this->assertCount(1, $searchResult['items']);
+
+        $cartData = $searchResult['items'][0];
+        $this->assertEquals($cart->getId(), $cartData['id']);
+        $this->assertEquals($cart->getCreatedAt(), $cartData['created_at']);
+        $this->assertEquals($cart->getUpdatedAt(), $cartData['updated_at']);
+        $this->assertEquals($cart->getIsActive(), $cartData['is_active']);
+        $this->assertEquals($cart->getStoreId(), $cartData['store_id']);
+
+        $this->assertContains('totals', $cartData);
+        $this->assertEquals($cart->getBaseSubtotal(), $cartData['totals']['base_subtotal']);
+        $this->assertEquals($cart->getBaseGrandTotal(), $cartData['totals']['base_grand_total']);
+        $this->assertContains('customer', $cartData);
+        $this->assertEquals(1, $cartData['customer']['is_guest']);
+    }
+
+    /**
+     * @expectedException \Exception
+     * @expectedExceptionMessage Field 'invalid_field' cannot be used for search.
+     */
+    public function testGetCartListThrowsExceptionIfProvidedSearchFieldIsInvalid()
+    {
+        $serviceInfo = [
+            'soap' => [
+                'service' => 'checkoutCartReadServiceV1',
+                'serviceVersion' => 'V1',
+                'operation' => 'checkoutCartReadServiceV1GetCartList',
+            ],
+            'rest' => [
+                'resourcePath' => '/V1/carts',
+                'httpMethod' => RestConfig::HTTP_METHOD_PUT,
+            ],
+        ];
+
+        $invalidFilter = $this->filterBuilder->setField('invalid_field')
+            ->setConditionType('eq')
+            ->setValue(0)
+            ->create();
+
+        $this->searchBuilder->addFilter([$invalidFilter]);
+        $searchCriteria = $this->searchBuilder->create()->__toArray();
+        $requestData = ['searchCriteria' => $searchCriteria];
+        $this->_webApiCall($serviceInfo, $requestData);
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Checkout/Service/V1/Cart/TotalsServiceTest.php b/dev/tests/api-functional/testsuite/Magento/Checkout/Service/V1/Cart/TotalsServiceTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..9c6a50f2d7e7249d1843797faede152509b29224
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Checkout/Service/V1/Cart/TotalsServiceTest.php
@@ -0,0 +1,174 @@
+<?php
+/**
+ *
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Checkout\Service\V1\Cart;
+
+use Magento\Checkout\Service\V1\Data\Cart\Totals;
+use Magento\Checkout\Service\V1\Data\Cart\Totals\Item as ItemTotals;
+use Magento\Framework\Api\FilterBuilder;
+use Magento\Framework\Api\SearchCriteriaBuilder;
+use Magento\TestFramework\ObjectManager;
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+class TotalsServiceTest extends WebapiAbstract
+{
+    /**
+     * @var ObjectManager
+     */
+    private $objectManager;
+
+    /**
+     * @var SearchCriteriaBuilder
+     */
+    private $searchBuilder;
+
+    /**
+     * @var FilterBuilder
+     */
+    private $filterBuilder;
+
+    protected function setUp()
+    {
+        $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+        $this->searchBuilder = $this->objectManager->create(
+            'Magento\Framework\Api\SearchCriteriaBuilder'
+        );
+        $this->filterBuilder = $this->objectManager->create(
+            'Magento\Framework\Api\FilterBuilder'
+        );
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Checkout/_files/quote_with_shipping_method.php
+     */
+    public function testGetTotals()
+    {
+        /** @var \Magento\Sales\Model\Quote $quote */
+        $quote = $this->objectManager->create('Magento\Sales\Model\Quote');
+        $quote->load('test_order_1', 'reserved_order_id');
+        $cartId = $quote->getId();
+
+        /** @var \Magento\Sales\Model\Quote\Address $shippingAddress */
+        $shippingAddress = $quote->getShippingAddress();
+
+        $data = [
+            Totals::BASE_GRAND_TOTAL => $quote->getBaseGrandTotal(),
+            Totals::GRAND_TOTAL => $quote->getGrandTotal(),
+            Totals::BASE_SUBTOTAL => $quote->getBaseSubtotal(),
+            Totals::SUBTOTAL => $quote->getSubtotal(),
+            Totals::BASE_SUBTOTAL_WITH_DISCOUNT => $quote->getBaseSubtotalWithDiscount(),
+            Totals::SUBTOTAL_WITH_DISCOUNT => $quote->getSubtotalWithDiscount(),
+            Totals::DISCOUNT_AMOUNT => $shippingAddress->getDiscountAmount(),
+            Totals::BASE_DISCOUNT_AMOUNT => $shippingAddress->getBaseDiscountAmount(),
+            Totals::SHIPPING_AMOUNT => $shippingAddress->getShippingAmount(),
+            Totals::BASE_SHIPPING_AMOUNT => $shippingAddress->getBaseShippingAmount(),
+            Totals::SHIPPING_DISCOUNT_AMOUNT => $shippingAddress->getShippingDiscountAmount(),
+            Totals::BASE_SHIPPING_DISCOUNT_AMOUNT => $shippingAddress->getBaseShippingDiscountAmount(),
+            Totals::TAX_AMOUNT => $shippingAddress->getTaxAmount(),
+            Totals::BASE_TAX_AMOUNT => $shippingAddress->getBaseTaxAmount(),
+            Totals::SHIPPING_TAX_AMOUNT => $shippingAddress->getShippingTaxAmount(),
+            Totals::BASE_SHIPPING_TAX_AMOUNT => $shippingAddress->getBaseShippingTaxAmount(),
+            Totals::SUBTOTAL_INCL_TAX => $shippingAddress->getSubtotalInclTax(),
+            Totals::BASE_SUBTOTAL_INCL_TAX => $shippingAddress->getBaseSubtotalTotalInclTax(),
+            Totals::SHIPPING_INCL_TAX => $shippingAddress->getShippingInclTax(),
+            Totals::BASE_SHIPPING_INCL_TAX => $shippingAddress->getBaseShippingInclTax(),
+            Totals::BASE_CURRENCY_CODE => $quote->getBaseCurrencyCode(),
+            Totals::QUOTE_CURRENCY_CODE => $quote->getQuoteCurrencyCode(),
+            Totals::ITEMS => [$this->getQuoteItemTotalsData($quote)],
+        ];
+
+        $requestData = ['cartId' => $cartId];
+
+        $data = $this->formatTotalsData($data);
+
+        $this->assertEquals($data, $this->_webApiCall($this->getServiceInfoForTotalsService($cartId), $requestData));
+    }
+
+    /**
+     * @expectedException \Exception
+     * @expectedExceptionMessage No such entity
+     */
+    public function testGetTotalsWithAbsentQuote()
+    {
+        $cartId = 'unknownCart';
+        $requestData = ['cartId' => $cartId];
+        $this->_webApiCall($this->getServiceInfoForTotalsService($cartId), $requestData);
+    }
+
+    /**
+     * Get service info for totals service
+     *
+     * @param string $cartId
+     * @return array
+     */
+    protected function getServiceInfoForTotalsService($cartId)
+    {
+        return [
+            'soap' => [
+                'service' => 'checkoutCartTotalsServiceV1',
+                'serviceVersion' => 'V1',
+                'operation' => 'checkoutCartTotalsServiceV1GetTotals',
+            ],
+            'rest' => [
+                'resourcePath' => '/V1/carts/' . $cartId . '/totals',
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+        ];
+    }
+
+    /**
+     * Adjust response details for SOAP protocol
+     *
+     * @param array $data
+     * @return array
+     */
+    protected function formatTotalsData($data)
+    {
+        foreach ($data as $key => $field) {
+            if (is_numeric($field)) {
+                $data[$key] = round($field, 1);
+                if ($data[$key] === null) {
+                    $data[$key] = 0.0;
+                }
+            }
+        }
+
+        unset($data[Totals::BASE_SUBTOTAL_INCL_TAX]);
+
+        return $data;
+    }
+
+    /**
+     * Fetch quote item totals data from quote
+     *
+     * @param \Magento\Sales\Model\Quote $quote
+     * @return array
+     */
+    protected function getQuoteItemTotalsData(\Magento\Sales\Model\Quote $quote)
+    {
+        $items = $quote->getAllItems();
+        $item = array_shift($items);
+
+        return [
+            ItemTotals::PRICE => $item->getPrice(),
+            ItemTotals::BASE_PRICE => $item->getBasePrice(),
+            ItemTotals::QTY => $item->getQty(),
+            ItemTotals::ROW_TOTAL => $item->getRowTotal(),
+            ItemTotals::BASE_ROW_TOTAL => $item->getBaseRowTotal(),
+            ItemTotals::ROW_TOTAL_WITH_DISCOUNT => $item->getRowTotalWithDiscount(),
+            ItemTotals::TAX_AMOUNT => $item->getTaxAmount(),
+            ItemTotals::BASE_TAX_AMOUNT => $item->getBaseTaxAmount(),
+            ItemTotals::TAX_PERCENT => $item->getTaxPercent(),
+            ItemTotals::DISCOUNT_AMOUNT => $item->getDiscountAmount(),
+            ItemTotals::BASE_DISCOUNT_AMOUNT => $item->getBaseDiscountAmount(),
+            ItemTotals::DISCOUNT_PERCENT => $item->getDiscountPercent(),
+            ItemTotals::PRICE_INCL_TAX => $item->getPriceInclTax(),
+            ItemTotals::BASE_PRICE_INCL_TAX => $item->getBasePriceInclTax(),
+            ItemTotals::ROW_TOTAL_INCL_TAX => $item->getRowTotalInclTax(),
+            ItemTotals::BASE_ROW_TOTAL_INCL_TAX => $item->getBaseRowTotalInclTax(),
+        ];
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Checkout/Service/V1/Cart/WriteServiceTest.php b/dev/tests/api-functional/testsuite/Magento/Checkout/Service/V1/Cart/WriteServiceTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..781ce89f50be79a94b2cf8d44595ef5f7db2b797
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Checkout/Service/V1/Cart/WriteServiceTest.php
@@ -0,0 +1,299 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+namespace Magento\Checkout\Service\V1\Cart;
+
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+class WriteServiceTest extends WebapiAbstract
+{
+    const SERVICE_VERSION = 'V1';
+    const SERVICE_NAME = 'checkoutCartWriteServiceV1';
+    const RESOURCE_PATH = '/V1/carts/';
+
+    protected $createdQuotes = [];
+
+    /**
+     * @var \Magento\TestFramework\ObjectManager
+     */
+    protected $objectManager;
+
+    protected function setUp()
+    {
+        $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+    }
+
+    public function testCreate()
+    {
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH,
+                'httpMethod' => RestConfig::HTTP_METHOD_POST,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Create',
+            ],
+        ];
+
+        $quoteId = $this->_webApiCall($serviceInfo);
+        $this->assertGreaterThan(0, $quoteId);
+        $this->createdQuotes[] = $quoteId;
+    }
+
+    public function tearDown()
+    {
+        /** @var \Magento\Sales\Model\Quote $quote */
+        $quote = $this->objectManager->create('Magento\Sales\Model\Quote');
+        foreach ($this->createdQuotes as $quoteId) {
+            $quote->load($quoteId);
+            $quote->delete();
+        }
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Sales/_files/quote.php
+     * @magentoApiDataFixture Magento/Customer/_files/customer.php
+     */
+    public function testAssignCustomer()
+    {
+        /** @var $quote \Magento\Sales\Model\Quote */
+        $quote = $this->objectManager->create('Magento\Sales\Model\Quote')->load('test01', 'reserved_order_id');
+        $cartId = $quote->getId();
+        /** @var $repository \Magento\Customer\Api\CustomerRepositoryInterface */
+        $repository = $this->objectManager->create('Magento\Customer\Api\CustomerRepositoryInterface');
+        /** @var $customer \Magento\Customer\Api\Data\CustomerInterface */
+        $customer = $repository->getById(1);
+        $customerId = $customer->getId();
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/carts/' . $cartId,
+                'httpMethod' => RestConfig::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => 'checkoutCartWriteServiceV1',
+                'serviceVersion' => 'V1',
+                'operation' => 'checkoutCartWriteServiceV1AssignCustomer',
+            ],
+        ];
+
+        $requestData = [
+            'cartId' => $cartId,
+            'customerId' => $customerId,
+        ];
+        // Cart must be anonymous (see fixture)
+        $this->assertEmpty($quote->getCustomerId());
+
+        $this->assertTrue($this->_webApiCall($serviceInfo, $requestData));
+        // Reload target quote
+        $quote = $this->objectManager->create('Magento\Sales\Model\Quote')->load('test01', 'reserved_order_id');
+        $this->assertEquals(0, $quote->getCustomerIsGuest());
+        $this->assertEquals($customer->getId(), $quote->getCustomerId());
+        $this->assertEquals($customer->getFirstname(), $quote->getCustomerFirstname());
+        $this->assertEquals($customer->getLastname(), $quote->getCustomerLastname());
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Sales/_files/quote.php
+     * @expectedException \Exception
+     */
+    public function testAssignCustomerThrowsExceptionIfThereIsNoCustomerWithGivenId()
+    {
+        /** @var $quote \Magento\Sales\Model\Quote */
+        $quote = $this->objectManager->create('Magento\Sales\Model\Quote')->load('test01', 'reserved_order_id');
+        $cartId = $quote->getId();
+        $customerId = 9999;
+        $serviceInfo = [
+            'soap' => [
+                'serviceVersion' => 'V1',
+                'service' => 'checkoutCartWriteServiceV1',
+                'operation' => 'checkoutCartWriteServiceV1AssignCustomer',
+            ],
+            'rest' => [
+                'resourcePath' => '/V1/carts/' . $cartId,
+                'httpMethod' => RestConfig::HTTP_METHOD_PUT,
+            ],
+        ];
+        $requestData = [
+            'cartId' => $cartId,
+            'customerId' => $customerId,
+        ];
+
+        $this->_webApiCall($serviceInfo, $requestData);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Customer/_files/customer.php
+     * @expectedException \Exception
+     */
+    public function testAssignCustomerThrowsExceptionIfThereIsNoCartWithGivenId()
+    {
+        $cartId = 9999;
+        $customerId = 1;
+        $serviceInfo = [
+            'soap' => [
+                'service' => 'checkoutCartWriteServiceV1',
+                'serviceVersion' => 'V1',
+                'operation' => 'checkoutCartWriteServiceV1AssignCustomer',
+            ],
+            'rest' => [
+                'resourcePath' => '/V1/carts/' . $cartId,
+                'httpMethod' => RestConfig::HTTP_METHOD_PUT,
+            ],
+        ];
+        $requestData = [
+            'cartId' => $cartId,
+            'customerId' => $customerId,
+        ];
+
+        $this->_webApiCall($serviceInfo, $requestData);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Sales/_files/quote_with_customer.php
+     * @expectedException \Exception
+     * @expectedExceptionMessage Cannot assign customer to the given cart. The cart is not anonymous.
+     */
+    public function testAssignCustomerThrowsExceptionIfTargetCartIsNotAnonymous()
+    {
+        /** @var $customer \Magento\Customer\Model\Customer */
+        $customer = $this->objectManager->create('Magento\Customer\Model\Customer')->load(1);
+        $customerId = $customer->getId();
+        /** @var $quote \Magento\Sales\Model\Quote */
+        $quote = $this->objectManager->create('Magento\Sales\Model\Quote')->load('test01', 'reserved_order_id');
+        $cartId = $quote->getId();
+
+        $serviceInfo = [
+            'rest' => [
+                'httpMethod' => RestConfig::HTTP_METHOD_PUT,
+                'resourcePath' => '/V1/carts/' . $cartId,
+            ],
+            'soap' => [
+                'service' => 'checkoutCartWriteServiceV1',
+                'serviceVersion' => 'V1',
+                'operation' => 'checkoutCartWriteServiceV1AssignCustomer',
+            ],
+        ];
+
+        $requestData = [
+            'cartId' => $cartId,
+            'customerId' => $customerId,
+        ];
+        $this->_webApiCall($serviceInfo, $requestData);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Sales/_files/quote.php
+     * @magentoApiDataFixture Magento/Customer/_files/customer_non_default_website_id.php
+     * @expectedException \Exception
+     * @expectedExceptionMessage Cannot assign customer to the given cart. The cart belongs to different store.
+     */
+    public function testAssignCustomerThrowsExceptionIfCartIsAssignedToDifferentStore()
+    {
+        $repository = $this->objectManager->create('Magento\Customer\Api\CustomerRepositoryInterface');
+        /** @var $customer \Magento\Customer\Api\Data\CustomerInterface */
+        $customer = $repository->getById(1);
+        /** @var $quote \Magento\Sales\Model\Quote */
+        $quote = $this->objectManager->create('Magento\Sales\Model\Quote')->load('test01', 'reserved_order_id');
+
+        $customerId = $customer->getId();
+        $cartId = $quote->getId();
+
+        $serviceInfo = [
+            'soap' => [
+                'service' => 'checkoutCartWriteServiceV1',
+                'serviceVersion' => 'V1',
+                'operation' => 'checkoutCartWriteServiceV1AssignCustomer',
+            ],
+            'rest' => [
+                'httpMethod' => RestConfig::HTTP_METHOD_PUT,
+                'resourcePath' => '/V1/carts/' . $cartId,
+            ],
+        ];
+
+        $requestData = [
+            'cartId' => $cartId,
+            'customerId' => $customerId,
+        ];
+        $this->_webApiCall($serviceInfo, $requestData);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php
+     * @magentoApiDataFixture Magento/Sales/_files/quote.php
+     * @expectedException \Exception
+     * @expectedExceptionMessage Cannot assign customer to the given cart. Customer already has active cart.
+     */
+    public function testAssignCustomerThrowsExceptionIfCustomerAlreadyHasActiveCart()
+    {
+        /** @var $customer \Magento\Customer\Model\Customer */
+        $customer = $this->objectManager->create('Magento\Customer\Model\Customer')->load(1);
+        // Customer has a quote with reserved order ID test_order_1 (see fixture)
+        /** @var $customerQuote \Magento\Sales\Model\Quote */
+        $customerQuote = $this->objectManager->create('Magento\Sales\Model\Quote')
+            ->load('test_order_1', 'reserved_order_id');
+        $customerQuote->setIsActive(1)->save();
+        /** @var $quote \Magento\Sales\Model\Quote */
+        $quote = $this->objectManager->create('Magento\Sales\Model\Quote')->load('test01', 'reserved_order_id');
+
+        $cartId = $quote->getId();
+        $customerId = $customer->getId();
+
+        $serviceInfo = [
+            'soap' => [
+                'service' => 'checkoutCartWriteServiceV1',
+                'operation' => 'checkoutCartWriteServiceV1AssignCustomer',
+                'serviceVersion' => 'V1',
+            ],
+            'rest' => [
+                'resourcePath' => '/V1/carts/' . $cartId,
+                'httpMethod' => RestConfig::HTTP_METHOD_PUT,
+            ],
+        ];
+
+        $requestData = [
+            'cartId' => $cartId,
+            'customerId' => $customerId,
+        ];
+        $this->_webApiCall($serviceInfo, $requestData);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Checkout/_files/quote_with_check_payment.php
+     */
+    public function testOrderPlacesOrder()
+    {
+        /** @var $quote \Magento\Sales\Model\Quote */
+        $quote = $this->objectManager->create('Magento\Sales\Model\Quote')->load('test_order_1', 'reserved_order_id');
+
+        $cartId = $quote->getId();
+
+        $serviceInfo = [
+            'soap' => [
+                'service' => 'checkoutCartWriteServiceV1',
+                'operation' => 'checkoutCartWriteServiceV1Order',
+                'serviceVersion' => 'V1',
+            ],
+            'rest' => [
+                'resourcePath' => '/V1/carts/' . $cartId . '/order',
+                'httpMethod' => RestConfig::HTTP_METHOD_PUT,
+            ],
+        ];
+
+        $requestData = [
+            'cartId' => $cartId,
+        ];
+        $this->_webApiCall($serviceInfo, $requestData);
+        /** @var \Magento\Sales\Model\Order $order */
+        $order = $this->objectManager->create('Magento\Sales\Model\Order')->load(1);
+        $items = $order->getAllItems();
+        $this->assertCount(1, $items);
+        $this->assertEquals('Simple Product', $items[0]->getName());
+        $quote->delete();
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Checkout/Service/V1/Coupon/ReadServiceTest.php b/dev/tests/api-functional/testsuite/Magento/Checkout/Service/V1/Coupon/ReadServiceTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..d36d4289fec3b938c49d92c19287d14b87dbb672
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Checkout/Service/V1/Coupon/ReadServiceTest.php
@@ -0,0 +1,55 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+namespace Magento\Checkout\Service\V1\Coupon;
+
+use Magento\Checkout\Service\V1\Data\Cart\Coupon as Coupon;
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+class ReadServiceTest extends WebapiAbstract
+{
+    const SERVICE_VERSION = 'V1';
+    const SERVICE_NAME = 'checkoutCouponReadServiceV1';
+    const RESOURCE_PATH = '/V1/carts/';
+
+    /**
+     * @var \Magento\TestFramework\ObjectManager
+     */
+    protected $objectManager;
+
+    protected function setUp()
+    {
+        $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Checkout/_files/quote_with_coupon_saved.php
+     */
+    public function testGet()
+    {
+        /** @var \Magento\Sales\Model\Quote  $quote */
+        $quote = $this->objectManager->create('Magento\Sales\Model\Quote');
+        $quote->load('test_order_1', 'reserved_order_id');
+        $cartId = $quote->getId();
+        $data = [
+            Coupon::COUPON_CODE => $quote->getCouponCode(),
+        ];
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . $cartId . '/coupons',
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Get',
+            ],
+        ];
+
+        $requestData = ["cartId" => $cartId];
+        $this->assertEquals($data, $this->_webApiCall($serviceInfo, $requestData));
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Checkout/Service/V1/Coupon/WriteServiceTest.php b/dev/tests/api-functional/testsuite/Magento/Checkout/Service/V1/Coupon/WriteServiceTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..f6dfbf57eb0e4b5928db2d5e9b8b1e6cf9699270
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Checkout/Service/V1/Coupon/WriteServiceTest.php
@@ -0,0 +1,129 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+namespace Magento\Checkout\Service\V1\Coupon;
+
+use Magento\Checkout\Service\V1\Data\Cart\Coupon;
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+class WriteServiceTest extends WebapiAbstract
+{
+    const SERVICE_VERSION = 'V1';
+    const SERVICE_NAME = 'checkoutCouponWriteServiceV1';
+    const RESOURCE_PATH = '/V1/carts/';
+
+    /**
+     * @var \Magento\TestFramework\ObjectManager
+     */
+    protected $objectManager;
+
+    protected function setUp()
+    {
+        $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Checkout/_files/quote_with_coupon_saved.php
+     */
+    public function testDelete()
+    {
+        /** @var \Magento\Sales\Model\Quote $quote */
+        $quote = $this->objectManager->create('Magento\Sales\Model\Quote');
+        $quote->load('test_order_1', 'reserved_order_id');
+        $cartId = $quote->getId();
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . $cartId . '/coupons',
+                'httpMethod' => RestConfig::HTTP_METHOD_DELETE,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Delete',
+            ],
+        ];
+        $requestData = ["cartId" => $cartId];
+        $this->assertTrue($this->_webApiCall($serviceInfo, $requestData));
+        $quote->load('test_order_1', 'reserved_order_id');
+        $this->assertEquals('', $quote->getCouponCode());
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php
+     * @expectedException \Exception
+     * @expectedExceptionMessage Coupon code is not valid
+     */
+    public function testSetCouponThrowsExceptionIfCouponDoesNotExist()
+    {
+        /** @var \Magento\Sales\Model\Quote $quote */
+        $quote = $this->objectManager->create('Magento\Sales\Model\Quote');
+        $quote->load('test_order_1', 'reserved_order_id');
+        $cartId = $quote->getId();
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . $cartId . '/coupons',
+                'httpMethod' => RestConfig::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Set',
+            ],
+        ];
+
+        $data = [Coupon::COUPON_CODE => 'invalid_coupon_code'];
+
+        $requestData = [
+            "cartId" => $cartId,
+            "couponCodeData" => $data,
+        ];
+
+        $this->_webApiCall($serviceInfo, $requestData);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Sales/_files/quote.php
+     * @magentoApiDataFixture Magento/Checkout/_files/discount_10percent.php
+     */
+    public function testSetCouponSuccess()
+    {
+        /** @var \Magento\Sales\Model\Quote $quote */
+        $quote = $this->objectManager->create('Magento\Sales\Model\Quote');
+        $quote->load('test01', 'reserved_order_id');
+        $cartId = $quote->getId();
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . $cartId . '/coupons',
+                'httpMethod' => RestConfig::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Set',
+            ],
+        ];
+
+        $salesRule = $this->objectManager->create('Magento\SalesRule\Model\Rule');
+        $salesRule->load('Test Coupon', 'name');
+
+        $couponCode = $salesRule->getCouponCode();
+        $data = [Coupon::COUPON_CODE => $couponCode];
+
+        $requestData = [
+            "cartId" => $cartId,
+            "couponCodeData" => $data,
+        ];
+
+        $this->assertTrue($this->_webApiCall($serviceInfo, $requestData));
+
+        $quoteWithCoupon = $this->objectManager->create('Magento\Sales\Model\Quote');
+        $quoteWithCoupon->load('test01', 'reserved_order_id');
+
+        $this->assertEquals($quoteWithCoupon->getCouponCode(), $couponCode);
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Checkout/Service/V1/Item/ReadServiceTest.php b/dev/tests/api-functional/testsuite/Magento/Checkout/Service/V1/Item/ReadServiceTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..6f099951a762b2a8c7cf9468749002192b3909c4
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Checkout/Service/V1/Item/ReadServiceTest.php
@@ -0,0 +1,65 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+namespace Magento\Checkout\Service\V1\Item;
+
+use Magento\Checkout\Service\V1\Data\Cart\Item as Item;
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+class ReadServiceTest extends WebapiAbstract
+{
+    const SERVICE_VERSION = 'V1';
+    const SERVICE_NAME = 'checkoutItemReadServiceV1';
+    const RESOURCE_PATH = '/V1/carts/';
+
+    /**
+     * @var \Magento\TestFramework\ObjectManager
+     */
+    protected $objectManager;
+
+    protected function setUp()
+    {
+        $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Checkout/_files/quote_with_items_saved.php
+     */
+    public function testGetList()
+    {
+        /** @var \Magento\Sales\Model\Quote  $quote */
+        $quote = $this->objectManager->create('Magento\Sales\Model\Quote');
+        $quote->load('test_order_item_with_items', 'reserved_order_id');
+        $cartId = $quote->getId();
+        $output = [];
+        foreach ($quote->getAllItems() as $item) {
+            $data = [
+                Item::ITEM_ID => $item->getId(),
+                Item::SKU => $item->getSku(),
+                Item::NAME => $item->getName(),
+                Item::PRICE => $item->getPrice(),
+                Item::QTY => $item->getQty(),
+                Item::PRODUCT_TYPE => $item->getProductType(),
+            ];
+
+            $output[] = $data;
+        }
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . $cartId . '/items',
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'GetList',
+            ],
+        ];
+
+        $requestData = ["cartId" => $cartId];
+        $this->assertEquals($output, $this->_webApiCall($serviceInfo, $requestData));
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Checkout/Service/V1/Item/WriteServiceTest.php b/dev/tests/api-functional/testsuite/Magento/Checkout/Service/V1/Item/WriteServiceTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..857475f87e8af2ca2d17960b31b7453dd9090c97
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Checkout/Service/V1/Item/WriteServiceTest.php
@@ -0,0 +1,136 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+namespace Magento\Checkout\Service\V1\Item;
+
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+class WriteServiceTest extends WebapiAbstract
+{
+    const SERVICE_VERSION = 'V1';
+    const SERVICE_NAME = 'checkoutItemWriteServiceV1';
+    const RESOURCE_PATH = '/V1/carts/';
+
+    /**
+     * @var \Magento\TestFramework\ObjectManager
+     */
+    protected $objectManager;
+
+    protected function setUp()
+    {
+        $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php
+     * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+     */
+    public function testAddItem()
+    {
+        $product = $this->objectManager->create('Magento\Catalog\Model\Product')->load(2);
+        $productSku = $product->getSku();
+        /** @var \Magento\Sales\Model\Quote  $quote */
+        $quote = $this->objectManager->create('Magento\Sales\Model\Quote');
+        $quote->load('test_order_1', 'reserved_order_id');
+        $cartId = $quote->getId();
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . $cartId . '/items',
+                'httpMethod' => RestConfig::HTTP_METHOD_POST,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'AddItem',
+            ],
+        ];
+
+        $requestData = [
+            "cartId" => $cartId,
+            "data" => [
+                "sku" => $productSku,
+                "qty" => 7,
+            ],
+        ];
+        $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertTrue($quote->hasProductId(2));
+        $this->assertEquals(7, $quote->getItemByProduct($product)->getQty());
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Checkout/_files/quote_with_items_saved.php
+     */
+    public function testRemoveItem()
+    {
+        /** @var \Magento\Sales\Model\Quote  $quote */
+        $quote = $this->objectManager->create('Magento\Sales\Model\Quote');
+        $quote->load('test_order_item_with_items', 'reserved_order_id');
+        $cartId = $quote->getId();
+        $product = $this->objectManager->create('Magento\Catalog\Model\Product');
+        $productId = $product->getIdBySku('simple_one');
+        $product->load($productId);
+        $itemId = $quote->getItemByProduct($product)->getId();
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . $cartId . '/items/' . $itemId,
+                'httpMethod' => RestConfig::HTTP_METHOD_DELETE,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'RemoveItem',
+            ],
+        ];
+
+        $requestData = [
+            "cartId" => $cartId,
+            "itemId" => $itemId,
+        ];
+        $this->assertTrue($this->_webApiCall($serviceInfo, $requestData));
+        $quote = $this->objectManager->create('Magento\Sales\Model\Quote');
+        $quote->load('test_order_item_with_items', 'reserved_order_id');
+        $this->assertFalse($quote->hasProductId($productId));
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Checkout/_files/quote_with_items_saved.php
+     */
+    public function testUpdateItem()
+    {
+        /** @var \Magento\Sales\Model\Quote  $quote */
+        $quote = $this->objectManager->create('Magento\Sales\Model\Quote');
+        $quote->load('test_order_item_with_items', 'reserved_order_id');
+        $cartId = $quote->getId();
+        $product = $this->objectManager->create('Magento\Catalog\Model\Product');
+        $productId = $product->getIdBySku('simple_one');
+        $product->load($productId);
+        $itemId = $quote->getItemByProduct($product)->getId();
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . $cartId . '/items/' . $itemId,
+                'httpMethod' => RestConfig::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'UpdateItem',
+            ],
+        ];
+
+        $requestData = [
+            "cartId" => $cartId,
+            "itemId" => $itemId,
+            "data" => [
+                "qty" => 5,
+            ],
+        ];
+        $this->assertTrue($this->_webApiCall($serviceInfo, $requestData));
+        $quote = $this->objectManager->create('Magento\Sales\Model\Quote');
+        $quote->load('test_order_item_with_items', 'reserved_order_id');
+        $this->assertTrue($quote->hasProductId(1));
+        $this->assertEquals(5, $quote->getItemByProduct($product)->getQty());
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Checkout/Service/V1/PaymentMethod/ReadServiceTest.php b/dev/tests/api-functional/testsuite/Magento/Checkout/Service/V1/PaymentMethod/ReadServiceTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..befd81cb2cdbeaac0196e06f594b559d051ef89b
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Checkout/Service/V1/PaymentMethod/ReadServiceTest.php
@@ -0,0 +1,89 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Checkout\Service\V1\PaymentMethod;
+
+use Magento\Checkout\Service\V1\Data\PaymentMethod;
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+class ReadServiceTest extends WebapiAbstract
+{
+    const SERVICE_VERSION = 'V1';
+    const SERVICE_NAME = 'checkoutPaymentMethodReadServiceV1';
+    const RESOURCE_PATH = '/V1/carts/';
+
+    /**
+     * @var \Magento\TestFramework\ObjectManager
+     */
+    protected $objectManager;
+
+    protected function setUp()
+    {
+        $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php
+     */
+    public function testGetList()
+    {
+        /** @var \Magento\Sales\Model\Quote  $quote */
+        $quote = $this->objectManager->create('Magento\Sales\Model\Quote');
+        $quote->load('test_order_1', 'reserved_order_id');
+        $cartId = $quote->getId();
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . $cartId . '/payment-methods',
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'getList',
+            ],
+        ];
+
+        $requestData = ["cartId" => $cartId];
+        $requestResponse = $this->_webApiCall($serviceInfo, $requestData);
+
+        $expectedResponse = [
+            PaymentMethod::CODE => 'checkmo',
+            PaymentMethod::TITLE => 'Check / Money order',
+        ];
+
+        $this->assertGreaterThan(0, count($requestResponse));
+        $this->assertContains($expectedResponse, $requestResponse);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Checkout/_files/quote_with_payment_saved.php
+     */
+    public function testGetPayment()
+    {
+        /** @var \Magento\Sales\Model\Quote  $quote */
+        $quote = $this->objectManager->create('Magento\Sales\Model\Quote');
+        $quote->load('test_order_1_with_payment', 'reserved_order_id');
+        $cartId = $quote->getId();
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . $cartId . '/selected-payment-methods',
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'getPayment',
+            ],
+        ];
+
+        $requestData = ["cartId" => $cartId];
+        $requestResponse = $this->_webApiCall($serviceInfo, $requestData);
+
+        $this->assertArrayHasKey('method', $requestResponse);
+        $this->assertEquals('checkmo', $requestResponse['method']);
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Checkout/Service/V1/PaymentMethod/WriteServiceTest.php b/dev/tests/api-functional/testsuite/Magento/Checkout/Service/V1/PaymentMethod/WriteServiceTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..fd796eeb90da93200ee9638c21ff1c3d66445a09
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Checkout/Service/V1/PaymentMethod/WriteServiceTest.php
@@ -0,0 +1,211 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Checkout\Service\V1\PaymentMethod;
+
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+class WriteServiceTest extends WebapiAbstract
+{
+    const SERVICE_VERSION = 'V1';
+    const SERVICE_NAME = 'checkoutPaymentMethodWriteServiceV1';
+    const RESOURCE_PATH = '/V1/carts/';
+
+    /**
+     * @var \Magento\TestFramework\ObjectManager
+     */
+    protected $objectManager;
+
+    protected function setUp()
+    {
+        $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Checkout/_files/quote_with_payment_saved.php
+     */
+    public function testReSetPayment()
+    {
+        /** @var \Magento\Sales\Model\Quote  $quote */
+        $quote = $this->objectManager->create('Magento\Sales\Model\Quote');
+        $quote->load('test_order_1_with_payment', 'reserved_order_id');
+        $cartId = $quote->getId();
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . $cartId . '/selected-payment-methods',
+                'httpMethod' => RestConfig::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'set',
+            ],
+        ];
+
+        $requestData = [
+            "cartId" => $cartId,
+            "method" => [
+                'method' => 'checkmo',
+                'po_number' => null,
+                'cc_owner' => 'John',
+                'cc_type' => null,
+                'cc_exp_year' => null,
+                'cc_exp_month' => null,
+            ],
+        ];
+
+        $this->_webApiCall($serviceInfo, $requestData);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Checkout/_files/quote_with_virtual_product_and_address.php
+     */
+    public function testSetPaymentWithVirtualProduct()
+    {
+        /** @var \Magento\Sales\Model\Quote  $quote */
+        $quote = $this->objectManager->create('Magento\Sales\Model\Quote');
+        $quote->load('test_order_with_virtual_product', 'reserved_order_id');
+        $cartId = $quote->getId();
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . $cartId . '/selected-payment-methods',
+                'httpMethod' => RestConfig::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'set',
+            ],
+        ];
+
+        $requestData = [
+            "cartId" => $cartId,
+            "method" => [
+                'method' => 'checkmo',
+                'po_number' => '200',
+                'cc_owner' => 'tester',
+                'cc_type' => 'test',
+                'cc_exp_year' => '2014',
+                'cc_exp_month' => '1',
+            ],
+        ];
+        $this->assertNotNull($this->_webApiCall($serviceInfo, $requestData));
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php
+     */
+    public function testSetPaymentWithSimpleProduct()
+    {
+        /** @var \Magento\Sales\Model\Quote  $quote */
+        $quote = $this->objectManager->create('Magento\Sales\Model\Quote');
+        $quote->load('test_order_1', 'reserved_order_id');
+        $cartId = $quote->getId();
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . $cartId . '/selected-payment-methods',
+                'httpMethod' => RestConfig::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'set',
+            ],
+        ];
+
+        $requestData = [
+            "cartId" => $cartId,
+            "method" => [
+                'method' => 'checkmo',
+                'po_number' => '200',
+                'cc_owner' => 'tester',
+                'cc_type' => 'test',
+                'cc_exp_year' => '2014',
+                'cc_exp_month' => '1',
+            ],
+        ];
+
+        $this->assertNotNull($this->_webApiCall($serviceInfo, $requestData));
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Checkout/_files/quote_with_virtual_product_saved.php
+     * @expectedException \Exception
+     * @expectedExceptionMessage Billing address is not set
+     */
+    public function testSetPaymentWithVirtualProductWithoutAddress()
+    {
+        /** @var \Magento\Sales\Model\Quote  $quote */
+        $quote = $this->objectManager->create('Magento\Sales\Model\Quote');
+        $quote->load('test_order_with_virtual_product_without_address', 'reserved_order_id');
+        $cartId = $quote->getId();
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . $cartId . '/selected-payment-methods',
+                'httpMethod' => RestConfig::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'set',
+            ],
+        ];
+
+        $requestData = [
+            "cartId" => $cartId,
+            "method" => [
+                'method' => 'checkmo',
+                'po_number' => '200',
+                'cc_owner' => 'tester',
+                'cc_type' => 'test',
+                'cc_exp_year' => '2014',
+                'cc_exp_month' => '1',
+            ],
+        ];
+        $this->_webApiCall($serviceInfo, $requestData);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php
+     * @expectedException \Exception
+     * @expectedExceptionMessage Shipping address is not set
+     */
+    public function testSetPaymentWithSimpleProductWithoutAddress()
+    {
+        /** @var \Magento\Sales\Model\Quote  $quote */
+        $quote = $this->objectManager->create('Magento\Sales\Model\Quote');
+        $quote->load('test_order_with_simple_product_without_address', 'reserved_order_id');
+        $cartId = $quote->getId();
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . $cartId . '/selected-payment-methods',
+                'httpMethod' => RestConfig::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'set',
+            ],
+        ];
+
+        $requestData = [
+            "cartId" => $cartId,
+            "method" => [
+                'method' => 'checkmo',
+                'po_number' => '200',
+                'cc_owner' => 'tester',
+                'cc_type' => 'test',
+                'cc_exp_year' => '2014',
+                'cc_exp_month' => '1',
+            ],
+        ];
+        $this->_webApiCall($serviceInfo, $requestData);
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Checkout/Service/V1/ShippingMethod/ReadServiceTest.php b/dev/tests/api-functional/testsuite/Magento/Checkout/Service/V1/ShippingMethod/ReadServiceTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..a1d95dea7a13d6036da233cdddff674a33360b57
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Checkout/Service/V1/ShippingMethod/ReadServiceTest.php
@@ -0,0 +1,174 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+namespace Magento\Checkout\Service\V1\ShippingMethod;
+
+use Magento\Checkout\Service\V1\Data\Cart\ShippingMethod;
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+class ReadServiceTest extends WebapiAbstract
+{
+    const SERVICE_VERSION = 'V1';
+    const SERVICE_NAME = 'checkoutShippingMethodReadServiceV1';
+    const RESOURCE_PATH = '/V1/carts/';
+
+    /**
+     * @var \Magento\TestFramework\ObjectManager
+     */
+    protected $objectManager;
+
+    protected function setUp()
+    {
+        $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Checkout/_files/quote_with_shipping_method.php
+     */
+    public function testGetMethod()
+    {
+        /** @var \Magento\Sales\Model\Quote $quote */
+        $quote = $this->objectManager->create('Magento\Sales\Model\Quote');
+        $quote->load('test_order_1', 'reserved_order_id');
+
+        $cartId = $quote->getId();
+
+        $shippingAddress = $quote->getShippingAddress();
+        list($carrierCode, $methodCode) = explode('_', $shippingAddress->getShippingMethod());
+        list($carrierTitle, $methodTitle) = explode(' - ', $shippingAddress->getShippingDescription());
+        $data = [
+            ShippingMethod::CARRIER_CODE => $carrierCode,
+            ShippingMethod::METHOD_CODE => $methodCode,
+            ShippingMethod::CARRIER_TITLE => $carrierTitle,
+            ShippingMethod::METHOD_TITLE => $methodTitle,
+            ShippingMethod::SHIPPING_AMOUNT => $shippingAddress->getShippingAmount(),
+            ShippingMethod::BASE_SHIPPING_AMOUNT => $shippingAddress->getBaseShippingAmount(),
+            ShippingMethod::AVAILABLE => true,
+        ];
+
+        $requestData = ["cartId" => $cartId];
+        $this->assertEquals($data, $this->_webApiCall($this->getSelectedMethodServiceInfo($cartId), $requestData));
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Checkout/_files/quote_with_virtual_product_and_address.php
+     */
+    public function testGetMethodOfVirtualCart()
+    {
+        $quote = $this->objectManager->create('Magento\Sales\Model\Quote');
+        $cartId = $quote->load('test_order_with_virtual_product', 'reserved_order_id')->getId();
+
+        $result = $this->_webApiCall($this->getSelectedMethodServiceInfo($cartId), ["cartId" => $cartId]);
+        $this->assertEquals([], $result);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php
+     */
+    public function testGetMethodOfCartWithNoShippingMethod()
+    {
+        $quote = $this->objectManager->create('Magento\Sales\Model\Quote');
+        $cartId = $quote->load('test_order_1', 'reserved_order_id')->getId();
+
+        $result = $this->_webApiCall($this->getSelectedMethodServiceInfo($cartId), ["cartId" => $cartId]);
+        $this->assertEquals([], $result);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Checkout/_files/quote_with_virtual_product_and_address.php
+     *
+     */
+    public function testGetListForVirtualCart()
+    {
+        $quote = $this->objectManager->create('Magento\Sales\Model\Quote');
+        $cartId = $quote->load('test_order_with_virtual_product', 'reserved_order_id')->getId();
+
+        $this->assertEquals([], $this->_webApiCall($this->getListServiceInfo($cartId), ["cartId" => $cartId]));
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php
+     */
+    public function testGetList()
+    {
+        /** @var \Magento\Sales\Model\Quote $quote */
+        $quote = $this->objectManager->create('Magento\Sales\Model\Quote');
+        $quote->load('test_order_1', 'reserved_order_id');
+        $cartId = $quote->getId();
+        if (!$cartId) {
+            $this->fail('quote fixture failed');
+        }
+        $quote->getShippingAddress()->collectShippingRates();
+        $expectedRates = $quote->getShippingAddress()->getGroupedAllShippingRates();
+
+        $expectedData = $this->convertRates($expectedRates, $quote->getQuoteCurrencyCode());
+
+        $requestData = ["cartId" => $cartId];
+
+        $returnedRates = $this->_webApiCall($this->getListServiceInfo($cartId), $requestData);
+        $this->assertEquals($expectedData, $returnedRates);
+    }
+
+    /**
+     * @param string $cartId
+     * @return array
+     */
+    protected function getSelectedMethodServiceInfo($cartId)
+    {
+        return $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . $cartId . '/selected-shipping-method',
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'GetMethod',
+            ],
+        ];
+    }
+
+    /**
+     * Service info
+     *
+     * @param int $cartId
+     * @return array
+     */
+    protected function getListServiceInfo($cartId)
+    {
+        return [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . $cartId . '/shipping-methods',
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'GetList',
+            ],
+        ];
+    }
+
+    /**
+     * Convert rate models array to data array
+     *
+     * @param string $currencyCode
+     * @param \Magento\Sales\Model\Quote\Address\Rate[] $groupedRates
+     * @return array
+     */
+    protected function convertRates($groupedRates, $currencyCode)
+    {
+        $result = [];
+        /** @var \Magento\Checkout\Service\V1\Data\Cart\ShippingMethodConverter $converter */
+        $converter = $this->objectManager->create('Magento\Checkout\Service\V1\Data\Cart\ShippingMethodConverter');
+        foreach ($groupedRates as $carrierRates) {
+            foreach ($carrierRates as $rate) {
+                $result[] = $converter->modelToDataObject($rate, $currencyCode)->__toArray();
+            }
+        }
+        return $result;
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Checkout/Service/V1/ShippingMethod/WriteServiceTest.php b/dev/tests/api-functional/testsuite/Magento/Checkout/Service/V1/ShippingMethod/WriteServiceTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..95539b9aa22a6f6cfc348f1172822cff73c704f6
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Checkout/Service/V1/ShippingMethod/WriteServiceTest.php
@@ -0,0 +1,106 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Checkout\Service\V1\ShippingMethod;
+
+use Magento\TestFramework\ObjectManager;
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+class WriteServiceTest extends WebapiAbstract
+{
+    /**
+     * @var ObjectManager
+     */
+    private $objectManager;
+
+    /**
+     * @var \Magento\Sales\Model\Quote
+     */
+    protected $quote;
+
+    protected function setUp()
+    {
+        $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+        $this->quote = $this->objectManager->create('Magento\Sales\Model\Quote');
+    }
+
+    protected function getServiceInfo()
+    {
+        return [
+            'rest' => [
+                'resourcePath' => '/V1/carts/' . $this->quote->getId() . '/selected-shipping-method',
+                'httpMethod' => RestConfig::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => 'checkoutShippingMethodWriteServiceV1',
+                'serviceVersion' => 'V1',
+                'operation' => 'checkoutShippingMethodWriteServiceV1SetMethod',
+            ],
+        ];
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php
+     */
+    public function testSetMethod()
+    {
+        $this->quote->load('test_order_1', 'reserved_order_id');
+        $serviceInfo = $this->getServiceInfo();
+
+        $requestData = [
+            'cartId' => $this->quote->getId(),
+            'carrierCode' => 'flatrate',
+            'methodCode' => 'flatrate',
+        ];
+        $result = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertEquals(true, $result);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php
+     */
+    public function testSetMethodWrongMethod()
+    {
+        $this->quote->load('test_order_1', 'reserved_order_id');
+        $serviceInfo = $this->getServiceInfo();
+
+        $requestData = [
+            'cartId' => $this->quote->getId(),
+            'carrierCode' => 'flatrate',
+            'methodCode' => 'wrongMethod',
+        ];
+        try {
+            $this->_webApiCall($serviceInfo, $requestData);
+        } catch (\SoapFault $e) {
+            $message = $e->getMessage();
+        } catch (\Exception $e) {
+            $message = json_decode($e->getMessage())->message;
+        }
+        $this->assertEquals('Carrier with such method not found: flatrate, wrongMethod', $message);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php
+     */
+    public function testSetMethodWithoutShippingAddress()
+    {
+        $this->quote->load('test_order_with_simple_product_without_address', 'reserved_order_id');
+        $serviceInfo = $this->getServiceInfo();
+
+        $requestData = [
+            'cartId' => $this->quote->getId(),
+            'carrierCode' => 'flatrate',
+            'methodCode' => 'flatrate',
+        ];
+        try {
+            $this->_webApiCall($serviceInfo, $requestData);
+        } catch (\SoapFault $e) {
+            $message = $e->getMessage();
+        } catch (\Exception $e) {
+            $message = json_decode($e->getMessage())->message;
+        }
+        $this->assertEquals('Shipping address is not set', $message);
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/CheckoutAgreements/Service/V1/Agreement/ReadServiceTest.php b/dev/tests/api-functional/testsuite/Magento/CheckoutAgreements/Service/V1/Agreement/ReadServiceTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..db6133696df0e1c5b26ffee84bba70c1a9f53504
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/CheckoutAgreements/Service/V1/Agreement/ReadServiceTest.php
@@ -0,0 +1,84 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\CheckoutAgreements\Service\V1\Agreement;
+
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+class ReadServiceTest extends WebapiAbstract
+{
+    /**
+     * @var array
+     */
+    private $listServiceInfo;
+
+    protected function setUp()
+    {
+        $this->listServiceInfo = [
+            'soap' => [
+                'service' => 'checkoutAgreementsAgreementReadServiceV1',
+                'serviceVersion' => 'V1',
+                'operation' => 'checkoutAgreementsAgreementReadServiceV1GetList',
+            ],
+            'rest' => [
+                'resourcePath' => '/V1/carts/licence/',
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+        ];
+    }
+
+    /**
+     * Retrieve agreement by given name
+     *
+     * @param string $name
+     * @return \Magento\CheckoutAgreements\Model\Agreement
+     * @throws \InvalidArgumentException
+     */
+    protected function getAgreementByName($name)
+    {
+        $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+        /** @var $agreement \Magento\CheckoutAgreements\Model\Agreement */
+        $agreement = $objectManager->create('Magento\CheckoutAgreements\Model\Agreement');
+        $agreement->load($name, 'name');
+        if (!$agreement->getId()) {
+            throw new \InvalidArgumentException('There is no checkout agreement with provided ID.');
+        }
+        return $agreement;
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/CheckoutAgreements/_files/agreement_active_with_html_content.php
+     * @magentoApiDataFixture Magento/CheckoutAgreements/_files/agreement_inactive_with_text_content.php
+     */
+    public function testGetListReturnsEmptyListIfCheckoutAgreementsAreDisabledOnFrontend()
+    {
+        // Checkout agreements are disabled by default
+        $agreements = $this->_webApiCall($this->listServiceInfo, []);
+        $this->assertEmpty($agreements);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/CheckoutAgreements/_files/agreement_active_with_html_content.php
+     * @magentoApiDataFixture Magento/CheckoutAgreements/_files/agreement_inactive_with_text_content.php
+     */
+    public function testGetListReturnsTheListOfActiveCheckoutAgreements()
+    {
+        // checkout/options/enable_agreements must be set to 1 in system configuration
+        // @todo remove next statement when \Magento\TestFramework\TestCase\WebapiAbstract::_updateAppConfig is fixed
+        $this->markTestIncomplete('This test relies on system configuration state.');
+        $agreementModel = $this->getAgreementByName('Checkout Agreement (active)');
+
+        $agreements = $this->_webApiCall($this->listServiceInfo, []);
+        $this->assertCount(1, $agreements);
+        $agreementData = $agreements[0];
+        $this->assertEquals($agreementModel->getId(), $agreementData['id']);
+        $this->assertEquals($agreementModel->getName(), $agreementData['name']);
+        $this->assertEquals($agreementModel->getContent(), $agreementData['content']);
+        $this->assertEquals($agreementModel->getContentHeight(), $agreementData['content_height']);
+        $this->assertEquals($agreementModel->getCheckboxText(), $agreementData['checkbox_text']);
+        $this->assertEquals($agreementModel->getIsActive(), $agreementData['active']);
+        $this->assertEquals($agreementModel->getIsHtml(), $agreementData['html']);
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/ConfigurableProduct/Api/ConfigurableProductManagementTest.php b/dev/tests/api-functional/testsuite/Magento/ConfigurableProduct/Api/ConfigurableProductManagementTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..487580b00352b660e8be5962a2fbddfe552203b4
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/ConfigurableProduct/Api/ConfigurableProductManagementTest.php
@@ -0,0 +1,79 @@
+<?php
+/**
+ *
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\ConfigurableProduct\Api;
+
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+use Magento\TestFramework\Helper\Bootstrap;
+
+class ConfigurableProductManagementTest extends \Magento\TestFramework\TestCase\WebapiAbstract
+{
+    const SERVICE_NAME = 'configurableProductConfigurableProductManagementV1';
+    const SERVICE_VERSION = 'V1';
+    const RESOURCE_PATH = '/V1/configurable-products/variation';
+
+    /**
+     * @magentoApiDataFixture Magento/ConfigurableProduct/_files/configurable_attribute.php
+     */
+    public function testGetVariation()
+    {
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH,
+                'httpMethod' => RestConfig::HTTP_METHOD_PUT
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'GenerateVariation'
+            ]
+        ];
+        /** @var \Magento\Catalog\Api\ProductAttributeRepositoryInterface $attributeRepository */
+        $attributeRepository = Bootstrap::getObjectManager()->get(
+            'Magento\Catalog\Api\ProductAttributeRepositoryInterface'
+        );
+        $attribute = $attributeRepository->get('test_configurable');
+        $attributeOptionValue = $attribute->getOptions()[0]->getValue();
+        $data = [
+            'product' => [
+                'sku' => 'test',
+                'price' => 10.0
+            ],
+            'options' => [
+                [
+                    'attribute_id' => 'test_configurable',
+                    'values' => [
+                        [
+                            'value_index' => $attributeOptionValue,
+                            'pricing_value' => 100.0
+                        ]
+                    ]
+                ]
+            ]
+
+        ];
+        $actual = $this->_webApiCall($serviceInfo, $data);
+
+        $expectedItems = [
+            [
+                'sku' => 'test-',
+                'price' => 110.0,
+                'name' => '-',
+                'store_id' => 1,
+                'status' => 1,
+                'visibility' => \Magento\Catalog\Model\Product\Visibility::VISIBILITY_NOT_VISIBLE,
+                'custom_attributes' => [
+                    [
+                        'attribute_code' => 'test_configurable',
+                        'value' => $attributeOptionValue
+                    ]
+                ]
+            ]
+        ];
+        ksort($expectedItems);
+        ksort($actual);
+        $this->assertEquals($expectedItems, $actual);
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/ConfigurableProduct/Api/LinkManagementTest.php b/dev/tests/api-functional/testsuite/Magento/ConfigurableProduct/Api/LinkManagementTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..e6c37af767e59e3017e0056be72fd05afebe7eda
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/ConfigurableProduct/Api/LinkManagementTest.php
@@ -0,0 +1,116 @@
+<?php
+/**
+ *
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\ConfigurableProduct\Api;
+
+use Magento\Webapi\Model\Rest\Config;
+
+class LinkManagementTest extends \Magento\TestFramework\TestCase\WebapiAbstract
+{
+    const SERVICE_NAME = 'configurableProductLinkManagementV1';
+    const SERVICE_VERSION = 'V1';
+    const RESOURCE_PATH = '/V1/configurable-products';
+
+    /**
+     * @magentoApiDataFixture Magento/ConfigurableProduct/_files/product_configurable.php
+     */
+    public function testGetChildren()
+    {
+        $productSku = 'configurable';
+
+        /** @var array $result */
+        $result = $this->getChildren($productSku);
+        $this->assertCount(2, $result);
+
+        foreach ($result as $product) {
+            $this->assertArrayHasKey('custom_attributes', $product);
+            $this->assertArrayHasKey('price', $product);
+            $this->assertArrayHasKey('updated_at', $product);
+
+            $this->assertArrayHasKey('name', $product);
+            $this->assertContains('Configurable Option', $product['name']);
+
+            $this->assertArrayHasKey('sku', $product);
+            $this->assertContains('simple_', $product['sku']);
+
+            $this->assertArrayHasKey('status', $product);
+            $this->assertEquals('1', $product['status']);
+
+            $this->assertArrayHasKey('visibility', $product);
+            $this->assertEquals('1', $product['visibility']);
+        }
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/ConfigurableProduct/_files/product_configurable.php
+     * @magentoApiDataFixture Magento/ConfigurableProduct/_files/delete_association.php
+     */
+    public function testAddChild()
+    {
+        $productSku = 'configurable';
+        $childSku = 'simple_10';
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/' . $productSku . '/child',
+                'httpMethod' => Config::HTTP_METHOD_POST
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'AddChild'
+            ]
+        ];
+        $res = $this->_webApiCall($serviceInfo, ['productSku' => $productSku, 'childSku' => $childSku]);
+        $this->assertTrue($res);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/ConfigurableProduct/_files/product_configurable.php
+     */
+    public function testRemoveChild()
+    {
+        $productSku = 'configurable';
+        $childSku = 'simple_10';
+        $this->assertTrue($this->removeChild($productSku, $childSku));
+    }
+
+    protected function removeChild($productSku, $childSku)
+    {
+        $resourcePath = self::RESOURCE_PATH . '/%s/child/%s';
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => sprintf($resourcePath, $productSku, $childSku),
+                'httpMethod' => Config::HTTP_METHOD_DELETE
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'RemoveChild'
+            ]
+        ];
+        $requestData = ['productSku' => $productSku, 'childSku' => $childSku];
+        return $this->_webApiCall($serviceInfo, $requestData);
+    }
+
+    /**
+     * @param string $productSku
+     * @return string
+     */
+    protected function getChildren($productSku)
+    {
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/' . $productSku  . '/children',
+                'httpMethod' => Config::HTTP_METHOD_GET
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'GetChildren'
+            ]
+        ];
+        return $this->_webApiCall($serviceInfo, ['productSku' => $productSku]);
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/ConfigurableProduct/Api/OptionRepositoryTest.php b/dev/tests/api-functional/testsuite/Magento/ConfigurableProduct/Api/OptionRepositoryTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..01f3bd7a1bddec8b358529b3ea79d86fa5134ee9
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/ConfigurableProduct/Api/OptionRepositoryTest.php
@@ -0,0 +1,289 @@
+<?php
+/**
+ *
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\ConfigurableProduct\Api;
+
+use Magento\Webapi\Model\Rest\Config;
+
+class OptionRepositoryTest extends \Magento\TestFramework\TestCase\WebapiAbstract
+{
+    const SERVICE_NAME = 'configurableProductOptionRepositoryV1';
+    const SERVICE_VERSION = 'V1';
+    const RESOURCE_PATH = '/V1/configurable-products';
+
+    /**
+     * @magentoApiDataFixture Magento/ConfigurableProduct/_files/product_configurable.php
+     */
+    public function testGet()
+    {
+        $productSku = 'configurable';
+
+        $options = $this->getList($productSku);
+        $this->assertTrue(is_array($options));
+        $this->assertNotEmpty($options);
+
+        foreach ($options as $option) {
+            /** @var array $result */
+            $result = $this->get($productSku, $option['id']);
+
+            $this->assertTrue(is_array($result));
+            $this->assertNotEmpty($result);
+
+            $this->assertArrayHasKey('id', $result);
+            $this->assertEquals($option['id'], $result['id']);
+
+            $this->assertArrayHasKey('attribute_id', $result);
+            $this->assertEquals($option['attribute_id'], $result['attribute_id']);
+
+            $this->assertArrayHasKey('label', $result);
+            $this->assertEquals($option['label'], $result['label']);
+
+            $this->assertArrayHasKey('values', $result);
+            $this->assertTrue(is_array($result['values']));
+            $this->assertEquals($option['values'], $result['values']);
+        }
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/ConfigurableProduct/_files/product_configurable.php
+     */
+    public function testGetList()
+    {
+        $productSku = 'configurable';
+
+        /** @var array $result */
+        $result = $this->getList($productSku);
+
+        $this->assertNotEmpty($result);
+        $this->assertTrue(is_array($result));
+        $this->assertArrayHasKey(0, $result);
+
+        $option = $result[0];
+
+        $this->assertNotEmpty($option);
+        $this->assertTrue(is_array($option));
+
+        $this->assertArrayHasKey('id', $option);
+        $this->assertArrayHasKey('label', $option);
+        $this->assertEquals($option['label'], 'Test Configurable');
+
+        $this->assertArrayHasKey('values', $option);
+        $this->assertTrue(is_array($option));
+        $this->assertNotEmpty($option);
+
+        $expectedValues = [
+            ['pricing_value' => 5, 'is_percent' => 0],
+            ['pricing_value' => 5, 'is_percent' => 0]
+        ];
+
+        $this->assertCount(count($expectedValues), $option['values']);
+
+        foreach ($option['values'] as $key => $value) {
+            $this->assertTrue(is_array($value));
+            $this->assertNotEmpty($value);
+
+            $this->assertArrayHasKey($key, $expectedValues);
+            $expectedValue = $expectedValues[$key];
+
+            $this->assertArrayHasKey('pricing_value', $value);
+            $this->assertEquals($expectedValue['pricing_value'], $value['pricing_value']);
+
+            $this->assertArrayHasKey('is_percent', $value);
+            $this->assertEquals($expectedValue['is_percent'], $value['is_percent']);
+        }
+    }
+
+    /**
+     * @expectedException \Exception
+     * @expectedExceptionMessage Requested product doesn't exist
+     */
+    public function testGetUndefinedProduct()
+    {
+        $productSku = 'product_not_exist';
+        $this->getList($productSku);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/ConfigurableProduct/_files/product_configurable.php
+     * @expectedException \Exception
+     * @expectedExceptionMessage Requested option doesn't exist: -42
+     */
+    public function testGetUndefinedOption()
+    {
+        $productSku = 'configurable';
+        $attributeId = -42;
+        $this->get($productSku, $attributeId);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/ConfigurableProduct/_files/product_configurable.php
+     */
+    public function testDelete()
+    {
+        $productSku = 'configurable';
+
+        $optionList = $this->getList($productSku);
+        $optionId = $optionList[0]['id'];
+        $resultRemove = $this->delete($productSku, $optionId);
+        $optionListRemoved = $this->getList($productSku);
+
+        $this->assertTrue($resultRemove);
+        $this->assertEquals(count($optionList) - 1, count($optionListRemoved));
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+     * @magentoApiDataFixture Magento/ConfigurableProduct/_files/configurable_attribute.php
+     */
+    public function testAdd()
+    {
+        $productSku = 'simple';
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/' . $productSku . '/options',
+                'httpMethod' => Config::HTTP_METHOD_POST
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Save'
+            ]
+        ];
+        $option = [
+            'attribute_id' => 'test_configurable',
+            'type' => 'select',
+            'label' => 'Test',
+            'values' => [
+                [
+                    'value_index' => 1,
+                    'pricing_value' => '3',
+                    'is_percent' => 0
+                ]
+            ],
+        ];
+        /** @var int $result */
+        $result = $this->_webApiCall($serviceInfo, ['productSku' => $productSku, 'option' => $option]);
+        $this->assertGreaterThan(0, $result);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/ConfigurableProduct/_files/product_configurable.php
+     */
+    public function testUpdate()
+    {
+        $productSku = 'configurable';
+        $configurableAttribute = $this->getConfigurableAttribute($productSku);
+        $optionId = $configurableAttribute[0]['id'];
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/' . $productSku . '/options' . '/' . $optionId,
+                'httpMethod' => Config::HTTP_METHOD_PUT
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Save'
+            ]
+        ];
+
+        $option = [
+            'label' => 'Update Test Configurable'
+        ];
+
+        $requestBody = ['option' => $option];
+        if (TESTS_WEB_API_ADAPTER == self::ADAPTER_SOAP) {
+            $requestBody['productSku'] = $productSku;
+            $requestBody['option']['id'] = $optionId;
+        }
+
+        $result = $this->_webApiCall($serviceInfo, $requestBody);
+        $this->assertGreaterThan(0, $result);
+        $configurableAttribute = $this->getConfigurableAttribute($productSku);
+        $this->assertEquals($option['label'], $configurableAttribute[0]['label']);
+    }
+
+    /**
+     * @param string $productSku
+     * @return array
+     */
+    protected function getConfigurableAttribute($productSku)
+    {
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/' . $productSku . '/options/all',
+                'httpMethod' => Config::HTTP_METHOD_GET
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'GetList'
+            ]
+        ];
+        return $this->_webApiCall($serviceInfo, ['productSku' => $productSku]);
+    }
+
+    /**
+     * @param string $productSku
+     * @param int $optionId
+     * @return bool
+     */
+    private function delete($productSku, $optionId)
+    {
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/' . $productSku . '/options/' . $optionId,
+                'httpMethod' => Config::HTTP_METHOD_DELETE
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'DeleteById'
+            ]
+        ];
+        return $this->_webApiCall($serviceInfo, ['productSku' => $productSku, 'optionId' => $optionId]);
+    }
+
+    /**
+     * @param $productSku
+     * @param $optionId
+     * @return array
+     */
+    protected function get($productSku, $optionId)
+    {
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/' . $productSku . '/options/' . $optionId,
+                'httpMethod'   => Config::HTTP_METHOD_GET
+            ],
+            'soap' => [
+                'service'        => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation'      => self::SERVICE_NAME . 'Get'
+            ]
+        ];
+        return $this->_webApiCall($serviceInfo, ['productSku' => $productSku, 'optionId' => $optionId]);
+    }
+
+    /**
+     * @param $productSku
+     * @return array
+     */
+    protected function getList($productSku)
+    {
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/' . $productSku . '/options/all',
+                'httpMethod'   => Config::HTTP_METHOD_GET
+            ],
+            'soap' => [
+                'service'        => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation'      => self::SERVICE_NAME . 'GetList'
+            ]
+        ];
+        return $this->_webApiCall($serviceInfo, ['productSku' => $productSku]);
+    }
+
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/ConfigurableProduct/Api/OptionTypesListTest.php b/dev/tests/api-functional/testsuite/Magento/ConfigurableProduct/Api/OptionTypesListTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..1a99fd960dba16752a08df70f60cf05ca7d041e6
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/ConfigurableProduct/Api/OptionTypesListTest.php
@@ -0,0 +1,41 @@
+<?php
+/**
+ *
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\ConfigurableProduct\Api;
+
+use Magento\Webapi\Model\Rest\Config;
+
+class OptionTypesListTest extends \Magento\TestFramework\TestCase\WebapiAbstract
+{
+    const SERVICE_READ_NAME = 'configurableProductOptionTypesListV1';
+    const SERVICE_VERSION = 'V1';
+    const RESOURCE_PATH = '/V1/configurable-products/:productSku/options/';
+
+    public function testGetTypes()
+    {
+        $expectedTypes = ['multiselect', 'select'];
+        $result = $this->getTypes();
+        $this->assertEquals($expectedTypes, $result);
+    }
+
+    /**
+     * @return array
+     */
+    protected function getTypes()
+    {
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => str_replace(':productSku/', '', self::RESOURCE_PATH) . 'types',
+                'httpMethod'   => Config::HTTP_METHOD_GET
+            ],
+            'soap' => [
+                'service'        => self::SERVICE_READ_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation'      => self::SERVICE_READ_NAME . 'GetItems'
+            ]
+        ];
+        return $this->_webApiCall($serviceInfo);
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Customer/Api/AccountManagementMeTest.php b/dev/tests/api-functional/testsuite/Magento/Customer/Api/AccountManagementMeTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..32eca4e2256338056e569e6523e1f83c4dd3e31c
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Customer/Api/AccountManagementMeTest.php
@@ -0,0 +1,335 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+namespace Magento\Customer\Api;
+
+use Magento\Customer\Api\Data\CustomerInterface;
+use Magento\Customer\Model\CustomerRegistry;
+use Magento\Integration\Model\Oauth\Token as TokenModel;
+use Magento\TestFramework\Helper\Bootstrap;
+use Magento\TestFramework\Helper\Customer as CustomerHelper;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+/**
+ * Class AccountManagementMeTest
+ *
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ * @magentoApiDataFixture Magento/Customer/_files/customer.php
+ * @magentoApiDataFixture Magento/Customer/_files/customer_two_addresses.php
+ */
+class AccountManagementMeTest extends \Magento\TestFramework\TestCase\WebapiAbstract
+{
+    const RESOURCE_PATH = '/V1/customers/me';
+    const RESOURCE_PATH_CUSTOMER_TOKEN = "/V1/integration/customer/token";
+
+    /**
+     * @var CustomerRepositoryInterface
+     */
+    private $customerRepository;
+
+    /**
+     * @var AccountManagementInterface
+     */
+    private $customerAccountManagement;
+
+    /**
+     * @var CustomerRegistry
+     */
+    private $customerRegistry;
+
+    /**
+     * @var CustomerHelper
+     */
+    private $customerHelper;
+
+    /**
+     * @var TokenModel
+     */
+    private $token;
+
+    /**
+     * @var CustomerInterface
+     */
+    private $customerData;
+
+    /**
+     * @var \Magento\Framework\Reflection\DataObjectProcessor
+     */
+    private $dataObjectProcessor;
+
+    /**
+     * Execute per test initialization.
+     */
+    public function setUp()
+    {
+        $this->_markTestAsRestOnly();
+
+        $this->customerRegistry = Bootstrap::getObjectManager()->get(
+            'Magento\Customer\Model\CustomerRegistry'
+        );
+
+        $this->customerRepository = Bootstrap::getObjectManager()->get(
+            'Magento\Customer\Api\CustomerRepositoryInterface',
+            ['customerRegistry' => $this->customerRegistry]
+        );
+
+        $this->customerAccountManagement = Bootstrap::getObjectManager()
+            ->get('Magento\Customer\Api\AccountManagementInterface');
+
+        $this->customerHelper = new CustomerHelper();
+        $this->customerData = $this->customerHelper->createSampleCustomer();
+
+        // get token
+        $this->resetTokenForCustomerSampleData();
+
+        $this->dataObjectProcessor = Bootstrap::getObjectManager()->create(
+            'Magento\Framework\Reflection\DataObjectProcessor'
+        );
+    }
+
+    /**
+     * Ensure that fixture customer and his addresses are deleted.
+     */
+    public function tearDown()
+    {
+        unset($this->customerRepository);
+
+        /** @var \Magento\Framework\Registry $registry */
+        $registry = Bootstrap::getObjectManager()->get('Magento\Framework\Registry');
+        $registry->unregister('isSecureArea');
+        $registry->register('isSecureArea', true);
+
+        $registry->unregister('isSecureArea');
+        $registry->register('isSecureArea', false);
+        parent::tearDown();
+    }
+
+    public function testChangePassword()
+    {
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/password',
+                'httpMethod' => RestConfig::HTTP_METHOD_PUT,
+                'token' => $this->token,
+            ],
+        ];
+        $requestData = ['currentPassword' => 'test@123', 'newPassword' => '123@test'];
+        $this->assertTrue($this->_webApiCall($serviceInfo, $requestData));
+
+        $customerResponseData = $this->customerAccountManagement
+            ->authenticate($this->customerData[CustomerInterface::EMAIL], '123@test');
+        $this->assertEquals($this->customerData[CustomerInterface::ID], $customerResponseData->getId());
+    }
+
+    public function testUpdateCustomer()
+    {
+        $customerData = $this->_getCustomerData($this->customerData[CustomerInterface::ID]);
+        $lastName = $customerData->getLastname();
+
+        $updatedCustomerData = $this->dataObjectProcessor->buildOutputDataArray(
+            $customerData,
+            'Magento\Customer\Api\Data\CustomerInterface'
+        );
+        $updatedCustomerData[CustomerInterface::LASTNAME] = $lastName . 'Updated';
+        $updatedCustomerData[CustomerInterface::ID] = 25;
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH,
+                'httpMethod' => RestConfig::HTTP_METHOD_PUT,
+                'token' => $this->token,
+            ],
+        ];
+        $requestData = ['customer' => $updatedCustomerData];
+
+        $response = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertEquals($lastName . "Updated", $response[CustomerInterface::LASTNAME]);
+
+        $customerData = $this->_getCustomerData($this->customerData[CustomerInterface::ID]);
+        $this->assertEquals($lastName . "Updated", $customerData->getLastname());
+    }
+
+    public function testGetCustomerData()
+    {
+        //Get expected details from the Service directly
+        $customerData = $this->_getCustomerData($this->customerData[CustomerInterface::ID]);
+        $expectedCustomerDetails = $this->dataObjectProcessor->buildOutputDataArray(
+            $customerData,
+            'Magento\Customer\Api\Data\CustomerInterface'
+        );
+        $expectedCustomerDetails['addresses'][0]['id'] =
+            (int)$expectedCustomerDetails['addresses'][0]['id'];
+
+        $expectedCustomerDetails['addresses'][1]['id'] =
+            (int)$expectedCustomerDetails['addresses'][1]['id'];
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH,
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+                'token' => $this->token,
+            ],
+        ];
+        $customerDetailsResponse = $this->_webApiCall($serviceInfo);
+
+        unset($expectedCustomerDetails['custom_attributes']);
+        unset($customerDetailsResponse['custom_attributes']); //for REST
+
+        $this->assertEquals($expectedCustomerDetails, $customerDetailsResponse);
+    }
+
+    public function testGetCustomerActivateCustomer()
+    {
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/activate',
+                'httpMethod' => RestConfig::HTTP_METHOD_PUT,
+                'token' => $this->token,
+            ],
+        ];
+        $requestData = ['confirmationKey' => $this->customerData[CustomerInterface::CONFIRMATION]];
+        $customerResponseData = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertEquals($this->customerData[CustomerInterface::ID], $customerResponseData[CustomerInterface::ID]);
+        // Confirmation key is removed after confirmation
+        $this->assertFalse(isset($customerResponseData[CustomerInterface::CONFIRMATION]));
+    }
+
+    /**
+     * Return the customer details.
+     *
+     * @param int $customerId
+     * @return \Magento\Customer\Api\Data\CustomerInterface
+     */
+    protected function _getCustomerData($customerId)
+    {
+        $data = $this->customerRepository->getById($customerId);
+        $this->customerRegistry->remove($customerId);
+        return $data;
+    }
+
+    public function testGetDefaultBillingAddress()
+    {
+        $this->resetTokenForCustomerFixture();
+
+        $fixtureCustomerId = 1;
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => "/V1/customers/me/billingAddress",
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_GET,
+                'token' => $this->token,
+            ],
+        ];
+        $requestData = ['customerId' => $fixtureCustomerId];
+        $addressData = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertEquals(
+            $this->getFirstFixtureAddressData(),
+            $addressData,
+            "Default billing address data is invalid."
+        );
+    }
+
+    public function testGetDefaultShippingAddress()
+    {
+        $this->resetTokenForCustomerFixture();
+
+        $fixtureCustomerId = 1;
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => "/V1/customers/me/shippingAddress",
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_GET,
+                'token' => $this->token,
+            ],
+        ];
+        $requestData = ['customerId' => $fixtureCustomerId];
+        $addressData = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertEquals(
+            $this->getFirstFixtureAddressData(),
+            $addressData,
+            "Default shipping address data is invalid."
+        );
+    }
+
+    /**
+     * Retrieve data of the first fixture address.
+     *
+     * @return array
+     */
+    protected function getFirstFixtureAddressData()
+    {
+        return [
+            'firstname' => 'John',
+            'lastname' => 'Smith',
+            'city' => 'CityM',
+            'country_id' => 'US',
+            'company' => 'CompanyName',
+            'postcode' => '75477',
+            'telephone' => '3468676',
+            'street' => ['Green str, 67'],
+            'id' => 1,
+            'default_billing' => true,
+            'default_shipping' => true,
+            'customer_id' => '1',
+            'region' => ['region' => 'Alabama', 'region_id' => 1, 'region_code' => 'AL'],
+        ];
+    }
+
+    /**
+     * Retrieve data of the second fixture address.
+     *
+     * @return array
+     */
+    protected function getSecondFixtureAddressData()
+    {
+        return [
+            'firstname' => 'John',
+            'lastname' => 'Smith',
+            'city' => 'CityX',
+            'country_id' => 'US',
+            'postcode' => '47676',
+            'telephone' => '3234676',
+            'street' => ['Black str, 48'],
+            'id' => 2,
+            'default_billing' => false,
+            'default_shipping' => false,
+            'customer_id' => '1',
+            'region' => ['region' => 'Alabama', 'region_id' => 1, 'region_code' => 'AL'],
+        ];
+    }
+
+    /**
+     * Sets the test's access token for the customer fixture
+     */
+    protected function resetTokenForCustomerFixture()
+    {
+        $this->resetTokenForCustomer('customer@example.com', 'password');
+    }
+
+    /**
+     * Sets the test's access token for the created customer sample data
+     */
+    protected function resetTokenForCustomerSampleData()
+    {
+        $this->resetTokenForCustomer($this->customerData[CustomerInterface::EMAIL], 'test@123');
+    }
+
+    /**
+     * Sets the test's access token for a particular username and password.
+     *
+     * @param string $username
+     * @param string $password
+     */
+    protected function resetTokenForCustomer($username, $password)
+    {
+        // get customer ID token
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH_CUSTOMER_TOKEN,
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_POST,
+            ],
+        ];
+        $requestData = ['username' => $username, 'password' => $password];
+        $this->token = $this->_webApiCall($serviceInfo, $requestData);
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Customer/Api/AccountManagementTest.php b/dev/tests/api-functional/testsuite/Magento/Customer/Api/AccountManagementTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..50a2790634cf7a5ec4d45a3b6d885f7fb8d35fcd
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Customer/Api/AccountManagementTest.php
@@ -0,0 +1,778 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Customer\Api;
+
+use Magento\Customer\Api\Data\CustomerInterface as Customer;
+use Magento\Customer\Model\AccountManagement;
+use Magento\Framework\Exception\InputException;
+use Magento\Framework\Exception\NoSuchEntityException;
+use Magento\TestFramework\Helper\Bootstrap;
+use Magento\TestFramework\Helper\Customer as CustomerHelper;
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Exception as HTTPExceptionCodes;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+/**
+ * Test class for Magento\Customer\Api\AccountManagementInterface
+ *
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ */
+class AccountManagementTest extends WebapiAbstract
+{
+    const SERVICE_VERSION = 'V1';
+    const SERVICE_NAME = 'customerAccountManagementV1';
+    const RESOURCE_PATH = '/V1/customers';
+
+    /**
+     * Sample values for testing
+     */
+    const ATTRIBUTE_CODE = 'attribute_code';
+    const ATTRIBUTE_VALUE = 'attribute_value';
+
+    /**
+     * @var AccountManagementInterface
+     */
+    private $accountManagement;
+
+    /**
+     * @var \Magento\Customer\Api\Data\AddressDataBuilder
+     */
+    private $addressBuilder;
+
+    /**
+     * @var \Magento\Customer\Api\Data\CustomerDataBuilder
+     */
+    private $customerBuilder;
+
+    /**
+     * @var \Magento\Framework\Api\SearchCriteriaBuilder
+     */
+    private $searchCriteriaBuilder;
+
+    /**
+     * @var \Magento\Framework\Api\SortOrderBuilder
+     */
+    private $sortOrderBuilder;
+
+    /**
+     * @var \Magento\Framework\Api\Search\FilterGroupBuilder
+     */
+    private $filterGroupBuilder;
+
+    /**
+     * @var CustomerHelper
+     */
+    private $customerHelper;
+
+    /**
+     * @var array
+     */
+    private $currentCustomerId;
+
+    /**
+     * @var \Magento\Framework\Reflection\DataObjectProcessor
+     */
+    private $dataObjectProcessor;
+
+    /**
+     * Execute per test initialization.
+     */
+    public function setUp()
+    {
+        $this->accountManagement = Bootstrap::getObjectManager()->get(
+            'Magento\Customer\Api\AccountManagementInterface'
+        );
+        $this->addressBuilder = Bootstrap::getObjectManager()->create(
+            'Magento\Customer\Api\Data\AddressDataBuilder'
+        );
+        $this->customerBuilder = Bootstrap::getObjectManager()->create(
+            'Magento\Customer\Api\Data\CustomerDataBuilder'
+        );
+        $this->searchCriteriaBuilder = Bootstrap::getObjectManager()->create(
+            'Magento\Framework\Api\SearchCriteriaBuilder'
+        );
+        $this->sortOrderBuilder = Bootstrap::getObjectManager()->create(
+            'Magento\Framework\Api\SortOrderBuilder'
+        );
+        $this->filterGroupBuilder = Bootstrap::getObjectManager()->create(
+            'Magento\Framework\Api\Search\FilterGroupBuilder'
+        );
+        $this->customerHelper = new CustomerHelper();
+
+        $this->dataObjectProcessor = Bootstrap::getObjectManager()->create(
+            'Magento\Framework\Reflection\DataObjectProcessor'
+        );
+    }
+
+    public function tearDown()
+    {
+        if (!empty($this->currentCustomerId)) {
+            foreach ($this->currentCustomerId as $customerId) {
+                $serviceInfo = [
+                    'rest' => [
+                        'resourcePath' => self::RESOURCE_PATH . '/' . $customerId,
+                        'httpMethod' => RestConfig::HTTP_METHOD_DELETE,
+                    ],
+                    'soap' => [
+                        'service' => CustomerRepositoryTest::SERVICE_NAME,
+                        'serviceVersion' => self::SERVICE_VERSION,
+                        'operation' => CustomerRepositoryTest::SERVICE_NAME . 'DeleteById',
+                    ],
+                ];
+
+                $response = $this->_webApiCall($serviceInfo, ['customerId' => $customerId]);
+
+                $this->assertTrue($response);
+            }
+        }
+        unset($this->accountManagement);
+    }
+
+    public function testCreateCustomer()
+    {
+        $customerData = $this->_createCustomer();
+        $this->assertNotNull($customerData['id']);
+    }
+
+    public function testCreateCustomerWithErrors()
+    {
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH,
+                'httpMethod' => RestConfig::HTTP_METHOD_POST, ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'CreateAccount',
+            ],
+        ];
+
+        $customerDataArray = $this->dataObjectProcessor->buildOutputDataArray(
+            $this->customerHelper->createSampleCustomerDataObject(),
+            '\Magento\Customer\Api\Data\CustomerInterface'
+        );
+        $invalidEmail = 'invalid';
+        $customerDataArray['email'] = $invalidEmail;
+        $requestData = ['customer' => $customerDataArray, 'password' => CustomerHelper::PASSWORD];
+        try {
+            $this->_webApiCall($serviceInfo, $requestData);
+            $this->fail('Expected exception did not occur.');
+        } catch (\Exception $e) {
+            if (TESTS_WEB_API_ADAPTER == self::ADAPTER_SOAP) {
+                $expectedException = new InputException();
+                $expectedException->addError(
+                    InputException::INVALID_FIELD_VALUE,
+                    ['fieldName' => 'email', 'value' => $invalidEmail]
+                );
+                $this->assertInstanceOf('SoapFault', $e);
+                $this->checkSoapFault(
+                    $e,
+                    $expectedException->getRawMessage(),
+                    'env:Sender',
+                    $expectedException->getParameters() // expected error parameters
+                );
+            } else {
+                $this->assertEquals(HTTPExceptionCodes::HTTP_BAD_REQUEST, $e->getCode());
+                $exceptionData = $this->processRestExceptionResult($e);
+                $expectedExceptionData = [
+                    'message' => InputException::INVALID_FIELD_VALUE,
+                    'parameters' => [
+                        'fieldName' => 'email',
+                        'value' => $invalidEmail,
+                    ],
+                ];
+                $this->assertEquals($expectedExceptionData, $exceptionData);
+            }
+        }
+    }
+
+    /**
+     * Test customer activation when it is required
+     *
+     * @magentoConfigFixture default_store customer/create_account/confirm 0
+     */
+    public function testActivateCustomer()
+    {
+        $customerData = $this->_createCustomer();
+        $this->assertNotNull($customerData[Customer::CONFIRMATION], 'Customer activation is not required');
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/' . $customerData[Customer::EMAIL] . '/activate',
+                'httpMethod' => RestConfig::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Activate',
+            ],
+        ];
+
+        $requestData = [
+            'email' => $customerData[Customer::EMAIL],
+            'confirmationKey' => $customerData[Customer::CONFIRMATION],
+        ];
+
+        $result = $this->_webApiCall($serviceInfo, $requestData);
+
+        $this->assertEquals($customerData[Customer::ID], $result[Customer::ID], 'Wrong customer!');
+        $this->assertTrue(
+            !isset($result[Customer::CONFIRMATION]) || $result[Customer::CONFIRMATION] === null,
+            'Customer is not activated!'
+        );
+    }
+
+    public function testGetCustomerActivateCustomer()
+    {
+        $customerData = $this->_createCustomer();
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/' . $customerData[Customer::EMAIL] . '/activate',
+                'httpMethod' => RestConfig::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Activate',
+            ],
+        ];
+        $requestData = [
+            'email' => $customerData[Customer::EMAIL],
+            'confirmationKey' => $customerData[Customer::CONFIRMATION],
+        ];
+
+        $customerResponseData = $this->_webApiCall($serviceInfo, $requestData);
+
+        $this->assertEquals($customerData[Customer::ID], $customerResponseData[Customer::ID]);
+        // Confirmation key is removed after confirmation
+        $this->assertFalse(isset($customerResponseData[Customer::CONFIRMATION]));
+    }
+
+    public function testValidateResetPasswordLinkToken()
+    {
+        $customerData = $this->_createCustomer();
+        /** @var \Magento\Customer\Model\Customer $customerModel */
+        $customerModel = Bootstrap::getObjectManager()->create('Magento\Customer\Model\CustomerFactory')
+            ->create();
+        $customerModel->load($customerData[Customer::ID]);
+        $rpToken = 'lsdj579slkj5987slkj595lkj';
+        $customerModel->setRpToken('lsdj579slkj5987slkj595lkj');
+        $customerModel->setRpTokenCreatedAt(date('Y-m-d'));
+        $customerModel->save();
+        $path = self::RESOURCE_PATH . '/' . $customerData[Customer::ID] . '/password/resetLinkToken/' . $rpToken;
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => $path,
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'ValidateResetPasswordLinkToken',
+            ],
+        ];
+
+        $this->_webApiCall(
+            $serviceInfo,
+            ['customerId' => $customerData['id'], 'resetPasswordLinkToken' => $rpToken]
+        );
+    }
+
+    public function testValidateResetPasswordLinkTokenInvalidToken()
+    {
+        $customerData = $this->_createCustomer();
+        $invalidToken = 'fjjkafjie';
+        $path = self::RESOURCE_PATH . '/' . $customerData[Customer::ID] . '/password/resetLinkToken/' . $invalidToken;
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => $path,
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'ValidateResetPasswordLinkToken',
+            ],
+        ];
+
+        $expectedMessage = 'Reset password token mismatch.';
+
+        try {
+            if (TESTS_WEB_API_ADAPTER == self::ADAPTER_SOAP) {
+                $this->_webApiCall(
+                    $serviceInfo,
+                    ['customerId' => $customerData['id'], 'resetPasswordLinkToken' => 'invalid']
+                );
+            } else {
+                $this->_webApiCall($serviceInfo);
+            }
+            $this->fail("Expected exception to be thrown.");
+        } catch (\SoapFault $e) {
+            $this->assertContains(
+                $expectedMessage,
+                $e->getMessage(),
+                "Exception message does not match"
+            );
+        } catch (\Exception $e) {
+            $errorObj = $this->processRestExceptionResult($e);
+            $this->assertEquals($expectedMessage, $errorObj['message']);
+            $this->assertEquals(HTTPExceptionCodes::HTTP_BAD_REQUEST, $e->getCode());
+        }
+    }
+
+    public function testInitiatePasswordMissingRequiredFields()
+    {
+        $this->_markTestAsRestOnly('Soap clients explicitly check for required fields based on WSDL.');
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/password',
+                'httpMethod' => RestConfig::HTTP_METHOD_PUT,
+            ]
+        ];
+
+        try {
+            $this->_webApiCall($serviceInfo);
+        } catch (\Exception $e) {
+            $this->assertEquals(\Magento\Webapi\Exception::HTTP_BAD_REQUEST, $e->getCode());
+            $exceptionData = $this->processRestExceptionResult($e);
+            $expectedExceptionData = [
+                'message' => InputException::DEFAULT_MESSAGE,
+                'errors' => [
+                    [
+                        'message' => InputException::REQUIRED_FIELD,
+                        'parameters' => [
+                            'fieldName' => 'email',
+                        ],
+                    ],
+                    [
+                        'message' => InputException::REQUIRED_FIELD,
+                        'parameters' => [
+                            'fieldName' => 'template',
+                        ]
+                    ],
+                ],
+            ];
+            $this->assertEquals($expectedExceptionData, $exceptionData);
+        }
+    }
+
+    public function testInitiatePasswordReset()
+    {
+        $customerData = $this->_createCustomer();
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/password',
+                'httpMethod' => RestConfig::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'InitiatePasswordReset',
+            ],
+        ];
+        $requestData = [
+            'email' => $customerData[Customer::EMAIL],
+            'template' => AccountManagement::EMAIL_RESET,
+            'websiteId' => $customerData[Customer::WEBSITE_ID],
+        ];
+        // This api doesn't return any response.
+        // No exception or response means the request was processed successfully.
+        // The webapi framework does not return the header information as yet. A check for HTTP 200 would be ideal here
+        $this->_webApiCall($serviceInfo, $requestData);
+    }
+
+    public function testSendPasswordResetLinkBadEmailOrWebsite()
+    {
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/password',
+                'httpMethod' => RestConfig::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'InitiatePasswordReset',
+            ],
+        ];
+        $requestData = [
+            'email' => 'dummy@example.com',
+            'template' => AccountManagement::EMAIL_RESET,
+            'websiteId' => 0,
+        ];
+        try {
+            $this->_webApiCall($serviceInfo, $requestData);
+        } catch (\Exception $e) {
+            $expectedErrorParameters =
+                [
+                    'fieldName' => 'email',
+                    'fieldValue' => 'dummy@example.com',
+                    'field2Name' => 'websiteId',
+                    'field2Value' => 0,
+                ];
+            if (TESTS_WEB_API_ADAPTER == self::ADAPTER_REST) {
+                $errorObj = $this->processRestExceptionResult($e);
+                $this->assertEquals(
+                    NoSuchEntityException::MESSAGE_DOUBLE_FIELDS,
+                    $errorObj['message']
+                );
+                $this->assertEquals($expectedErrorParameters, $errorObj['parameters']);
+                $this->assertEquals(HTTPExceptionCodes::HTTP_NOT_FOUND, $e->getCode());
+            } else {
+                $this->assertInstanceOf('SoapFault', $e);
+                $this->checkSoapFault(
+                    $e,
+                    NoSuchEntityException::MESSAGE_DOUBLE_FIELDS,
+                    'env:Sender',
+                    $expectedErrorParameters
+                );
+            }
+        }
+    }
+
+    public function testGetConfirmationStatus()
+    {
+        $customerData = $this->_createCustomer();
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/' . $customerData[Customer::ID] . '/confirm',
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'GetConfirmationStatus',
+            ],
+        ];
+
+        $confirmationResponse = $this->_webApiCall($serviceInfo, ['customerId' => $customerData['id']]);
+
+        $this->assertEquals(AccountManagement::ACCOUNT_CONFIRMATION_NOT_REQUIRED, $confirmationResponse);
+    }
+
+    public function testResendConfirmation()
+    {
+        $customerData = $this->_createCustomer();
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/confirm',
+                'httpMethod' => RestConfig::HTTP_METHOD_POST,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'ResendConfirmation',
+            ],
+        ];
+        $requestData = [
+            'email' => $customerData[Customer::EMAIL],
+            'websiteId' => $customerData[Customer::WEBSITE_ID],
+        ];
+        // This api doesn't return any response.
+        // No exception or response means the request was processed successfully.
+        // The webapi framework does not return the header information as yet. A check for HTTP 200 would be ideal here
+        $this->_webApiCall($serviceInfo, $requestData);
+    }
+
+    public function testResendConfirmationBadEmailOrWebsite()
+    {
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/confirm',
+                'httpMethod' => RestConfig::HTTP_METHOD_POST,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'ResendConfirmation',
+            ],
+        ];
+        $requestData = [
+            'email' => 'dummy@example.com',
+            'websiteId' => 0,
+        ];
+        try {
+            $this->_webApiCall($serviceInfo, $requestData);
+        } catch (\Exception $e) {
+            $expectedErrorParameters =
+                [
+                    'fieldName' => 'email',
+                    'fieldValue' => 'dummy@example.com',
+                    'field2Name' => 'websiteId',
+                    'field2Value' => 0,
+                ];
+            if (TESTS_WEB_API_ADAPTER == self::ADAPTER_REST) {
+                $errorObj = $this->processRestExceptionResult($e);
+                $this->assertEquals(
+                    NoSuchEntityException::MESSAGE_DOUBLE_FIELDS,
+                    $errorObj['message']
+                );
+                $this->assertEquals($expectedErrorParameters, $errorObj['parameters']);
+                $this->assertEquals(HTTPExceptionCodes::HTTP_NOT_FOUND, $e->getCode());
+            } else {
+                $this->assertInstanceOf('SoapFault', $e);
+                $this->checkSoapFault(
+                    $e,
+                    NoSuchEntityException::MESSAGE_DOUBLE_FIELDS,
+                    'env:Sender',
+                    $expectedErrorParameters
+                );
+            }
+        }
+    }
+
+    public function testValidateCustomerData()
+    {
+        $customerData = $this->customerHelper->createSampleCustomerDataObject();
+        $customerData = $this->customerBuilder->populate($customerData)
+            ->setFirstname(null)->setLastname(null)->create();
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/validate',
+                'httpMethod' => RestConfig::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Validate',
+            ],
+        ];
+        $customerData = $this->dataObjectProcessor->buildOutputDataArray(
+            $customerData,
+            '\Magento\Customer\Api\Data\CustomerInterface'
+        );
+        $requestData = ['customer' => $customerData];
+        $validationResponse = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertFalse($validationResponse['valid']);
+        $this->assertEquals('The first name cannot be empty.', $validationResponse['messages'][0]);
+        $this->assertEquals('The last name cannot be empty.', $validationResponse['messages'][1]);
+    }
+
+    public function testIsReadonly()
+    {
+        $customerData = $this->_createCustomer();
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/' . $customerData[Customer::ID] . '/permissions/readonly',
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'IsReadonly',
+            ],
+        ];
+
+        $response = $this->_webApiCall($serviceInfo, ['customerId' => $customerData['id']]);
+
+        $this->assertFalse($response);
+    }
+
+    public function testEmailAvailable()
+    {
+        $customerData = $this->_createCustomer();
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/isEmailAvailable',
+                'httpMethod' => RestConfig::HTTP_METHOD_POST,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'IsEmailAvailable',
+            ],
+        ];
+        $requestData = [
+            'customerEmail' => $customerData[Customer::EMAIL],
+            'websiteId' => $customerData[Customer::WEBSITE_ID],
+        ];
+        $this->assertFalse($this->_webApiCall($serviceInfo, $requestData));
+    }
+
+    public function testEmailAvailableInvalidEmail()
+    {
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/isEmailAvailable',
+                'httpMethod' => RestConfig::HTTP_METHOD_POST,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'IsEmailAvailable',
+            ],
+        ];
+        $requestData = [
+            'customerEmail' => 'invalid',
+            'websiteId' => 0,
+        ];
+        $this->assertTrue($this->_webApiCall($serviceInfo, $requestData));
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Customer/_files/attribute_user_defined_address.php
+     * @magentoApiDataFixture Magento/Customer/_files/attribute_user_defined_customer.php
+     */
+    public function testCustomAttributes()
+    {
+        //Sample customer data comes with the disable_auto_group_change custom attribute
+        $customerData = $this->customerHelper->createSampleCustomerDataObject();
+        //address attribute code from fixture
+        $fixtureAddressAttributeCode = 'address_user_attribute';
+        //customer attribute code from fixture
+        $fixtureCustomerAttributeCode = 'user_attribute';
+        //Custom Attribute Values
+        $address1CustomAttributeValue = 'value1';
+        $address2CustomAttributeValue = 'value2';
+        $customerCustomAttributeValue = 'value3';
+
+        $addresses = $customerData->getAddresses();
+        $address1 = $this->addressBuilder
+            ->populate($addresses[0])
+            ->setCustomAttribute($fixtureAddressAttributeCode, $address1CustomAttributeValue)
+            ->create();
+        $address2 = $this->addressBuilder
+            ->populate($addresses[1])
+            ->setCustomAttribute($fixtureAddressAttributeCode, $address2CustomAttributeValue)
+            ->create();
+
+        $customer = $this->customerBuilder
+            ->populate($customerData)
+            ->setAddresses([$address1, $address2])
+            ->setCustomAttribute($fixtureCustomerAttributeCode, $customerCustomAttributeValue)
+            ->create();
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH,
+                'httpMethod' => RestConfig::HTTP_METHOD_POST,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'CreateAccount',
+            ],
+        ];
+
+        $customerDataArray = $this->dataObjectProcessor->buildOutputDataArray(
+            $customer,
+            '\Magento\Customer\Api\Data\CustomerInterface'
+        );
+        $requestData = ['customer' => $customerDataArray, 'password' => CustomerHelper::PASSWORD];
+        $customerData = $this->_webApiCall($serviceInfo, $requestData);
+        $customerId = $customerData['id'];
+        //TODO: Fix assertions to verify custom attributes
+        $this->assertNotNull($customerData);
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/' . $customerId ,
+                'httpMethod' => RestConfig::HTTP_METHOD_DELETE,
+            ],
+            'soap' => [
+                'service' => CustomerRepositoryTest::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => CustomerRepositoryTest::SERVICE_NAME . 'DeleteById',
+            ],
+        ];
+
+        $response = $this->_webApiCall($serviceInfo, ['customerId' => $customerId]);
+        $this->assertTrue($response);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Customer/_files/customer.php
+     * @magentoApiDataFixture Magento/Customer/_files/customer_two_addresses.php
+     */
+    public function testGetDefaultBillingAddress()
+    {
+        $fixtureCustomerId = 1;
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . "/$fixtureCustomerId/billingAddress",
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'GetDefaultBillingAddress',
+            ],
+        ];
+        $requestData = ['customerId' => $fixtureCustomerId];
+        $addressData = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertEquals(
+            $this->getFirstFixtureAddressData(),
+            $addressData,
+            "Default billing address data is invalid."
+        );
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Customer/_files/customer.php
+     * @magentoApiDataFixture Magento/Customer/_files/customer_two_addresses.php
+     */
+    public function testGetDefaultShippingAddress()
+    {
+        $fixtureCustomerId = 1;
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . "/$fixtureCustomerId/shippingAddress",
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'GetDefaultShippingAddress',
+            ],
+        ];
+        $requestData = ['customerId' => $fixtureCustomerId];
+        $addressData = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertEquals(
+            $this->getFirstFixtureAddressData(),
+            $addressData,
+            "Default shipping address data is invalid."
+        );
+    }
+
+    /**
+     * @return array|bool|float|int|string
+     */
+    protected function _createCustomer()
+    {
+        $customerData = $this->customerHelper->createSampleCustomer();
+        $this->currentCustomerId[] = $customerData['id'];
+        return $customerData;
+    }
+
+    /**
+     * Retrieve data of the first fixture address.
+     *
+     * @return array
+     */
+    protected function getFirstFixtureAddressData()
+    {
+        return [
+            'firstname' => 'John',
+            'lastname' => 'Smith',
+            'city' => 'CityM',
+            'country_id' => 'US',
+            'company' => 'CompanyName',
+            'postcode' => '75477',
+            'telephone' => '3468676',
+            'street' => ['Green str, 67'],
+            'id' => 1,
+            'default_billing' => true,
+            'default_shipping' => true,
+            'customer_id' => '1',
+            'region' => ['region' => 'Alabama', 'region_id' => 1, 'region_code' => 'AL'],
+        ];
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Customer/Api/AddressMetadataTest.php b/dev/tests/api-functional/testsuite/Magento/Customer/Api/AddressMetadataTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..f3acea94fc9bff6081ddd8f0647fc6e3f17020a7
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Customer/Api/AddressMetadataTest.php
@@ -0,0 +1,274 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+namespace Magento\Customer\Api;
+
+use Magento\Customer\Api\Data\AddressInterface as Address;
+use Magento\Customer\Model\Data\AttributeMetadata;
+use Magento\TestFramework\TestCase\WebapiAbstract;
+
+/**
+ * Class AddressMetadataTest
+ */
+class AddressMetadataTest extends WebapiAbstract
+{
+    const SERVICE_NAME = "customerAddressMetadataV1";
+    const SERVICE_VERSION = "V1";
+    const RESOURCE_PATH = "/V1/attributeMetadata/customerAddress";
+
+    /**
+     * Test retrieval of attribute metadata for the address entity type.
+     *
+     * @param string $attributeCode The attribute code of the requested metadata.
+     * @param array $expectedMetadata Expected entity metadata for the attribute code.
+     * @dataProvider getAttributeMetadataDataProvider
+     */
+    public function testGetAttributeMetadata($attributeCode, $expectedMetadata)
+    {
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . "/attribute/$attributeCode",
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'GetAttributeMetadata',
+            ],
+        ];
+
+        $requestData = [
+            'attributeCode' => $attributeCode,
+        ];
+
+        $attributeMetadata = $this->_webapiCall($serviceInfo, $requestData);
+        $validationResult = $this->checkValidationRules($expectedMetadata, $attributeMetadata);
+        list($expectedMetadata, $attributeMetadata) = $validationResult;
+        $this->assertEquals($expectedMetadata, $attributeMetadata);
+    }
+
+    /**
+     * Data provider for testGetAttributeMetadata.
+     *
+     * @return array
+     */
+    public function getAttributeMetadataDataProvider()
+    {
+        return [
+            Address::POSTCODE => [
+                Address::POSTCODE,
+                [
+                    AttributeMetadata::ATTRIBUTE_CODE => 'postcode',
+                    AttributeMetadata::FRONTEND_INPUT => 'text',
+                    AttributeMetadata::INPUT_FILTER => '',
+                    AttributeMetadata::STORE_LABEL => 'Zip/Postal Code',
+                    AttributeMetadata::VALIDATION_RULES => [],
+                    AttributeMetadata::VISIBLE => true,
+                    AttributeMetadata::REQUIRED => false,
+                    AttributeMetadata::MULTILINE_COUNT => 0,
+                    AttributeMetadata::DATA_MODEL => 'Magento\Customer\Model\Attribute\Data\Postcode',
+                    AttributeMetadata::OPTIONS => [],
+                    AttributeMetadata::FRONTEND_CLASS => '',
+                    AttributeMetadata::FRONTEND_LABEL => 'Zip/Postal Code',
+                    AttributeMetadata::NOTE => '',
+                    AttributeMetadata::SYSTEM => true,
+                    AttributeMetadata::USER_DEFINED => false,
+                    AttributeMetadata::BACKEND_TYPE => 'varchar',
+                    AttributeMetadata::SORT_ORDER => 110
+                ],
+            ]
+        ];
+    }
+
+    /**
+     * Test retrieval of all address attribute metadata.
+     */
+    public function testGetAllAttributesMetadata()
+    {
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH,
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'GetAllAttributesMetadata',
+            ],
+        ];
+
+        $attributeMetadata = $this->_webApiCall($serviceInfo);
+        $this->assertCount(19, $attributeMetadata);
+        $postcode = $this->getAttributeMetadataDataProvider()[Address::POSTCODE][1];
+        $validationResult = $this->checkMultipleAttributesValidationRules($postcode, $attributeMetadata);
+        list($postcode, $attributeMetadata) = $validationResult;
+        $this->assertContains($postcode, $attributeMetadata);
+    }
+
+    /**
+     * Test retrieval of custom address attribute metadata.
+     *
+     * @magentoApiDataFixture Magento/Customer/_files/attribute_user_defined_address_custom_attribute.php
+     */
+    public function testGetCustomAttributesMetadata()
+    {
+        $customAttributeCode = 'custom_attribute1';
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/custom',
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'GetCustomAttributesMetadata',
+            ],
+        ];
+
+        $requestData = ['attribute_code' => $customAttributeCode];
+        $attributeMetadata = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertCount(2, $attributeMetadata);
+        $this->assertEquals($customAttributeCode, $attributeMetadata[0]['attribute_code']);
+    }
+
+    /**
+     * Test retrieval of attributes
+     *
+     * @param string $formCode Form code
+     * @param array $expectedMetadata The expected attribute metadata
+     * @dataProvider getAttributesDataProvider
+     */
+    public function testGetAttributes($formCode, $expectedMetadata)
+    {
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . "/form/$formCode",
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'GetAttributes',
+            ],
+        ];
+
+        $requestData = [
+            'formCode' => $formCode,
+        ];
+
+        $attributeMetadataList = $this->_webApiCall($serviceInfo, $requestData);
+        foreach ($attributeMetadataList as $attributeMetadata) {
+            if (isset($attributeMetadata['attribute_code'])
+                && $attributeMetadata['attribute_code'] == $expectedMetadata['attribute_code']
+            ) {
+                $validationResult = $this->checkValidationRules($expectedMetadata, $attributeMetadata);
+                list($expectedMetadata, $attributeMetadata) = $validationResult;
+                $this->assertEquals($expectedMetadata, $attributeMetadata);
+                break;
+            }
+        }
+    }
+
+    /**
+     * Data provider for testGetAttributes.
+     *
+     * @return array
+     */
+    public function getAttributesDataProvider()
+    {
+        $attributeMetadata = $this->getAttributeMetadataDataProvider();
+        return [
+            [
+                'customer_address_edit',
+                $attributeMetadata[Address::POSTCODE][1],
+            ]
+        ];
+    }
+
+    /**
+     * Checks that expected and actual attribute metadata validation rules are equal
+     * and removes the validation rules entry from expected and actual attribute metadata
+     *
+     * @param array $expectedResult
+     * @param array $actualResult
+     * @return array
+     */
+    public function checkValidationRules($expectedResult, $actualResult)
+    {
+        $expectedRules = [];
+        $actualRules = [];
+
+        if (isset($expectedResult[AttributeMetadata::VALIDATION_RULES])) {
+            $expectedRules = $expectedResult[AttributeMetadata::VALIDATION_RULES];
+            unset($expectedResult[AttributeMetadata::VALIDATION_RULES]);
+        }
+        if (isset($actualResult[AttributeMetadata::VALIDATION_RULES])) {
+            $actualRules = $actualResult[AttributeMetadata::VALIDATION_RULES];
+            unset($actualResult[AttributeMetadata::VALIDATION_RULES]);
+        }
+
+        if (is_array($expectedRules) && is_array($actualRules)) {
+            foreach ($expectedRules as $expectedRule) {
+                if (isset($expectedRule['name']) && isset($expectedRule['value'])) {
+                    $found = false;
+                    foreach ($actualRules as $actualRule) {
+                        if (isset($actualRule['name']) && isset($actualRule['value'])) {
+                            if ($expectedRule['name'] == $actualRule['name']
+                                && $expectedRule['value'] == $actualRule['value']
+                            ) {
+                                $found = true;
+                                break;
+                            }
+                        }
+                    }
+                    $this->assertTrue($found);
+                }
+            }
+        }
+        return [$expectedResult, $actualResult];
+    }
+
+    /**
+     * Check specific attribute validation rules in set of multiple attributes
+     *
+     * @param array $expectedResult Set of expected attribute metadata
+     * @param array $actualResultSet Set of actual attribute metadata
+     * @return array
+     */
+    public function checkMultipleAttributesValidationRules($expectedResult, $actualResultSet)
+    {
+        if (is_array($expectedResult) && is_array($actualResultSet)) {
+            if (isset($expectedResult[AttributeMetadata::ATTRIBUTE_CODE])) {
+                foreach ($actualResultSet as $actualAttributeKey => $actualAttribute) {
+                    if (isset($actualAttribute[AttributeMetadata::ATTRIBUTE_CODE])
+                        && $expectedResult[AttributeMetadata::ATTRIBUTE_CODE]
+                        == $actualAttribute[AttributeMetadata::ATTRIBUTE_CODE]
+                    ) {
+                        $this->checkValidationRules($expectedResult, $actualAttribute);
+                        unset($actualResultSet[$actualAttributeKey][AttributeMetadata::VALIDATION_RULES]);
+                    }
+                }
+                unset($expectedResult[AttributeMetadata::VALIDATION_RULES]);
+            }
+        }
+        return [$expectedResult, $actualResultSet];
+    }
+
+    /**
+     * Remove test attribute
+     */
+    public static function tearDownAfterClass()
+    {
+        parent::tearDownAfterClass();
+        /** @var \Magento\Customer\Model\Attribute $attribute */
+        $attribute = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(
+            'Magento\Customer\Model\Attribute'
+        );
+        foreach (['custom_attribute1', 'custom_attribute2'] as $attributeCode) {
+            $attribute->loadByCode('customer_address', $attributeCode);
+            $attribute->delete();
+        }
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Customer/Api/AddressRepositoryTest.php b/dev/tests/api-functional/testsuite/Magento/Customer/Api/AddressRepositoryTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..5bc344be7173a39a1ddc571cdc56189029e56a6e
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Customer/Api/AddressRepositoryTest.php
@@ -0,0 +1,166 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+namespace Magento\Customer\Api;
+
+use Magento\TestFramework\Helper\Bootstrap;
+
+class AddressRepositoryTest extends \Magento\TestFramework\TestCase\WebapiAbstract
+{
+    const SOAP_SERVICE_NAME = 'customerAddressRepositoryV1';
+    const SOAP_SERVICE_VERSION = 'V1';
+
+    /** @var \Magento\Customer\Api\AddressRepositoryInterface */
+    protected $addressRepository;
+
+    /** @var \Magento\Customer\Api\CustomerRepositoryInterface */
+    protected $customerRepository;
+
+    protected function setUp()
+    {
+        $objectManager = Bootstrap::getObjectManager();
+        $this->customerRepository = $objectManager->get(
+            'Magento\Customer\Api\CustomerRepositoryInterface'
+        );
+        $this->addressRepository = $objectManager->get(
+            'Magento\Customer\Api\AddressRepositoryInterface'
+        );
+        parent::setUp();
+    }
+
+    /**
+     * Ensure that fixture customer and his addresses are deleted.
+     */
+    protected function tearDown()
+    {
+        /** @var \Magento\Framework\Registry $registry */
+        $registry = Bootstrap::getObjectManager()->get('Magento\Framework\Registry');
+        $registry->unregister('isSecureArea');
+        $registry->register('isSecureArea', true);
+
+        try {
+            $fixtureFirstAddressId = 1;
+            $this->addressRepository->deleteById($fixtureFirstAddressId);
+        } catch (\Magento\Framework\Exception\NoSuchEntityException $e) {
+            /** First address fixture was not used */
+        }
+        try {
+            $fixtureSecondAddressId = 2;
+            $this->addressRepository->deleteById($fixtureSecondAddressId);
+        } catch (\Magento\Framework\Exception\NoSuchEntityException $e) {
+            /** Second address fixture was not used */
+        }
+        try {
+            $fixtureCustomerId = 1;
+            $this->customerRepository->deleteById($fixtureCustomerId);
+        } catch (\Magento\Framework\Exception\NoSuchEntityException $e) {
+            /** Customer fixture was not used */
+        }
+
+        $registry->unregister('isSecureArea');
+        $registry->register('isSecureArea', false);
+        parent::tearDown();
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Customer/_files/customer.php
+     * @magentoApiDataFixture Magento/Customer/_files/customer_address.php
+     */
+    public function testGetAddress()
+    {
+        $fixtureAddressId = 1;
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => "/V1/customers/addresses/{$fixtureAddressId}",
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SOAP_SERVICE_NAME,
+                'serviceVersion' => self::SOAP_SERVICE_VERSION,
+                'operation' => self::SOAP_SERVICE_NAME . 'GetById',
+            ],
+        ];
+        $requestData = ['addressId' => $fixtureAddressId];
+        $addressData = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertEquals(
+            $this->getFirstFixtureAddressData(),
+            $addressData,
+            "Address data is invalid."
+        );
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Customer/_files/customer.php
+     * @magentoApiDataFixture Magento/Customer/_files/customer_address.php
+     */
+    public function testDeleteAddress()
+    {
+        $fixtureAddressId = 1;
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => "/V1/addresses/{$fixtureAddressId}",
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_DELETE,
+            ],
+            'soap' => [
+                'service' => self::SOAP_SERVICE_NAME,
+                'serviceVersion' => self::SOAP_SERVICE_VERSION,
+                'operation' => self::SOAP_SERVICE_NAME . 'DeleteById',
+            ],
+        ];
+        $requestData = ['addressId' => $fixtureAddressId];
+        $response = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertTrue($response, 'Expected response should be true.');
+
+        $this->setExpectedException('Magento\Framework\Exception\NoSuchEntityException', 'No such entity with addressId = 1');
+        $this->addressRepository->getById($fixtureAddressId);
+    }
+
+    /**
+     * Retrieve data of the first fixture address.
+     *
+     * @return array
+     */
+    protected function getFirstFixtureAddressData()
+    {
+        return [
+            'firstname' => 'John',
+            'lastname' => 'Smith',
+            'city' => 'CityM',
+            'country_id' => 'US',
+            'company' => 'CompanyName',
+            'postcode' => '75477',
+            'telephone' => '3468676',
+            'street' => ['Green str, 67'],
+            'id' => 1,
+            'default_billing' => true,
+            'default_shipping' => true,
+            'customer_id' => '1',
+            'region' => ['region' => 'Alabama', 'region_id' => 1, 'region_code' => 'AL'],
+        ];
+    }
+
+    /**
+     * Retrieve data of the second fixture address.
+     *
+     * @return array
+     */
+    protected function getSecondFixtureAddressData()
+    {
+        return [
+            'firstname' => 'John',
+            'lastname' => 'Smith',
+            'city' => 'CityX',
+            'country_id' => 'US',
+            'postcode' => '47676',
+            'telephone' => '3234676',
+            'street' => ['Black str, 48'],
+            'id' => 2,
+            'default_billing' => false,
+            'default_shipping' => false,
+            'customer_id' => '1',
+            'region' => ['region' => 'Alabama', 'region_id' => 1, 'region_code' => 'AL'],
+        ];
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Customer/Api/CustomerMetadataTest.php b/dev/tests/api-functional/testsuite/Magento/Customer/Api/CustomerMetadataTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..bba031a61ccd5a60271279e14809de94ab72d468
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Customer/Api/CustomerMetadataTest.php
@@ -0,0 +1,321 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+namespace Magento\Customer\Api;
+
+use Magento\Customer\Api\Data\CustomerInterface as Customer;
+use Magento\Customer\Model\Data\AttributeMetadata;
+use Magento\TestFramework\TestCase\WebapiAbstract;
+
+/**
+ * Class CustomerMetadataTest
+ */
+class CustomerMetadataTest extends WebapiAbstract
+{
+    const SERVICE_NAME = "customerCustomerMetadataV1";
+    const SERVICE_VERSION = "V1";
+    const RESOURCE_PATH = "/V1/attributeMetadata/customer";
+
+    /**
+     * Test retrieval of attribute metadata for the customer entity type.
+     *
+     * @param string $attributeCode The attribute code of the requested metadata.
+     * @param array $expectedMetadata Expected entity metadata for the attribute code.
+     * @dataProvider getAttributeMetadataDataProvider
+     */
+    public function testGetAttributeMetadata($attributeCode, $expectedMetadata)
+    {
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . "/attribute/$attributeCode",
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'GetAttributeMetadata',
+            ],
+        ];
+
+        $requestData = [
+            'attributeCode' => $attributeCode,
+        ];
+
+        $attributeMetadata = $this->_webapiCall($serviceInfo, $requestData);
+
+        $validationResult = $this->checkValidationRules($expectedMetadata, $attributeMetadata);
+        list($expectedMetadata, $attributeMetadata) = $validationResult;
+        $this->assertEquals($expectedMetadata, $attributeMetadata);
+    }
+
+    /**
+     * Data provider for testGetAttributeMetadata.
+     *
+     * @return array
+     */
+    public function getAttributeMetadataDataProvider()
+    {
+        return [
+            Customer::FIRSTNAME => [
+                Customer::FIRSTNAME,
+                [
+                    AttributeMetadata::ATTRIBUTE_CODE   => 'firstname',
+                    AttributeMetadata::FRONTEND_INPUT   => 'text',
+                    AttributeMetadata::INPUT_FILTER     => '',
+                    AttributeMetadata::STORE_LABEL      => 'First Name',
+                    AttributeMetadata::VALIDATION_RULES => [
+                        ['name' => 'min_text_length', 'value' => 1],
+                        ['name' => 'max_text_length', 'value' => 255],
+                    ],
+                    AttributeMetadata::VISIBLE          => true,
+                    AttributeMetadata::REQUIRED         => true,
+                    AttributeMetadata::MULTILINE_COUNT  => 0,
+                    AttributeMetadata::DATA_MODEL       => '',
+                    AttributeMetadata::OPTIONS          => [],
+                    AttributeMetadata::FRONTEND_CLASS   => ' required-entry',
+                    AttributeMetadata::FRONTEND_LABEL   => 'First Name',
+                    AttributeMetadata::NOTE             => '',
+                    AttributeMetadata::SYSTEM           => true,
+                    AttributeMetadata::USER_DEFINED     => false,
+                    AttributeMetadata::BACKEND_TYPE     => 'varchar',
+                    AttributeMetadata::SORT_ORDER       => 40
+                ],
+            ],
+            Customer::GENDER => [
+                Customer::GENDER,
+                [
+                    AttributeMetadata::ATTRIBUTE_CODE   => 'gender',
+                    AttributeMetadata::FRONTEND_INPUT   => 'select',
+                    AttributeMetadata::INPUT_FILTER     => '',
+                    AttributeMetadata::STORE_LABEL      => 'Gender',
+                    AttributeMetadata::VALIDATION_RULES => [],
+                    AttributeMetadata::VISIBLE          => false,
+                    AttributeMetadata::REQUIRED         => false,
+                    AttributeMetadata::MULTILINE_COUNT  => 0,
+                    AttributeMetadata::DATA_MODEL       => '',
+                    AttributeMetadata::OPTIONS          => [
+                        ['label' => '', 'value' => ''],
+                        ['label' => 'Male', 'value' => '1'],
+                        ['label' => 'Female', 'value' => '2'],
+                    ],
+                    AttributeMetadata::FRONTEND_CLASS   => '',
+                    AttributeMetadata::FRONTEND_LABEL   => 'Gender',
+                    AttributeMetadata::NOTE             => '',
+                    AttributeMetadata::SYSTEM           => false,
+                    AttributeMetadata::USER_DEFINED     => false,
+                    AttributeMetadata::BACKEND_TYPE     => 'int',
+                    AttributeMetadata::SORT_ORDER       => 110
+                ],
+            ],
+            Customer::WEBSITE_ID => [
+                Customer::WEBSITE_ID,
+                [
+                    AttributeMetadata::ATTRIBUTE_CODE   => 'website_id',
+                    AttributeMetadata::FRONTEND_INPUT   => 'select',
+                    AttributeMetadata::INPUT_FILTER     => '',
+                    AttributeMetadata::STORE_LABEL      => 'Associate to Website',
+                    AttributeMetadata::VALIDATION_RULES => [],
+                    AttributeMetadata::VISIBLE          => true,
+                    AttributeMetadata::REQUIRED         => true,
+                    AttributeMetadata::MULTILINE_COUNT  => 0,
+                    AttributeMetadata::DATA_MODEL       => '',
+                    AttributeMetadata::OPTIONS          => [
+                        ['label' => 'Admin', 'value' => '0'],
+                        ['label' => 'Main Website', 'value' => '1'],
+                    ],
+                    AttributeMetadata::FRONTEND_CLASS   => ' required-entry',
+                    AttributeMetadata::FRONTEND_LABEL   => 'Associate to Website',
+                    AttributeMetadata::NOTE             => '',
+                    AttributeMetadata::SYSTEM           => true,
+                    AttributeMetadata::USER_DEFINED     => false,
+                    AttributeMetadata::BACKEND_TYPE     => 'static',
+                    AttributeMetadata::SORT_ORDER       => 10
+                ],
+            ]
+        ];
+    }
+
+    /**
+     * Test retrieval of all customer attribute metadata.
+     */
+    public function testGetAllAttributesMetadata()
+    {
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH,
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'GetAllAttributesMetadata',
+            ],
+        ];
+
+        $attributeMetadata = $this->_webApiCall($serviceInfo);
+
+        $this->assertCount(23, $attributeMetadata);
+
+        $firstName = $this->getAttributeMetadataDataProvider()[Customer::FIRSTNAME][1];
+        $validationResult = $this->checkMultipleAttributesValidationRules($firstName, $attributeMetadata);
+        list($firstName, $attributeMetadata) = $validationResult;
+        $this->assertContains($firstName, $attributeMetadata);
+
+        $websiteId = $this->getAttributeMetadataDataProvider()[Customer::WEBSITE_ID][1];
+        $validationResult = $this->checkMultipleAttributesValidationRules($websiteId, $attributeMetadata);
+        list($websiteId, $attributeMetadata) = $validationResult;
+        $this->assertContains($websiteId, $attributeMetadata);
+    }
+
+    /**
+     * Test retrieval of custom customer attribute metadata.
+     */
+    public function testGetCustomAttributesMetadata()
+    {
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/custom',
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'GetCustomAttributesMetadata',
+            ],
+        ];
+
+        $attributeMetadata = $this->_webApiCall($serviceInfo);
+
+        //Default custom attribute code 'disable_auto_group_change'
+        $this->assertCount(1, $attributeMetadata);
+        $this->assertEquals('disable_auto_group_change', $attributeMetadata[0]['attribute_code']);
+    }
+
+    /**
+     * Test retrieval of attributes
+     *
+     * @param string $formCode Form code
+     * @param array $expectedMetadata The expected attribute metadata
+     * @dataProvider getAttributesDataProvider
+     */
+    public function testGetAttributes($formCode, $expectedMetadata)
+    {
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . "/form/$formCode",
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'GetAttributes',
+            ],
+        ];
+
+        $requestData = [
+            'formCode' => $formCode,
+        ];
+
+        $attributeMetadataList = $this->_webApiCall($serviceInfo, $requestData);
+        foreach ($attributeMetadataList as $attributeMetadata) {
+            if (isset($attributeMetadata['attribute_code'])
+                && $attributeMetadata['attribute_code'] == $expectedMetadata['attribute_code']) {
+                $validationResult = $this->checkValidationRules($expectedMetadata, $attributeMetadata);
+                list($expectedMetadata, $attributeMetadata) = $validationResult;
+                $this->assertEquals($expectedMetadata, $attributeMetadata);
+                break;
+            }
+        }
+    }
+
+    /**
+     * Data provider for testGetAttributes.
+     *
+     * @return array
+     */
+    public function getAttributesDataProvider()
+    {
+        $attributeMetadata = $this->getAttributeMetadataDataProvider();
+        return [
+            [
+                'adminhtml_customer',
+                $attributeMetadata[Customer::FIRSTNAME][1],
+            ],
+            [
+                'adminhtml_customer',
+                $attributeMetadata[Customer::GENDER][1]
+            ]
+        ];
+    }
+
+    /**
+     * Checks that expected and actual attribute metadata validation rules are equal
+     * and removes the validation rules entry from expected and actual attribute metadata
+     *
+     * @param array $expectedResult
+     * @param array $actualResult
+     * @return array
+     */
+    public function checkValidationRules($expectedResult, $actualResult)
+    {
+        $expectedRules = [];
+        $actualRules   = [];
+
+        if (isset($expectedResult[AttributeMetadata::VALIDATION_RULES])) {
+            $expectedRules = $expectedResult[AttributeMetadata::VALIDATION_RULES];
+            unset($expectedResult[AttributeMetadata::VALIDATION_RULES]);
+        }
+        if (isset($actualResult[AttributeMetadata::VALIDATION_RULES])) {
+            $actualRules = $actualResult[AttributeMetadata::VALIDATION_RULES];
+            unset($actualResult[AttributeMetadata::VALIDATION_RULES]);
+        }
+
+        if (is_array($expectedRules) && is_array($actualRules)) {
+            foreach ($expectedRules as $expectedRule) {
+                if (isset($expectedRule['name']) && isset($expectedRule['value'])) {
+                    $found = false;
+                    foreach ($actualRules as $actualRule) {
+                        if (isset($actualRule['name']) && isset($actualRule['value'])) {
+                            if ($expectedRule['name'] == $actualRule['name']
+                                && $expectedRule['value'] == $actualRule['value']
+                            ) {
+                                $found = true;
+                                break;
+                            }
+                        }
+                    }
+                    $this->assertTrue($found);
+                }
+            }
+        }
+        return [$expectedResult, $actualResult];
+    }
+
+    /**
+     * Check specific attribute validation rules in set of multiple attributes
+     *
+     * @param array $expectedResult Set of expected attribute metadata
+     * @param array $actualResultSet Set of actual attribute metadata
+     * @return array
+     */
+    public function checkMultipleAttributesValidationRules($expectedResult, $actualResultSet)
+    {
+        if (is_array($expectedResult) && is_array($actualResultSet)) {
+            if (isset($expectedResult[AttributeMetadata::ATTRIBUTE_CODE])) {
+                foreach ($actualResultSet as $actualAttributeKey => $actualAttribute) {
+                    if (isset($actualAttribute[AttributeMetadata::ATTRIBUTE_CODE])
+                        && $expectedResult[AttributeMetadata::ATTRIBUTE_CODE]
+                        == $actualAttribute[AttributeMetadata::ATTRIBUTE_CODE]
+                    ) {
+                        $this->checkValidationRules($expectedResult, $actualAttribute);
+                        unset($actualResultSet[$actualAttributeKey][AttributeMetadata::VALIDATION_RULES]);
+                    }
+                }
+                unset($expectedResult[AttributeMetadata::VALIDATION_RULES]);
+            }
+        }
+        return [$expectedResult, $actualResultSet];
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Customer/Api/CustomerRepositoryTest.php b/dev/tests/api-functional/testsuite/Magento/Customer/Api/CustomerRepositoryTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..db1b03f1f4772c921d98cb043c5d2d63dc6fd41a
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Customer/Api/CustomerRepositoryTest.php
@@ -0,0 +1,659 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Customer\Api;
+
+use Magento\Customer\Api\Data\CustomerInterface as Customer;
+use Magento\Framework\Api\SearchCriteria;
+use Magento\Framework\Exception\InputException;
+use Magento\TestFramework\Helper\Bootstrap;
+use Magento\TestFramework\Helper\Customer as CustomerHelper;
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Exception as HTTPExceptionCodes;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+/**
+ * Test class for Magento\Customer\Api\CustomerRepositoryInterface
+ *
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ */
+class CustomerRepositoryTest extends WebapiAbstract
+{
+    const SERVICE_VERSION = 'V1';
+    const SERVICE_NAME = 'customerCustomerRepositoryV1';
+    const RESOURCE_PATH = '/V1/customers';
+
+    /**
+     * Sample values for testing
+     */
+    const ATTRIBUTE_CODE = 'attribute_code';
+    const ATTRIBUTE_VALUE = 'attribute_value';
+
+    /**
+     * @var CustomerRepositoryInterface
+     */
+    private $customerRepository;
+
+    /**
+     * @var \Magento\Customer\Api\Data\AddressDataBuilder
+     */
+    private $addressBuilder;
+
+    /**
+     * @var \Magento\Customer\Api\Data\CustomerDataBuilder
+     */
+    private $customerBuilder;
+
+    /**
+     * @var \Magento\Framework\Api\SearchCriteriaBuilder
+     */
+    private $searchCriteriaBuilder;
+
+    /**
+     * @var \Magento\Framework\Api\SortOrderBuilder
+     */
+    private $sortOrderBuilder;
+
+    /**
+     * @var \Magento\Framework\Api\Search\FilterGroupBuilder
+     */
+    private $filterGroupBuilder;
+
+    /**
+     * @var \Magento\Customer\Model\CustomerRegistry
+     */
+    private $customerRegistry;
+
+    /**
+     * @var CustomerHelper
+     */
+    private $customerHelper;
+
+    /**
+     * @var array
+     */
+    private $currentCustomerId;
+
+    /**
+     * @var \Magento\Framework\Reflection\DataObjectProcessor
+     */
+    private $dataObjectProcessor;
+
+    /**
+     * Execute per test initialization.
+     */
+    public function setUp()
+    {
+        $this->customerRegistry = Bootstrap::getObjectManager()->get(
+            'Magento\Customer\Model\CustomerRegistry'
+        );
+
+        $this->customerRepository = Bootstrap::getObjectManager()->get(
+            'Magento\Customer\Api\CustomerRepositoryInterface',
+            ['customerRegistry' => $this->customerRegistry]
+        );
+        $this->addressBuilder = Bootstrap::getObjectManager()->create(
+            'Magento\Customer\Api\Data\AddressDataBuilder'
+        );
+        $this->customerBuilder = Bootstrap::getObjectManager()->create(
+            'Magento\Customer\Api\Data\CustomerDataBuilder'
+        );
+        $this->searchCriteriaBuilder = Bootstrap::getObjectManager()->create(
+            'Magento\Framework\Api\SearchCriteriaBuilder'
+        );
+        $this->sortOrderBuilder = Bootstrap::getObjectManager()->create(
+            'Magento\Framework\Api\SortOrderBuilder'
+        );
+        $this->filterGroupBuilder = Bootstrap::getObjectManager()->create(
+            'Magento\Framework\Api\Search\FilterGroupBuilder'
+        );
+        $this->customerHelper = new CustomerHelper();
+
+        $this->dataObjectProcessor = Bootstrap::getObjectManager()->create(
+            'Magento\Framework\Reflection\DataObjectProcessor'
+        );
+    }
+
+    public function tearDown()
+    {
+        if (!empty($this->currentCustomerId)) {
+            foreach ($this->currentCustomerId as $customerId) {
+                $serviceInfo = [
+                    'rest' => [
+                        'resourcePath' => self::RESOURCE_PATH . '/' . $customerId,
+                        'httpMethod' => RestConfig::HTTP_METHOD_DELETE,
+                    ],
+                    'soap' => [
+                        'service' => self::SERVICE_NAME,
+                        'serviceVersion' => self::SERVICE_VERSION,
+                        'operation' => self::SERVICE_NAME . 'DeleteById',
+                    ],
+                ];
+
+                $response = $this->_webApiCall($serviceInfo, ['customerId' => $customerId]);
+
+                $this->assertTrue($response);
+            }
+        }
+        unset($this->customerRepository);
+    }
+
+    public function testDeleteCustomer()
+    {
+        $customerData = $this->_createCustomer();
+        $this->currentCustomerId = [];
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/' . $customerData[Customer::ID],
+                'httpMethod' => RestConfig::HTTP_METHOD_DELETE,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'DeleteById',
+            ],
+        ];
+        if (TESTS_WEB_API_ADAPTER == self::ADAPTER_SOAP) {
+            $response = $this->_webApiCall($serviceInfo, ['customerId' => $customerData['id']]);
+        } else {
+            $response = $this->_webApiCall($serviceInfo);
+        }
+
+        $this->assertTrue($response);
+
+        //Verify if the customer is deleted
+        $this->setExpectedException(
+            'Magento\Framework\Exception\NoSuchEntityException',
+            sprintf("No such entity with customerId = %s", $customerData[Customer::ID])
+        );
+        $this->_getCustomerData($customerData[Customer::ID]);
+    }
+
+    public function testDeleteCustomerInvalidCustomerId()
+    {
+        $invalidId = -1;
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/' . $invalidId,
+                'httpMethod' => RestConfig::HTTP_METHOD_DELETE,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'DeleteById',
+            ],
+        ];
+
+        $expectedMessage = 'No such entity with %fieldName = %fieldValue';
+
+        try {
+            $this->_webApiCall($serviceInfo, ['customerId' => $invalidId]);
+
+            $this->fail("Expected exception");
+        } catch (\SoapFault $e) {
+            $this->assertContains(
+                $expectedMessage,
+                $e->getMessage(),
+                "SoapFault does not contain expected message."
+            );
+        } catch (\Exception $e) {
+            $errorObj = $this->processRestExceptionResult($e);
+            $this->assertEquals($expectedMessage, $errorObj['message']);
+            $this->assertEquals(['fieldName' => 'customerId', 'fieldValue' => $invalidId], $errorObj['parameters']);
+            $this->assertEquals(HTTPExceptionCodes::HTTP_NOT_FOUND, $e->getCode());
+        }
+    }
+
+    public function testUpdateCustomer()
+    {
+        $customerData = $this->_createCustomer();
+        $existingCustomerDataObject = $this->_getCustomerData($customerData[Customer::ID]);
+        $lastName = $existingCustomerDataObject->getLastname();
+        $customerData[Customer::LASTNAME] = $lastName . 'Updated';
+        $newCustomerDataObject = $this->customerBuilder->populateWithArray($customerData)->create();
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . "/{$customerData[Customer::ID]}",
+                'httpMethod' => RestConfig::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Save',
+            ],
+        ];
+        $newCustomerDataObject = $this->dataObjectProcessor->buildOutputDataArray(
+            $newCustomerDataObject,
+            'Magento\Customer\Api\Data\CustomerInterface'
+        );
+        $requestData = ['customer' => $newCustomerDataObject];
+        $response = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertTrue(!is_null($response));
+
+        //Verify if the customer is updated
+        $existingCustomerDataObject = $this->_getCustomerData($customerData[Customer::ID]);
+        $this->assertEquals($lastName . "Updated", $existingCustomerDataObject->getLastname());
+    }
+
+    /**
+     * Verify expected behavior when the website id is not set
+     */
+    public function testUpdateCustomerNoWebsiteId()
+    {
+        $customerData = $this->customerHelper->createSampleCustomer();
+        $existingCustomerDataObject = $this->_getCustomerData($customerData[Customer::ID]);
+        $lastName = $existingCustomerDataObject->getLastname();
+        $customerData[Customer::LASTNAME] = $lastName . 'Updated';
+        $newCustomerDataObject = $this->customerBuilder->populateWithArray($customerData)->create();
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . "/{$customerData[Customer::ID]}",
+                'httpMethod' => RestConfig::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Save',
+            ],
+        ];
+        $newCustomerDataObject = $this->dataObjectProcessor->buildOutputDataArray(
+            $newCustomerDataObject,
+            'Magento\Customer\Api\Data\CustomerInterface'
+        );
+        unset($newCustomerDataObject['website_id']);
+        $requestData = ['customer' => $newCustomerDataObject];
+
+        $expectedMessage = '"Associate to Website" is a required value.';
+        try {
+            $this->_webApiCall($serviceInfo, $requestData);
+            $this->fail("Expected exception.");
+        } catch (\SoapFault $e) {
+            $this->assertContains(
+                $expectedMessage,
+                $e->getMessage(),
+                "SoapFault does not contain expected message."
+            );
+        } catch (\Exception $e) {
+            $errorObj =  $this->customerHelper->processRestExceptionResult($e);
+            $this->assertEquals($expectedMessage, $errorObj['message'], 'Invalid message: "' . $e->getMessage() . '"');
+            $this->assertEquals(HTTPExceptionCodes::HTTP_BAD_REQUEST, $e->getCode());
+        }
+    }
+
+    public function testUpdateCustomerException()
+    {
+        $customerData = $this->_createCustomer();
+        $existingCustomerDataObject = $this->_getCustomerData($customerData[Customer::ID]);
+        $lastName = $existingCustomerDataObject->getLastname();
+
+        //Set non-existent id = -1
+        $customerData[Customer::LASTNAME] = $lastName . 'Updated';
+        $customerData[Customer::ID] = -1;
+
+        $newCustomerDataObject = $this->customerBuilder->populateWithArray($customerData)->create();
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . "/-1",
+                'httpMethod' => RestConfig::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Save',
+            ],
+        ];
+        $newCustomerDataObject = $this->dataObjectProcessor->buildOutputDataArray(
+            $newCustomerDataObject,
+            'Magento\Customer\Api\Data\CustomerInterface'
+        );
+        $requestData = ['customer' => $newCustomerDataObject];
+
+        $expectedMessage = 'No such entity with %fieldName = %fieldValue';
+
+        try {
+            $this->_webApiCall($serviceInfo, $requestData);
+            $this->fail("Expected exception.");
+        } catch (\SoapFault $e) {
+            $this->assertContains(
+                $expectedMessage,
+                $e->getMessage(),
+                "SoapFault does not contain expected message."
+            );
+        } catch (\Exception $e) {
+            $errorObj = $this->processRestExceptionResult($e);
+            $this->assertEquals($expectedMessage, $errorObj['message']);
+            $this->assertEquals(['fieldName' => 'customerId', 'fieldValue' => -1], $errorObj['parameters']);
+            $this->assertEquals(HTTPExceptionCodes::HTTP_NOT_FOUND, $e->getCode());
+        }
+    }
+
+    /**
+     * Test with a single filter
+     */
+    public function testSearchCustomers()
+    {
+        $builder = Bootstrap::getObjectManager()->create('Magento\Framework\Api\FilterBuilder');
+        $customerData = $this->_createCustomer();
+        $filter = $builder
+            ->setField(Customer::EMAIL)
+            ->setValue($customerData[Customer::EMAIL])
+            ->create();
+        $this->searchCriteriaBuilder->addFilter([$filter]);
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/search',
+                'httpMethod' => RestConfig::HTTP_METHOD_POST,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'getList',
+            ],
+        ];
+        $searchData = $this->dataObjectProcessor->buildOutputDataArray(
+            $this->searchCriteriaBuilder->create(),
+            'Magento\Framework\Api\SearchCriteriaInterface'
+        );
+        $requestData = ['searchCriteria' => $searchData];
+        $searchResults = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertEquals(1, $searchResults['total_count']);
+        $this->assertEquals($customerData[Customer::ID], $searchResults['items'][0][Customer::ID]);
+    }
+
+    /**
+     * Test with a single filter using GET
+     */
+    public function testSearchCustomersUsingGET()
+    {
+        $this->_markTestAsRestOnly('SOAP test is covered in testSearchCustomers');
+        $builder = Bootstrap::getObjectManager()->create('Magento\Framework\Api\FilterBuilder');
+        $customerData = $this->_createCustomer();
+        $filter = $builder
+            ->setField(Customer::EMAIL)
+            ->setValue($customerData[Customer::EMAIL])
+            ->create();
+        $this->searchCriteriaBuilder->addFilter([$filter]);
+
+        $searchData = $this->searchCriteriaBuilder->create()->__toArray();
+        $requestData = ['searchCriteria' => $searchData];
+        $searchQueryString = http_build_query($requestData);
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/search?' . $searchQueryString,
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+        ];
+        $searchResults = $this->_webApiCall($serviceInfo);
+        $this->assertEquals(1, $searchResults['total_count']);
+        $this->assertEquals($customerData[Customer::ID], $searchResults['items'][0][Customer::ID]);
+    }
+
+    /**
+     * Test with empty GET based filter
+     */
+    public function testSearchCustomersUsingGETEmptyFilter()
+    {
+        $this->_markTestAsRestOnly('Soap clients explicitly check for required fields based on WSDL.');
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/search',
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+        ];
+        try {
+            $this->_webApiCall($serviceInfo);
+        } catch (\Exception $e) {
+            $this->assertEquals(HTTPExceptionCodes::HTTP_BAD_REQUEST, $e->getCode());
+            $exceptionData = $this->processRestExceptionResult($e);
+            $expectedExceptionData = [
+                'message' => InputException::REQUIRED_FIELD,
+                'parameters' => [
+                    'fieldName' => 'searchCriteria'
+                ],
+            ];
+            $this->assertEquals($expectedExceptionData, $exceptionData);
+        }
+    }
+
+    /**
+     * Test using multiple filters
+     */
+    public function testSearchCustomersMultipleFiltersWithSort()
+    {
+        $builder = Bootstrap::getObjectManager()->create('Magento\Framework\Api\FilterBuilder');
+        $customerData1 = $this->_createCustomer();
+        $customerData2 = $this->_createCustomer();
+        $filter1 = $builder->setField(Customer::EMAIL)
+            ->setValue($customerData1[Customer::EMAIL])
+            ->create();
+        $filter2 = $builder->setField(Customer::EMAIL)
+            ->setValue($customerData2[Customer::EMAIL])
+            ->create();
+        $filter3 = $builder->setField(Customer::LASTNAME)
+            ->setValue($customerData1[Customer::LASTNAME])
+            ->create();
+        $this->searchCriteriaBuilder->addFilter([$filter1, $filter2]);
+        $this->searchCriteriaBuilder->addFilter([$filter3]);
+
+        /**@var \Magento\Framework\Api\SortOrderBuilder $sortOrderBuilder */
+        $sortOrderBuilder = Bootstrap::getObjectManager()->create(
+            'Magento\Framework\Api\SortOrderBuilder'
+        );
+        /** @var \Magento\Framework\Api\SortOrder $sortOrder */
+        $sortOrder = $sortOrderBuilder->setField(Customer::EMAIL)->setDirection(SearchCriteria::SORT_ASC)->create();
+        $this->searchCriteriaBuilder->setSortOrders([$sortOrder]);
+
+        $searchCriteria = $this->searchCriteriaBuilder->create();
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/search',
+                'httpMethod' => RestConfig::HTTP_METHOD_POST,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'getList',
+            ],
+        ];
+        $searchData = $searchCriteria->__toArray();
+        $requestData = ['searchCriteria' => $searchData];
+        $searchResults = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertEquals(2, $searchResults['total_count']);
+        $this->assertEquals($customerData1[Customer::ID], $searchResults['items'][0][Customer::ID]);
+        $this->assertEquals($customerData2[Customer::ID], $searchResults['items'][1][Customer::ID]);
+    }
+
+    /**
+     * Test using multiple filters using GET
+     */
+    public function testSearchCustomersMultipleFiltersWithSortUsingGET()
+    {
+        $this->_markTestAsRestOnly('SOAP test is covered in testSearchCustomers');
+        $builder = Bootstrap::getObjectManager()->create('Magento\Framework\Api\FilterBuilder');
+        $customerData1 = $this->_createCustomer();
+        $customerData2 = $this->_createCustomer();
+        $filter1 = $builder->setField(Customer::EMAIL)
+            ->setValue($customerData1[Customer::EMAIL])
+            ->create();
+        $filter2 = $builder->setField(Customer::EMAIL)
+            ->setValue($customerData2[Customer::EMAIL])
+            ->create();
+        $filter3 = $builder->setField(Customer::LASTNAME)
+            ->setValue($customerData1[Customer::LASTNAME])
+            ->create();
+        $this->searchCriteriaBuilder->addFilter([$filter1, $filter2]);
+        $this->searchCriteriaBuilder->addFilter([$filter3]);
+        $this->searchCriteriaBuilder->setSortOrders([Customer::EMAIL => SearchCriteria::SORT_ASC]);
+        $searchCriteria = $this->searchCriteriaBuilder->create();
+        $searchData = $searchCriteria->__toArray();
+        $requestData = ['searchCriteria' => $searchData];
+        $searchQueryString = http_build_query($requestData);
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/search?' . $searchQueryString,
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+        ];
+        $searchResults = $this->_webApiCall($serviceInfo);
+        $this->assertEquals(2, $searchResults['total_count']);
+        $this->assertEquals($customerData1[Customer::ID], $searchResults['items'][0][Customer::ID]);
+        $this->assertEquals($customerData2[Customer::ID], $searchResults['items'][1][Customer::ID]);
+    }
+
+    /**
+     * Test and verify multiple filters using And-ed non-existent filter value
+     */
+    public function testSearchCustomersNonExistentMultipleFilters()
+    {
+        $builder = Bootstrap::getObjectManager()->create('Magento\Framework\Api\FilterBuilder');
+        $customerData1 = $this->_createCustomer();
+        $customerData2 = $this->_createCustomer();
+        $filter1 = $filter1 = $builder->setField(Customer::EMAIL)
+            ->setValue($customerData1[Customer::EMAIL])
+            ->create();
+        $filter2 = $builder->setField(Customer::EMAIL)
+            ->setValue($customerData2[Customer::EMAIL])
+            ->create();
+        $filter3 = $builder->setField(Customer::LASTNAME)
+            ->setValue('INVALID')
+            ->create();
+        $this->searchCriteriaBuilder->addFilter([$filter1, $filter2]);
+        $this->searchCriteriaBuilder->addFilter([$filter3]);
+        $searchCriteria = $this->searchCriteriaBuilder->create();
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/search',
+                'httpMethod' => RestConfig::HTTP_METHOD_POST,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'getList',
+            ],
+        ];
+        $searchData = $searchCriteria->__toArray();
+        $requestData = ['searchCriteria' => $searchData];
+        $searchResults = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertEquals(0, $searchResults['total_count'], 'No results expected for non-existent email.');
+    }
+
+    /**
+     * Test and verify multiple filters using And-ed non-existent filter value using GET
+     */
+    public function testSearchCustomersNonExistentMultipleFiltersGET()
+    {
+        $this->_markTestAsRestOnly('SOAP test is covered in testSearchCustomers');
+        $builder = Bootstrap::getObjectManager()->create('Magento\Framework\Api\FilterBuilder');
+        $customerData1 = $this->_createCustomer();
+        $customerData2 = $this->_createCustomer();
+        $filter1 = $filter1 = $builder->setField(Customer::EMAIL)
+            ->setValue($customerData1[Customer::EMAIL])
+            ->create();
+        $filter2 = $builder->setField(Customer::EMAIL)
+            ->setValue($customerData2[Customer::EMAIL])
+            ->create();
+        $filter3 = $builder->setField(Customer::LASTNAME)
+            ->setValue('INVALID')
+            ->create();
+        $this->searchCriteriaBuilder->addFilter([$filter1, $filter2]);
+        $this->searchCriteriaBuilder->addFilter([$filter3]);
+        $searchCriteria = $this->searchCriteriaBuilder->create();
+        $searchData = $searchCriteria->__toArray();
+        $requestData = ['searchCriteria' => $searchData];
+        $searchQueryString = http_build_query($requestData);
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/search?' . $searchQueryString,
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+        ];
+        $searchResults = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertEquals(0, $searchResults['total_count'], 'No results expected for non-existent email.');
+    }
+
+    /**
+     * Test using multiple filters
+     */
+    public function testSearchCustomersMultipleFilterGroups()
+    {
+        $customerData1 = $this->_createCustomer();
+
+        /** @var \Magento\Framework\Api\FilterBuilder $builder */
+        $builder = Bootstrap::getObjectManager()->create('Magento\Framework\Api\FilterBuilder');
+        $filter1 = $builder->setField(Customer::EMAIL)
+            ->setValue($customerData1[Customer::EMAIL])
+            ->create();
+        $filter2 = $builder->setField(Customer::MIDDLENAME)
+            ->setValue($customerData1[Customer::MIDDLENAME])
+            ->create();
+        $filter3 = $builder->setField(Customer::MIDDLENAME)
+            ->setValue('invalid')
+            ->create();
+        $filter4 = $builder->setField(Customer::LASTNAME)
+            ->setValue($customerData1[Customer::LASTNAME])
+            ->create();
+
+        $this->searchCriteriaBuilder->addFilter([$filter1]);
+        $this->searchCriteriaBuilder->addFilter([$filter2, $filter3]);
+        $this->searchCriteriaBuilder->addFilter([$filter4]);
+        $searchCriteria = $this->searchCriteriaBuilder->setCurrentPage(1)->setPageSize(10)->create();
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/search',
+                'httpMethod' => RestConfig::HTTP_METHOD_POST,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'getList',
+            ],
+        ];
+        $searchData = $searchCriteria->__toArray();
+        $requestData = ['searchCriteria' => $searchData];
+        $searchResults = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertEquals(1, $searchResults['total_count']);
+        $this->assertEquals($customerData1[Customer::ID], $searchResults['items'][0][Customer::ID]);
+
+        // Add an invalid And-ed data with multiple groups to yield no result
+        $filter4 = $builder->setField(Customer::LASTNAME)
+            ->setValue('invalid')
+            ->create();
+
+        $this->searchCriteriaBuilder->addFilter([$filter1]);
+        $this->searchCriteriaBuilder->addFilter([$filter2, $filter3]);
+        $this->searchCriteriaBuilder->addFilter([$filter4]);
+        $searchCriteria = $this->searchCriteriaBuilder->create();
+        $searchData = $searchCriteria->__toArray();
+        $requestData = ['searchCriteria' => $searchData];
+        $searchResults = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertEquals(0, $searchResults['total_count']);
+    }
+
+    /**
+     * Retrieve customer data by Id
+     *
+     * @param int $customerId
+     * @return \Magento\Customer\Api\Data\CustomerInterface
+     */
+    protected function _getCustomerData($customerId)
+    {
+        $customerData =  $this->customerRepository->getById($customerId);
+        $this->customerRegistry->remove($customerId);
+        return $customerData;
+    }
+
+    /**
+     * @return array|bool|float|int|string
+     */
+    protected function _createCustomer()
+    {
+        $customerData = $this->customerHelper->createSampleCustomer();
+        $this->currentCustomerId[] = $customerData['id'];
+        return $customerData;
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Customer/Api/GroupManagementTest.php b/dev/tests/api-functional/testsuite/Magento/Customer/Api/GroupManagementTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..70c6294ebb972258359846a461128d8e34822830
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Customer/Api/GroupManagementTest.php
@@ -0,0 +1,228 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+namespace Magento\Customer\Api;
+
+use Magento\Customer\Model\Data\Group as CustomerGroup;
+use Magento\Customer\Model\GroupRegistry;
+use Magento\Customer\Model\Resource\GroupRepository;
+use Magento\TestFramework\Helper\Bootstrap;
+use Magento\TestFramework\TestCase\WebapiAbstract;
+
+/**
+ * Class GroupManagementTest
+ */
+class GroupManagementTest extends WebapiAbstract
+{
+    const SERVICE_NAME = "customerGroupManagementV1";
+    const SERVICE_VERSION = "V1";
+    const RESOURCE_PATH = "/V1/customerGroups";
+
+    /**
+     * @var GroupRegistry
+     */
+    private $groupRegistry;
+
+    /**
+     * @var GroupRepository
+     */
+    private $groupRepository;
+
+    /**
+     * Execute per test initialization.
+     */
+    public function setUp()
+    {
+        $objectManager = Bootstrap::getObjectManager();
+        $this->groupRegistry = $objectManager->get('Magento\Customer\Model\GroupRegistry');
+        $this->groupRepository = $objectManager->get('Magento\Customer\Model\Resource\GroupRepository');
+    }
+
+    /**
+     * Verify the retrieval of the default group for storeId equal to 1.
+     *
+     * @param int $storeId The store Id
+     * @param array $defaultGroupData The default group data for the store with the specified Id.
+     *
+     * @dataProvider getDefaultGroupDataProvider
+     */
+    public function testGetDefaultGroup($storeId, $defaultGroupData)
+    {
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . "/default/$storeId",
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => 'customerGroupManagementV1GetDefaultGroup',
+            ],
+        ];
+        $requestData = ['storeId' => $storeId];
+        $groupData = $this->_webApiCall($serviceInfo, $requestData);
+
+        $this->assertEquals($defaultGroupData, $groupData, "The default group does not match.");
+    }
+
+    /**
+     * The testGetDefaultGroup data provider.
+     *
+     * @return array
+     */
+    public function getDefaultGroupDataProvider()
+    {
+        return [
+            'admin' => [
+                0,
+                [
+                    CustomerGroup::ID => 1,
+                    CustomerGroup::CODE => 'General',
+                    CustomerGroup::TAX_CLASS_ID => 3,
+                    CustomerGroup::TAX_CLASS_NAME => 'Retail Customer'
+                ],
+            ],
+            'base' => [
+                1,
+                [
+                    CustomerGroup::ID => 1,
+                    CustomerGroup::CODE => 'General',
+                    CustomerGroup::TAX_CLASS_ID => 3,
+                    CustomerGroup::TAX_CLASS_NAME => 'Retail Customer'
+                ],
+            ]
+        ];
+    }
+
+    /**
+     * Verify the retrieval of a non-existent storeId will return an expected fault.
+     */
+    public function testGetDefaultGroupNonExistentStore()
+    {
+        /* Store id should not exist */
+        $nonExistentStoreId = 9876;
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . "/default/$nonExistentStoreId",
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => 'customerGroupManagementV1GetDefaultGroup',
+            ],
+        ];
+        $requestData = ['storeId' => $nonExistentStoreId];
+        $expectedMessage = 'No such entity with %fieldName = %fieldValue';
+
+        try {
+            $this->_webApiCall($serviceInfo, $requestData);
+            $this->fail("Expected exception");
+        } catch (\SoapFault $e) {
+            $this->assertContains(
+                $expectedMessage,
+                $e->getMessage(),
+                "SoapFault does not contain expected message."
+            );
+        } catch (\Exception $e) {
+            $this->assertContains(
+                $expectedMessage,
+                $e->getMessage(),
+                "Exception does not contain expected message."
+            );
+            $this->assertContains((string)$nonExistentStoreId, $e->getMessage());
+        }
+    }
+
+    /**
+     * Verify that the group with the specified Id can or cannot be deleted.
+     *
+     * @param int $groupId The group Id
+     * @param bool $isDeleteable Whether the group can or cannot be deleted.
+     *
+     * @dataProvider isReadonlyDataProvider
+     */
+    public function testIsReadonly($groupId, $isDeleteable)
+    {
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . "/$groupId/permissions",
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => 'customerGroupManagementV1IsReadonly',
+            ],
+        ];
+
+        $requestData = [CustomerGroup::ID => $groupId];
+
+        $isReadonly = $this->_webApiCall($serviceInfo, $requestData);
+
+        $failureMessage = $isDeleteable
+            ? 'The group should be deleteable.' : 'The group should not be deleteable.';
+        $this->assertEquals($isDeleteable, !$isReadonly, $failureMessage);
+    }
+
+    /**
+     * The testIsReadonly data provider.
+     *
+     * @return array
+     */
+    public function isReadonlyDataProvider()
+    {
+        return [
+            'NOT LOGGED IN' => [0, false],
+            'General' => [1, false],
+            'Wholesale' => [2, true],
+            'Retailer' => [3, true]
+        ];
+    }
+
+    /**
+     * Verify that the group with the specified Id can or cannot be deleted.
+     */
+    public function testIsReadonlyNoSuchGroup()
+    {
+        /* This group ID should not exist in the store. */
+        $groupId = 9999;
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . "/$groupId/permissions",
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => 'customerGroupManagementV1IsReadonly',
+            ],
+        ];
+
+        $requestData = [CustomerGroup::ID => $groupId];
+
+        $expectedMessage = 'No such entity with %fieldName = %fieldValue';
+
+        try {
+            $this->_webApiCall($serviceInfo, $requestData);
+            $this->fail("Expected exception.");
+        } catch (\SoapFault $e) {
+            $this->assertContains(
+                $expectedMessage,
+                $e->getMessage(),
+                "SoapFault does not contain expected message."
+            );
+        } catch (\Exception $e) {
+            $this->assertContains(
+                $expectedMessage,
+                $e->getMessage(),
+                "Exception does not contain expected message."
+            );
+            $this->assertContains((string)$groupId, $e->getMessage());
+        }
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Customer/Api/GroupRepositoryTest.php b/dev/tests/api-functional/testsuite/Magento/Customer/Api/GroupRepositoryTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..862349c1cbe539b7bee28b8a844457e4c6e153c2
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Customer/Api/GroupRepositoryTest.php
@@ -0,0 +1,1007 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+namespace Magento\Customer\Api;
+
+use Magento\Customer\Model\Data\Group as CustomerGroup;
+use Magento\Customer\Model\GroupRegistry;
+use Magento\Customer\Model\Resource\GroupRepository;
+use Magento\Framework\Exception\NoSuchEntityException;
+use Magento\TestFramework\Helper\Bootstrap;
+use Magento\TestFramework\TestCase\WebapiAbstract;
+
+/**
+ * Class GroupRepositoryTest
+ */
+class GroupRepositoryTest extends WebapiAbstract
+{
+    const SERVICE_NAME = "customerGroupRepositoryV1";
+    const SERVICE_VERSION = "V1";
+    const RESOURCE_PATH = "/V1/customerGroups";
+
+    /**
+     * @var GroupRegistry
+     */
+    private $groupRegistry;
+
+    /**
+     * @var GroupRepository
+     */
+    private $groupRepository;
+
+    /**
+     * Execute per test initialization.
+     */
+    public function setUp()
+    {
+        $objectManager = Bootstrap::getObjectManager();
+        $this->groupRegistry = $objectManager->get('Magento\Customer\Model\GroupRegistry');
+        $this->groupRepository = $objectManager->get('Magento\Customer\Model\Resource\GroupRepository');
+    }
+
+    /**
+     * Execute per test cleanup.
+     */
+    public function tearDown()
+    {
+    }
+
+    /**
+     * Cleaning up the extra groups that might have been created as part of the testing.
+     */
+    public static function tearDownAfterClass()
+    {
+    }
+
+    /**
+     * Verify the retrieval of a customer group by Id.
+     *
+     * @param array $testGroup The group data for the group being retrieved.
+     *
+     * @dataProvider getGroupDataProvider
+     */
+    public function testGetGroupById($testGroup)
+    {
+        $groupId = $testGroup[CustomerGroup::ID];
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . "/$groupId",
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => 'customerGroupRepositoryV1GetById',
+            ],
+        ];
+        $requestData = [CustomerGroup::ID => $groupId];
+        $groupData = $this->_webApiCall($serviceInfo, $requestData);
+
+        $this->assertEquals($testGroup, $groupData, "The group data does not match.");
+    }
+
+    /**
+     * The testGetGroup data provider.
+     *
+     * @return array
+     */
+    public function getGroupDataProvider()
+    {
+        return [
+            'NOT LOGGED IN' => [
+                [
+                    CustomerGroup::ID => 0,
+                    CustomerGroup::CODE => 'NOT LOGGED IN',
+                    CustomerGroup::TAX_CLASS_ID => 3,
+                    CustomerGroup::TAX_CLASS_NAME => 'Retail Customer',
+                ],
+            ],
+            'General' => [
+                [
+                    CustomerGroup::ID => 1,
+                    CustomerGroup::CODE => 'General',
+                    CustomerGroup::TAX_CLASS_ID => 3,
+                    CustomerGroup::TAX_CLASS_NAME => 'Retail Customer',
+                ],
+            ],
+            'Wholesale' => [
+                [
+                    CustomerGroup::ID => 2,
+                    CustomerGroup::CODE => 'Wholesale',
+                    CustomerGroup::TAX_CLASS_ID => 3,
+                    CustomerGroup::TAX_CLASS_NAME => 'Retail Customer',
+                ],
+            ],
+            'Retailer' => [
+                [
+                    CustomerGroup::ID => 3,
+                    CustomerGroup::CODE => 'Retailer',
+                    CustomerGroup::TAX_CLASS_ID => 3,
+                    CustomerGroup::TAX_CLASS_NAME => 'Retail Customer',
+                ],
+            ],
+        ];
+    }
+
+    /**
+     * Verify that creating a new group works via REST.
+     */
+    public function testCreateGroupRest()
+    {
+        $this->_markTestAsRestOnly();
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH,
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_POST,
+            ],
+        ];
+
+        $groupData = [
+            CustomerGroup::ID => null,
+            CustomerGroup::CODE => 'Create Group REST',
+            CustomerGroup::TAX_CLASS_ID => 3,
+        ];
+        $requestData = ['group' => $groupData];
+
+        $groupId = $this->_webApiCall($serviceInfo, $requestData)[CustomerGroup::ID];
+        $this->assertNotNull($groupId);
+
+        $newGroup = $this->groupRepository->getById($groupId);
+        $this->assertEquals($groupId, $newGroup->getId(), 'The group id does not match.');
+        $this->assertEquals($groupData[CustomerGroup::CODE], $newGroup->getCode(), 'The group code does not match.');
+        $this->assertEquals(
+            $groupData[CustomerGroup::TAX_CLASS_ID],
+            $newGroup->getTaxClassId(),
+            'The group tax class id does not match.'
+        );
+    }
+
+    /**
+     * Verify that creating a new group with a duplicate group name fails with an error via REST.
+     */
+    public function testCreateGroupDuplicateGroupRest()
+    {
+        $builder = Bootstrap::getObjectManager()->create('Magento\Customer\Api\Data\GroupDataBuilder');
+        $this->_markTestAsRestOnly();
+
+        $duplicateGroupCode = 'Duplicate Group Code REST';
+
+        $this->createGroup(
+            $builder->populateWithArray([
+                CustomerGroup::ID => null,
+                CustomerGroup::CODE => $duplicateGroupCode,
+                CustomerGroup::TAX_CLASS_ID => 3,
+            ])->create()
+        );
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH,
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_POST,
+            ],
+        ];
+
+        $groupData = [
+            CustomerGroup::ID => null,
+            CustomerGroup::CODE => $duplicateGroupCode,
+            CustomerGroup::TAX_CLASS_ID => 3,
+        ];
+        $requestData = ['group' => $groupData];
+
+        try {
+            $this->_webApiCall($serviceInfo, $requestData);
+            $this->fail("Expected exception");
+        } catch (\Exception $e) {
+            $errorData = json_decode($e->getMessage(), true);
+
+            $this->assertEquals(
+                'Customer Group already exists.',
+                $errorData['message']
+            );
+            $this->assertEquals(400, $e->getCode(), 'Invalid HTTP code');
+        }
+    }
+
+    /**
+     * Verify that creating a new group works via REST if tax class id is empty, defaults 3.
+     */
+    public function testCreateGroupDefaultTaxClassIdRest()
+    {
+        $this->_markTestAsRestOnly();
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH,
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_POST,
+            ],
+        ];
+
+        $groupData = [
+            CustomerGroup::ID => null,
+            CustomerGroup::CODE => 'Default Class Tax ID REST',
+            CustomerGroup::TAX_CLASS_ID => null,
+        ];
+        $requestData = ['group' => $groupData];
+
+        $groupId = $this->_webApiCall($serviceInfo, $requestData)[CustomerGroup::ID];
+        $this->assertNotNull($groupId);
+
+        $newGroup = $this->groupRepository->getById($groupId);
+        $this->assertEquals($groupId, $newGroup->getId(), 'The group id does not match.');
+        $this->assertEquals($groupData[CustomerGroup::CODE], $newGroup->getCode(), 'The group code does not match.');
+        $this->assertEquals(
+            GroupRepository::DEFAULT_TAX_CLASS_ID,
+            $newGroup->getTaxClassId(),
+            'The group tax class id does not match.'
+        );
+    }
+
+    /**
+     * Verify that creating a new group without a code fails with an error.
+     */
+    public function testCreateGroupNoCodeExpectExceptionRest()
+    {
+        $this->_markTestAsRestOnly();
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH,
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_POST,
+            ],
+        ];
+
+        $groupData = [
+            CustomerGroup::ID => null,
+            CustomerGroup::CODE => null,
+            CustomerGroup::TAX_CLASS_ID => null,
+        ];
+        $requestData = ['group' => $groupData];
+
+        try {
+            $this->_webApiCall($serviceInfo, $requestData);
+            $this->fail("Expected exception");
+        } catch (\Exception $e) {
+            // @codingStandardsIgnoreStart
+            $this->assertContains(
+                '{"message":"%fieldName is a required field.","parameters":{"fieldName":"code"}',
+                $e->getMessage(),
+                "Exception does not contain expected message."
+            );
+            // @codingStandardsIgnoreEnd
+        }
+    }
+
+    /**
+     * Verify that creating a new group with an invalid tax class id fails with an error.
+     */
+    public function testCreateGroupInvalidTaxClassIdRest()
+    {
+        $this->_markTestAsRestOnly();
+
+        $invalidTaxClassId = 9999;
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH,
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_POST,
+            ],
+        ];
+
+        $groupData = [
+            CustomerGroup::ID => null,
+            CustomerGroup::CODE => 'Invalid Tax Class Id Code',
+            CustomerGroup::TAX_CLASS_ID => $invalidTaxClassId,
+        ];
+        $requestData = ['group' => $groupData];
+
+        try {
+            $this->_webApiCall($serviceInfo, $requestData);
+            $this->fail("Expected exception");
+        } catch (\Exception $e) {
+            // @codingStandardsIgnoreStart
+            $this->assertContains(
+                '{"message":"Invalid value of \"%value\" provided for the %fieldName field.","parameters":{"fieldName":"taxClassId","value":9999}',
+                $e->getMessage(),
+                "Exception does not contain expected message."
+            );
+            // codingStandardsIgnoreEnd
+        }
+    }
+
+    /**
+     * Verify that an attempt to update via POST is not allowed.
+     */
+    public function testCreateGroupWithIdRest()
+    {
+        $this->_markTestAsRestOnly();
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH,
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_POST,
+            ],
+        ];
+
+        $groupData = [
+            CustomerGroup::ID => 88,
+            CustomerGroup::CODE => 'Create Group With Id REST',
+            CustomerGroup::TAX_CLASS_ID => 3,
+        ];
+        $requestData = ['group' => $groupData];
+
+        try {
+            $this->_webApiCall($serviceInfo, $requestData);
+            $this->fail('Expected exception');
+        } catch (\Exception $e) {
+            $this->assertContains(
+                '{"message":"No such entity with %fieldName = %fieldValue","parameters":{"fieldName":"id","fieldValue":88}',
+                $e->getMessage(),
+                "Exception does not contain expected message."
+            );
+        }
+    }
+
+    /**
+     * Verify that creating a new group fails via SOAP if there is an Id specified.
+     */
+    public function testCreateGroupWithIdSoap()
+    {
+        $this->_markTestAsSoapOnly();
+
+        $serviceInfo = [
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => 'customerGroupRepositoryV1Save',
+            ],
+        ];
+
+        $groupData = [
+            CustomerGroup::ID => 88,
+            CustomerGroup::CODE => 'Create Group with Id SOAP',
+            CustomerGroup::TAX_CLASS_ID => 3,
+        ];
+        $requestData = ['group' => $groupData];
+
+        try {
+            $this->_webApiCall($serviceInfo, $requestData);
+            $this->fail("Expected exception");
+        } catch (\SoapFault $e) {
+            $this->assertContains(
+                'No such entity with %fieldName = %fieldValue',
+                $e->getMessage(),
+                "SoapFault does not contain expected message."
+            );
+        }
+    }
+
+    /**
+     * Verify that updating an existing group works via REST.
+     */
+    public function testUpdateGroupRest()
+    {
+        $this->_markTestAsRestOnly();
+        $builder = Bootstrap::getObjectManager()->create('Magento\Customer\Api\Data\GroupDataBuilder');
+        $groupId = $this->createGroup(
+            $builder->populateWithArray([
+                CustomerGroup::ID => null,
+                CustomerGroup::CODE => 'New Group REST',
+                CustomerGroup::TAX_CLASS_ID => 3,
+            ])->create()
+        );
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . "/$groupId",
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_PUT,
+            ],
+        ];
+
+        $groupData = [
+            CustomerGroup::ID => $groupId,
+            CustomerGroup::CODE => 'Updated Group REST',
+            CustomerGroup::TAX_CLASS_ID => 3,
+        ];
+        $requestData = ['group' => $groupData];
+
+        $this->assertEquals($groupId, $this->_webApiCall($serviceInfo, $requestData)[CustomerGroup::ID]);
+
+        $group = $this->groupRepository->getById($groupId);
+        $this->assertEquals($groupData[CustomerGroup::CODE], $group->getCode(), 'The group code did not change.');
+        $this->assertEquals(
+            $groupData[CustomerGroup::TAX_CLASS_ID],
+            $group->getTaxClassId(),
+            'The group tax class id did not change'
+        );
+    }
+
+    /**
+     * Verify that updating a non-existing group throws an exception.
+     */
+    public function testUpdateGroupNotExistingGroupRest()
+    {
+        $this->_markTestAsRestOnly();
+
+        $nonExistentGroupId = '9999';
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . "/$nonExistentGroupId",
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_PUT,
+            ],
+        ];
+
+        $groupData = [
+            CustomerGroup::ID => $nonExistentGroupId,
+            CustomerGroup::CODE => 'Updated Group REST Does Not Exist',
+            CustomerGroup::TAX_CLASS_ID => 3,
+        ];
+        $requestData = ['group' => $groupData];
+
+        try {
+            $this->_webApiCall($serviceInfo, $requestData);
+            $this->fail('Expected exception');
+        } catch (\Exception $e) {
+            $expectedMessage = '{"message":"No such entity with %fieldName = %fieldValue",'
+             . '"parameters":{"fieldName":"id","fieldValue":9999}';
+            $this->assertContains(
+                $expectedMessage,
+                $e->getMessage(),
+                "Exception does not contain expected message."
+            );
+        }
+    }
+
+    /**
+     * Verify that creating a new group works via SOAP.
+     */
+    public function testCreateGroupSoap()
+    {
+        $this->_markTestAsSoapOnly();
+
+        $serviceInfo = [
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => 'customerGroupRepositoryV1Save',
+            ],
+        ];
+
+        $groupData = [
+            CustomerGroup::ID => null,
+            CustomerGroup::CODE => 'Create Group SOAP',
+            'taxClassId' => 3,
+        ];
+        $requestData = ['group' => $groupData];
+
+        $groupId = $this->_webApiCall($serviceInfo, $requestData)[CustomerGroup::ID];
+        $this->assertNotNull($groupId);
+
+        $newGroup = $this->groupRepository->getById($groupId);
+        $this->assertEquals($groupId, $newGroup->getId(), "The group id does not match.");
+        $this->assertEquals($groupData[CustomerGroup::CODE], $newGroup->getCode(), "The group code does not match.");
+        $this->assertEquals(
+            $groupData['taxClassId'],
+            $newGroup->getTaxClassId(),
+            "The group tax class id does not match."
+        );
+    }
+
+    /**
+     * Verify that creating a new group with a duplicate code fails with an error via SOAP.
+     */
+    public function testCreateGroupDuplicateGroupSoap()
+    {
+        $this->_markTestAsSoapOnly();
+        $builder = Bootstrap::getObjectManager()->create('Magento\Customer\Api\Data\GroupDataBuilder');
+        $duplicateGroupCode = 'Duplicate Group Code SOAP';
+
+        $this->createGroup(
+            $builder->populateWithArray([
+                CustomerGroup::ID => null,
+                CustomerGroup::CODE => $duplicateGroupCode,
+                CustomerGroup::TAX_CLASS_ID => 3,
+            ])->create()
+        );
+
+        $serviceInfo = [
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => 'customerGroupRepositoryV1Save',
+            ],
+        ];
+
+        $groupData = [
+            CustomerGroup::ID => null,
+            CustomerGroup::CODE => $duplicateGroupCode,
+            'taxClassId' => 3,
+        ];
+        $requestData = ['group' => $groupData];
+
+        $expectedMessage = 'Customer Group already exists.';
+
+        try {
+            $this->_webApiCall($serviceInfo, $requestData);
+            $this->fail("Expected exception");
+        } catch (\SoapFault $e) {
+            $this->assertContains(
+                $expectedMessage,
+                $e->getMessage(),
+                "Exception does not contain expected message."
+            );
+        }
+    }
+
+    /**
+     * Verify that creating a new group works via SOAP if tax class id is empty, defaults 3.
+     */
+    public function testCreateGroupDefaultTaxClassIdSoap()
+    {
+        $this->_markTestAsSoapOnly();
+
+        $serviceInfo = [
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => 'customerGroupRepositoryV1Save',
+            ],
+        ];
+
+        $groupData = [
+            CustomerGroup::ID => null,
+            CustomerGroup::CODE => 'Default Class Tax ID SOAP',
+            'taxClassId' => null,
+            'taxClassName' => null,
+        ];
+        $requestData = ['group' => $groupData];
+
+        $groupResponseData = $this->_webApiCall($serviceInfo, $requestData);
+        $groupId = $groupResponseData[CustomerGroup::ID];
+        $this->assertNotNull($groupId);
+
+        $newGroup = $this->groupRepository->getById($groupId);
+        $this->assertEquals($groupId, $newGroup->getId(), "The group id does not match.");
+        $this->assertEquals($groupData[CustomerGroup::CODE], $newGroup->getCode(), "The group code does not match.");
+        $this->assertEquals(
+            GroupRepository::DEFAULT_TAX_CLASS_ID,
+            $newGroup->getTaxClassId(),
+            "The group tax class id does not match."
+        );
+    }
+
+    /**
+     * Verify that creating a new group without a code fails with an error.
+     */
+    public function testCreateGroupNoCodeExpectExceptionSoap()
+    {
+        $this->_markTestAsSoapOnly();
+
+        $serviceInfo = [
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => 'customerGroupRepositoryV1Save',
+            ],
+        ];
+
+        $groupData = [
+            CustomerGroup::ID => null,
+            CustomerGroup::CODE => null,
+            'taxClassId' => null,
+        ];
+        $requestData = ['group' => $groupData];
+
+        try {
+            $this->_webApiCall($serviceInfo, $requestData);
+            $this->fail("Expected exception");
+        } catch (\SoapFault $e) {
+            $this->assertContains(
+                '%fieldName is a required field.',
+                $e->getMessage(),
+                "SoapFault does not contain expected message."
+            );
+        }
+    }
+
+    /**
+     * Verify that creating a new group fails via SOAP if tax class id is invalid.
+     */
+    public function testCreateGroupInvalidTaxClassIdSoap()
+    {
+        $this->_markTestAsSoapOnly();
+
+        $invalidTaxClassId = 9999;
+
+        $serviceInfo = [
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => 'customerGroupRepositoryV1Save',
+            ],
+        ];
+
+        $groupData = [
+            CustomerGroup::ID => null,
+            CustomerGroup::CODE => 'Invalid Class Tax ID SOAP',
+            'taxClassId' => $invalidTaxClassId,
+        ];
+        $requestData = ['group' => $groupData];
+
+        $expectedMessage = 'Invalid value of "%value" provided for the %fieldName field.';
+
+        try {
+            $this->_webApiCall($serviceInfo, $requestData);
+            $this->fail("Expected exception");
+        } catch (\SoapFault $e) {
+            $this->assertContains(
+                $expectedMessage,
+                $e->getMessage(),
+                "SoapFault does not contain expected message."
+            );
+        }
+    }
+
+    /**
+     * Verify that updating an existing group works via SOAP.
+     */
+    public function testUpdateGroupSoap()
+    {
+        $this->_markTestAsSoapOnly();
+        $builder = Bootstrap::getObjectManager()->create('Magento\Customer\Api\Data\GroupDataBuilder');
+        $groupId = $this->createGroup(
+            $builder->populateWithArray([
+                    CustomerGroup::ID => null,
+                    CustomerGroup::CODE => 'New Group SOAP',
+                    CustomerGroup::TAX_CLASS_ID => 3,
+                ])->create()
+        );
+
+        $serviceInfo = [
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => 'customerGroupRepositoryV1Save',
+            ],
+        ];
+
+        $groupData = [
+            CustomerGroup::ID => $groupId,
+            CustomerGroup::CODE => 'Updated Group SOAP',
+            'taxClassId' => 3,
+        ];
+        $this->_webApiCall($serviceInfo, ['group' => $groupData]);
+
+        $group = $this->groupRepository->getById($groupId);
+        $this->assertEquals($groupData[CustomerGroup::CODE], $group->getCode(), 'The group code did not change.');
+        $this->assertEquals(
+            $groupData['taxClassId'],
+            $group->getTaxClassId(),
+            'The group tax class id did not change'
+        );
+    }
+
+    /**
+     * Verify that updating a non-existing group throws an exception via SOAP.
+     */
+    public function testUpdateGroupNotExistingGroupSoap()
+    {
+        $this->_markTestAsSoapOnly();
+
+        $nonExistentGroupId = '9999';
+
+        $serviceInfo = [
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => 'customerGroupRepositoryV1Save',
+            ],
+        ];
+
+        $groupData = [
+            CustomerGroup::ID => $nonExistentGroupId,
+            CustomerGroup::CODE => 'Updated Non-Existent Group SOAP',
+            'taxClassId' => 3,
+        ];
+        $requestData = ['group' => $groupData];
+
+        try {
+            $this->_webApiCall($serviceInfo, $requestData);
+        } catch (\Exception $e) {
+            $expectedMessage = 'No such entity with %fieldName = %fieldValue';
+
+            $this->assertContains(
+                $expectedMessage,
+                $e->getMessage(),
+                "Exception does not contain expected message."
+            );
+        }
+    }
+
+    /**
+     * Verify that deleting an existing group works.
+     */
+    public function testDeleteGroupExists()
+    {
+        $builder = Bootstrap::getObjectManager()->create('Magento\Customer\Api\Data\GroupDataBuilder');
+        $groupId = $this->createGroup(
+            $builder->populateWithArray([
+                CustomerGroup::ID => null,
+                CustomerGroup::CODE => 'Delete Group',
+                CustomerGroup::TAX_CLASS_ID => 3,
+            ])->create()
+        );
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . "/$groupId",
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_DELETE,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => 'customerGroupRepositoryV1DeleteById',
+            ],
+        ];
+
+        $requestData = [CustomerGroup::ID => $groupId];
+        $response = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertTrue($response, 'Expected response should be true.');
+
+        try {
+            $this->groupRepository->getById($groupId);
+            $this->fail('An expected NoSuchEntityException was not thrown.');
+        } catch (NoSuchEntityException $e) {
+            $exception = NoSuchEntityException::singleField(CustomerGroup::ID, $groupId);
+            $this->assertEquals(
+                $exception->getMessage(),
+                $e->getMessage(),
+                'Exception message does not match expected message.'
+            );
+        }
+    }
+
+    /**
+     * Verify that deleting an non-existing group works.
+     */
+    public function testDeleteGroupNotExists()
+    {
+        $groupId = 4200;
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . "/$groupId",
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_DELETE,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => 'customerGroupRepositoryV1DeleteById',
+            ],
+        ];
+
+        $requestData = [CustomerGroup::ID => $groupId];
+        $expectedMessage = NoSuchEntityException::MESSAGE_SINGLE_FIELD;
+        $expectedParameters = ['fieldName' => CustomerGroup::ID, 'fieldValue' => $groupId];
+
+        try {
+            $this->_webApiCall($serviceInfo, $requestData);
+        } catch (\SoapFault $e) {
+            $this->assertContains($expectedMessage, $e->getMessage(), "SoapFault does not contain expected message.");
+        } catch (\Exception $e) {
+            $errorObj = $this->processRestExceptionResult($e);
+            $this->assertEquals($expectedMessage, $errorObj['message']);
+            $this->assertEquals($expectedParameters, $errorObj['parameters']);
+        }
+    }
+
+    /**
+     * Verify that the group with the specified Id cannot be deleted because it is the default group and a proper
+     * fault is returned.
+     */
+    public function testDeleteGroupCannotDelete()
+    {
+        $groupIdAssignedDefault = 1;
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . "/$groupIdAssignedDefault",
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_DELETE,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => 'customerGroupRepositoryV1DeleteById',
+            ],
+        ];
+
+        $requestData = [CustomerGroup::ID => $groupIdAssignedDefault];
+        $expectedMessage = "Cannot delete group.";
+
+        try {
+            $this->_webApiCall($serviceInfo, $requestData);
+            $this->fail("Expected exception");
+        } catch (\SoapFault $e) {
+            $this->assertContains(
+                $expectedMessage,
+                $e->getMessage(),
+                "SoapFault does not contain expected message."
+            );
+        } catch (\Exception $e) {
+            $this->assertContains(
+                $expectedMessage,
+                $e->getMessage(),
+                "Exception does not contain expected message."
+            );
+        }
+
+        $this->assertNotNull($this->groupRepository->getById($groupIdAssignedDefault));
+    }
+
+    /**
+     * Create a test group.
+     *
+     * @param CustomerGroup $group The group to create and save.
+     * @return int The group Id of the group that was created.
+     */
+    private function createGroup($group)
+    {
+        $groupId = $this->groupRepository->save($group)->getId();
+        $this->assertNotNull($groupId);
+
+        $newGroup = $this->groupRepository->getById($groupId);
+        $this->assertEquals($groupId, $newGroup->getId(), 'The group id does not match.');
+        $this->assertEquals($group->getCode(), $newGroup->getCode(), 'The group code does not match.');
+        $this->assertEquals(
+            $group->getTaxClassId(),
+            $newGroup->getTaxClassId(),
+            'The group tax class id does not match.'
+        );
+
+        $this->groupRegistry->remove($groupId);
+
+        return $groupId;
+    }
+
+    /**
+     * Data provider for testSearchGroups
+     */
+    public function testSearchGroupsDataProvider()
+    {
+        return [
+            ['tax_class_id', '3', []],
+            ['tax_class_id', '0', null],
+            ['code', md5(mt_rand(0, 10000000000) . time()), null],
+            [
+                'id',
+                '0',
+                [
+                    'id' => '0',
+                    'code' => 'NOT LOGGED IN',
+                    'tax_class_id' => '3',
+                    'tax_class_name' => 'Retail Customer'
+                ]
+            ],
+            [
+                'code',
+                'General',
+                [
+                    'id' => '1',
+                    'code' => 'General',
+                    'tax_class_id' => '3',
+                    'tax_class_name' => 'Retail Customer'
+                ]
+            ],
+            [
+                'id',
+                '2',
+                [
+                    'id' => '2',
+                    'code' => 'Wholesale',
+                    'tax_class_id' => '3',
+                    'tax_class_name' => 'Retail Customer'
+                ]
+            ],
+            [
+                'code',
+                'Retailer',
+                [
+                    'id' => '3',
+                    'code' => 'Retailer',
+                    'tax_class_id' => '3',
+                    'tax_class_name' => 'Retail Customer'
+                ]
+            ]
+        ];
+    }
+
+    /**
+     * Test search customer group
+     *
+     * @param string $filterField Customer Group field to filter by
+     * @param string $filterValue Value of the field to be filtered by
+     * @param array $expectedResult Expected search result
+     *
+     * @dataProvider testSearchGroupsDataProvider
+     */
+    public function testSearchGroups($filterField, $filterValue, $expectedResult)
+    {
+        $filterBuilder = Bootstrap::getObjectManager()->create('Magento\Framework\Api\FilterBuilder');
+        $searchCriteriaBuilder =  Bootstrap::getObjectManager()
+            ->create('Magento\Framework\Api\SearchCriteriaBuilder');
+        $filter = $filterBuilder
+                    ->setField($filterField)
+                    ->setValue($filterValue)
+                    ->create();
+        $searchCriteriaBuilder->addFilter([$filter]);
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . "/search",
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_POST,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => 'customerGroupRepositoryV1GetList',
+            ],
+        ];
+
+        $searchData = $searchCriteriaBuilder->create()->__toArray();
+        $requestData = ['searchCriteria' => $searchData];
+
+        $searchResult = $this->_webApiCall($serviceInfo, $requestData);
+
+        if (is_null($expectedResult)) {
+            $this->assertEquals(0, $searchResult['total_count']);
+        } elseif (is_array($expectedResult)) {
+            $this->assertGreaterThan(0, $searchResult['total_count']);
+            if (!empty($expectedResult)) {
+                $this->assertEquals($expectedResult, $searchResult['items'][0]);
+            }
+        }
+    }
+
+    /**
+     * Test search customer group using GET
+     *
+     * @param string $filterField Customer Group field to filter by
+     * @param string $filterValue Value of the field to be filtered by
+     * @param array $expectedResult Expected search result
+     *
+     * @dataProvider testSearchGroupsDataProvider
+     */
+    public function testSearchGroupsWithGET($filterField, $filterValue, $expectedResult)
+    {
+        $this->_markTestAsRestOnly('SOAP is covered in ');
+        $filterBuilder = Bootstrap::getObjectManager()->create('Magento\Framework\Api\FilterBuilder');
+        $searchCriteriaBuilder =  Bootstrap::getObjectManager()
+            ->create('Magento\Framework\Api\SearchCriteriaBuilder');
+        $filter = $filterBuilder
+            ->setField($filterField)
+            ->setValue($filterValue)
+            ->create();
+        $searchCriteriaBuilder->addFilter([$filter]);
+        $searchData = $searchCriteriaBuilder->create()->__toArray();
+        $requestData = ['searchCriteria' => $searchData];
+        $searchQueryString = http_build_query($requestData);
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/search?' . $searchQueryString,
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_GET,
+            ],
+        ];
+        $searchResult = $this->_webApiCall($serviceInfo);
+
+        if (is_null($expectedResult)) {
+            $this->assertEquals(0, $searchResult['total_count']);
+        } elseif (is_array($expectedResult)) {
+            $this->assertGreaterThan(0, $searchResult['total_count']);
+            if (!empty($expectedResult)) {
+                $this->assertEquals($expectedResult, $searchResult['items'][0]);
+            }
+        }
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Downloadable/Service/V1/DownloadableLink/ReadServiceTest.php b/dev/tests/api-functional/testsuite/Magento/Downloadable/Service/V1/DownloadableLink/ReadServiceTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..4bade51570029bb3a5755c18db7e84ca553961d7
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Downloadable/Service/V1/DownloadableLink/ReadServiceTest.php
@@ -0,0 +1,155 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Downloadable\Service\V1\DownloadableLink;
+
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+class ReadServiceTest extends \Magento\TestFramework\TestCase\WebapiAbstract
+{
+    /**
+     * @dataProvider getListForAbsentProductProvider()
+     */
+    public function testGetListForAbsentProduct($urlTail, $method)
+    {
+        $sku = 'absent-product' . time();
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/products/' . $sku . $urlTail,
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => 'downloadableDownloadableLinkReadServiceV1',
+                'serviceVersion' => 'V1',
+                'operation' => 'downloadableDownloadableLinkReadServiceV1' . $method,
+            ],
+        ];
+
+        $requestData = ['productSku' => $sku];
+
+        $expectedMessage = 'Requested product doesn\'t exist';
+        try {
+            $this->_webApiCall($serviceInfo, $requestData);
+        } catch (\SoapFault $e) {
+            $this->assertEquals($expectedMessage, $e->getMessage());
+        } catch (\Exception $e) {
+            $this->assertContains($expectedMessage, $e->getMessage());
+        }
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+     * @dataProvider getListForAbsentProductProvider
+     */
+    public function testGetListForSimpleProduct($urlTail, $method)
+    {
+        $sku = 'simple';
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/products/' . $sku . $urlTail,
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => 'downloadableDownloadableLinkReadServiceV1',
+                'serviceVersion' => 'V1',
+                'operation' => 'downloadableDownloadableLinkReadServiceV1' . $method,
+            ],
+        ];
+
+        $requestData = ['productSku' => $sku];
+
+        $list = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertEmpty($list);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Downloadable/_files/product_downloadable_with_files.php
+     * @dataProvider getListForAbsentProductProvider
+     */
+    public function testGetList($urlTail, $method, $expectations)
+    {
+        $sku = 'downloadable-product';
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/products/' . $sku . $urlTail,
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => 'downloadableDownloadableLinkReadServiceV1',
+                'serviceVersion' => 'V1',
+                'operation' => 'downloadableDownloadableLinkReadServiceV1' . $method,
+            ],
+        ];
+
+        $requestData = ['productSku' => $sku];
+
+        $list = $this->_webApiCall($serviceInfo, $requestData);
+
+        $this->assertEquals(1, count($list));
+
+        $link = reset($list);
+        foreach ($expectations['fields'] as $index => $value) {
+            $this->assertEquals($value, $link[$index]);
+        }
+
+        foreach ($expectations['resources'] as $name => $fields) {
+            $this->assertNotEmpty($link[$name]);
+            $this->assertEquals($fields['file'], $link[$name]['file']);
+            $this->assertEquals($fields['type'], $link[$name]['type']);
+        }
+    }
+
+    public function getListForAbsentProductProvider()
+    {
+        $sampleIndex = 'sample_resource';
+        $linkIndex = 'link_resource';
+
+        $linkExpectation = [
+            'fields' => [
+                'shareable' => 2,
+                'price' => 15,
+                'number_of_downloads' => 15,
+            ],
+            'resources' => [
+                $sampleIndex => [
+                    'file' => '/n/d/jellyfish_1_3.jpg',
+                    'type' => 'file',
+                ],
+                $linkIndex => [
+                    'file' => '/j/e/jellyfish_2_4.jpg',
+                    'type' => 'file',
+                ],
+            ],
+        ];
+
+        $sampleExpectation = [
+            'fields' => [
+                'title' => 'Downloadable Product Sample Title',
+                'sort_order' => 0,
+            ],
+            'resources' => [
+                $sampleIndex => [
+                    'file' => '/f/u/jellyfish_1_4.jpg',
+                    'type' => 'file',
+                ],
+            ],
+        ];
+
+        return [
+            'links' => [
+                '/downloadable-links',
+                'GetLinks',
+                $linkExpectation,
+            ],
+            'samples' => [
+                '/downloadable-links/samples',
+                'GetSamples',
+                $sampleExpectation,
+            ],
+        ];
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Downloadable/Service/V1/DownloadableLink/WriteServiceTest.php b/dev/tests/api-functional/testsuite/Magento/Downloadable/Service/V1/DownloadableLink/WriteServiceTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..6f96f1951755853aa409493058a9164cc0b68601
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Downloadable/Service/V1/DownloadableLink/WriteServiceTest.php
@@ -0,0 +1,796 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Downloadable\Service\V1\DownloadableLink;
+
+use Magento\Catalog\Model\Product;
+use Magento\Downloadable\Model\Link;
+use Magento\TestFramework\Helper\Bootstrap;
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+class WriteServiceTest extends WebapiAbstract
+{
+    /**
+
+     * @var array
+     */
+    protected $createServiceInfo;
+
+    /**
+     * @var array
+     */
+    protected $updateServiceInfo;
+
+    /**
+     * @var array
+     */
+    protected $deleteServiceInfo;
+
+    /**
+     * @var string
+     */
+    protected $testImagePath;
+
+    protected function setUp()
+    {
+        $this->createServiceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/products/downloadable-product/downloadable-links',
+                'httpMethod' => RestConfig::HTTP_METHOD_POST,
+            ],
+            'soap' => [
+                'service' => 'downloadableDownloadableLinkWriteServiceV1',
+                'serviceVersion' => 'V1',
+                'operation' => 'downloadableDownloadableLinkWriteServiceV1Create',
+            ],
+        ];
+
+        $this->updateServiceInfo = [
+            'rest' => [
+                'httpMethod' => RestConfig::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => 'downloadableDownloadableLinkWriteServiceV1',
+                'serviceVersion' => 'V1',
+                'operation' => 'downloadableDownloadableLinkWriteServiceV1Update',
+            ],
+        ];
+
+        $this->deleteServiceInfo = [
+            'rest' => [
+                'httpMethod' => RestConfig::HTTP_METHOD_DELETE,
+            ],
+            'soap' => [
+                'service' => 'downloadableDownloadableLinkWriteServiceV1',
+                'serviceVersion' => 'V1',
+                'operation' => 'downloadableDownloadableLinkWriteServiceV1Delete',
+            ],
+        ];
+
+        $this->testImagePath = __DIR__ . DIRECTORY_SEPARATOR . '_files' . DIRECTORY_SEPARATOR . 'test_image.jpg';
+    }
+
+    /**
+     * Retrieve product that was updated by test
+     *
+     * @param bool $isScopeGlobal if true product store ID will be set to 0
+     * @return Product
+     */
+    protected function getTargetProduct($isScopeGlobal = false)
+    {
+        $objectManager = Bootstrap::getObjectManager();
+        $product = $objectManager->get('Magento\Catalog\Model\ProductFactory')->create()->load(1);
+        if ($isScopeGlobal) {
+            $product->setStoreId(0);
+        }
+        return $product;
+    }
+
+    /**
+     * Retrieve product link by its ID (or first link if ID is not specified)
+     *
+     * @param Product $product
+     * @param int|null $linkId
+     * @return Link|null
+     */
+    protected function getTargetLink(Product $product, $linkId = null)
+    {
+        $links = $product->getTypeInstance()->getLinks($product);
+        if (!is_null($linkId)) {
+            return isset($links[$linkId]) ? $links[$linkId] : null;
+        }
+        // return first link
+        return reset($links);
+    }
+
+    /**
+     *  @magentoApiDataFixture Magento/Downloadable/_files/product_downloadable.php
+     */
+    public function testCreateUploadsProvidedFileContent()
+    {
+        $requestData = [
+            'isGlobalScopeContent' => true,
+            'productSku' => 'downloadable-product',
+            'linkContent' => [
+                'title' => 'Title',
+                'sort_order' => 1,
+                'price' => 10.1,
+                'shareable' => true,
+                'number_of_downloads' => 100,
+                'link_type' => 'file',
+                'link_file' => [
+                    'data' => base64_encode(file_get_contents($this->testImagePath)),
+                    'name' => 'image.jpg',
+                ],
+                'sample_file' => [
+                    'data' => base64_encode(file_get_contents($this->testImagePath)),
+                    'name' => 'image.jpg',
+                ],
+                'sample_type' => 'file',
+            ],
+        ];
+
+        $newLinkId = $this->_webApiCall($this->createServiceInfo, $requestData);
+        $globalScopeLink = $this->getTargetLink($this->getTargetProduct(true), $newLinkId);
+        $link = $this->getTargetLink($this->getTargetProduct(), $newLinkId);
+        $this->assertNotNull($link);
+        $this->assertEquals($requestData['linkContent']['title'], $link->getTitle());
+        $this->assertEquals($requestData['linkContent']['title'], $globalScopeLink->getTitle());
+        $this->assertEquals($requestData['linkContent']['sort_order'], $link->getSortOrder());
+        $this->assertEquals($requestData['linkContent']['price'], $link->getPrice());
+        $this->assertEquals($requestData['linkContent']['price'], $globalScopeLink->getPrice());
+        $this->assertEquals($requestData['linkContent']['shareable'], $link->getIsShareable());
+        $this->assertEquals($requestData['linkContent']['number_of_downloads'], $link->getNumberOfDownloads());
+        $this->assertEquals($requestData['linkContent']['link_type'], $link->getLinkType());
+        $this->assertEquals($requestData['linkContent']['sample_type'], $link->getSampleType());
+        $this->assertStringEndsWith('.jpg', $link->getSampleFile());
+        $this->assertStringEndsWith('.jpg', $link->getLinkFile());
+        $this->assertNull($link->getLinkUrl());
+        $this->assertNull($link->getSampleUrl());
+    }
+
+    /**
+     *  @magentoApiDataFixture Magento/Downloadable/_files/product_downloadable.php
+     */
+    public function testCreateSavesPriceAndTitleInStoreViewScope()
+    {
+        $requestData = [
+            'isGlobalScopeContent' => false,
+            'productSku' => 'downloadable-product',
+            'linkContent' => [
+                'title' => 'Store View Title',
+                'sort_order' => 1,
+                'price' => 150,
+                'shareable' => true,
+                'number_of_downloads' => 100,
+                'link_url' => 'http://www.example.com/',
+                'link_type' => 'url',
+                'sample_url' => 'http://www.sample.example.com/',
+                'sample_type' => 'url',
+            ],
+        ];
+
+        $newLinkId = $this->_webApiCall($this->createServiceInfo, $requestData);
+        $link = $this->getTargetLink($this->getTargetProduct(), $newLinkId);
+        $globalScopeLink = $this->getTargetLink($this->getTargetProduct(true), $newLinkId);
+        $this->assertNotNull($link);
+        $this->assertEquals($requestData['linkContent']['title'], $link->getTitle());
+        $this->assertEquals($requestData['linkContent']['sort_order'], $link->getSortOrder());
+        $this->assertEquals($requestData['linkContent']['price'], $link->getPrice());
+        $this->assertEquals($requestData['linkContent']['shareable'], $link->getIsShareable());
+        $this->assertEquals($requestData['linkContent']['number_of_downloads'], $link->getNumberOfDownloads());
+        $this->assertEquals($requestData['linkContent']['link_url'], $link->getLinkUrl());
+        $this->assertEquals($requestData['linkContent']['link_type'], $link->getLinkType());
+        $this->assertEquals($requestData['linkContent']['sample_url'], $link->getSampleUrl());
+        $this->assertEquals($requestData['linkContent']['sample_type'], $link->getSampleType());
+        $this->assertEmpty($globalScopeLink->getTitle());
+        $this->assertEmpty($globalScopeLink->getPrice());
+    }
+
+    /**
+     *  @magentoApiDataFixture Magento/Downloadable/_files/product_downloadable.php
+     */
+    public function testCreateSavesProvidedUrls()
+    {
+        $requestData = [
+            'isGlobalScopeContent' => false,
+            'productSku' => 'downloadable-product',
+            'linkContent' => [
+                'title' => 'Link with URL resources',
+                'sort_order' => 1,
+                'price' => 10.1,
+                'shareable' => true,
+                'number_of_downloads' => 100,
+                'link_url' => 'http://www.example.com/',
+                'link_type' => 'url',
+                'sample_url' => 'http://www.sample.example.com/',
+                'sample_type' => 'url',
+            ],
+        ];
+
+        $newLinkId = $this->_webApiCall($this->createServiceInfo, $requestData);
+        $link = $this->getTargetLink($this->getTargetProduct(), $newLinkId);
+        $this->assertNotNull($link);
+        $this->assertEquals($requestData['linkContent']['title'], $link->getTitle());
+        $this->assertEquals($requestData['linkContent']['sort_order'], $link->getSortOrder());
+        $this->assertEquals($requestData['linkContent']['price'], $link->getPrice());
+        $this->assertEquals($requestData['linkContent']['shareable'], $link->getIsShareable());
+        $this->assertEquals($requestData['linkContent']['number_of_downloads'], $link->getNumberOfDownloads());
+        $this->assertEquals($requestData['linkContent']['link_url'], $link->getLinkUrl());
+        $this->assertEquals($requestData['linkContent']['link_type'], $link->getLinkType());
+        $this->assertEquals($requestData['linkContent']['sample_type'], $link->getSampleType());
+        $this->assertEquals($requestData['linkContent']['sample_url'], $link->getSampleUrl());
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Downloadable/_files/product_downloadable.php
+     * @expectedException \Exception
+     * @expectedExceptionMessage Invalid link type.
+     */
+    public function testCreateThrowsExceptionIfLinkTypeIsNotSpecified()
+    {
+        $requestData = [
+            'isGlobalScopeContent' => false,
+            'productSku' => 'downloadable-product',
+            'linkContent' => [
+                'title' => 'Link with URL resources',
+                'sort_order' => 1,
+                'price' => 10.1,
+                'shareable' => true,
+                'number_of_downloads' => 100,
+            ],
+        ];
+
+        $this->_webApiCall($this->createServiceInfo, $requestData);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Downloadable/_files/product_downloadable.php
+     * @expectedException \Exception
+     * @expectedExceptionMessage Provided content must be valid base64 encoded data.
+     */
+    public function testCreateThrowsExceptionIfLinkFileContentIsNotAValidBase64EncodedString()
+    {
+        $requestData = [
+            'isGlobalScopeContent' => false,
+            'productSku' => 'downloadable-product',
+            'linkContent' => [
+                'title' => 'Link Title',
+                'sort_order' => 1,
+                'price' => 10,
+                'shareable' => true,
+                'number_of_downloads' => 100,
+                'link_type' => 'url',
+                'link_url' => 'http://www.example.com/',
+                'sample_type' => 'file',
+                'sample_file' => [
+                    'data' => 'not_a_base64_encoded_content',
+                    'name' => 'image.jpg',
+                ],
+            ],
+        ];
+
+        $this->_webApiCall($this->createServiceInfo, $requestData);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Downloadable/_files/product_downloadable.php
+     * @expectedException \Exception
+     * @expectedExceptionMessage Provided content must be valid base64 encoded data.
+     */
+    public function testCreateThrowsExceptionIfSampleFileContentIsNotAValidBase64EncodedString()
+    {
+        $requestData = [
+            'isGlobalScopeContent' => false,
+            'productSku' => 'downloadable-product',
+            'linkContent' => [
+                'title' => 'Link Title',
+                'sort_order' => 1,
+                'price' => 10,
+                'shareable' => true,
+                'number_of_downloads' => 100,
+                'link_type' => 'file',
+                'link_file' => [
+                    'data' => 'not_a_base64_encoded_content',
+                    'name' => 'image.jpg',
+                ],
+            ],
+        ];
+
+        $this->_webApiCall($this->createServiceInfo, $requestData);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Downloadable/_files/product_downloadable.php
+     * @expectedException \Exception
+     * @expectedExceptionMessage Provided file name contains forbidden characters.
+     */
+    public function testCreateThrowsExceptionIfLinkFileNameContainsForbiddenCharacters()
+    {
+        $requestData = [
+            'isGlobalScopeContent' => false,
+            'productSku' => 'downloadable-product',
+            'linkContent' => [
+                'title' => 'Title',
+                'sort_order' => 15,
+                'price' => 10,
+                'shareable' => true,
+                'number_of_downloads' => 100,
+                'link_type' => 'file',
+                'link_file' => [
+                    'data' => base64_encode(file_get_contents($this->testImagePath)),
+                    'name' => 'name/with|forbidden{characters',
+                ],
+            ],
+        ];
+
+        $this->_webApiCall($this->createServiceInfo, $requestData);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Downloadable/_files/product_downloadable.php
+     * @expectedException \Exception
+     * @expectedExceptionMessage Provided file name contains forbidden characters.
+     */
+    public function testCreateThrowsExceptionIfSampleFileNameContainsForbiddenCharacters()
+    {
+        $requestData = [
+            'isGlobalScopeContent' => false,
+            'productSku' => 'downloadable-product',
+            'linkContent' => [
+                'title' => 'Link Title',
+                'sort_order' => 1,
+                'price' => 10,
+                'shareable' => true,
+                'number_of_downloads' => 100,
+                'link_type' => 'url',
+                'link_url' => 'http://www.example.com/',
+                'sample_type' => 'file',
+                'sample_file' => [
+                    'data' => base64_encode(file_get_contents($this->testImagePath)),
+                    'name' => 'name/with|forbidden{characters',
+                ],
+            ],
+        ];
+
+        $this->_webApiCall($this->createServiceInfo, $requestData);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Downloadable/_files/product_downloadable.php
+     * @expectedException \Exception
+     * @expectedExceptionMessage Link URL must have valid format.
+     */
+    public function testCreateThrowsExceptionIfLinkUrlHasWrongFormat()
+    {
+        $requestData = [
+            'isGlobalScopeContent' => false,
+            'productSku' => 'downloadable-product',
+            'linkContent' => [
+                'title' => 'Link Title',
+                'sort_order' => 1,
+                'price' => 10,
+                'shareable' => true,
+                'number_of_downloads' => 100,
+                'link_type' => 'url',
+                'link_url' => 'http://example<.>com/',
+            ],
+        ];
+
+        $this->_webApiCall($this->createServiceInfo, $requestData);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Downloadable/_files/product_downloadable.php
+     * @expectedException \Exception
+     * @expectedExceptionMessage Sample URL must have valid format.
+     */
+    public function testCreateThrowsExceptionIfSampleUrlHasWrongFormat()
+    {
+        $requestData = [
+            'isGlobalScopeContent' => false,
+            'productSku' => 'downloadable-product',
+            'linkContent' => [
+                'title' => 'Link Title',
+                'sort_order' => 1,
+                'price' => 150,
+                'shareable' => true,
+                'number_of_downloads' => 0,
+                'sample_type' => 'url',
+                'sample_url' => 'http://example<.>com/',
+                'link_type' => 'url',
+                'link_url' => 'http://example.com/',
+            ],
+        ];
+
+        $this->_webApiCall($this->createServiceInfo, $requestData);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Downloadable/_files/product_downloadable.php
+     * @expectedException \Exception
+     * @expectedExceptionMessage Link price must have numeric positive value.
+     * @dataProvider getInvalidLinkPrice
+     */
+    public function testCreateThrowsExceptionIfLinkPriceIsInvalid($linkPrice)
+    {
+        $requestData = [
+            'isGlobalScopeContent' => false,
+            'productSku' => 'downloadable-product',
+            'linkContent' => [
+                'title' => 'Link Title',
+                'sort_order' => 1,
+                'price' => $linkPrice,
+                'shareable' => true,
+                'number_of_downloads' => 0,
+                'sample_type' => 'url',
+                'sample_url' => 'http://example.com/',
+                'link_type' => 'url',
+                'link_url' => 'http://example.com/',
+            ],
+        ];
+
+        $this->_webApiCall($this->createServiceInfo, $requestData);
+    }
+
+    /**
+     * @return array
+     */
+    public function getInvalidLinkPrice()
+    {
+        return [
+            ['string_value'],
+            [-1.5],
+        ];
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Downloadable/_files/product_downloadable.php
+     * @expectedException \Exception
+     * @expectedExceptionMessage Sort order must be a positive integer.
+     * @dataProvider getInvalidSortOrder
+     */
+    public function testCreateThrowsExceptionIfSortOrderIsInvalid($sortOrder)
+    {
+        $requestData = [
+            'isGlobalScopeContent' => false,
+            'productSku' => 'downloadable-product',
+            'linkContent' => [
+                'title' => 'Link Title',
+                'sort_order' => $sortOrder,
+                'price' => 10,
+                'shareable' => false,
+                'number_of_downloads' => 0,
+                'sample_type' => 'url',
+                'sample_url' => 'http://example.com/',
+                'link_type' => 'url',
+                'link_url' => 'http://example.com/',
+            ],
+        ];
+        $this->_webApiCall($this->createServiceInfo, $requestData);
+    }
+
+    /**
+     * @return array
+     */
+    public function getInvalidSortOrder()
+    {
+        return [
+            [-1],
+        ];
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Downloadable/_files/product_downloadable.php
+     * @expectedException \Exception
+     * @expectedExceptionMessage Number of downloads must be a positive integer.
+     * @dataProvider getInvalidNumberOfDownloads
+     */
+    public function testCreateThrowsExceptionIfNumberOfDownloadsIsInvalid($numberOfDownloads)
+    {
+        $requestData = [
+            'isGlobalScopeContent' => false,
+            'productSku' => 'downloadable-product',
+            'linkContent' => [
+                'title' => 'Link Title',
+                'sort_order' => 0,
+                'price' => 10,
+                'shareable' => false,
+                'number_of_downloads' => $numberOfDownloads,
+                'sample_type' => 'url',
+                'sample_url' => 'http://example.com/',
+                'link_type' => 'url',
+                'link_url' => 'http://example.com/',
+            ],
+        ];
+        $this->_webApiCall($this->createServiceInfo, $requestData);
+    }
+
+    /**
+     * @return array
+     */
+    public function getInvalidNumberOfDownloads()
+    {
+        return [
+            [-1],
+        ];
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+     * @expectedException \Exception
+     * @expectedExceptionMessage Product type of the product must be 'downloadable'.
+     */
+    public function testCreateThrowsExceptionIfTargetProductTypeIsNotDownloadable()
+    {
+        $this->createServiceInfo['rest']['resourcePath'] = '/V1/products/simple/downloadable-links';
+        $requestData = [
+            'isGlobalScopeContent' => false,
+            'productSku' => 'simple',
+            'linkContent' => [
+                'title' => 'Link Title',
+                'sort_order' => 50,
+                'price' => 200,
+                'shareable' => false,
+                'number_of_downloads' => 10,
+                'sample_type' => 'url',
+                'sample_url' => 'http://example.com/',
+                'link_type' => 'url',
+                'link_url' => 'http://example.com/',
+            ],
+        ];
+        $this->_webApiCall($this->createServiceInfo, $requestData);
+    }
+
+    /**
+     * @expectedException \Exception
+     * @expectedExceptionMessage Requested product doesn't exist
+     */
+    public function testCreateThrowsExceptionIfTargetProductDoesNotExist()
+    {
+        $this->createServiceInfo['rest']['resourcePath'] = '/V1/products/wrong-sku/downloadable-links';
+        $requestData = [
+            'isGlobalScopeContent' => false,
+            'productSku' => 'wrong-sku',
+            'linkContent' => [
+                'title' => 'Link Title',
+                'sort_order' => 15,
+                'price' => 200,
+                'shareable' => true,
+                'number_of_downloads' => 100,
+                'sample_type' => 'url',
+                'sample_url' => 'http://example.com/',
+                'link_type' => 'url',
+                'link_url' => 'http://example.com/',
+            ],
+        ];
+        $this->_webApiCall($this->createServiceInfo, $requestData);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Downloadable/_files/product_downloadable.php
+     */
+    public function testUpdate()
+    {
+        $linkId = $this->getTargetLink($this->getTargetProduct())->getId();
+        $this->updateServiceInfo['rest']['resourcePath']
+            = "/V1/products/downloadable-product/downloadable-links/{$linkId}";
+        $requestData = [
+            'isGlobalScopeContent' => false,
+            'linkId' => $linkId,
+            'productSku' => 'downloadable-product',
+            'linkContent' => [
+                'title' => 'Updated Title',
+                'sort_order' => 2,
+                'price' => 100.10,
+                'shareable' => false,
+                'number_of_downloads' => 50,
+            ],
+        ];
+
+        $this->assertTrue($this->_webApiCall($this->updateServiceInfo, $requestData));
+        $link = $this->getTargetLink($this->getTargetProduct(), $linkId);
+        $this->assertNotNull($link);
+        $this->assertEquals($requestData['linkContent']['title'], $link->getTitle());
+        $this->assertEquals($requestData['linkContent']['sort_order'], $link->getSortOrder());
+        $this->assertEquals($requestData['linkContent']['price'], $link->getPrice());
+        $this->assertEquals($requestData['linkContent']['shareable'], (bool)$link->getIsShareable());
+        $this->assertEquals($requestData['linkContent']['number_of_downloads'], $link->getNumberOfDownloads());
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Downloadable/_files/product_downloadable.php
+     */
+    public function testUpdateSavesDataInGlobalScopeAndDoesNotAffectValuesStoredInStoreViewScope()
+    {
+        $originalLink = $this->getTargetLink($this->getTargetProduct());
+        $linkId = $originalLink->getId();
+        $this->updateServiceInfo['rest']['resourcePath']
+            = "/V1/products/downloadable-product/downloadable-links/{$linkId}";
+        $requestData = [
+            'isGlobalScopeContent' => true,
+            'linkId' => $linkId,
+            'productSku' => 'downloadable-product',
+            'linkContent' => [
+                'title' => 'Updated Title',
+                'sort_order' => 2,
+                'price' => 100.10,
+                'shareable' => false,
+                'number_of_downloads' => 50,
+            ],
+        ];
+
+        $this->assertTrue($this->_webApiCall($this->updateServiceInfo, $requestData));
+        $link = $this->getTargetLink($this->getTargetProduct(), $linkId);
+        $globalScopeLink = $this->getTargetLink($this->getTargetProduct(true), $linkId);
+        $this->assertNotNull($link);
+        // Title and price were set on store view level in fixture so they must be the same
+        $this->assertEquals($originalLink->getTitle(), $link->getTitle());
+        $this->assertEquals($originalLink->getPrice(), $link->getPrice());
+        $this->assertEquals($requestData['linkContent']['title'], $globalScopeLink->getTitle());
+        $this->assertEquals($requestData['linkContent']['price'], $globalScopeLink->getPrice());
+        $this->assertEquals($requestData['linkContent']['sort_order'], $link->getSortOrder());
+        $this->assertEquals($requestData['linkContent']['shareable'], (bool)$link->getIsShareable());
+        $this->assertEquals($requestData['linkContent']['number_of_downloads'], $link->getNumberOfDownloads());
+    }
+
+    /**
+     * @expectedException \Exception
+     * @expectedExceptionMessage Requested product doesn't exist
+     */
+    public function testUpdateThrowsExceptionIfTargetProductDoesNotExist()
+    {
+        $this->updateServiceInfo['rest']['resourcePath'] = '/V1/products/wrong-sku/downloadable-links/1';
+        $requestData = [
+            'isGlobalScopeContent' => true,
+            'linkId' => 1,
+            'productSku' => 'wrong-sku',
+            'linkContent' => [
+                'title' => 'Updated Title',
+                'sort_order' => 2,
+                'price' => 100.10,
+                'shareable' => false,
+                'number_of_downloads' => 50,
+            ],
+        ];
+        $this->_webApiCall($this->updateServiceInfo, $requestData);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Downloadable/_files/product_downloadable.php
+     * @expectedException \Exception
+     * @expectedExceptionMessage There is no downloadable link with provided ID.
+     */
+    public function testUpdateThrowsExceptionIfThereIsNoDownloadableLinkWithGivenId()
+    {
+        $linkId = 9999;
+        $this->updateServiceInfo['rest']['resourcePath']
+            = "/V1/products/downloadable-product/downloadable-links/{$linkId}";
+        $requestData = [
+            'isGlobalScopeContent' => true,
+            'linkId' => 9999,
+            'productSku' => 'downloadable-product',
+            'linkContent' => [
+                'title' => 'Title',
+                'sort_order' => 2,
+                'price' => 100.10,
+                'shareable' => false,
+                'number_of_downloads' => 50,
+            ],
+        ];
+
+        $this->_webApiCall($this->updateServiceInfo, $requestData);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Downloadable/_files/product_downloadable.php
+     * @expectedException \Exception
+     * @expectedExceptionMessage Link price must have numeric positive value.
+     * @dataProvider getInvalidLinkPrice
+     */
+    public function testUpdateThrowsExceptionIfLinkPriceIsInvalid($linkPrice)
+    {
+        $linkId = $this->getTargetLink($this->getTargetProduct())->getId();
+        $this->updateServiceInfo['rest']['resourcePath']
+            = "/V1/products/downloadable-product/downloadable-links/{$linkId}";
+        $requestData = [
+            'isGlobalScopeContent' => false,
+            'linkId' => $linkId,
+            'productSku' => 'downloadable-product',
+            'linkContent' => [
+                'title' => 'Updated Link Title',
+                'sort_order' => 2,
+                'price' => $linkPrice,
+                'shareable' => false,
+                'number_of_downloads' => 50,
+            ],
+        ];
+
+        $this->_webApiCall($this->updateServiceInfo, $requestData);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Downloadable/_files/product_downloadable.php
+     * @expectedException \Exception
+     * @expectedExceptionMessage Sort order must be a positive integer.
+     * @dataProvider getInvalidSortOrder
+     */
+    public function testUpdateThrowsExceptionIfSortOrderIsInvalid($sortOrder)
+    {
+        $linkId = $this->getTargetLink($this->getTargetProduct())->getId();
+        $this->updateServiceInfo['rest']['resourcePath']
+            = "/V1/products/downloadable-product/downloadable-links/{$linkId}";
+        $requestData = [
+            'isGlobalScopeContent' => false,
+            'linkId' => $linkId,
+            'productSku' => 'downloadable-product',
+            'linkContent' => [
+                'title' => 'Updated Link Title',
+                'sort_order' => $sortOrder,
+                'price' => 100.50,
+                'shareable' => false,
+                'number_of_downloads' => 50,
+            ],
+        ];
+        $this->_webApiCall($this->updateServiceInfo, $requestData);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Downloadable/_files/product_downloadable.php
+     * @expectedException \Exception
+     * @expectedExceptionMessage Number of downloads must be a positive integer.
+     * @dataProvider getInvalidNumberOfDownloads
+     */
+    public function testUpdateThrowsExceptionIfNumberOfDownloadsIsInvalid($numberOfDownloads)
+    {
+        $linkId = $this->getTargetLink($this->getTargetProduct())->getId();
+        $this->updateServiceInfo['rest']['resourcePath']
+            = "/V1/products/downloadable-product/downloadable-links/{$linkId}";
+        $requestData = [
+            'isGlobalScopeContent' => false,
+            'linkId' => $linkId,
+            'productSku' => 'downloadable-product',
+            'linkContent' => [
+                'title' => 'Updated Link Title',
+                'sort_order' => 200,
+                'price' => 100.50,
+                'shareable' => false,
+                'number_of_downloads' => $numberOfDownloads,
+            ],
+        ];
+        $this->_webApiCall($this->updateServiceInfo, $requestData);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Downloadable/_files/product_downloadable.php
+     */
+    public function testDelete()
+    {
+        $linkId = $this->getTargetLink($this->getTargetProduct())->getId();
+        $this->deleteServiceInfo['rest']['resourcePath'] = "/V1/products/downloadable-links/{$linkId}";
+        $requestData = [
+            'linkId' => $linkId,
+        ];
+
+        $this->assertTrue($this->_webApiCall($this->deleteServiceInfo, $requestData));
+        $link = $this->getTargetLink($this->getTargetProduct(), $linkId);
+        $this->assertNull($link);
+    }
+
+    /**
+     * @expectedException \Exception
+     * @expectedExceptionMessage There is no downloadable link with provided ID.
+     */
+    public function testDeleteThrowsExceptionIfThereIsNoDownloadableLinkWithGivenId()
+    {
+        $linkId = 9999;
+        $this->deleteServiceInfo['rest']['resourcePath'] = "/V1/products/downloadable-links/{$linkId}";
+        $requestData = [
+            'linkId' => $linkId,
+        ];
+
+        $this->_webApiCall($this->deleteServiceInfo, $requestData);
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Downloadable/Service/V1/DownloadableLink/_files/test_image.jpg b/dev/tests/api-functional/testsuite/Magento/Downloadable/Service/V1/DownloadableLink/_files/test_image.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..ad6b747f73dd9d20f73b8fc780e74d1ff7952762
Binary files /dev/null and b/dev/tests/api-functional/testsuite/Magento/Downloadable/Service/V1/DownloadableLink/_files/test_image.jpg differ
diff --git a/dev/tests/api-functional/testsuite/Magento/Downloadable/Service/V1/DownloadableSample/WriteServiceTest.php b/dev/tests/api-functional/testsuite/Magento/Downloadable/Service/V1/DownloadableSample/WriteServiceTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..542e1527f8eb58111d8768c4d16708827a6374f1
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Downloadable/Service/V1/DownloadableSample/WriteServiceTest.php
@@ -0,0 +1,509 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Downloadable\Service\V1\DownloadableSample;
+
+use Magento\Catalog\Model\Product;
+use Magento\Downloadable\Model\Sample;
+use Magento\TestFramework\Helper\Bootstrap;
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+class WriteServiceTest extends WebapiAbstract
+{
+    /**
+     * @var array
+     */
+    protected $createServiceInfo;
+
+    /**
+     * @var string
+     */
+    protected $testImagePath;
+
+    /**
+     * @var array
+     */
+    protected $updateServiceInfo;
+
+    /**
+     * @var array
+     */
+    protected $deleteServiceInfo;
+
+    protected function setUp()
+    {
+        $this->createServiceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/products/downloadable-product/downloadable-links/samples',
+                'httpMethod' => RestConfig::HTTP_METHOD_POST,
+            ],
+            'soap' => [
+                'service' => 'downloadableDownloadableSampleWriteServiceV1',
+                'serviceVersion' => 'V1',
+                'operation' => 'downloadableDownloadableSampleWriteServiceV1Create',
+            ],
+        ];
+
+        $this->updateServiceInfo = [
+            'rest' => [
+                'httpMethod' => RestConfig::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => 'downloadableDownloadableSampleWriteServiceV1',
+                'serviceVersion' => 'V1',
+                'operation' => 'downloadableDownloadableSampleWriteServiceV1Update',
+            ],
+        ];
+
+        $this->deleteServiceInfo = [
+            'rest' => [
+                'httpMethod' => RestConfig::HTTP_METHOD_DELETE,
+            ],
+            'soap' => [
+                'service' => 'downloadableDownloadableSampleWriteServiceV1',
+                'serviceVersion' => 'V1',
+                'operation' => 'downloadableDownloadableSampleWriteServiceV1Delete',
+            ],
+        ];
+
+        $this->testImagePath = __DIR__
+            . str_replace('/', DIRECTORY_SEPARATOR, '/../DownloadableLink/_files/test_image.jpg');
+    }
+
+    /**
+     * Retrieve product that was updated by test
+     *
+     * @param bool $isScopeGlobal if true product store ID will be set to 0
+     * @return Product
+     */
+    protected function getTargetProduct($isScopeGlobal = false)
+    {
+        $product = Bootstrap::getObjectManager()->get('Magento\Catalog\Model\ProductFactory')->create()->load(1);
+        if ($isScopeGlobal) {
+            $product->setStoreId(0);
+        }
+        return $product;
+    }
+
+    /**
+     * Retrieve product sample by its ID (or first sample if ID is not specified)
+     *
+     * @param Product $product
+     * @param int|null $sampleId
+     * @return Sample|null
+     */
+    protected function getTargetSample(Product $product, $sampleId = null)
+    {
+        /** @var $samples \Magento\Downloadable\Model\Resource\Sample\Collection */
+        $samples = $product->getTypeInstance()->getSamples($product);
+        if (!is_null($sampleId)) {
+            /* @var $sample \Magento\Downloadable\Model\Sample  */
+            foreach ($samples as $sample) {
+                if ($sample->getId() == $sampleId) {
+                    return $sample;
+                }
+            }
+            return null;
+        }
+        // return first sample
+        return $samples->getFirstItem();
+    }
+
+    /**
+     *  @magentoApiDataFixture Magento/Downloadable/_files/product_downloadable.php
+     */
+    public function testCreateUploadsProvidedFileContent()
+    {
+        $requestData = [
+            'isGlobalScopeContent' => true,
+            'productSku' => 'downloadable-product',
+            'sampleContent' => [
+                'title' => 'Title',
+                'sort_order' => 1,
+                'sample_file' => [
+                    'data' => base64_encode(file_get_contents($this->testImagePath)),
+                    'name' => 'image.jpg',
+                ],
+                'sample_type' => 'file',
+            ],
+        ];
+
+        $newSampleId = $this->_webApiCall($this->createServiceInfo, $requestData);
+        $globalScopeSample = $this->getTargetSample($this->getTargetProduct(true), $newSampleId);
+        $sample = $this->getTargetSample($this->getTargetProduct(), $newSampleId);
+        $this->assertNotNull($sample);
+        $this->assertEquals($requestData['sampleContent']['title'], $sample->getTitle());
+        $this->assertEquals($requestData['sampleContent']['title'], $globalScopeSample->getTitle());
+        $this->assertEquals($requestData['sampleContent']['sort_order'], $sample->getSortOrder());
+        $this->assertEquals($requestData['sampleContent']['sample_type'], $sample->getSampleType());
+        $this->assertStringEndsWith('.jpg', $sample->getSampleFile());
+        $this->assertNull($sample->getSampleUrl());
+    }
+
+    /**
+     *  @magentoApiDataFixture Magento/Downloadable/_files/product_downloadable.php
+     */
+    public function testCreateSavesTitleInStoreViewScope()
+    {
+        $requestData = [
+            'isGlobalScopeContent' => false,
+            'productSku' => 'downloadable-product',
+            'sampleContent' => [
+                'title' => 'Store View Title',
+                'sort_order' => 1,
+                'sample_url' => 'http://www.sample.example.com/',
+                'sample_type' => 'url',
+            ],
+        ];
+
+        $newSampleId = $this->_webApiCall($this->createServiceInfo, $requestData);
+        $sample = $this->getTargetSample($this->getTargetProduct(), $newSampleId);
+        $globalScopeSample = $this->getTargetSample($this->getTargetProduct(true), $newSampleId);
+        $this->assertNotNull($sample);
+        $this->assertEquals($requestData['sampleContent']['title'], $sample->getTitle());
+        $this->assertEquals($requestData['sampleContent']['sort_order'], $sample->getSortOrder());
+        $this->assertEquals($requestData['sampleContent']['sample_url'], $sample->getSampleUrl());
+        $this->assertEquals($requestData['sampleContent']['sample_type'], $sample->getSampleType());
+        $this->assertEmpty($globalScopeSample->getTitle());
+    }
+
+    /**
+     *  @magentoApiDataFixture Magento/Downloadable/_files/product_downloadable.php
+     */
+    public function testCreateSavesProvidedUrls()
+    {
+        $requestData = [
+            'isGlobalScopeContent' => false,
+            'productSku' => 'downloadable-product',
+            'sampleContent' => [
+                'title' => 'Sample with URL resource',
+                'sort_order' => 1,
+                'sample_url' => 'http://www.sample.example.com/',
+                'sample_type' => 'url',
+            ],
+        ];
+
+        $newSampleId = $this->_webApiCall($this->createServiceInfo, $requestData);
+        $sample = $this->getTargetSample($this->getTargetProduct(), $newSampleId);
+        $this->assertNotNull($sample);
+        $this->assertEquals($requestData['sampleContent']['title'], $sample->getTitle());
+        $this->assertEquals($requestData['sampleContent']['sort_order'], $sample->getSortOrder());
+        $this->assertEquals($requestData['sampleContent']['sample_type'], $sample->getSampleType());
+        $this->assertEquals($requestData['sampleContent']['sample_url'], $sample->getSampleUrl());
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Downloadable/_files/product_downloadable.php
+     * @expectedException \Exception
+     * @expectedExceptionMessage Invalid sample type.
+     */
+    public function testCreateThrowsExceptionIfSampleTypeIsNotSpecified()
+    {
+        $requestData = [
+            'isGlobalScopeContent' => false,
+            'productSku' => 'downloadable-product',
+            'sampleContent' => [
+                'title' => 'Sample with URL resource',
+                'sort_order' => 1,
+            ],
+        ];
+
+        $this->_webApiCall($this->createServiceInfo, $requestData);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Downloadable/_files/product_downloadable.php
+     * @expectedException \Exception
+     * @expectedExceptionMessage Provided content must be valid base64 encoded data.
+     */
+    public function testCreateThrowsExceptionIfSampleFileContentIsNotAValidBase64EncodedString()
+    {
+        $requestData = [
+            'isGlobalScopeContent' => false,
+            'productSku' => 'downloadable-product',
+            'sampleContent' => [
+                'title' => 'Sample Title',
+                'sort_order' => 1,
+                'sample_type' => 'file',
+                'sample_file' => [
+                    'data' => 'not_a_base64_encoded_content',
+                    'name' => 'image.jpg',
+                ],
+            ],
+        ];
+
+        $this->_webApiCall($this->createServiceInfo, $requestData);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Downloadable/_files/product_downloadable.php
+     * @expectedException \Exception
+     * @expectedExceptionMessage Provided file name contains forbidden characters.
+     */
+    public function testCreateThrowsExceptionIfSampleFileNameContainsForbiddenCharacters()
+    {
+        $requestData = [
+            'isGlobalScopeContent' => false,
+            'productSku' => 'downloadable-product',
+            'sampleContent' => [
+                'title' => 'Title',
+                'sort_order' => 15,
+                'sample_type' => 'file',
+                'sample_file' => [
+                    'data' => base64_encode(file_get_contents($this->testImagePath)),
+                    'name' => 'name/with|forbidden{characters',
+                ],
+            ],
+        ];
+
+        $this->_webApiCall($this->createServiceInfo, $requestData);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Downloadable/_files/product_downloadable.php
+     * @expectedException \Exception
+     * @expectedExceptionMessage Sample URL must have valid format.
+     */
+    public function testCreateThrowsExceptionIfSampleUrlHasWrongFormat()
+    {
+        $requestData = [
+            'isGlobalScopeContent' => false,
+            'productSku' => 'downloadable-product',
+            'sampleContent' => [
+                'title' => 'Sample Title',
+                'sort_order' => 1,
+                'sample_type' => 'url',
+                'sample_url' => 'http://example<.>com/',
+            ],
+        ];
+
+        $this->_webApiCall($this->createServiceInfo, $requestData);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Downloadable/_files/product_downloadable.php
+     * @expectedException \Exception
+     * @expectedExceptionMessage Sort order must be a positive integer.
+     * @dataProvider getInvalidSortOrder
+     */
+    public function testCreateThrowsExceptionIfSortOrderIsInvalid($sortOrder)
+    {
+        $requestData = [
+            'isGlobalScopeContent' => false,
+            'productSku' => 'downloadable-product',
+            'sampleContent' => [
+                'title' => 'Sample Title',
+                'sort_order' => $sortOrder,
+                'sample_type' => 'url',
+                'sample_url' => 'http://example.com/',
+            ],
+        ];
+        $this->_webApiCall($this->createServiceInfo, $requestData);
+    }
+
+    /**
+     * @return array
+     */
+    public function getInvalidSortOrder()
+    {
+        return [
+            [-1],
+        ];
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+     * @expectedException \Exception
+     * @expectedExceptionMessage Product type of the product must be 'downloadable'.
+     */
+    public function testCreateThrowsExceptionIfTargetProductTypeIsNotDownloadable()
+    {
+        $this->createServiceInfo['rest']['resourcePath']
+            = '/V1/products/simple/downloadable-links/samples';
+        $requestData = [
+            'isGlobalScopeContent' => false,
+            'productSku' => 'simple',
+            'sampleContent' => [
+                'title' => 'Sample Title',
+                'sort_order' => 50,
+                'sample_type' => 'url',
+                'sample_url' => 'http://example.com/',
+            ],
+        ];
+        $this->_webApiCall($this->createServiceInfo, $requestData);
+    }
+
+    /**
+     * @expectedException \Exception
+     * @expectedExceptionMessage Requested product doesn't exist
+     */
+    public function testCreateThrowsExceptionIfTargetProductDoesNotExist()
+    {
+        $this->createServiceInfo['rest']['resourcePath']
+            = '/V1/products/wrong-sku/downloadable-links/samples';
+        $requestData = [
+            'isGlobalScopeContent' => false,
+            'productSku' => 'wrong-sku',
+            'sampleContent' => [
+                'title' => 'Title',
+                'sort_order' => 15,
+                'sample_type' => 'url',
+                'sample_url' => 'http://example.com/',
+            ],
+        ];
+        $this->_webApiCall($this->createServiceInfo, $requestData);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Downloadable/_files/product_downloadable_with_files.php
+     */
+    public function testUpdate()
+    {
+        $sampleId = $this->getTargetSample($this->getTargetProduct())->getId();
+        $this->updateServiceInfo['rest']['resourcePath']
+            = "/V1/products/downloadable-product/downloadable-links/samples/{$sampleId}";
+        $requestData = [
+            'isGlobalScopeContent' => false,
+            'sampleId' => $sampleId,
+            'productSku' => 'downloadable-product',
+            'sampleContent' => [
+                'title' => 'Updated Title',
+                'sort_order' => 2,
+            ],
+        ];
+
+        $this->assertTrue($this->_webApiCall($this->updateServiceInfo, $requestData));
+        $sample = $this->getTargetSample($this->getTargetProduct(), $sampleId);
+        $this->assertNotNull($sample);
+        $this->assertEquals($requestData['sampleContent']['title'], $sample->getTitle());
+        $this->assertEquals($requestData['sampleContent']['sort_order'], $sample->getSortOrder());
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Downloadable/_files/product_downloadable_with_files.php
+     */
+    public function testUpdateSavesDataInGlobalScopeAndDoesNotAffectValuesStoredInStoreViewScope()
+    {
+        $originalSample = $this->getTargetSample($this->getTargetProduct());
+        $sampleId = $originalSample->getId();
+        $this->updateServiceInfo['rest']['resourcePath']
+            = "/V1/products/downloadable-product/downloadable-links/samples/{$sampleId}";
+        $requestData = [
+            'isGlobalScopeContent' => true,
+            'sampleId' => $sampleId,
+            'productSku' => 'downloadable-product',
+            'sampleContent' => [
+                'title' => 'Updated Title',
+                'sort_order' => 2,
+            ],
+        ];
+
+        $this->assertTrue($this->_webApiCall($this->updateServiceInfo, $requestData));
+        $sample = $this->getTargetSample($this->getTargetProduct(), $sampleId);
+        $globalScopeSample = $this->getTargetSample($this->getTargetProduct(true), $sampleId);
+        $this->assertNotNull($sample);
+        // Title was set on store view level in fixture so it must be the same
+        $this->assertEquals($originalSample->getTitle(), $sample->getTitle());
+        $this->assertEquals($requestData['sampleContent']['title'], $globalScopeSample->getTitle());
+        $this->assertEquals($requestData['sampleContent']['sort_order'], $sample->getSortOrder());
+    }
+
+    /**
+     * @expectedException \Exception
+     * @expectedExceptionMessage Requested product doesn't exist
+     */
+    public function testUpdateThrowsExceptionIfTargetProductDoesNotExist()
+    {
+        $this->updateServiceInfo['rest']['resourcePath'] = '/V1/products/wrong-sku/downloadable-links/samples/1';
+        $requestData = [
+            'isGlobalScopeContent' => true,
+            'sampleId' => 1,
+            'productSku' => 'wrong-sku',
+            'sampleContent' => [
+                'title' => 'Updated Title',
+                'sort_order' => 2,
+            ],
+        ];
+        $this->_webApiCall($this->updateServiceInfo, $requestData);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Downloadable/_files/product_downloadable_with_files.php
+     * @expectedException \Exception
+     * @expectedExceptionMessage There is no downloadable sample with provided ID.
+     */
+    public function testUpdateThrowsExceptionIfThereIsNoDownloadableSampleWithGivenId()
+    {
+        $sampleId = 9999;
+        $this->updateServiceInfo['rest']['resourcePath']
+            = "/V1/products/downloadable-product/downloadable-links/samples/{$sampleId}";
+        $requestData = [
+            'isGlobalScopeContent' => true,
+            'sampleId' => 9999,
+            'productSku' => 'downloadable-product',
+            'sampleContent' => [
+                'title' => 'Title',
+                'sort_order' => 2,
+            ],
+        ];
+
+        $this->_webApiCall($this->updateServiceInfo, $requestData);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Downloadable/_files/product_downloadable_with_files.php
+     * @expectedException \Exception
+     * @expectedExceptionMessage Sort order must be a positive integer.
+     * @dataProvider getInvalidSortOrder
+     */
+    public function testUpdateThrowsExceptionIfSortOrderIsInvalid($sortOrder)
+    {
+        $sampleId = $this->getTargetSample($this->getTargetProduct())->getId();
+        $this->updateServiceInfo['rest']['resourcePath']
+            = "/V1/products/downloadable-product/downloadable-links/samples/{$sampleId}";
+        $requestData = [
+            'isGlobalScopeContent' => false,
+            'sampleId' => $sampleId,
+            'productSku' => 'downloadable-product',
+            'sampleContent' => [
+                'title' => 'Updated Sample Title',
+                'sort_order' => $sortOrder,
+            ],
+        ];
+        $this->_webApiCall($this->updateServiceInfo, $requestData);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Downloadable/_files/product_downloadable_with_files.php
+     */
+    public function testDelete()
+    {
+        $sampleId = $this->getTargetSample($this->getTargetProduct())->getId();
+        $this->deleteServiceInfo['rest']['resourcePath'] = "/V1/products/downloadable-links/samples/{$sampleId}";
+        $requestData = [
+            'sampleId' => $sampleId,
+        ];
+
+        $this->assertTrue($this->_webApiCall($this->deleteServiceInfo, $requestData));
+        $sample = $this->getTargetSample($this->getTargetProduct(), $sampleId);
+        $this->assertNull($sample);
+    }
+
+    /**
+     * @expectedException \Exception
+     * @expectedExceptionMessage There is no downloadable sample with provided ID.
+     */
+    public function testDeleteThrowsExceptionIfThereIsNoDownloadableSampleWithGivenId()
+    {
+        $sampleId = 9999;
+        $this->deleteServiceInfo['rest']['resourcePath']
+            = "/V1/products/downloadable-links/samples/{$sampleId}";
+        $requestData = [
+            'sampleId' => $sampleId,
+        ];
+
+        $this->_webApiCall($this->deleteServiceInfo, $requestData);
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Eav/Api/AttributeSetManagementTest.php b/dev/tests/api-functional/testsuite/Magento/Eav/Api/AttributeSetManagementTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..83166269a7c332013e98eb9bbaf6f122fd3dd07d
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Eav/Api/AttributeSetManagementTest.php
@@ -0,0 +1,235 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Eav\Api;
+
+use Magento\TestFramework\Helper\Bootstrap;
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Exception as HTTPExceptionCodes;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+class AttributeSetManagementTest extends WebapiAbstract
+{
+    /**
+     * @var array
+     */
+    private $createServiceInfo;
+
+    protected function setUp()
+    {
+        $this->createServiceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/eav/attribute-sets',
+                'httpMethod' => RestConfig::HTTP_METHOD_POST,
+            ],
+            'soap' => [
+                'service' => 'eavAttributeSetManagementV1',
+                'serviceVersion' => 'V1',
+                'operation' => 'eavAttributeSetManagementV1Create',
+            ],
+        ];
+    }
+
+    public function testCreate()
+    {
+        $entityTypeCode = 'catalog_product';
+        $entityType = $this->getEntityTypeByCode($entityTypeCode);
+        $attributeSetName = 'new_attribute_set';
+
+        $arguments = [
+            'entityTypeCode' => $entityTypeCode,
+            'attributeSet' => [
+                'attribute_set_name' => $attributeSetName,
+                'sort_order' => 500,
+            ],
+            'skeletonId' => $entityType->getDefaultAttributeSetId(),
+        ];
+        $result = $this->_webApiCall($this->createServiceInfo, $arguments);
+        $this->assertNotNull($result);
+        $attributeSet = $this->getAttributeSetByName($attributeSetName);
+        $this->assertNotNull($attributeSet);
+        $this->assertEquals($attributeSet->getId(), $result['attribute_set_id']);
+        $this->assertEquals($attributeSet->getAttributeSetName(), $result['attribute_set_name']);
+        $this->assertEquals($attributeSet->getEntityTypeId(), $result['entity_type_id']);
+        $this->assertEquals($attributeSet->getEntityTypeId(), $entityType->getId());
+        $this->assertEquals($attributeSet->getSortOrder(), $result['sort_order']);
+        $this->assertEquals($attributeSet->getSortOrder(), 500);
+
+        // Clean up database
+        $attributeSet->delete();
+    }
+
+    /**
+     * @expectedException \Exception
+     * @expectedExceptionMessage Invalid value
+     */
+    public function testCreateThrowsExceptionIfGivenAttributeSetAlreadyHasId()
+    {
+        $entityTypeCode = 'catalog_product';
+        $entityType = $this->getEntityTypeByCode($entityTypeCode);
+        $attributeSetName = 'new_attribute_set';
+
+        $arguments = [
+            'entityTypeCode' => $entityTypeCode,
+            'attributeSet' => [
+                'attribute_set_id' => 1,
+                'attribute_set_name' => $attributeSetName,
+                'sort_order' => 100,
+            ],
+            'skeletonId' => $entityType->getDefaultAttributeSetId(),
+        ];
+        $this->_webApiCall($this->createServiceInfo, $arguments);
+    }
+
+    /**
+     * @expectedException \Exception
+     * @expectedExceptionMessage Invalid value
+     */
+    public function testCreateThrowsExceptionIfGivenSkeletonIdIsInvalid()
+    {
+        $entityTypeCode = 'catalog_product';
+        $attributeSetName = 'new_attribute_set';
+
+        $arguments = [
+            'entityTypeCode' => $entityTypeCode,
+            'attributeSet' => [
+                'attribute_set_name' => $attributeSetName,
+                'sort_order' => 200,
+            ],
+            'skeletonId' => 0,
+        ];
+        $this->_webApiCall($this->createServiceInfo, $arguments);
+    }
+
+    /**
+     * @expectedException \Exception
+     * @expectedExceptionMessage No such entity
+     */
+    public function testCreateThrowsExceptionIfGivenSkeletonAttributeSetDoesNotExist()
+    {
+        $attributeSetName = 'new_attribute_set';
+        $entityTypeCode = 'catalog_product';
+
+        $arguments = [
+            'entityTypeCode' => $entityTypeCode,
+            'attributeSet' => [
+                'attribute_set_name' => $attributeSetName,
+                'sort_order' => 300,
+            ],
+            'skeletonId' => 9999,
+        ];
+        $this->_webApiCall($this->createServiceInfo, $arguments);
+    }
+
+    /**
+     * @expectedException \Exception
+     * @expectedExceptionMessage Invalid entity_type specified: invalid_entity_type
+     */
+    public function testCreateThrowsExceptionIfGivenEntityTypeDoesNotExist()
+    {
+        $entityTypeCode = 'catalog_product';
+        $entityType = $this->getEntityTypeByCode($entityTypeCode);
+        $attributeSetName = 'new_attribute_set';
+
+        $arguments = [
+            'entityTypeCode' => 'invalid_entity_type',
+            'attributeSet' => [
+                'attribute_set_name' => $attributeSetName,
+                'sort_order' => 400,
+            ],
+            'skeletonId' => $entityType->getDefaultAttributeSetId(),
+        ];
+        $this->_webApiCall($this->createServiceInfo, $arguments);
+    }
+
+    /**
+     * @expectedException \Exception
+     * @expectedExceptionMessage Attribute set name is empty.
+     */
+    public function testCreateThrowsExceptionIfAttributeSetNameIsEmpty()
+    {
+        $entityTypeCode = 'catalog_product';
+        $entityType = $this->getEntityTypeByCode($entityTypeCode);
+        $attributeSetName = '';
+
+        $arguments = [
+            'entityTypeCode' => $entityTypeCode,
+            'attributeSet' => [
+                'attribute_set_name' => $attributeSetName,
+                'sort_order' => 500,
+            ],
+            'skeletonId' => $entityType->getDefaultAttributeSetId(),
+        ];
+        $this->_webApiCall($this->createServiceInfo, $arguments);
+    }
+
+    public function testCreateThrowsExceptionIfAttributeSetWithGivenNameAlreadyExists()
+    {
+        $entityTypeCode = 'catalog_product';
+        $entityType = $this->getEntityTypeByCode($entityTypeCode);
+        $attributeSetName = 'Default';
+        $expectedMessage = 'An attribute set with the "Default" name already exists.';
+
+        $arguments = [
+            'entityTypeCode' => $entityTypeCode,
+            'attributeSet' => [
+                'attribute_set_name' => $attributeSetName,
+                'sort_order' => 550,
+            ],
+            'skeletonId' => $entityType->getDefaultAttributeSetId(),
+        ];
+
+        try {
+            $this->_webApiCall($this->createServiceInfo, $arguments);
+            $this->fail("Expected exception");
+        } catch (\SoapFault $e) {
+            $this->assertContains(
+                $expectedMessage,
+                $e->getMessage(),
+                "SoapFault does not contain expected message."
+            );
+        } catch (\Exception $e) {
+            $errorObj = $this->processRestExceptionResult($e);
+            $this->assertEquals(
+                $expectedMessage,
+                $errorObj['message']
+            );
+            $this->assertEquals(HTTPExceptionCodes::HTTP_BAD_REQUEST, $e->getCode());
+        }
+    }
+
+    /**
+     * Retrieve attribute set based on given name.
+     * This utility methods assumes that there is only one attribute set with given name,
+     *
+     * @param string $attributeSetName
+     * @return \Magento\Eav\Model\Entity\Attribute\Set|null
+     */
+    protected function getAttributeSetByName($attributeSetName)
+    {
+        $objectManager = Bootstrap::getObjectManager();
+        /** @var \Magento\Eav\Model\Entity\Attribute\Set $attributeSet */
+        $attributeSet = $objectManager->create('Magento\Eav\Model\Entity\Attribute\Set')
+            ->load($attributeSetName, 'attribute_set_name');
+        if ($attributeSet->getId() === null) {
+            return null;
+        }
+        return $attributeSet;
+    }
+
+    /**
+     * Retrieve entity type based on given code.
+     *
+     * @param string $entityTypeCode
+     * @return \Magento\Eav\Model\Entity\Type|null
+     */
+    protected function getEntityTypeByCode($entityTypeCode)
+    {
+        $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+        /** @var \Magento\Eav\Model\Entity\Type $entityType */
+        $entityType = $objectManager->create('Magento\Eav\Model\Config')
+            ->getEntityType($entityTypeCode);
+        return $entityType;
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Eav/Api/AttributeSetRepositoryTest.php b/dev/tests/api-functional/testsuite/Magento/Eav/Api/AttributeSetRepositoryTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..2a7024a290a2937bd165d246c01ff0d578027906
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Eav/Api/AttributeSetRepositoryTest.php
@@ -0,0 +1,260 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Eav\Api;
+
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+class AttributeSetRepositoryTest extends WebapiAbstract
+{
+    /**
+     * @magentoApiDataFixture Magento/Eav/_files/empty_attribute_set.php
+     */
+    public function testGet()
+    {
+        $attributeSetName = 'empty_attribute_set';
+        $attributeSet = $this->getAttributeSetByName($attributeSetName);
+        $attributeSetId = $attributeSet->getId();
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/eav/attribute-sets/' . $attributeSetId,
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => 'eavAttributeSetRepositoryV1',
+                'serviceVersion' => 'V1',
+                'operation' => 'eavAttributeSetRepositoryV1Get',
+            ],
+        ];
+        $arguments = [
+            'attributeSetId' => $attributeSetId,
+        ];
+        $result = $this->_webApiCall($serviceInfo, $arguments);
+        $this->assertNotNull($result);
+        $this->assertEquals($attributeSet->getId(), $result['attribute_set_id']);
+        $this->assertEquals($attributeSet->getAttributeSetName(), $result['attribute_set_name']);
+        $this->assertEquals($attributeSet->getEntityTypeId(), $result['entity_type_id']);
+        $this->assertEquals($attributeSet->getSortOrder(), $result['sort_order']);
+    }
+
+    /**
+     * @expectedException \Exception
+     */
+    public function testGetThrowsExceptionIfRequestedAttributeSetDoesNotExist()
+    {
+        $attributeSetId = 9999;
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/eav/attribute-sets/' . $attributeSetId,
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => 'eavAttributeSetRepositoryV1',
+                'serviceVersion' => 'V1',
+                'operation' => 'eavAttributeSetRepositoryV1Get',
+            ],
+        ];
+        $arguments = [
+            'attributeSetId' => $attributeSetId,
+        ];
+        $this->_webApiCall($serviceInfo, $arguments);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Eav/_files/empty_attribute_set.php
+     */
+    public function testSave()
+    {
+        $attributeSetName = 'empty_attribute_set';
+        $attributeSet = $this->getAttributeSetByName($attributeSetName);
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/eav/attribute-sets',
+                'httpMethod' => RestConfig::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => 'eavAttributeSetRepositoryV1',
+                'serviceVersion' => 'V1',
+                'operation' => 'eavAttributeSetRepositoryV1Save',
+            ],
+        ];
+
+        $updatedSortOrder = $attributeSet->getSortOrder() + 200;
+
+        $arguments = [
+            'attributeSet' => [
+                'attribute_set_id' => $attributeSet->getId(),
+                // name is the same, because it is used by fixture rollback script
+                'attribute_set_name' => $attributeSet->getAttributeSetName(),
+                'entity_type_id' => $attributeSet->getEntityTypeId(),
+                'sort_order' => $updatedSortOrder,
+            ],
+        ];
+        $result = $this->_webApiCall($serviceInfo, $arguments);
+        $this->assertNotNull($result);
+        // Reload attribute set data
+        $attributeSet = $this->getAttributeSetByName($attributeSetName);
+        $this->assertEquals($attributeSet->getAttributeSetId(), $result['attribute_set_id']);
+        $this->assertEquals($attributeSet->getAttributeSetName(), $result['attribute_set_name']);
+        $this->assertEquals($attributeSet->getEntityTypeId(), $result['entity_type_id']);
+        $this->assertEquals($updatedSortOrder, $result['sort_order']);
+        $this->assertEquals($attributeSet->getSortOrder(), $result['sort_order']);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Eav/_files/empty_attribute_set.php
+     */
+    public function testDeleteById()
+    {
+        $attributeSetName = 'empty_attribute_set';
+        $attributeSet = $this->getAttributeSetByName($attributeSetName);
+        $attributeSetId = $attributeSet->getId();
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/eav/attribute-sets/' . $attributeSetId,
+                'httpMethod' => RestConfig::HTTP_METHOD_DELETE,
+            ],
+            'soap' => [
+                'service' => 'eavAttributeSetRepositoryV1',
+                'serviceVersion' => 'V1',
+                'operation' => 'eavAttributeSetRepositoryV1DeleteById',
+            ],
+        ];
+
+        $arguments = [
+            'attributeSetId' => $attributeSetId,
+        ];
+        $this->assertTrue($this->_webApiCall($serviceInfo, $arguments));
+        $this->assertNull($this->getAttributeSetByName($attributeSetName));
+    }
+
+    /**
+     * @expectedException \Exception
+     * @expectedExceptionMessage Default attribute set can not be deleted
+     */
+    public function testDeleteByIdDefaultAttributeSet()
+    {
+        $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+        /** @var \Magento\Eav\Model\Config */
+        $eavConfig = $objectManager->create('Magento\Eav\Model\Config');
+
+        $defaultAttributeSetId = $eavConfig
+            ->getEntityType(\Magento\Catalog\Api\Data\ProductAttributeInterface::ENTITY_TYPE_CODE)
+            ->getDefaultAttributeSetId();
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/eav/attribute-sets/' . $defaultAttributeSetId,
+                'httpMethod' => RestConfig::HTTP_METHOD_DELETE,
+            ],
+            'soap' => [
+                'service' => 'eavAttributeSetRepositoryV1',
+                'serviceVersion' => 'V1',
+                'operation' => 'eavAttributeSetRepositoryV1DeleteById',
+            ],
+        ];
+
+        $arguments = [
+            'attributeSetId' => $defaultAttributeSetId,
+        ];
+        $this->assertTrue($this->_webApiCall($serviceInfo, $arguments));
+    }
+
+    /**
+     * @expectedException \Exception
+     */
+    public function testDeleteByIdThrowsExceptionIfRequestedAttributeSetDoesNotExist()
+    {
+        $attributeSetId = 9999;
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/eav/attribute-sets/' . $attributeSetId,
+                'httpMethod' => RestConfig::HTTP_METHOD_DELETE,
+            ],
+            'soap' => [
+                'service' => 'eavAttributeSetRepositoryV1',
+                'serviceVersion' => 'V1',
+                'operation' => 'eavAttributeSetRepositoryV1DeleteById',
+            ],
+        ];
+
+        $arguments = [
+            'attributeSetId' => $attributeSetId,
+        ];
+        $this->_webApiCall($serviceInfo, $arguments);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Eav/_files/empty_attribute_set.php
+     */
+    public function testGetList()
+    {
+        $searchCriteria = [
+            'searchCriteria' => [
+                'filter_groups' => [
+                    [
+                        'filters' => [
+                            [
+                                'field' => 'entity_type_code',
+                                'value' => 'catalog_product',
+                                'condition_type' => 'eq',
+                            ],
+                        ],
+                    ],
+                ],
+                'current_page' => 1,
+                'page_size' => 2,
+            ],
+        ];
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/eav/attribute-sets/list',
+                'httpMethod' => RestConfig::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => 'eavAttributeSetRepositoryV1',
+                'serviceVersion' => 'V1',
+                'operation' => 'eavAttributeSetRepositoryV1GetList',
+            ],
+        ];
+
+        $response = $this->_webApiCall($serviceInfo, $searchCriteria);
+
+        $this->assertArrayHasKey('search_criteria', $response);
+        $this->assertArrayHasKey('total_count', $response);
+        $this->assertArrayHasKey('items', $response);
+
+        $this->assertEquals($searchCriteria['searchCriteria'], $response['search_criteria']);
+        $this->assertTrue($response['total_count'] > 0);
+        $this->assertTrue(count($response['items']) > 0);
+
+        $this->assertNotNull($response['items'][0]['attribute_set_id']);
+        $this->assertNotNull($response['items'][0]['attribute_set_name']);
+    }
+
+    /**
+     * Retrieve attribute set based on given name.
+     * This utility methods assumes that there is only one attribute set with given name,
+     *
+     * @param string $attributeSetName
+     * @return \Magento\Eav\Model\Entity\Attribute\Set|null
+     */
+    protected function getAttributeSetByName($attributeSetName)
+    {
+        $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+        /** @var \Magento\Eav\Model\Entity\Attribute\Set $attributeSet */
+        $attributeSet = $objectManager->create('Magento\Eav\Model\Entity\Attribute\Set')
+            ->load($attributeSetName, 'attribute_set_name');
+        if ($attributeSet->getId() === null) {
+            return null;
+        }
+        return $attributeSet;
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Framework/Stdlib/CookieManagerTest.php b/dev/tests/api-functional/testsuite/Magento/Framework/Stdlib/CookieManagerTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..1c90fcc223e698969319726ab87c9dec637e00d8
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Framework/Stdlib/CookieManagerTest.php
@@ -0,0 +1,164 @@
+<?php
+namespace Magento\Framework\Stdlib;
+
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+use Magento\TestFramework\Helper\Bootstrap;
+use Magento\TestFramework\TestCase\Webapi\Curl;
+
+/**
+ * End to end test of the Cookie Manager, using curl.
+ *
+ * Uses controllers in TestModule1 to set and delete cookies and verify 'Set-Cookie' headers that come back.
+ */
+class CookieManagerTest extends \Magento\TestFramework\TestCase\WebapiAbstract
+{
+    private $cookieTesterUrl = 'testmoduleone/CookieTester';
+
+    /** @var Curl */
+    protected $curlClient;
+
+    public function setUp()
+    {
+        $objectManager = Bootstrap::getObjectManager();
+        $this->config = $objectManager->get('Magento\Webapi\Model\Config');
+        $this->curlClient = $objectManager->get('Magento\TestFramework\TestCase\Webapi\Curl');
+    }
+
+    /**
+     * Set a sensitive Cookie and delete it.
+     *
+     */
+    public function testSensitiveCookie()
+    {
+        $url = $this->cookieTesterUrl . '/SetSensitiveCookie';
+        $cookieParams =
+            [
+                'cookie_name' => 'test-sensitive-cookie',
+                'cookie_value' => 'test-sensitive-cookie-value',
+            ];
+        $response = $this->curlClient->get($url, $cookieParams);
+
+        $cookie = $this->findCookie($cookieParams['cookie_name'], $response['cookies']);
+        $this->assertNotNull($cookie);
+        $this->assertEquals($cookieParams['cookie_name'], $cookie['name']);
+        $this->assertEquals($cookieParams['cookie_value'], $cookie['value']);
+        $this->assertFalse(isset($cookie['domain']));
+        $this->assertFalse(isset($cookie['path']));
+        $this->assertEquals('true', $cookie['httponly']);
+        $this->assertFalse(isset($cookie['secure']));
+        $this->assertFalse(isset($cookie['max-age']));
+    }
+
+    /**
+     * Set a public cookie
+     *
+     */
+    public function testPublicCookieNameValue()
+    {
+        $url = $this->cookieTesterUrl . '/SetPublicCookie';
+        $cookieParams =
+            [
+                'cookie_name' => 'test-cookie',
+                'cookie_value' => 'test-cookie-value',
+            ];
+
+        $response = $this->curlClient->get($url, $cookieParams);
+
+        $cookie = $this->findCookie($cookieParams['cookie_name'], $response['cookies']);
+        $this->assertNotNull($cookie);
+        $this->assertEquals($cookieParams['cookie_name'], $cookie['name']);
+        $this->assertEquals($cookieParams['cookie_value'], $cookie['value']);
+        $this->assertFalse(isset($cookie['domain']));
+        $this->assertFalse(isset($cookie['path']));
+        $this->assertFalse(isset($cookie['httponly']));
+        $this->assertFalse(isset($cookie['secure']));
+        $this->assertFalse(isset($cookie['max-age']));
+    }
+
+    /**
+     * Set a public cookie
+     *
+     */
+    public function testPublicCookieAll()
+    {
+        $url = $this->cookieTesterUrl . '/SetPublicCookie';
+        $cookieParams =
+            [
+                'cookie_name' => 'test-cookie',
+                'cookie_value' => 'test-cookie-value',
+                'cookie_domain' => 'www.example.com',
+                'cookie_path' => '/test/path',
+                'cookie_httponly' => 'true',
+                'cookie_secure' => 'true',
+                'cookie_duration' => '600',
+            ];
+
+        $response = $this->curlClient->get($url, $cookieParams);
+
+        $cookie = $this->findCookie($cookieParams['cookie_name'], $response['cookies']);
+        $this->assertNotNull($cookie);
+        $this->assertEquals($cookieParams['cookie_name'], $cookie['name']);
+        $this->assertEquals($cookieParams['cookie_value'], $cookie['value']);
+        $this->assertEquals($cookieParams['cookie_domain'], $cookie['domain']);
+        $this->assertEquals($cookieParams['cookie_path'], $cookie['path']);
+        $this->assertEquals($cookieParams['cookie_httponly'], $cookie['httponly']);
+        $this->assertEquals($cookieParams['cookie_secure'], $cookie['secure']);
+        if (isset($cookie['max-age'])) {
+            $this->assertEquals($cookieParams['cookie_duration'], $cookie['max-age']);
+        }
+        $this->assertTrue(isset($cookie['expires']));
+    }
+
+    /**
+     * Delete a cookie
+     *
+     */
+    public function testDeleteCookie()
+    {
+        $url = $this->cookieTesterUrl . '/DeleteCookie';
+        $cookieParams =
+            [
+                'cookie_name' => 'test-cookie',
+                'cookie_value' => 'test-cookie-value',
+            ];
+
+        $response = $this->curlClient->get(
+            $url,
+            $cookieParams,
+            ['Cookie: test-cookie=test-cookie-value; anothertestcookie=anothertestcookievalue']
+        );
+
+        $cookie = $this->findCookie($cookieParams['cookie_name'], $response['cookies']);
+        $this->assertNotNull($cookie);
+        $this->assertEquals($cookieParams['cookie_name'], $cookie['name']);
+        $this->assertEquals('deleted', $cookie['value']);
+        $this->assertFalse(isset($cookie['domain']));
+        $this->assertFalse(isset($cookie['path']));
+        $this->assertFalse(isset($cookie['httponly']));
+        $this->assertFalse(isset($cookie['secure']));
+        if (isset($cookie['max-age'])) {
+            $this->assertEquals(0, $cookie['max-age']);
+        }
+        $this->assertEquals('Thu, 01-Jan-1970 00:00:01 GMT', $cookie['expires']);
+    }
+
+    /**
+     * Find cookie with given name in the list of cookies
+     *
+     * @param string $cookieName
+     * @param array $cookies
+     * @return $cookie|null
+     */
+    private function findCookie($cookieName, $cookies)
+    {
+        foreach ($cookies as $cookieIndex => $cookie) {
+            if ($cookie['name'] === $cookieName) {
+                return $cookie;
+            }
+        }
+        return null;
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/GiftMessage/Service/V1/ReadServiceTest.php b/dev/tests/api-functional/testsuite/Magento/GiftMessage/Service/V1/ReadServiceTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..a287d6c8e5106cac474a9a80492f1c02808f4aeb
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/GiftMessage/Service/V1/ReadServiceTest.php
@@ -0,0 +1,101 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+namespace Magento\GiftMessage\Service\V1;
+
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+class ReadServiceTest extends WebapiAbstract
+{
+    const SERVICE_VERSION = 'V1';
+    const SERVICE_NAME = 'giftMessageReadServiceV1';
+    const RESOURCE_PATH = '/V1/carts/';
+
+    /**
+     * @var \Magento\TestFramework\ObjectManager
+     */
+    protected $objectManager;
+
+    protected function setUp()
+    {
+        $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/GiftMessage/_files/quote_with_message.php
+     */
+    public function testGet()
+    {
+        /** @var \Magento\Sales\Model\Quote $quote */
+        $quote = $this->objectManager->create('Magento\Sales\Model\Quote');
+        $quote->load('message_order_21', 'reserved_order_id');
+
+        $cartId = $quote->getId();
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . $cartId . '/gift-message',
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Get',
+            ],
+        ];
+
+        $expectedMessage = [
+            'recipient' => 'Mercutio',
+            'sender' => 'Romeo',
+            'message' => 'I thought all for the best.',
+        ];
+
+        $requestData = ["cartId" => $cartId];
+        $resultMessage = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertCount(5, $resultMessage);
+        unset($resultMessage['gift_message_id']);
+        unset($resultMessage['customer_id']);
+        $this->assertEquals($expectedMessage, $resultMessage);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/GiftMessage/_files/quote_with_item_message.php
+     */
+    public function testGetItemMessage()
+    {
+        /** @var \Magento\Sales\Model\Quote $quote */
+        $quote = $this->objectManager->create('Magento\Sales\Model\Quote');
+        $quote->load('test_order_item_with_message', 'reserved_order_id');
+        $product = $this->objectManager->create('Magento\Catalog\Model\Product');
+        $product->load($product->getIdBySku('simple_with_message'));
+        $itemId = $quote->getItemByProduct($product)->getId();
+        /** @var  \Magento\Catalog\Model\Product $product */
+        $cartId = $quote->getId();
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . $cartId . '/gift-message/' . $itemId,
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'getItemMessage',
+            ],
+        ];
+
+        $expectedMessage = [
+            'recipient' => 'Jane Roe',
+            'sender' => 'John Doe',
+            'message' => 'Gift Message Text',
+        ];
+
+        $requestData = ["cartId" => $cartId, "itemId" => $itemId];
+        $resultMessage = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertCount(5, $resultMessage);
+        unset($resultMessage['gift_message_id']);
+        unset($resultMessage['customer_id']);
+        $this->assertEquals($expectedMessage, $resultMessage);
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/GiftMessage/Service/V1/WriteServiceTest.php b/dev/tests/api-functional/testsuite/Magento/GiftMessage/Service/V1/WriteServiceTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..79185a68dca2b8618eaaa3e04e33ee82781f82e1
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/GiftMessage/Service/V1/WriteServiceTest.php
@@ -0,0 +1,115 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+namespace Magento\GiftMessage\Service\V1;
+
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+class WriteServiceTest extends WebapiAbstract
+{
+    const SERVICE_VERSION = 'V1';
+    const SERVICE_NAME = 'giftMessageWriteServiceV1';
+    const RESOURCE_PATH = '/V1/carts/';
+
+    /**
+     * @var \Magento\TestFramework\ObjectManager
+     */
+    protected $objectManager;
+
+    protected function setUp()
+    {
+        $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/GiftMessage/_files/quote_with_item_message.php
+     */
+    public function testSetForQuote()
+    {
+        // sales/gift_options/allow_order must be set to 1 in system configuration
+        // @todo remove next statement when \Magento\TestFramework\TestCase\WebapiAbstract::_updateAppConfig is fixed
+        $this->markTestIncomplete('This test relies on system configuration state.');
+        /** @var \Magento\Sales\Model\Quote $quote */
+        $quote = $this->objectManager->create('Magento\Sales\Model\Quote');
+        $quote->load('test_order_item_with_message', 'reserved_order_id');
+
+        $cartId = $quote->getId();
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . $cartId . '/gift-message',
+                'httpMethod' => RestConfig::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'SetForQuote',
+            ],
+        ];
+
+        $requestData = [
+            'cartId' => $cartId,
+            'giftMessage' => [
+                'recipient' => 'John Doe',
+                'sender' => 'Jane Roe',
+                'message' => 'Gift Message Text New',
+            ],
+        ];
+        $this->assertTrue($this->_webApiCall($serviceInfo, $requestData));
+        $quote->load('test_order_item_with_message', 'reserved_order_id');
+        $quote->getGiftMessageId();
+        /** @var  \Magento\GiftMessage\Model\Message $message */
+        $message = $this->objectManager->create('Magento\GiftMessage\Model\Message')->load($quote->getGiftMessageId());
+        $this->assertEquals('John Doe', $message->getRecipient());
+        $this->assertEquals('Jane Roe', $message->getSender());
+        $this->assertEquals('Gift Message Text New', $message->getMessage());
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/GiftMessage/_files/quote_with_item_message.php
+     */
+    public function testSetForItem()
+    {
+        // sales/gift_options/allow_items must be set to 1 in system configuration
+        // @todo remove next statement when \Magento\TestFramework\TestCase\WebapiAbstract::_updateAppConfig is fixed
+        $this->markTestIncomplete('This test relies on system configuration state.');
+        /** @var \Magento\Sales\Model\Quote $quote */
+        $quote = $this->objectManager->create('Magento\Sales\Model\Quote');
+        $quote->load('test_order_item_with_message', 'reserved_order_id');
+        $cartId = $quote->getId();
+        $product = $this->objectManager->create('Magento\Catalog\Model\Product');
+        $product->load($product->getIdBySku('simple_with_message'));
+        $itemId = $quote->getItemByProduct($product)->getId();
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . $cartId . '/gift-message/' .  $itemId,
+                'httpMethod' => RestConfig::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'SetForItem',
+            ],
+        ];
+
+        $requestData = [
+            'cartId' => $cartId,
+            'itemId' => $itemId,
+            'giftMessage' => [
+                'recipient' => 'John Doe',
+                'sender' => 'Jane Roe',
+                'message' => 'Gift Message Text New',
+            ],
+        ];
+        $this->assertTrue($this->_webApiCall($serviceInfo, $requestData));
+//        $quote->load('test_order_item_with_message', 'reserved_order_id');
+        $messageId = $quote->getItemByProduct($product)->getGiftMessageId();
+        /** @var  \Magento\GiftMessage\Model\Message $message */
+        $message = $this->objectManager->create('Magento\GiftMessage\Model\Message')->load($messageId);
+        $this->assertEquals('John Doe', $message->getRecipient());
+        $this->assertEquals('Jane Roe', $message->getSender());
+        $this->assertEquals('Gift Message Text New', $message->getMessage());
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/GroupedProduct/Api/ProductLinkManagementTest.php b/dev/tests/api-functional/testsuite/Magento/GroupedProduct/Api/ProductLinkManagementTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..4a6ce3ac19f6f397cc24757950279022163d075e
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/GroupedProduct/Api/ProductLinkManagementTest.php
@@ -0,0 +1,72 @@
+<?php
+/**
+ *
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\GroupedProduct\Api;
+
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+class ProductLinkManagementTest extends \Magento\TestFramework\TestCase\WebapiAbstract
+{
+    const SERVICE_NAME = 'catalogProductLinkManagementV1';
+    const SERVICE_VERSION = 'V1';
+    const RESOURCE_PATH = '/V1/products/';
+
+    /**
+     * @magentoApiDataFixture Magento/GroupedProduct/_files/product_grouped.php
+     */
+    public function testGetLinkedItemsByType()
+    {
+        $productSku = 'grouped-product';
+        $linkType = 'associated';
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . $productSku . '/links/' . $linkType,
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'GetLinkedItemsByType',
+            ],
+        ];
+
+        $actual = $this->_webApiCall($serviceInfo, ['productSku' => $productSku, 'type' => $linkType]);
+
+        $expected = [
+            [
+                'product_sku' => 'grouped-product',
+                'link_type' => 'associated',
+                'linked_product_sku' => 'simple-1',
+                'linked_product_type' => 'simple',
+                'position' => 1,
+            ],
+            [
+                'product_sku' => 'grouped-product',
+                'link_type' => 'associated',
+                'linked_product_sku' => 'virtual-product',
+                'linked_product_type' => 'virtual',
+                'position' => 2,
+            ],
+        ];
+
+        if (TESTS_WEB_API_ADAPTER == self::ADAPTER_SOAP) {
+            array_walk(
+                $expected,
+                function (&$item) {
+                    $item['custom_attributes'] = [['attribute_code' => 'qty', 'value' => 1.0000]];
+                }
+            );
+        } else {
+            array_walk(
+                $expected,
+                function (&$item) {
+                    $item['custom_attributes'] = [['attribute_code' => 'qty', 'value' => 1.0000]];
+                }
+            );
+        }
+        $this->assertEquals($expected, $actual);
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/GroupedProduct/Api/ProductLinkRepositoryTest.php b/dev/tests/api-functional/testsuite/Magento/GroupedProduct/Api/ProductLinkRepositoryTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..80e1ca7370759d0df8adc3aaf2a5defd3b7cc6f6
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/GroupedProduct/Api/ProductLinkRepositoryTest.php
@@ -0,0 +1,66 @@
+<?php
+/**
+ *
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\GroupedProduct\Api;
+
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+use Magento\TestFramework\Helper\Bootstrap;
+
+class ProductLinkRepositoryTest extends \Magento\TestFramework\TestCase\WebapiAbstract
+{
+    const SERVICE_NAME = 'catalogProductLinkRepositoryV1';
+    const SERVICE_VERSION = 'V1';
+    const RESOURCE_PATH = '/V1/products/';
+
+    /**
+     * @var \Magento\Framework\ObjectManager
+     */
+    protected $objectManager;
+
+    protected function setUp()
+    {
+        $this->objectManager = Bootstrap::getObjectManager();
+    }
+    /**
+     * @magentoApiDataFixture Magento/Catalog/_files/products_new.php
+     * @magentoApiDataFixture Magento/GroupedProduct/_files/product_grouped.php
+     */
+    public function testSave()
+    {
+        $productSku = 'grouped-product';
+        $linkType = 'associated';
+        $productData = [
+            'product_sku' => $productSku,
+            'link_type' => $linkType,
+            'linked_product_type' => 'simple',
+            'linked_product_sku' => 'simple',
+            'position' => 3,
+            'custom_attributes' => [
+                'qty' => ['attribute_code' => 'qty', 'value' => (float) 300.0000],
+            ],
+        ];
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . $productSku . '/links/' . $linkType,
+                'httpMethod' => RestConfig::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Save',
+            ],
+        ];
+        $this->_webApiCall($serviceInfo, ['entity' => $productData]);
+
+        /** @var \Magento\Catalog\Model\ProductLink\Management $linkManagement */
+        $linkManagement = $this->objectManager->get('Magento\Catalog\Api\ProductLinkManagementInterface');
+        $actual = $linkManagement->getLinkedItemsByType($productSku, $linkType);
+        array_walk($actual, function (&$item) {
+            $item = $item->__toArray();
+        });
+        $this->assertEquals($productData, $actual[2]);
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/GroupedProduct/Api/ProductLinkTypeListTest.php b/dev/tests/api-functional/testsuite/Magento/GroupedProduct/Api/ProductLinkTypeListTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..cec4ba0b6777c74ba37ec0aade980d18bcae2933
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/GroupedProduct/Api/ProductLinkTypeListTest.php
@@ -0,0 +1,64 @@
+<?php
+/**
+ *
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\GroupedProduct\Api;
+
+use Magento\GroupedProduct\Model\Resource\Product\Link;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+class ProductLinkTypeListTest extends \Magento\TestFramework\TestCase\WebapiAbstract
+{
+    const SERVICE_NAME = 'catalogProductLinkTypeListV1';
+    const SERVICE_VERSION = 'V1';
+    const RESOURCE_PATH = '/V1/products/';
+
+    public function testGetItems()
+    {
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . 'links/types',
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'GetItems',
+            ],
+        ];
+
+        $actual = $this->_webApiCall($serviceInfo);
+
+        /**
+         * Validate that product type links provided by Magento_GroupedProduct module are present
+         */
+        $expectedItems = ['name' => 'associated', 'code' => Link::LINK_TYPE_GROUPED];
+        $this->assertContains($expectedItems, $actual);
+    }
+
+    public function testGetItemAttributes()
+    {
+        $linkType = 'associated';
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . 'links/' . $linkType . '/attributes',
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'GetItemAttributes',
+            ],
+        ];
+
+        $actual = $this->_webApiCall($serviceInfo, ['type' => $linkType]);
+
+        $expected = [
+            ['code' => 'position', 'type' => 'int'],
+            ['code' => 'qty', 'type' => 'decimal'],
+        ];
+        $this->assertEquals($expected, $actual);
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Integration/Service/V1/AdminTokenServiceTest.php b/dev/tests/api-functional/testsuite/Magento/Integration/Service/V1/AdminTokenServiceTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..91e6a4cad97841feb4c1d2d91c61a97bd7dfa2cd
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Integration/Service/V1/AdminTokenServiceTest.php
@@ -0,0 +1,159 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+namespace Magento\Integration\Service\V1;
+
+use Magento\Framework\Exception\InputException;
+use Magento\Integration\Model\Oauth\Token as TokenModel;
+use Magento\TestFramework\Helper\Bootstrap;
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\User\Model\User as UserModel;
+use Magento\Webapi\Exception as HTTPExceptionCodes;
+
+/**
+ * api-functional test for \Magento\Integration\Service\V1\AdminTokenService.
+ */
+class AdminTokenServiceTest extends WebapiAbstract
+{
+    const SERVICE_NAME = "integrationAdminTokenServiceV1";
+    const SERVICE_VERSION = "V1";
+    const RESOURCE_PATH_ADMIN_TOKEN = "/V1/integration/admin/token";
+    const RESOURCE_PATH_CUSTOMER_TOKEN = "/V1/integration/customer/token";
+
+    /**
+     * @var AdminTokenServiceInterface
+     */
+    private $tokenService;
+
+    /**
+     * @var TokenModel
+     */
+    private $tokenModel;
+
+    /**
+     * @var UserModel
+     */
+    private $userModel;
+
+    /**
+     * Setup AdminTokenService
+     */
+    public function setUp()
+    {
+        $this->_markTestAsRestOnly();
+        $this->tokenService = Bootstrap::getObjectManager()->get('Magento\Integration\Service\V1\AdminTokenService');
+        $this->tokenModel = Bootstrap::getObjectManager()->get('Magento\Integration\Model\Oauth\Token');
+        $this->userModel = Bootstrap::getObjectManager()->get('Magento\User\Model\User');
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/User/_files/user_with_role.php
+     */
+    public function testCreateAdminAccessToken()
+    {
+        $adminUserNameFromFixture = 'adminUser';
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH_ADMIN_TOKEN,
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_POST,
+            ],
+        ];
+        $requestData = [
+            'username' => $adminUserNameFromFixture,
+            'password' => \Magento\TestFramework\Bootstrap::ADMIN_PASSWORD,
+        ];
+        $accessToken = $this->_webApiCall($serviceInfo, $requestData);
+
+        $adminUserId = $this->userModel->loadByUsername($adminUserNameFromFixture)->getId();
+        /** @var $token TokenModel */
+        $token = $this->tokenModel
+            ->loadByAdminId($adminUserId)
+            ->getToken();
+        $this->assertEquals($accessToken, $token);
+    }
+
+    /**
+     * @dataProvider validationDataProvider
+     */
+    public function testCreateAdminAccessTokenEmptyOrNullCredentials()
+    {
+        try {
+            $serviceInfo = [
+                'rest' => [
+                    'resourcePath' => self::RESOURCE_PATH_ADMIN_TOKEN,
+                    'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_POST,
+                ],
+            ];
+            $requestData = ['username' => '', 'password' => ''];
+            $this->_webApiCall($serviceInfo, $requestData);
+        } catch (\Exception $e) {
+            $this->assertInputExceptionMessages($e);
+        }
+    }
+
+    public function testCreateAdminAccessTokenInvalidCustomer()
+    {
+        $customerUserName = 'invalid';
+        $password = 'invalid';
+        try {
+            $serviceInfo = [
+                'rest' => [
+                    'resourcePath' => self::RESOURCE_PATH_CUSTOMER_TOKEN,
+                    'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_POST,
+                ],
+            ];
+            $requestData = ['username' => $customerUserName, 'password' => $password];
+            $this->_webApiCall($serviceInfo, $requestData);
+        } catch (\Exception $e) {
+            $this->assertEquals(HTTPExceptionCodes::HTTP_UNAUTHORIZED, $e->getCode());
+            $exceptionData = $this->processRestExceptionResult($e);
+            $expectedExceptionData = ['message' => 'Invalid login or password.'];
+        }
+        $this->assertEquals($expectedExceptionData, $exceptionData);
+    }
+
+    /**
+     * Provider to test input validation
+     *
+     * @return array
+     */
+    public function validationDataProvider()
+    {
+        return [
+            'Check for empty credentials' => ['', ''],
+            'Check for null credentials' => [null, null]
+        ];
+    }
+
+    /**
+     * Assert for presence of Input exception messages
+     *
+     * @param \Exception $e
+     */
+    private function assertInputExceptionMessages($e)
+    {
+        $this->assertEquals(HTTPExceptionCodes::HTTP_BAD_REQUEST, $e->getCode());
+        $exceptionData = $this->processRestExceptionResult($e);
+        $expectedExceptionData = [
+            'message' => InputException::DEFAULT_MESSAGE,
+            'errors' => [
+                [
+                    'message' => InputException::REQUIRED_FIELD,
+                    'parameters' => [
+                        'fieldName' => 'username',
+                    ],
+                ],
+                [
+                    'message' => InputException::REQUIRED_FIELD,
+                    'parameters' => [
+                        'fieldName' => 'password',
+                    ]
+                ],
+            ],
+        ];
+        $this->assertEquals($expectedExceptionData, $exceptionData);
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Integration/Service/V1/CustomerTokenServiceTest.php b/dev/tests/api-functional/testsuite/Magento/Integration/Service/V1/CustomerTokenServiceTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..f6a4f4f2ce28bbcbf002beb7a2af25c70d51eb88
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Integration/Service/V1/CustomerTokenServiceTest.php
@@ -0,0 +1,164 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+namespace Magento\Integration\Service\V1;
+
+use Magento\Customer\Api\AccountManagementInterface;
+use Magento\Framework\Exception\InputException;
+use Magento\Integration\Model\Oauth\Token as TokenModel;
+use Magento\TestFramework\Helper\Bootstrap;
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\User\Model\User as UserModel;
+use Magento\Webapi\Exception as HTTPExceptionCodes;
+
+/**
+ * api-functional test for \Magento\Integration\Service\V1\CustomerTokenService.
+ */
+class CustomerTokenServiceTest extends WebapiAbstract
+{
+    const SERVICE_NAME = "integrationCustomerTokenServiceV1";
+    const SERVICE_VERSION = "V1";
+    const RESOURCE_PATH_CUSTOMER_TOKEN = "/V1/integration/customer/token";
+    const RESOURCE_PATH_ADMIN_TOKEN = "/V1/integration/admin/token";
+
+    /**
+     * @var CustomerTokenServiceInterface
+     */
+    private $tokenService;
+
+    /**
+     * @var AccountManagementInterface
+     */
+    private $customerAccountManagement;
+
+    /**
+     * @var TokenModel
+     */
+    private $tokenModel;
+
+    /**
+     * @var UserModel
+     */
+    private $userModel;
+
+    /**
+     * Setup CustomerTokenService
+     */
+    public function setUp()
+    {
+        $this->_markTestAsRestOnly();
+        $this->tokenService = Bootstrap::getObjectManager()->get('Magento\Integration\Service\V1\CustomerTokenService');
+        $this->customerAccountManagement = Bootstrap::getObjectManager()->get(
+            'Magento\Customer\Api\AccountManagementInterface'
+        );
+        $this->tokenModel = Bootstrap::getObjectManager()->get('Magento\Integration\Model\Oauth\Token');
+        $this->userModel = Bootstrap::getObjectManager()->get('Magento\User\Model\User');
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Customer/_files/customer.php
+     */
+    public function testCreateCustomerAccessToken()
+    {
+        $customerUserName = 'customer@example.com';
+        $password = 'password';
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH_CUSTOMER_TOKEN,
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_POST,
+            ],
+        ];
+        $requestData = ['username' => $customerUserName, 'password' => $password];
+        $accessToken = $this->_webApiCall($serviceInfo, $requestData);
+
+        $customerData = $this->customerAccountManagement->authenticate($customerUserName, $password);
+        /** @var $token TokenModel */
+        $token = $this->tokenModel->loadByCustomerId($customerData->getId())->getToken();
+        $this->assertEquals($accessToken, $token);
+    }
+
+    /**
+     * @dataProvider validationDataProvider
+     */
+    public function testCreateCustomerAccessTokenEmptyOrNullCredentials($username, $password)
+    {
+        try {
+            $serviceInfo = [
+                'rest' => [
+                    'resourcePath' => self::RESOURCE_PATH_CUSTOMER_TOKEN,
+                    'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_POST,
+                ],
+            ];
+            $requestData = ['username' => '', 'password' => ''];
+            $this->_webApiCall($serviceInfo, $requestData);
+        } catch (\Exception $e) {
+            $this->assertInputExceptionMessages($e);
+        }
+    }
+
+    public function testCreateCustomerAccessTokenInvalidCustomer()
+    {
+        $customerUserName = 'invalid';
+        $password = 'invalid';
+        try {
+            $serviceInfo = [
+                'rest' => [
+                    'resourcePath' => self::RESOURCE_PATH_CUSTOMER_TOKEN,
+                    'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_POST,
+                ],
+            ];
+            $requestData = ['username' => $customerUserName, 'password' => $password];
+            $this->_webApiCall($serviceInfo, $requestData);
+        } catch (\Exception $e) {
+            $this->assertEquals(HTTPExceptionCodes::HTTP_UNAUTHORIZED, $e->getCode());
+            $exceptionData = $this->processRestExceptionResult($e);
+            $expectedExceptionData = ['message' => 'Invalid login or password.'];
+        }
+        $this->assertEquals($expectedExceptionData, $exceptionData);
+    }
+
+    /**
+     * Provider to test input validation
+     *
+     * @return array
+     */
+    public function validationDataProvider()
+    {
+        return [
+            'Check for empty credentials' => ['', ''],
+            'Check for null credentials' => [null, null]
+        ];
+    }
+
+    /**
+     * Assert for presence of Input exception messages
+     *
+     * @param \Exception $e
+     */
+    private function assertInputExceptionMessages($e)
+    {
+        $this->assertEquals(HTTPExceptionCodes::HTTP_BAD_REQUEST, $e->getCode());
+        $exceptionData = $this->processRestExceptionResult($e);
+        $expectedExceptionData = [
+            'message' => InputException::DEFAULT_MESSAGE,
+            'errors' => [
+                [
+                    'message' => InputException::REQUIRED_FIELD,
+                    'parameters' => [
+                        'fieldName' => 'username',
+                    ],
+                ],
+                [
+                    'message' => InputException::REQUIRED_FIELD,
+                    'parameters' => [
+                        'fieldName' => 'password',
+                    ]
+                ],
+            ],
+        ];
+        $this->assertEquals($expectedExceptionData, $exceptionData);
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/CreditmemoAddCommentTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/CreditmemoAddCommentTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..885bcb8f8b717e5063748b2c2d4cbf229c6c56aa
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/CreditmemoAddCommentTest.php
@@ -0,0 +1,82 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Sales\Service\V1;
+
+use Magento\Sales\Api\Data\CreditmemoCommentInterface as Comment;
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config;
+
+/**
+ * Class CreditmemoAddCommentTest
+ */
+class CreditmemoAddCommentTest extends WebapiAbstract
+{
+    /**
+     * Service read name
+     */
+    const SERVICE_READ_NAME = 'salesCreditmemoCommentRepositoryV1';
+
+    /**
+     * Service version
+     */
+    const SERVICE_VERSION = 'V1';
+
+    /**
+     * Creditmemo increment id
+     */
+    const CREDITMEMO_INCREMENT_ID = '100000001';
+
+    /**
+     * @var \Magento\Framework\ObjectManagerInterface
+     */
+    protected $objectManager;
+
+    /**
+     * Set up
+     *
+     * @return void
+     */
+    protected function setUp()
+    {
+        $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+    }
+
+    /**
+     * Test creditmemo add comment service
+     *
+     * @magentoApiDataFixture Magento/Sales/_files/creditmemo_with_list.php
+     */
+    public function testCreditmemoAddComment()
+    {
+        /** @var \Magento\Sales\Model\Resource\Order\Creditmemo\Collection $creditmemoCollection */
+        $creditmemoCollection = $this->objectManager->get('Magento\Sales\Model\Resource\Order\Creditmemo\Collection');
+        $creditmemo = $creditmemoCollection->getFirstItem();
+
+        $commentData = [
+            Comment::COMMENT => 'Hello world!',
+            Comment::ENTITY_ID => null,
+            Comment::CREATED_AT => null,
+            Comment::PARENT_ID => $creditmemo->getId(),
+            Comment::IS_VISIBLE_ON_FRONT => true,
+            Comment::IS_CUSTOMER_NOTIFIED => true,
+        ];
+
+        $requestData = ['entity' => $commentData];
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/creditmemo/comment',
+                'httpMethod' => Config::HTTP_METHOD_POST,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_READ_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_READ_NAME . 'save',
+            ],
+        ];
+
+        $result = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertNotEmpty($result);
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/CreditmemoCancelTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/CreditmemoCancelTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..e7ad70a57658f0c358b67f6d532b12e130e56d28
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/CreditmemoCancelTest.php
@@ -0,0 +1,47 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Sales\Service\V1;
+
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+/**
+ * Class CreditmemoCancelTest
+ */
+class CreditmemoCancelTest extends WebapiAbstract
+{
+    const SERVICE_VERSION = 'V1';
+
+    const SERVICE_NAME = 'salesCreditmemoManagementV1';
+
+    const CREDITMEMO_INCREMENT_ID = '100000001';
+
+    /**
+     * @magentoApiDataFixture Magento/Sales/_files/creditmemo_with_list.php
+     */
+    public function testCreditmemoCancel()
+    {
+        $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+
+        /** @var \Magento\Sales\Model\Resource\Order\Creditmemo\Collection $creditmemoCollection */
+        $creditmemoCollection = $objectManager->get('Magento\Sales\Model\Resource\Order\Creditmemo\Collection');
+        $creditmemo = $creditmemoCollection->getFirstItem();
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/creditmemo/' . $creditmemo->getId(),
+                'httpMethod' => RestConfig::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'cancel',
+            ],
+        ];
+        $requestData = ['id' => $creditmemo->getId()];
+        $result = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertTrue($result);
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/CreditmemoCommentsListTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/CreditmemoCommentsListTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..2566256747465ca80f3dcfa3f5f203675502e3d5
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/CreditmemoCommentsListTest.php
@@ -0,0 +1,62 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Sales\Service\V1;
+
+use Magento\Sales\Api\Data\CreditmemoCommentInterface;
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+/**
+ * Class CreditmemoCommentsListTest
+ */
+class CreditmemoCommentsListTest extends WebapiAbstract
+{
+    const SERVICE_NAME = 'salesCreditmemoManagementV1';
+
+    const SERVICE_VERSION = 'V1';
+
+    /**
+     * @magentoApiDataFixture Magento/Sales/_files/creditmemo_for_get.php
+     */
+    public function testCreditmemoCommentsList()
+    {
+        $comment = 'Test comment';
+        $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+        /** @var \Magento\Sales\Model\Resource\Order\Creditmemo\Collection $creditmemoCollection */
+        $creditmemoCollection = $objectManager->get('Magento\Sales\Model\Resource\Order\Creditmemo\Collection');
+        $creditmemo = $creditmemoCollection->getFirstItem();
+        $creditmemoComment = $objectManager->get('Magento\Sales\Model\Order\Creditmemo\Comment');
+
+        $commentData = [
+            CreditmemoCommentInterface::COMMENT => 'Hello world!',
+            CreditmemoCommentInterface::ENTITY_ID => null,
+            CreditmemoCommentInterface::CREATED_AT => null,
+            CreditmemoCommentInterface::PARENT_ID => $creditmemo->getId(),
+            CreditmemoCommentInterface::IS_VISIBLE_ON_FRONT => true,
+            CreditmemoCommentInterface::IS_CUSTOMER_NOTIFIED => true,
+        ];
+        $creditmemoComment->setData($commentData)->save();
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/creditmemo/' . $creditmemo->getId() . '/comments',
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'getCommentsList',
+            ],
+        ];
+        $requestData = ['id' => $creditmemo->getId()];
+        $result = $this->_webApiCall($serviceInfo, $requestData);
+        // TODO Test fails, due to the inability of the framework API to handle data collection
+        $this->assertNotEmpty($result);
+        foreach ($result['items'] as $item) {
+            $comment = $objectManager->get('Magento\Sales\Model\Order\Creditmemo\Comment')->load($item['entity_id']);
+            $this->assertEquals($comment->getComment(), $item['comment']);
+        }
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/CreditmemoCreateTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/CreditmemoCreateTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..e7fb134e9d20243d6e04bdd75f7f47768336973f
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/CreditmemoCreateTest.php
@@ -0,0 +1,111 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Sales\Service\V1;
+
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config;
+
+/**
+ * Class CreditmemoCreateTest
+ */
+class CreditmemoCreateTest extends WebapiAbstract
+{
+    const RESOURCE_PATH = '/V1/creditmemo';
+
+    const SERVICE_READ_NAME = 'salesCreditmemoRepositoryV1';
+
+    const SERVICE_VERSION = 'V1';
+
+    /**
+     * @var \Magento\Framework\ObjectManagerInterface
+     */
+    protected $objectManager;
+
+    protected function setUp()
+    {
+        $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Sales/_files/invoice.php
+     */
+    public function testInvoke()
+    {
+        /** @var \Magento\Sales\Model\Order $order */
+        $orderCollection = $this->objectManager->get('Magento\Sales\Model\Resource\Order\Collection');
+        $order = $orderCollection->getFirstItem();
+
+//        $order = $this->objectManager->create('Magento\Sales\Model\Order')->loadByIncrementId('100000001');
+        /** @var \Magento\Sales\Model\Order\Item $orderItem */
+        $orderItem = current($order->getAllItems());
+        $items = [
+            $orderItem->getId() => ['qty' => $orderItem->getQtyInvoiced(), 'order_item_id' => $orderItem->getId()],
+        ];
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH,
+                'httpMethod' => Config::HTTP_METHOD_POST,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_READ_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_READ_NAME . 'save',
+            ],
+        ];
+        $data = [
+            'adjustment' => null,
+            'adjustment_negative' => null,
+            'adjustment_positive' => null,
+            'base_adjustment' => null,
+            'base_adjustment_negative' => null,
+            'base_adjustment_positive' => null,
+            'base_currency_code' => null,
+            'base_discount_amount' => null,
+            'base_grand_total' => null,
+            'base_hidden_tax_amount' => null,
+            'base_shipping_amount' => null,
+            'base_shipping_hidden_tax_amnt' => null,
+            'base_shipping_incl_tax' => null,
+            'base_shipping_tax_amount' => null,
+            'base_subtotal' => null,
+            'base_subtotal_incl_tax' => null,
+            'base_tax_amount' => null,
+            'base_to_global_rate' => null,
+            'base_to_order_rate' => null,
+            'billing_address_id' => null,
+            'created_at' => null,
+            'creditmemo_status' => null,
+            'discount_amount' => null,
+            'discount_description' => null,
+            'email_sent' => null,
+            'entity_id' => null,
+            'global_currency_code' => null,
+            'grand_total' => null,
+            'hidden_tax_amount' => null,
+            'increment_id' => null,
+            'invoice_id' => null,
+            'order_currency_code' => null,
+            'order_id' => $order->getId(),
+            'shipping_address_id' => null,
+            'shipping_amount' => null,
+            'shipping_hidden_tax_amount' => null,
+            'shipping_incl_tax' => null,
+            'shipping_tax_amount' => null,
+            'state' => null,
+            'store_currency_code' => null,
+            'store_id' => null,
+            'store_to_base_rate' => null,
+            'store_to_order_rate' => null,
+            'subtotal' => null,
+            'subtotal_incl_tax' => null,
+            'tax_amount' => null,
+            'transaction_id' => null,
+            'updated_at' => null,
+            'items' => $items,
+        ];
+        $result = $this->_webApiCall($serviceInfo, ['entity' => $data]);
+        $this->assertNotEmpty($result);
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/CreditmemoEmailTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/CreditmemoEmailTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..828a0338c3669a7fab59d96d3dab1091f053fdc5
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/CreditmemoEmailTest.php
@@ -0,0 +1,45 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Sales\Service\V1;
+
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+/**
+ * Class CreditmemoEmailTest
+ */
+class CreditmemoEmailTest extends WebapiAbstract
+{
+    const SERVICE_VERSION = 'V1';
+
+    const SERVICE_NAME = 'salesCreditmemoManagementV1';
+
+    const CREDITMEMO_INCREMENT_ID = '100000001';
+
+    /**
+     * @magentoApiDataFixture Magento/Sales/_files/creditmemo_with_list.php
+     */
+    public function testCreditmemoEmail()
+    {
+        $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+
+        /** @var \Magento\Sales\Model\Resource\Order\Creditmemo\Collection $creditmemoCollection */
+        $creditmemoCollection = $objectManager->get('Magento\Sales\Model\Resource\Order\Creditmemo\Collection');
+        $creditmemo = $creditmemoCollection->getFirstItem();
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/creditmemo/' . $creditmemo->getId(),
+                'httpMethod' => RestConfig::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'notify',
+            ],
+        ];
+        $requestData = ['id' => $creditmemo->getId()];
+        $this->_webApiCall($serviceInfo, $requestData);
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/CreditmemoGetTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/CreditmemoGetTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..2e11ef7791c2c70bf02800040c6c7012f6690b35
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/CreditmemoGetTest.php
@@ -0,0 +1,108 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Sales\Service\V1;
+
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config;
+
+/**
+ * Class CreditmemoGetTest
+ */
+class CreditmemoGetTest extends WebapiAbstract
+{
+    /**
+     * Resource path
+     */
+    const RESOURCE_PATH = '/V1/creditmemo';
+
+    /**
+     * Service read name
+     */
+    const SERVICE_READ_NAME = 'salesCreditmemoRepositoryV1';
+
+    /**
+     * Service version
+     */
+    const SERVICE_VERSION = 'V1';
+
+    /**
+     * Creditmemo id
+     */
+    const CREDITMEMO_INCREMENT_ID = '100000001';
+
+    /**
+     * @var \Magento\Framework\ObjectManagerInterface
+     */
+    protected $objectManager;
+
+    /**
+     * Required fields are in the answer
+     *
+     * @var array
+     */
+    protected $requiredFields = [
+        'entity_id',
+        'store_id',
+        'base_shipping_tax_amount',
+        'base_discount_amount',
+        'grand_total',
+        'base_subtotal_incl_tax',
+        'shipping_amount',
+        'subtotal_incl_tax',
+        'base_shipping_amount',
+        'base_adjustment',
+        'base_subtotal',
+        'discount_amount',
+        'subtotal',
+        'adjustment',
+        'base_grand_total',
+        'base_tax_amount',
+        'shipping_tax_amount',
+        'tax_amount',
+        'order_id',
+        'state',
+        'increment_id',
+    ];
+
+    /**
+     * Set up
+     */
+    protected function setUp()
+    {
+        $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+    }
+
+    /**
+     * Test creditmemo get service
+     *
+     * @magentoApiDataFixture Magento/Sales/_files/creditmemo_for_get.php
+     */
+    public function testCreditmemoGet()
+    {
+        /** @var \Magento\Sales\Model\Resource\Order\Creditmemo\Collection $creditmemoCollection */
+        $creditmemoCollection = $this->objectManager->get('Magento\Sales\Model\Resource\Order\Creditmemo\Collection');
+        $creditmemo = $creditmemoCollection->getFirstItem();
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/' . $creditmemo->getId(),
+                'httpMethod' => Config::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_READ_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_READ_NAME . 'get',
+            ],
+        ];
+
+        $actual = $this->_webApiCall($serviceInfo, ['id' => $creditmemo->getId()]);
+        $expected = $creditmemo->getData();
+
+        foreach ($this->requiredFields as $field) {
+            $this->assertArrayHasKey($field, $actual);
+            $this->assertEquals($expected[$field], $actual[$field]);
+        }
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/CreditmemoListTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/CreditmemoListTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..a3b4dd59b1c3aa67bd9a95791454e1f679f0f091
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/CreditmemoListTest.php
@@ -0,0 +1,89 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Sales\Service\V1;
+
+use Magento\TestFramework\Helper\Bootstrap;
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config;
+
+/**
+ * Class CreditmemoListTest
+ */
+class CreditmemoListTest extends WebapiAbstract
+{
+    /**
+     * Resource path
+     */
+    const RESOURCE_PATH = '/V1/creditmemos';
+
+    /**
+     * Service read name
+     */
+    const SERVICE_READ_NAME = 'salesCreditmemoRepositoryV1';
+
+    /**
+     * Service version
+     */
+    const SERVICE_VERSION = 'V1';
+
+    /**
+     * @var \Magento\Framework\ObjectManagerInterface
+     */
+    protected $objectManager;
+
+    /**
+     * Set up
+     */
+    protected function setUp()
+    {
+        $this->objectManager = Bootstrap::getObjectManager();
+    }
+
+    /**
+     * Test creditmemo list service
+     *
+     * @magentoApiDataFixture Magento/Sales/_files/creditmemo_with_list.php
+     */
+    public function testCreditmemoList()
+    {
+        /** @var $searchCriteriaBuilder  \Magento\Framework\Api\SearchCriteriaBuilder */
+        $searchCriteriaBuilder = $this->objectManager->create(
+            'Magento\Framework\Api\SearchCriteriaBuilder'
+        );
+
+        /** @var $filterBuilder  \Magento\Framework\Api\FilterBuilder */
+        $filterBuilder = $this->objectManager->create(
+            'Magento\Framework\Api\FilterBuilder'
+        );
+
+        $searchCriteriaBuilder->addFilter(
+            [
+                $filterBuilder
+                    ->setField('state')
+                    ->setValue(\Magento\Sales\Model\Order\Creditmemo::STATE_OPEN)
+                    ->create(),
+            ]
+        );
+        $searchData = $searchCriteriaBuilder->create()->__toArray();
+
+        $requestData = ['criteria' => $searchData];
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '?' . http_build_query($requestData),
+                'httpMethod' => Config::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_READ_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_READ_NAME . 'getList',
+            ],
+        ];
+
+        $result = $this->_webApiCall($serviceInfo, $requestData);
+        // TODO Test fails, due to the inability of the framework API to handle data collection
+        $this->assertArrayHasKey('items', $result);
+        $this->assertCount(1, $result['items']);
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/InvoiceAddCommentTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/InvoiceAddCommentTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..d76ae8a2778065ca05e41abfeb374751362bb1b1
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/InvoiceAddCommentTest.php
@@ -0,0 +1,63 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Sales\Service\V1;
+
+use Magento\Sales\Api\Data\InvoiceCommentInterface;
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config;
+
+/**
+ * Class InvoiceAddCommentTest
+ */
+class InvoiceAddCommentTest extends WebapiAbstract
+{
+    /**
+     * Service read name
+     */
+    const SERVICE_READ_NAME = 'salesInvoiceCommentRepositoryV1';
+
+    /**
+     * Service version
+     */
+    const SERVICE_VERSION = 'V1';
+
+    /**
+     * Test invoice add comment service
+     *
+     * @magentoApiDataFixture Magento/Sales/_files/invoice.php
+     */
+    public function testInvoiceAddComment()
+    {
+        $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+        /** @var \Magento\Sales\Model\Order\Invoice $invoice */
+        $invoiceCollection = $objectManager->get('Magento\Sales\Model\Resource\Order\Invoice\Collection');
+        $invoice = $invoiceCollection->getFirstItem();
+
+        $commentData = [
+            InvoiceCommentInterface::COMMENT => 'Hello world!',
+            InvoiceCommentInterface::ENTITY_ID => null,
+            InvoiceCommentInterface::CREATED_AT => null,
+            InvoiceCommentInterface::PARENT_ID => $invoice->getId(),
+            InvoiceCommentInterface::IS_VISIBLE_ON_FRONT => true,
+            InvoiceCommentInterface::IS_CUSTOMER_NOTIFIED => true,
+        ];
+
+        $requestData = ['entity' => $commentData];
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/invoice/comment',
+                'httpMethod' => Config::HTTP_METHOD_POST,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_READ_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_READ_NAME . 'save',
+            ],
+        ];
+
+        $result = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertNotEmpty($result);
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/InvoiceCaptureTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/InvoiceCaptureTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..ca013830487ef16025c181088bdb332deb2d341d
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/InvoiceCaptureTest.php
@@ -0,0 +1,43 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+namespace Magento\Sales\Service\V1;
+
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+/**
+ * Class InvoiceCaptureTest
+ */
+class InvoiceCaptureTest extends WebapiAbstract
+{
+    const SERVICE_VERSION = 'V1';
+
+    const SERVICE_NAME = 'salesInvoiceManagementV1';
+
+    /**
+     * @magentoApiDataFixture Magento/Sales/_files/invoice.php
+     * @expectedException \Exception
+     */
+    public function testInvoiceCapture()
+    {
+        $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+        /** @var \Magento\Sales\Model\Order\Invoice $invoice */
+        $invoice = $objectManager->get('Magento\Sales\Model\Order\Invoice')->loadByIncrementId('100000001');
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/invoices/' . $invoice->getId() . '/capture',
+                'httpMethod' => RestConfig::HTTP_METHOD_POST,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'setCapture',
+            ],
+        ];
+        $requestData = ['id' => $invoice->getId()];
+        $this->_webApiCall($serviceInfo, $requestData);
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/InvoiceCommentsListTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/InvoiceCommentsListTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..1c5d7a5f343763f7b936202216113d6a4fffd523
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/InvoiceCommentsListTest.php
@@ -0,0 +1,56 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Sales\Service\V1;
+
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+/**
+ * Class InvoiceCommentsListTest
+ */
+class InvoiceCommentsListTest extends WebapiAbstract
+{
+    const SERVICE_NAME = 'salesInvoiceManagementV1';
+
+    const SERVICE_VERSION = 'V1';
+
+    /**
+     * @magentoApiDataFixture Magento/Sales/_files/invoice.php
+     */
+    public function testInvoiceCommentsList()
+    {
+        $comment = 'Test comment';
+        $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+
+        /** @var \Magento\Sales\Model\Resource\Order\Invoice\Collection $invoiceCollection */
+        $invoiceCollection = $objectManager->get('Magento\Sales\Model\Resource\Order\Invoice\Collection');
+        $invoice = $invoiceCollection->getFirstItem();
+        $invoiceComment = $objectManager->get('Magento\Sales\Model\Order\Invoice\Comment');
+        $invoiceComment->setComment($comment);
+        $invoiceComment->setParentId($invoice->getId());
+        $invoiceComment->save();
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/invoice/' . $invoice->getId() . '/comments',
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'getCommentsList',
+            ],
+        ];
+        $requestData = ['id' => $invoice->getId()];
+        // TODO Test fails, due to the inability of the framework API to handle data collection
+        $result = $this->_webApiCall($serviceInfo, $requestData);
+        foreach ($result['items'] as $item) {
+            /** @var \Magento\Sales\Model\Order\Invoice\Comment $invoiceHistoryStatus */
+            $invoiceHistoryStatus = $objectManager->get('Magento\Sales\Model\Order\Invoice\Comment')
+                ->load($item['entity_id']);
+            $this->assertEquals($invoiceHistoryStatus->getComment(), $item['comment']);
+        }
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/InvoiceCreateTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/InvoiceCreateTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..fc5600ccfa72e13126eb8608c06bafdcfbbcaa9e
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/InvoiceCreateTest.php
@@ -0,0 +1,127 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Sales\Service\V1;
+
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config;
+
+/**
+ * Class InvoiceCreateTest
+ */
+class InvoiceCreateTest extends WebapiAbstract
+{
+    const RESOURCE_PATH = '/V1/invoice';
+
+    const SERVICE_READ_NAME = 'salesInvoiceRepositoryV1';
+
+    const SERVICE_VERSION = 'V1';
+
+    /**
+     * @var \Magento\Framework\ObjectManagerInterface
+     */
+    protected $objectManager;
+
+    protected function setUp()
+    {
+        $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Sales/_files/order.php
+     */
+    public function testInvoke()
+    {
+        /** @var \Magento\Sales\Model\Order $order */
+        $order = $this->objectManager->create('Magento\Sales\Model\Order')->loadByIncrementId('100000001');
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH,
+                'httpMethod' => Config::HTTP_METHOD_POST,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_READ_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_READ_NAME . 'save',
+            ],
+        ];
+        $orderItems = $order->getAllItems();
+        $data = [
+            'order_id' => $order->getId(),
+            'base_currency_code' => null,
+            'base_discount_amount' => null,
+            'base_grand_total' => null,
+            'base_hidden_tax_amount' => null,
+            'base_shipping_amount' => null,
+            'base_shipping_hidden_tax_amnt' => null,
+            'base_shipping_incl_tax' => null,
+            'base_shipping_tax_amount' => null,
+            'base_subtotal' => null,
+            'base_subtotal_incl_tax' => null,
+            'base_tax_amount' => null,
+            'base_total_refunded' => null,
+            'base_to_global_rate' => null,
+            'base_to_order_rate' => null,
+            'billing_address_id' => null,
+            'can_void_flag' => null,
+            'created_at' => null,
+            'discount_amount' => null,
+            'discount_description' => null,
+            'email_sent' => null,
+            'entity_id' => null,
+            'global_currency_code' => null,
+            'grand_total' => null,
+            'hidden_tax_amount' => null,
+            'increment_id' => null,
+            'is_used_for_refund' => null,
+            'order_currency_code' => null,
+            'shipping_address_id' => null,
+            'shipping_amount' => null,
+            'shipping_hidden_tax_amount' => null,
+            'shipping_incl_tax' => null,
+            'shipping_tax_amount' => null,
+            'state' => null,
+            'store_currency_code' => null,
+            'store_id' => null,
+            'store_to_base_rate' => null,
+            'store_to_order_rate' => null,
+            'subtotal' => null,
+            'subtotal_incl_tax' => null,
+            'tax_amount' => null,
+            'total_qty' => '1',
+            'transaction_id' => null,
+            'updated_at' => null,
+            'items' => [
+                [
+                    'orderItemId' => $orderItems[0]->getId(),
+                    'qty' => 2,
+                    'additionalData' => null,
+                    'baseCost' => null,
+                    'baseDiscountAmount' => null,
+                    'baseHiddenTaxAmount' => null,
+                    'basePrice' => null,
+                    'basePriceInclTax' => null,
+                    'baseRowTotal' => null,
+                    'baseRowTotalInclTax' => null,
+                    'baseTaxAmount' => null,
+                    'description' => null,
+                    'discountAmount' => null,
+                    'hiddenTaxAmount' => null,
+                    'name' => null,
+                    'entity_id' => null,
+                    'parentId' => null,
+                    'price' => null,
+                    'priceInclTax' => null,
+                    'productId' => null,
+                    'rowTotal' => null,
+                    'rowTotalInclTax' => null,
+                    'sku' => 'sku' . uniqid(),
+                    'taxAmount' => null,
+                ],
+            ],
+        ];
+        $result = $this->_webApiCall($serviceInfo, ['entity' => $data]);
+        $this->assertNotEmpty($result);
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/InvoiceEmailTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/InvoiceEmailTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..51ab88ff60a70ef4fe80ac2e044e0a31d62482c0
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/InvoiceEmailTest.php
@@ -0,0 +1,42 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Sales\Service\V1;
+
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+/**
+ * Class InvoiceEmailTest
+ */
+class InvoiceEmailTest extends WebapiAbstract
+{
+    const SERVICE_VERSION = 'V1';
+
+    const SERVICE_NAME = 'salesInvoiceManagementV1';
+
+    /**
+     * @magentoApiDataFixture Magento/Sales/_files/invoice.php
+     */
+    public function testInvoiceEmail()
+    {
+        $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+        $invoiceCollection = $objectManager->get('Magento\Sales\Model\Resource\Order\Invoice\Collection');
+        $invoice = $invoiceCollection->getFirstItem();
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/invoice/' . $invoice->getId() . '/email',
+                'httpMethod' => RestConfig::HTTP_METHOD_POST,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'notify',
+            ],
+        ];
+        $requestData = ['id' => $invoice->getId()];
+        $result = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertTrue($result);
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/InvoiceGetTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/InvoiceGetTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..5de3f1ff0a5613431f7f9a158b684d343e4269b0
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/InvoiceGetTest.php
@@ -0,0 +1,52 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Sales\Service\V1;
+
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config;
+
+/**
+ * Class InvoiceGetTest
+ */
+class InvoiceGetTest extends WebapiAbstract
+{
+    const RESOURCE_PATH = '/V1/invoice';
+
+    const SERVICE_READ_NAME = 'salesInvoiceRepositoryV1';
+
+    const SERVICE_VERSION = 'V1';
+
+    /**
+     * @magentoApiDataFixture Magento/Sales/_files/invoice.php
+     */
+    public function testInvoiceGet()
+    {
+        $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+        /** @var \Magento\Sales\Model\Order\Invoice $invoice */
+        $invoiceCollection = $objectManager->get('Magento\Sales\Model\Resource\Order\Invoice\Collection');
+        $invoice = $invoiceCollection->getFirstItem();
+        $expectedInvoiceData = [
+            'grand_total' => '100.0000',
+            'subtotal' => '100.0000',
+            'increment_id' => $invoice->getIncrementId(),
+        ];
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/' . $invoice->getId(),
+                'httpMethod' => Config::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_READ_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_READ_NAME . 'get',
+            ],
+        ];
+        $result = $this->_webApiCall($serviceInfo, ['id' => $invoice->getId()]);
+        foreach ($expectedInvoiceData as $field => $value) {
+            $this->assertArrayHasKey($field, $result);
+            $this->assertEquals($value, $result[$field]);
+        }
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/InvoiceListTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/InvoiceListTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..ea0565c64bb8827ddb81a3205ad9403080039b2e
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/InvoiceListTest.php
@@ -0,0 +1,74 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Sales\Service\V1;
+
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config;
+
+/**
+ * Class InvoiceListTest
+ */
+class InvoiceListTest extends WebapiAbstract
+{
+    const RESOURCE_PATH = '/V1/invoices';
+
+    const SERVICE_READ_NAME = 'salesInvoiceRepositoryV1';
+
+    const SERVICE_VERSION = 'V1';
+
+    /**
+     * @var \Magento\Framework\ObjectManagerInterface
+     */
+    protected $objectManager;
+
+    protected function setUp()
+    {
+        $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Sales/_files/invoice.php
+     */
+    public function testInvoiceList()
+    {
+        /** @var $searchCriteriaBuilder  \Magento\Framework\Api\SearchCriteriaBuilder */
+        $searchCriteriaBuilder = $this->objectManager->create(
+            'Magento\Framework\Api\SearchCriteriaBuilder'
+        );
+
+        /** @var $filterBuilder  \Magento\Framework\Api\FilterBuilder */
+        $filterBuilder = $this->objectManager->create(
+            'Magento\Framework\Api\FilterBuilder'
+        );
+
+        $searchCriteriaBuilder->addFilter(
+            [
+                $filterBuilder
+                    ->setField('state')
+                    ->setValue(2)
+                    ->create(),
+            ]
+        );
+        $searchData = $searchCriteriaBuilder->create()->__toArray();
+
+        $requestData = ['criteria' => $searchData];
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '?' . http_build_query($requestData),
+                'httpMethod' => Config::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_READ_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_READ_NAME . 'getList',
+            ],
+        ];
+
+        $result = $this->_webApiCall($serviceInfo, $requestData);
+        // TODO Test fails, due to the inability of the framework API to handle data collection
+        $this->assertArrayHasKey('items', $result);
+        $this->assertCount(1, $result['items']);
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/InvoiceVoidTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/InvoiceVoidTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..58fe16154eb46c0ffe815c40d09cec9e04dd8daa
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/InvoiceVoidTest.php
@@ -0,0 +1,43 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+namespace Magento\Sales\Service\V1;
+
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+/**
+ * Class InvoiceVoidTest
+ */
+class InvoiceVoidTest extends WebapiAbstract
+{
+    const SERVICE_VERSION = 'V1';
+
+    const SERVICE_NAME = 'salesInvoiceManagementV1';
+
+    /**
+     * @magentoApiDataFixture Magento/Sales/_files/invoice.php
+     * @expectedException \Exception
+     */
+    public function testInvoiceVoid()
+    {
+        $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+        /** @var \Magento\Sales\Model\Order\Invoice $invoice */
+        $invoice = $objectManager->get('Magento\Sales\Model\Order\Invoice')->loadByIncrementId('100000001');
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/invoices/' . $invoice->getId() . '/void',
+                'httpMethod' => RestConfig::HTTP_METHOD_POST,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'setVoid',
+            ],
+        ];
+        $requestData = ['id' => $invoice->getId()];
+        $this->_webApiCall($serviceInfo, $requestData);
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderAddressUpdateTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderAddressUpdateTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..5b99a39ca69e65eba98805151947b4518633ef3a
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderAddressUpdateTest.php
@@ -0,0 +1,96 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Sales\Service\V1;
+
+use Magento\Sales\Api\Data\OrderAddressInterface as OrderAddress;
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+/**
+ * Class OrderAddressUpdateTest
+ */
+class OrderAddressUpdateTest extends WebapiAbstract
+{
+    const SERVICE_VERSION = 'V1';
+
+    const SERVICE_NAME = 'salesOrderAddressRepositoryV1';
+
+    /**
+     * @magentoApiDataFixture Magento/Sales/_files/order.php
+     */
+    public function testOrderAddressUpdate()
+    {
+        $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+        /** @var \Magento\Sales\Model\Order $order */
+        $order = $objectManager->get('Magento\Sales\Model\Order')->loadByIncrementId('100000001');
+
+        $address = [
+            OrderAddress::REGION => 'CA',
+            OrderAddress::POSTCODE => '11111',
+            OrderAddress::LASTNAME => 'lastname',
+            OrderAddress::STREET => ['street'],
+            OrderAddress::CITY => 'city',
+            OrderAddress::EMAIL => 'email@email.com',
+            OrderAddress::COMPANY => 'company',
+            OrderAddress::TELEPHONE => 't123456789',
+            OrderAddress::COUNTRY_ID => 'US',
+            OrderAddress::FIRSTNAME => 'firstname',
+            OrderAddress::ADDRESS_TYPE => 'billing',
+            OrderAddress::PARENT_ID => $order->getId(),
+            OrderAddress::ENTITY_ID => $order->getBillingAddressId(),
+            OrderAddress::CUSTOMER_ADDRESS_ID => null,
+            OrderAddress::CUSTOMER_ID => null,
+            OrderAddress::FAX => null,
+            OrderAddress::MIDDLENAME => null,
+            OrderAddress::PREFIX => null,
+            OrderAddress::QUOTE_ADDRESS_ID => null,
+            OrderAddress::REGION_ID => null,
+            OrderAddress::SUFFIX => null,
+            OrderAddress::VAT_ID => null,
+            OrderAddress::VAT_IS_VALID => null,
+            OrderAddress::VAT_REQUEST_DATE => null,
+            OrderAddress::VAT_REQUEST_ID => null,
+            OrderAddress::VAT_REQUEST_SUCCESS => null,
+        ];
+        $requestData = ['entity' => $address];
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/order/' . $order->getId(),
+                'httpMethod' => RestConfig::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'save',
+            ],
+        ];
+        $result = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertGreaterThan(1, count($result));
+
+        /** @var \Magento\Sales\Model\Order $actualOrder */
+        $actualOrder = $objectManager->get('Magento\Sales\Model\Order')->load($order->getId());
+        $billingAddress = $actualOrder->getBillingAddress();
+
+        $validate = [
+            OrderAddress::REGION => 'CA',
+            OrderAddress::POSTCODE => '11111',
+            OrderAddress::LASTNAME => 'lastname',
+            OrderAddress::STREET => 'street',
+            OrderAddress::CITY => 'city',
+            OrderAddress::EMAIL => 'email@email.com',
+            OrderAddress::COMPANY => 'company',
+            OrderAddress::TELEPHONE => 't123456789',
+            OrderAddress::COUNTRY_ID => 'US',
+            OrderAddress::FIRSTNAME => 'firstname',
+            OrderAddress::ADDRESS_TYPE => 'billing',
+            OrderAddress::PARENT_ID => $order->getId(),
+            OrderAddress::ENTITY_ID => $order->getBillingAddressId(),
+        ];
+        foreach ($validate as $key => $field) {
+            $this->assertEquals($validate[$key], $billingAddress->getData($key));
+        }
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderCancelTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderCancelTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..39a1efd249f994ac443386cb2a06ba58b8fd379e
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderCancelTest.php
@@ -0,0 +1,39 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+namespace Magento\Sales\Service\V1;
+
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+class OrderCancelTest extends WebapiAbstract
+{
+    const SERVICE_VERSION = 'V1';
+
+    const SERVICE_NAME = 'salesOrderManagementV1';
+
+    /**
+     * @magentoApiDataFixture Magento/Sales/_files/order.php
+     */
+    public function testOrderCancel()
+    {
+        $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+        $order = $objectManager->get('Magento\Sales\Model\Order')->loadByIncrementId('100000001');
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/order/' . $order->getId() . '/cancel',
+                'httpMethod' => RestConfig::HTTP_METHOD_POST,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'cancel',
+            ],
+        ];
+        $requestData = ['id' => $order->getId()];
+        $result = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertTrue($result);
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderCommentsListTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderCommentsListTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..6137432d1634aa1fbdd4ec94869431b261d2ab6c
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderCommentsListTest.php
@@ -0,0 +1,49 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Sales\Service\V1;
+
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+class OrderCommentsListTest extends WebapiAbstract
+{
+    const SERVICE_NAME = 'salesOrderManagementV1';
+
+    const SERVICE_VERSION = 'V1';
+
+    /**
+     * @magentoApiDataFixture Magento/Sales/_files/order.php
+     */
+    public function testOrderCommentsList()
+    {
+        $comment = 'Test comment';
+        $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+
+        /** @var \Magento\Sales\Model\Order $order */
+        $order = $objectManager->get('Magento\Sales\Model\Order')->loadByIncrementId('100000001');
+        $history = $order->addStatusHistoryComment($comment, $order->getStatus());
+        $history->save();
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/order/' . $order->getId() . '/comments',
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'getCommentsList',
+            ],
+        ];
+        $requestData = ['id' => $order->getId()];
+        $result = $this->_webApiCall($serviceInfo, $requestData);
+        foreach ($result['items'] as $history) {
+            $orderHistoryStatus = $objectManager->get('Magento\Sales\Model\Order\Status\History')
+                ->load($history['entity_id']);
+            $this->assertEquals($orderHistoryStatus->getComment(), $history['comment']);
+            $this->assertEquals($orderHistoryStatus->getStatus(), $history['status']);
+        }
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderCreateTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderCreateTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..9e7bd417de67b0830c41a8f81d38ba2a7254c574
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderCreateTest.php
@@ -0,0 +1,128 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Sales\Service\V1;
+
+use Magento\Sales\Api\Data\OrderInterface;
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config;
+
+class OrderCreateTest extends WebapiAbstract
+{
+    const RESOURCE_PATH = '/V1/order';
+
+    const SERVICE_READ_NAME = 'salesOrderRepositoryV1';
+
+    const SERVICE_VERSION = 'V1';
+
+    const ORDER_INCREMENT_ID = '100000001';
+
+    /**
+     * @var \Magento\Framework\ObjectManagerInterface
+     */
+    protected $objectManager;
+
+    protected function setUp()
+    {
+        $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+    }
+
+    protected function prepareOrder()
+    {
+        /** @var \Magento\Sales\Model\Order $orderBuilder */
+        $orderFactory = $this->objectManager->get('Magento\Sales\Model\OrderFactory');
+        /** @var \Magento\Sales\Service\V1\Data\OrderItemBuilder $orderItemBuilder */
+        $orderItemFactory = $this->objectManager->get('Magento\Sales\Model\Order\ItemFactory');
+        /** @var \Magento\Sales\Service\V1\Data\OrderPaymentBuilder $orderPaymentBuilder */
+        $orderPaymentFactory = $this->objectManager->get('Magento\Sales\Model\Order\PaymentFactory');
+        /** @var \Magento\Sales\Service\V1\Data\OrderAddressBuilder $orderAddressBuilder */
+        $orderAddressFactory = $this->objectManager->get('Magento\Sales\Model\Order\AddressFactory');
+
+        $order = $orderFactory->create(
+            ['data' => $this->getDataStructure('Magento\Sales\Api\Data\OrderInterface')]
+        );
+        $orderItem = $orderItemFactory->create(
+            ['data' => $this->getDataStructure('Magento\Sales\Api\Data\OrderItemInterface')]
+        );
+        $orderPayment = $orderPaymentFactory->create(
+            ['data' => $this->getDataStructure('Magento\Sales\Api\Data\OrderPaymentInterface')]
+        );
+        $orderAddressBilling = $orderAddressFactory->create(
+            ['data' => $this->getDataStructure('Magento\Sales\Api\Data\OrderAddressInterface')]
+        );
+
+        $email = uniqid() . 'email@example.com';
+        $orderItem->setSku('sku#1');
+        $orderPayment->setCcLast4('4444');
+        $orderPayment->setMethod('checkmo');
+        $orderPayment->setAccountStatus('ok');
+        $orderPayment->setAdditionalInformation([]);
+        $order->setCustomerEmail($email);
+        $order->setBaseGrandTotal(100);
+        $order->setGrandTotal(100);
+        $order->setItems([$orderItem->getData()]);
+        $order->setPayments([$orderPayment->getData()]);
+        $orderAddressBilling->setCity('City');
+        $orderAddressBilling->setPostcode('12345');
+        $orderAddressBilling->setLastname('Last Name');
+        $orderAddressBilling->setFirstname('First Name');
+        $orderAddressBilling->setTelephone('+00(000)-123-45-57');
+        $orderAddressBilling->setStreet(['Street']);
+        $orderAddressBilling->setCountryId(1);
+        $orderAddressBilling->setAddressType('billing');
+
+        $orderAddressShipping = $orderAddressFactory->create(
+            ['data' => $this->getDataStructure('Magento\Sales\Api\Data\OrderAddressInterface')]
+        );
+        $orderAddressShipping->setCity('City');
+        $orderAddressShipping->setPostcode('12345');
+        $orderAddressShipping->setLastname('Last Name');
+        $orderAddressShipping->setFirstname('First Name');
+        $orderAddressShipping->setTelephone('+00(000)-123-45-57');
+        $orderAddressShipping->setStreet(['Street']);
+        $orderAddressShipping->setCountryId(1);
+        $orderAddressShipping->setAddressType('shipping');
+
+        $orderData = $order->getData();
+        $orderData['billing_address'] = $orderAddressBilling->getData();
+        $orderData['billing_address']['street'] = ['Street'];
+        $orderData['shipping_address'] = $orderAddressShipping->getData();
+        $orderData['shipping_address']['street'] = ['Street'];
+        return $orderData;
+    }
+
+    protected function getDataStructure($className)
+    {
+        $refClass = new \ReflectionClass($className);
+        $constants = $refClass->getConstants();
+        $data = array_fill_keys($constants, null);
+        unset($data['custom_attributes']);
+        return $data;
+    }
+
+    public function testOrderCreate()
+    {
+        $order = $this->prepareOrder();
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH,
+                'httpMethod' => Config::HTTP_METHOD_POST,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_READ_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_READ_NAME . 'save',
+            ],
+        ];
+        $this->assertNotEmpty($this->_webApiCall($serviceInfo, ['entity' => $order]));
+
+        /** @var \Magento\Sales\Model\Order $model */
+        $model = $this->objectManager->get('Magento\Sales\Model\Order');
+        $model->load($order['customer_email'], 'customer_email');
+        $this->assertTrue((bool)$model->getId());
+        $this->assertEquals($order['base_grand_total'], $model->getBaseGrandTotal());
+        $this->assertEquals($order['grand_total'], $model->getGrandTotal());
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderEmailTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderEmailTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..3bdb00820ba4a8b0e04114158db4b0b1c08cb56d
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderEmailTest.php
@@ -0,0 +1,39 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+namespace Magento\Sales\Service\V1;
+
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+class OrderEmailTest extends WebapiAbstract
+{
+    const SERVICE_VERSION = 'V1';
+
+    const SERVICE_NAME = 'salesOrderManagementV1';
+
+    /**
+     * @magentoApiDataFixture Magento/Sales/_files/order.php
+     */
+    public function testOrderEmail()
+    {
+        $order = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()
+            ->create('Magento\Sales\Model\Order');
+        $order->loadByIncrementId('100000001');
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/order/' . $order->getId() . '/email',
+                'httpMethod' => RestConfig::HTTP_METHOD_POST,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'notify',
+            ],
+        ];
+        $requestData = ['id' => $order->getId()];
+        $this->assertTrue($this->_webApiCall($serviceInfo, $requestData));
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderGetStatusTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderGetStatusTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..97786a34970d133736c29c08e0651d67853f8b01
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderGetStatusTest.php
@@ -0,0 +1,57 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Sales\Service\V1;
+
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config;
+
+/**
+ * Class OrderGetStatusTest
+ * @package Magento\Sales\Service\V1
+ */
+class OrderGetStatusTest extends WebapiAbstract
+{
+    const RESOURCE_PATH = '/V1/order/%d/status';
+
+    const SERVICE_READ_NAME = 'salesOrderManagementV1';
+
+    const SERVICE_VERSION = 'V1';
+
+    const ORDER_INCREMENT_ID = '100000001';
+
+    /**
+     * @var \Magento\Framework\ObjectManagerInterface
+     */
+    protected $objectManager;
+
+    protected function setUp()
+    {
+        $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Sales/_files/order.php
+     */
+    public function testOrderGetStatus()
+    {
+        /** @var \Magento\Sales\Model\Order $order */
+        $order = $this->objectManager->create('Magento\Sales\Model\Order');
+        $order->loadByIncrementId(self::ORDER_INCREMENT_ID);
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => sprintf(self::RESOURCE_PATH, $order->getId()),
+                'httpMethod' => Config::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_READ_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_READ_NAME . 'getStatus',
+            ],
+        ];
+
+        $this->assertEquals($order->getStatus(), $this->_webApiCall($serviceInfo, ['id' => $order->getId()]));
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderGetTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderGetTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..d9421a9d4cd50a8e36eec50bb4c683595ee4607c
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderGetTest.php
@@ -0,0 +1,89 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Sales\Service\V1;
+
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config;
+
+class OrderGetTest extends WebapiAbstract
+{
+    const RESOURCE_PATH = '/V1/order';
+
+    const SERVICE_READ_NAME = 'salesOrderRepositoryV1';
+
+    const SERVICE_VERSION = 'V1';
+
+    const ORDER_INCREMENT_ID = '100000001';
+
+    /**
+     * @var \Magento\Framework\ObjectManagerInterface
+     */
+    protected $objectManager;
+
+    protected function setUp()
+    {
+        $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Sales/_files/order.php
+     */
+    public function testOrderGet()
+    {
+        $expectedOrderData = [
+            'base_subtotal' => '100.0000',
+            'subtotal' => '100.0000',
+            'customer_is_guest' => '1',
+            'increment_id' => self::ORDER_INCREMENT_ID,
+        ];
+        $expectedPayments = ['method' => 'checkmo'];
+        $expectedBillingAddressNotEmpty = [
+            'city',
+            'postcode',
+            'lastname',
+            'street',
+            'region',
+            'telephone',
+            'country_id',
+            'firstname',
+        ];
+
+        /** @var \Magento\Sales\Model\Order $order */
+        $order = $this->objectManager->create('Magento\Sales\Model\Order');
+        $order->loadByIncrementId(self::ORDER_INCREMENT_ID);
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/' . $order->getId(),
+                'httpMethod' => Config::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_READ_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_READ_NAME . 'get',
+            ],
+        ];
+        $result = $this->_webApiCall($serviceInfo, ['id' => $order->getId()]);
+
+        foreach ($expectedOrderData as $field => $value) {
+            $this->assertArrayHasKey($field, $result);
+            $this->assertEquals($value, $result[$field]);
+        }
+
+        $this->assertArrayHasKey('payments', $result);
+        foreach ($expectedPayments as $field => $value) {
+            $paymentsKey = key($result['payments']);
+            $this->assertArrayHasKey($field, $result['payments'][$paymentsKey]);
+            $this->assertEquals($value, $result['payments'][$paymentsKey][$field]);
+        }
+
+        $this->assertArrayHasKey('billing_address', $result);
+        $this->assertArrayHasKey('shipping_address', $result);
+        foreach ($expectedBillingAddressNotEmpty as $field) {
+            $this->assertArrayHasKey($field, $result['billing_address']);
+            $this->assertArrayHasKey($field, $result['shipping_address']);
+        }
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderHoldTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderHoldTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..a8bdc8e78a42170050fe7299415690abaf88be6a
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderHoldTest.php
@@ -0,0 +1,39 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+namespace Magento\Sales\Service\V1;
+
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+class OrderHoldTest extends WebapiAbstract
+{
+    const SERVICE_VERSION = 'V1';
+
+    const SERVICE_NAME = 'salesOrderManagementV1';
+
+    /**
+     * @magentoApiDataFixture Magento/Sales/_files/order.php
+     */
+    public function testOrderHold()
+    {
+        $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+        $order = $objectManager->get('Magento\Sales\Model\Order')->loadByIncrementId('100000001');
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/order/' . $order->getId() . '/hold',
+                'httpMethod' => RestConfig::HTTP_METHOD_POST,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'hold',
+            ],
+        ];
+        $requestData = ['id' => $order->getId()];
+        $result = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertTrue($result);
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderListTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderListTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..a0d9743fdbc4bca93211cc3bd2d97b6887556895
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderListTest.php
@@ -0,0 +1,74 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Sales\Service\V1;
+
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config;
+
+/**
+ * Class OrderListTest
+ * @package Magento\Sales\Service\V1
+ */
+class OrderListTest extends WebapiAbstract
+{
+    const RESOURCE_PATH = '/V1/orders';
+
+    const SERVICE_READ_NAME = 'salesOrderRepositoryV1';
+
+    const SERVICE_VERSION = 'V1';
+
+    /**
+     * @var \Magento\Framework\ObjectManagerInterface
+     */
+    protected $objectManager;
+
+    protected function setUp()
+    {
+        $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Sales/_files/order.php
+     */
+    public function testOrderList()
+    {
+        /** @var $searchCriteriaBuilder  \Magento\Framework\Api\SearchCriteriaBuilder */
+        $searchCriteriaBuilder = $this->objectManager->create(
+            'Magento\Framework\Api\SearchCriteriaBuilder'
+        );
+
+        /** @var $filterBuilder  \Magento\Framework\Api\FilterBuilder */
+        $filterBuilder = $this->objectManager->create(
+            'Magento\Framework\Api\FilterBuilder'
+        );
+
+        $searchCriteriaBuilder->addFilter(
+            [
+                $filterBuilder
+                    ->setField('status')
+                    ->setValue('processing')
+                    ->create(),
+            ]
+        );
+        $searchData = $searchCriteriaBuilder->create()->__toArray();
+
+        $requestData = ['criteria' => $searchData];
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '?' . http_build_query($requestData),
+                'httpMethod' => Config::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_READ_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_READ_NAME . 'getList',
+            ],
+        ];
+
+        $result = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertArrayHasKey('items', $result);
+        $this->assertCount(1, $result['items']);
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderStatusHistoryAddTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderStatusHistoryAddTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..c9a46103a9c033ea8958f4ab373c16bea9519d89
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderStatusHistoryAddTest.php
@@ -0,0 +1,83 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Sales\Service\V1;
+
+use Magento\Sales\Api\Data\OrderStatusHistoryInterface;
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config;
+
+/**
+ * Class OrderCommentAddTest
+ * @package Magento\Sales\Service\V1
+ */
+class OrderStatusHistoryAddTest extends WebapiAbstract
+{
+    const SERVICE_READ_NAME = 'salesOrderManagementV1';
+
+    const SERVICE_VERSION = 'V1';
+
+    const ORDER_INCREMENT_ID = '100000001';
+
+    /**
+     * @var \Magento\Framework\ObjectManagerInterface
+     */
+    protected $objectManager;
+
+    protected function setUp()
+    {
+        $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Sales/_files/order.php
+     */
+    public function testOrderCommentAdd()
+    {
+        /** @var \Magento\Sales\Model\Order $order */
+        $order = $this->objectManager->create('Magento\Sales\Model\Order');
+        $order->loadByIncrementId(self::ORDER_INCREMENT_ID);
+
+        $commentData = [
+            OrderStatusHistoryInterface::COMMENT => 'Hello',
+            OrderStatusHistoryInterface::ENTITY_ID => null,
+            OrderStatusHistoryInterface::IS_CUSTOMER_NOTIFIED => true,
+            OrderStatusHistoryInterface::CREATED_AT => null,
+            OrderStatusHistoryInterface::PARENT_ID => $order->getId(),
+            OrderStatusHistoryInterface::ENTITY_NAME => null,
+            OrderStatusHistoryInterface::STATUS => null,
+            OrderStatusHistoryInterface::IS_VISIBLE_ON_FRONT => true,
+        ];
+
+        $requestData = ['id' => $order->getId(), 'statusHistory' => $commentData];
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/order/' . $order->getId() . '/comment',
+                'httpMethod' => Config::HTTP_METHOD_POST,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_READ_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_READ_NAME . 'addComment',
+            ],
+        ];
+
+        $this->_webApiCall($serviceInfo, $requestData);
+
+        //Verification
+        $comments = $order->load($order->getId())->getAllStatusHistory();
+
+        $commentData = reset($comments);
+        foreach ($commentData as $key => $value) {
+            $this->assertEquals($commentData[OrderStatusHistoryInterface::COMMENT], $statusHistoryComment->getComment());
+            $this->assertEquals($commentData[OrderStatusHistoryInterface::PARENT_ID], $statusHistoryComment->getParentId());
+            $this->assertEquals(
+                $commentData[OrderStatusHistoryInterface::IS_CUSTOMER_NOTIFIED], $statusHistoryComment->getIsCustomerNotified()
+            );
+            $this->assertEquals(
+                $commentData[OrderStatusHistoryInterface::IS_VISIBLE_ON_FRONT], $statusHistoryComment->getIsVisibleOnFront()
+            );
+        }
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderUnHoldTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderUnHoldTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..41d877f25b75d03ae86929389cc008c932243e24
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderUnHoldTest.php
@@ -0,0 +1,43 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+namespace Magento\Sales\Service\V1;
+
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+class OrderUnHoldTest extends WebapiAbstract
+{
+    const SERVICE_VERSION = 'V1';
+
+    const SERVICE_NAME = 'salesOrderManagementV1';
+
+    /**
+     * @magentoApiDataFixture Magento/Sales/_files/order.php
+     */
+    public function testOrderUnHold()
+    {
+        $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+        /** @var \Magento\Sales\Model\Order $order */
+        $order = $objectManager->get('Magento\Sales\Model\Order')->loadByIncrementId('100000001');
+        if ($order->canHold()) {
+            $order->hold()->save();
+        }
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/order/' . $order->getId() . '/unhold',
+                'httpMethod' => RestConfig::HTTP_METHOD_POST,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'unHold',
+            ],
+        ];
+        $requestData = ['id' => $order->getId()];
+        $result = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertTrue($result);
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/ShipmentAddCommentTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/ShipmentAddCommentTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..dbdd8768fcedf49aa9c13e2c5eb55468c3c3e597
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/ShipmentAddCommentTest.php
@@ -0,0 +1,77 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Sales\Service\V1;
+
+use Magento\Sales\Api\Data\ShipmentCommentInterface;
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config;
+
+/**
+ * Class ShipmentAddCommentTest
+ */
+class ShipmentAddCommentTest extends WebapiAbstract
+{
+    /**
+     * Service read name
+     */
+    const SERVICE_READ_NAME = 'salesShipmentCommentRepositoryV1';
+
+    /**
+     * Service version
+     */
+    const SERVICE_VERSION = 'V1';
+
+    /**
+     * Shipment increment id
+     */
+    const SHIPMENT_INCREMENT_ID = '100000001';
+
+    /**
+     * @var \Magento\Framework\ObjectManagerInterface
+     */
+    protected $objectManager;
+
+    protected function setUp()
+    {
+        $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+    }
+
+    /**
+     * Test shipment add comment service
+     *
+     * @magentoApiDataFixture Magento/Sales/_files/shipment.php
+     */
+    public function testShipmentAddComment()
+    {
+        /** @var \Magento\Sales\Model\Resource\Order\Shipment\Collection $shipmentCollection */
+        $shipmentCollection = $this->objectManager->get('Magento\Sales\Model\Resource\Order\Shipment\Collection');
+        $shipment = $shipmentCollection->getFirstItem();
+
+        $commentData = [
+            ShipmentCommentInterface::COMMENT => 'Hello world!',
+            ShipmentCommentInterface::ENTITY_ID => null,
+            ShipmentCommentInterface::CREATED_AT => null,
+            ShipmentCommentInterface::PARENT_ID => $shipment->getId(),
+            ShipmentCommentInterface::IS_VISIBLE_ON_FRONT => true,
+            ShipmentCommentInterface::IS_CUSTOMER_NOTIFIED => true,
+        ];
+
+        $requestData = ['entity' => $commentData];
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/shipment/comment',
+                'httpMethod' => Config::HTTP_METHOD_POST,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_READ_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_READ_NAME . 'save',
+            ],
+        ];
+
+        $result = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertNotEmpty($result);
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/ShipmentAddTrackTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/ShipmentAddTrackTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..af1e624793ab4adf4db0d05c5d0ce53efadbfb7a
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/ShipmentAddTrackTest.php
@@ -0,0 +1,83 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Sales\Service\V1;
+
+use Magento\Sales\Api\Data\ShipmentTrackInterface;
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config;
+
+/**
+ * Class ShipmentAddTrackTest
+ */
+class ShipmentAddTrackTest extends WebapiAbstract
+{
+    /**
+     * Service read name
+     */
+    const SERVICE_READ_NAME = 'salesShipmentTrackRepositoryV1';
+
+    /**
+     * Service version
+     */
+    const SERVICE_VERSION = 'V1';
+
+    /**
+     * Shipment increment id
+     */
+    const SHIPMENT_INCREMENT_ID = '100000001';
+
+    /**
+     * @var \Magento\Framework\ObjectManagerInterface
+     */
+    protected $objectManager;
+
+    protected function setUp()
+    {
+        $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+    }
+
+    /**
+     * Test shipment add track service
+     *
+     * @magentoApiDataFixture Magento/Sales/_files/shipment.php
+     */
+    public function testShipmentAddTrack()
+    {
+        /** @var \Magento\Sales\Model\Order\Shipment $shipment */
+        $shipmentCollection = $this->objectManager->get('Magento\Sales\Model\Resource\Order\Shipment\Collection');
+        $shipment = $shipmentCollection->getFirstItem();
+
+        $trackData = [
+            ShipmentTrackInterface::ENTITY_ID => null,
+            ShipmentTrackInterface::ORDER_ID => $shipment->getOrderId(),
+            ShipmentTrackInterface::CREATED_AT => null,
+            ShipmentTrackInterface::PARENT_ID => $shipment->getId(),
+            ShipmentTrackInterface::WEIGHT => 20,
+            ShipmentTrackInterface::QTY => 5,
+            ShipmentTrackInterface::TRACK_NUMBER => 2,
+            ShipmentTrackInterface::DESCRIPTION => 'Shipment description',
+            ShipmentTrackInterface::TITLE => 'Shipment title',
+            ShipmentTrackInterface::CARRIER_CODE => \Magento\Sales\Model\Order\Shipment\Track::CUSTOM_CARRIER_CODE,
+            ShipmentTrackInterface::CREATED_AT => null,
+            ShipmentTrackInterface::UPDATED_AT => null,
+        ];
+
+        $requestData = ['entity' => $trackData];
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/shipment/track',
+                'httpMethod' => Config::HTTP_METHOD_POST,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_READ_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_READ_NAME . 'save',
+            ],
+        ];
+
+        $result = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertNotEmpty($result);
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/ShipmentCommentsListTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/ShipmentCommentsListTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..ea79127aee85f7b1a81369f6671c90e2afa7020c
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/ShipmentCommentsListTest.php
@@ -0,0 +1,56 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Sales\Service\V1;
+
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+/**
+ * Class ShipmentCommentsListTest
+ */
+class ShipmentCommentsListTest extends WebapiAbstract
+{
+    const SERVICE_NAME = 'salesShipmentManagementV1';
+
+    const SERVICE_VERSION = 'V1';
+
+    /**
+     * @magentoApiDataFixture Magento/Sales/_files/shipment.php
+     */
+    public function testShipmentCommentsList()
+    {
+        $comment = 'Test comment';
+        $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+
+        /** @var \Magento\Sales\Model\Resource\Order\Shipment\Collection $shipmentCollection */
+        $shipmentCollection = $objectManager->get('Magento\Sales\Model\Resource\Order\Shipment\Collection');
+        $shipment = $shipmentCollection->getFirstItem();
+        $shipmentComment = $objectManager->get('Magento\Sales\Model\Order\Shipment\Comment');
+        $shipmentComment->setComment($comment);
+        $shipmentComment->setParentId($shipment->getId());
+        $shipmentComment->save();
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/shipment/' . $shipment->getId() . '/comments',
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'getCommentsList',
+            ],
+        ];
+        $requestData = ['id' => $shipment->getId()];
+        $result = $this->_webApiCall($serviceInfo, $requestData);
+        // TODO Test fails, due to the inability of the framework API to handle data collection
+        foreach ($result['items'] as $item) {
+            /** @var \Magento\Sales\Model\Order\Shipment\Comment $shipmentHistoryStatus */
+            $shipmentHistoryStatus = $objectManager->get('Magento\Sales\Model\Order\Shipment\Comment')
+                ->load($item['entity_id']);
+            $this->assertEquals($shipmentHistoryStatus->getComment(), $item['comment']);
+        }
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/ShipmentCreateTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/ShipmentCreateTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..b73cd43ee8299cdf7e612e755928cf4043315af9
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/ShipmentCreateTest.php
@@ -0,0 +1,88 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Sales\Service\V1;
+
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config;
+
+/**
+ * Class ShipmentCreateTest
+ */
+class ShipmentCreateTest extends WebapiAbstract
+{
+    const RESOURCE_PATH = '/V1/shipment';
+
+    const SERVICE_READ_NAME = 'salesShipmentRepositoryV1';
+
+    const SERVICE_VERSION = 'V1';
+
+    /**
+     * @var \Magento\Framework\ObjectManagerInterface
+     */
+    protected $objectManager;
+
+    protected function setUp()
+    {
+        $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+    }
+    /**
+     * @magentoApiDataFixture Magento/Sales/_files/order.php
+     */
+    public function testInvoke()
+    {
+        /** @var \Magento\Sales\Model\Order $order */
+        $order = $this->objectManager->create('Magento\Sales\Model\Order')->loadByIncrementId('100000001');
+        $orderItem = current($order->getAllItems());
+        $items = [
+            [
+                'order_item_id' => $orderItem->getId(),
+                'qty' => $orderItem->getQtyOrdered(),
+                'additional_data' => null,
+                'description' => null,
+                'entity_id' => null,
+                'name' => null,
+                'parent_id' => null,
+                'price' => null,
+                'product_id' => null,
+                'row_total' => null,
+                'sku' => null,
+                'weight' => null,
+            ],
+        ];
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH,
+                'httpMethod' => Config::HTTP_METHOD_POST,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_READ_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_READ_NAME . 'save',
+            ],
+        ];
+        $data = [
+            'order_id' => $order->getId(),
+            'entity_id' => null,
+            'store_id' => null,
+            'total_weight' => null,
+            'total_qty' => null,
+            'email_sent' => null,
+            'customer_id' => null,
+            'shipping_address_id' => null,
+            'billing_address_id' => null,
+            'shipment_status' => null,
+            'increment_id' => null,
+            'created_at' => null,
+            'updated_at' => null,
+//            'packages' => null,
+            'shipping_label' => null,
+            'tracks' => [],
+            'items' => $items,
+            'comments' => [],
+        ];
+        $result = $this->_webApiCall($serviceInfo, ['entity' => $data]);
+        $this->assertNotEmpty($result);
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/ShipmentEmailTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/ShipmentEmailTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..3329e8addf061b734d575a3a99883897ae1e8d4c
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/ShipmentEmailTest.php
@@ -0,0 +1,43 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+namespace Magento\Sales\Service\V1;
+
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+/**
+ * Class ShipmentEmailTest
+ */
+class ShipmentEmailTest extends WebapiAbstract
+{
+    const SERVICE_VERSION = 'V1';
+
+    const SERVICE_NAME = 'salesShipmentManagementV1';
+
+    /**
+     * @magentoApiDataFixture Magento/Sales/_files/shipment.php
+     */
+    public function testShipmentEmail()
+    {
+        $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+        $shipmentCollection = $objectManager->get('Magento\Sales\Model\Resource\Order\Shipment\Collection');
+        $shipment = $shipmentCollection->getFirstItem();
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/shipment/' . $shipment->getId() . '/email',
+                'httpMethod' => RestConfig::HTTP_METHOD_POST,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'notify',
+            ],
+        ];
+        $requestData = ['id' => $shipment->getId()];
+        $result = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertTrue($result);
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/ShipmentGetTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/ShipmentGetTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..5bc2a21698d8dedd25c4b0f6100d21bd9d446bef
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/ShipmentGetTest.php
@@ -0,0 +1,76 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Sales\Service\V1;
+
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config;
+
+/**
+ * Class ShipmentGetTest
+ */
+class ShipmentGetTest extends WebapiAbstract
+{
+    const RESOURCE_PATH = '/V1/shipment';
+    const SERVICE_READ_NAME = 'salesShipmentRepositoryV1';
+    const SERVICE_VERSION = 'V1';
+
+    /**
+     * @var \Magento\Framework\ObjectManagerInterface
+     */
+    protected $objectManager;
+
+    protected function setUp()
+    {
+        $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Sales/_files/shipment.php
+     */
+    public function testShipmentGet()
+    {
+        /** @var \Magento\Sales\Model\Order\Shipment $shipment */
+        $shipmentCollection = $this->objectManager->get('Magento\Sales\Model\Resource\Order\Shipment\Collection');
+        $shipment = $shipmentCollection->getFirstItem();
+        $shipment->load($shipment->getId());
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/' . $shipment->getId(),
+                'httpMethod' => Config::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_READ_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_READ_NAME . 'get',
+            ],
+        ];
+        $result = $this->_webApiCall($serviceInfo, ['id' => $shipment->getId()]);
+        $data = $result;
+        $this->assertArrayHasKey('items', $result);
+        $this->assertArrayHasKey('tracks', $result);
+        unset($data['items']);
+        unset($data['packages']);
+        unset($data['tracks']);
+        foreach ($data as $key => $value) {
+            if (!empty($value)) {
+                $this->assertEquals($shipment->getData($key), $value, $key);
+            }
+        }
+        $shipmentItem = $this->objectManager->get('Magento\Sales\Model\Order\Shipment\Item');
+        foreach ($result['items'] as $item) {
+            $shipmentItem->load($item['entity_id']);
+            foreach ($item as $key => $value) {
+                $this->assertEquals($shipmentItem->getData($key), $value, $key);
+            }
+        }
+        $shipmentTrack = $this->objectManager->get('Magento\Sales\Model\Order\Shipment\Track');
+        foreach ($result['tracks'] as $item) {
+            $shipmentTrack->load($item['entity_id']);
+            foreach ($item as $key => $value) {
+                $this->assertEquals($shipmentTrack->getData($key), $value, $key);
+            }
+        }
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/ShipmentLabelGetTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/ShipmentLabelGetTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..f03bad65a110117b8bdbf1f2e199047862f01ffc
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/ShipmentLabelGetTest.php
@@ -0,0 +1,53 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Sales\Service\V1;
+
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config;
+
+/**
+ * Class ShipmentLabelGetTest
+ */
+class ShipmentLabelGetTest extends WebapiAbstract
+{
+    const RESOURCE_PATH = '/V1/shipment';
+    const SERVICE_READ_NAME = 'salesShipmentManagementV1';
+    const SERVICE_VERSION = 'V1';
+
+    /**
+     * @var \Magento\Framework\ObjectManagerInterface
+     */
+    protected $objectManager;
+
+    protected function setUp()
+    {
+        $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Sales/_files/shipment.php
+     */
+    public function testShipmentGet()
+    {
+        /** @var \Magento\Sales\Model\Order\Shipment $shipment */
+        $shipmentCollection = $this->objectManager->get('Magento\Sales\Model\Resource\Order\Shipment\Collection');
+        $shipment = $shipmentCollection->getFirstItem();
+        $shipment->setShippingLabel('test_shipping_label');
+        $shipment->save();
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/' . $shipment->getId() . '/label',
+                'httpMethod' => Config::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_READ_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_READ_NAME . 'getLabel',
+            ],
+        ];
+        $result = $this->_webApiCall($serviceInfo, ['id' => $shipment->getId()]);
+        $this->assertEquals($result, 'test_shipping_label');
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/ShipmentListTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/ShipmentListTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..b68d6900eab7879332577843259123c8465b4f2a
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/ShipmentListTest.php
@@ -0,0 +1,67 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Sales\Service\V1;
+
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config;
+
+/**
+ * Class ShipmentListTest
+ */
+class ShipmentListTest extends WebapiAbstract
+{
+    const RESOURCE_PATH = '/V1/shipments';
+
+    const SERVICE_READ_NAME = 'salesShipmentRepositoryV1';
+
+    const SERVICE_VERSION = 'V1';
+
+    /**
+     * @var \Magento\Framework\ObjectManagerInterface
+     */
+    protected $objectManager;
+
+    protected function setUp()
+    {
+        $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Sales/_files/shipment.php
+     */
+    public function testShipmentList()
+    {
+        /** @var $searchCriteriaBuilder  \Magento\Framework\Api\SearchCriteriaBuilder */
+        $searchCriteriaBuilder = $this->objectManager->create(
+            'Magento\Framework\Api\SearchCriteriaBuilder'
+        );
+
+        /** @var $filterBuilder  \Magento\Framework\Api\FilterBuilder */
+        $filterBuilder = $this->objectManager->create(
+            'Magento\Framework\Api\FilterBuilder'
+        );
+
+        $searchCriteriaBuilder->addFilter([$filterBuilder->setField('shipment_status')->setValue(1)->create()]);
+        $searchData = $searchCriteriaBuilder->create()->__toArray();
+
+        $requestData = ['criteria' => $searchData];
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '?' . http_build_query($requestData),
+                'httpMethod' => Config::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_READ_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_READ_NAME . 'getList',
+            ],
+        ];
+
+        $result = $this->_webApiCall($serviceInfo, $requestData);
+        // TODO Test fails, due to the inability of the framework API to handle data collection
+        $this->assertArrayHasKey('items', $result);
+        $this->assertCount(1, $result['items']);
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/ShipmentRemoveTrackTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/ShipmentRemoveTrackTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..4dc7637b37de6588e998f919d7d1e8c00f6ba0fe
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/ShipmentRemoveTrackTest.php
@@ -0,0 +1,87 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Sales\Service\V1;
+
+use Magento\Sales\Api\Data\ShipmentTrackInterface;
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config;
+
+/**
+ * Class ShipmentRemoveTrackTest
+ */
+class ShipmentRemoveTrackTest extends WebapiAbstract
+{
+    /**
+     * Service read name
+     */
+    const SERVICE_READ_NAME = 'salesShipmentTrackRepositoryV1';
+
+    /**
+     * Service version
+     */
+    const SERVICE_VERSION = 'V1';
+
+    /**
+     * Shipment increment id
+     */
+    const SHIPMENT_INCREMENT_ID = '100000001';
+
+    /**
+     * @var \Magento\Framework\ObjectManagerInterface
+     */
+    protected $objectManager;
+
+    protected function setUp()
+    {
+        $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+    }
+
+    /**
+     * Test shipment remove track service
+     *
+     * @magentoApiDataFixture Magento/Sales/_files/shipment.php
+     */
+    public function testShipmentRemoveTrack()
+    {
+        /** @var \Magento\Sales\Model\Order\Shipment $shipment */
+        $shipmentCollection = $this->objectManager->get('Magento\Sales\Model\Resource\Order\Shipment\Collection');
+        $shipment = $shipmentCollection->getFirstItem();
+
+        /** @var \Magento\Sales\Model\Order\Shipment\Track $track */
+        $track = $this->objectManager->create('Magento\Sales\Model\Order\Shipment\TrackFactory')->create();
+        $track->addData(
+            [
+                ShipmentTrackInterface::ENTITY_ID => null,
+                ShipmentTrackInterface::ORDER_ID => 12,
+                ShipmentTrackInterface::CREATED_AT => null,
+                ShipmentTrackInterface::PARENT_ID => $shipment->getId(),
+                ShipmentTrackInterface::WEIGHT => 20,
+                ShipmentTrackInterface::QTY => 5,
+                ShipmentTrackInterface::TRACK_NUMBER => 2,
+                ShipmentTrackInterface::DESCRIPTION => 'Shipment description',
+                ShipmentTrackInterface::TITLE => 'Shipment title',
+                ShipmentTrackInterface::CARRIER_CODE => \Magento\Sales\Model\Order\Shipment\Track::CUSTOM_CARRIER_CODE,
+                ShipmentTrackInterface::CREATED_AT => null,
+                ShipmentTrackInterface::UPDATED_AT => null,
+            ]
+        );
+        $track->save();
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/shipment/track/' . $track->getId(),
+                'httpMethod' => Config::HTTP_METHOD_DELETE,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_READ_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_READ_NAME . 'deleteById',
+            ],
+        ];
+
+        $result = $this->_webApiCall($serviceInfo, ['id' => $track->getId()]);
+        $this->assertNotEmpty($result);
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/TransactionTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/TransactionTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..2138fd6a6afa7ec3085fcdd5d89f80004551d2fa
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/TransactionTest.php
@@ -0,0 +1,190 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+namespace Magento\Sales\Service\V1;
+
+use Magento\Sales\Model\Order;
+use Magento\Sales\Model\Order\Payment;
+use Magento\Sales\Model\Order\Payment\Transaction;
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config;
+
+/**
+ * Class TransactionReadTest
+ */
+class TransactionTest extends WebapiAbstract
+{
+    /**
+     * Service read name
+     */
+    const SERVICE_READ_NAME = 'salesTransactionRepositoryV1';
+
+    /**
+     * Resource path for REST
+     */
+    const RESOURCE_PATH = '/V1/transactions';
+
+    /**
+     * Service version
+     */
+    const SERVICE_VERSION = 'V1';
+
+    /**
+     * @var \Magento\Framework\ObjectManagerInterface
+     */
+    protected $objectManager;
+
+    protected function setUp()
+    {
+        $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+    }
+
+    /**
+     * Tests list of order transactions
+     *
+     * @magentoApiDataFixture Magento/Sales/_files/transactions_detailed.php
+     */
+    public function testTransactionGet()
+    {
+        /** @var Order $order */
+        $order = $this->objectManager->create('Magento\Sales\Model\Order');
+        $order->loadByIncrementId('100000006');
+
+        /** @var Payment $payment */
+        $payment = $order->getPayment();
+        /** @var Transaction $transaction */
+        $transaction = $payment->getTransaction('trx_auth');
+
+        $childTransactions = $transaction->getChildTransactions();
+        $childTransaction = reset($childTransactions);
+
+        $expectedData = $this->getPreparedTransactionData($transaction);
+        $childTransactionData = $this->getPreparedTransactionData($childTransaction);
+        $expectedData['child_transactions'][] = $childTransactionData;
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/' . $transaction->getId(),
+                'httpMethod' => Config::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_READ_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_READ_NAME . 'get',
+            ],
+        ];
+        $result = $this->_webApiCall($serviceInfo, ['id' => $transaction->getId()]);
+        ksort($expectedData);
+        ksort($result);
+        $this->assertEquals($expectedData, $result);
+    }
+
+    /**
+     * Tests list of order transactions
+     * @dataProvider filtersDataProvider
+     */
+    public function testTransactionList($filters)
+    {
+        /** @var Order $order */
+        $order = $this->objectManager->create('Magento\Sales\Model\Order');
+        $order->loadByIncrementId('100000006');
+
+        /** @var Payment $payment */
+        $payment = $order->getPayment();
+        /** @var Transaction $transaction */
+        $transaction = $payment->getTransaction('trx_auth');
+
+        $childTransactions = $transaction->getChildTransactions();
+
+        $childTransaction = reset($childTransactions);
+
+        /** @var $searchCriteriaBuilder  \Magento\Framework\Api\SearchCriteriaBuilder */
+        $searchCriteriaBuilder = $this->objectManager->create(
+            'Magento\Framework\Api\SearchCriteriaBuilder'
+        );
+
+        $searchCriteriaBuilder->addFilter($filters);
+        $searchData = $searchCriteriaBuilder->create()->__toArray();
+
+        $requestData = ['criteria' => $searchData];
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '?' . http_build_query($requestData),
+                'httpMethod' => Config::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_READ_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_READ_NAME . 'getList',
+            ],
+        ];
+        $result = $this->_webApiCall($serviceInfo, $requestData);
+
+        $this->assertArrayHasKey('items', $result);
+
+        $transactionData = $this->getPreparedTransactionData($transaction);
+        $childTransactionData = $this->getPreparedTransactionData($childTransaction);
+        $transactionData['child_transactions'][] = $childTransactionData;
+        $expectedData = [$transactionData, $childTransactionData];
+
+        $this->assertEquals($expectedData, $result['items']);
+    }
+
+    /**
+     * @param Transaction $transaction
+     * @return array
+     */
+    private function getPreparedTransactionData(Transaction $transaction)
+    {
+        $additionalInfo = [];
+        foreach ($transaction->getAdditionalInformation() as $value) {
+            $additionalInfo[] = $value;
+        }
+
+        $expectedData = ['transaction_id' => (int)$transaction->getId()];
+
+        if (!is_null($transaction->getParentId())) {
+            $expectedData['parent_id'] = (int)$transaction->getParentId();
+        }
+
+        $expectedData = array_merge(
+            $expectedData,
+            [
+                'order_id' => (int)$transaction->getOrderId(),
+                'payment_id' => (int)$transaction->getPaymentId(),
+                'txn_id' => $transaction->getTxnId(),
+                'parent_txn_id' => ($transaction->getParentTxnId() ? (string)$transaction->getParentTxnId() : ''),
+                'txn_type' => $transaction->getTxnType(),
+                'is_closed' => (int)$transaction->getIsClosed(),
+                'additional_information' => ['data'],
+                'created_at' => $transaction->getCreatedAt(),
+                'child_transactions' => [],
+            ]
+        );
+
+        return $expectedData;
+    }
+
+    /**
+     * @return array
+     */
+    public function filtersDataProvider()
+    {
+        /** @var $filterBuilder  \Magento\Framework\Api\FilterBuilder */
+        $filterBuilder = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(
+            'Magento\Framework\Api\FilterBuilder'
+        );
+
+        return [
+            [
+                [
+                    $filterBuilder->setField('created_at')->setValue('2020-12-12 00:00:00')
+                        ->setConditionType('lteq')->create(),
+                ],
+            ]
+        ];
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Tax/Api/TaxClassRepositoryTest.php b/dev/tests/api-functional/testsuite/Magento/Tax/Api/TaxClassRepositoryTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..4a462cbc3fb321bcc0447c9e4dda6aef70c33633
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Tax/Api/TaxClassRepositoryTest.php
@@ -0,0 +1,309 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+namespace Magento\Tax\Api;
+
+use Magento\Framework\Api\FilterBuilder;
+use Magento\Framework\Api\SearchCriteriaBuilder;
+use Magento\Framework\Exception\NoSuchEntityException;
+use Magento\Tax\Api\Data\TaxClassDataBuilder;
+use Magento\Tax\Model\ClassModelRegistry;
+use Magento\TestFramework\Helper\Bootstrap;
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+/**
+ * Tests for tax class service.
+ */
+class TaxClassRepositoryTest extends WebapiAbstract
+{
+    const SERVICE_NAME = 'taxTaxClassRepositoryV1';
+    const SERVICE_VERSION = 'V1';
+    const RESOURCE_PATH = '/V1/taxClass';
+
+    /** @var SearchCriteriaBuilder */
+    private $searchCriteriaBuilder;
+
+    /** @var FilterBuilder */
+    private $filterBuilder;
+
+    /** @var TaxClassDataBuilder */
+    private $taxClassBuilder;
+
+    /** @var TaxClassRepositoryInterface */
+    private $taxClassRepository;
+
+    /** @var ClassModelRegistry */
+    private $taxClassRegistry;
+
+    const SAMPLE_TAX_CLASS_NAME = 'Wholesale Customer';
+
+    /**
+     * Execute per test initialization.
+     */
+    public function setUp()
+    {
+        $this->searchCriteriaBuilder = Bootstrap::getObjectManager()->create(
+            'Magento\Framework\Api\SearchCriteriaBuilder'
+        );
+        $this->filterBuilder = Bootstrap::getObjectManager()->create(
+            'Magento\Framework\Api\FilterBuilder'
+        );
+        $this->taxClassBuilder = Bootstrap::getObjectManager()->create(
+            'Magento\Tax\Api\Data\TaxClassDataBuilder'
+        );
+        $this->taxClassRegistry = Bootstrap::getObjectManager()->create(
+            'Magento\Tax\Model\ClassModelRegistry'
+        );
+        $this->taxClassRepository = Bootstrap::getObjectManager()->create(
+            'Magento\Tax\Model\TaxClass\Repository',
+            ['classModelRegistry' => $this->taxClassRegistry]
+        );
+    }
+
+    /**
+     * Test create Data\TaxClassInterface
+     */
+    public function testCreateTaxClass()
+    {
+        $taxClassName = self::SAMPLE_TAX_CLASS_NAME . uniqid();
+        /** @var  \Magento\Tax\Api\Data\TaxClassInterface $taxClassDataObject */
+        $taxClassDataObject = $this->taxClassBuilder->setClassName($taxClassName)
+            ->setClassType(TaxClassManagementInterface::TYPE_CUSTOMER)
+            ->create();
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH,
+                'httpMethod' => RestConfig::HTTP_METHOD_POST,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Save',
+            ],
+        ];
+
+        $requestData = ['taxClass' => [
+                'class_id' => $taxClassDataObject->getClassId(),
+                'class_name' => $taxClassDataObject->getClassName(),
+                'class_type' => $taxClassDataObject->getClassType(),
+            ],
+        ];
+        $taxClassId = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertNotNull($taxClassId);
+
+        //Verify by getting the Data\TaxClassInterface
+        $taxClassData = $this->taxClassRepository->get($taxClassId);
+        $this->assertEquals($taxClassData->getClassName(), $taxClassName);
+        $this->assertEquals($taxClassData->getClassType(), TaxClassManagementInterface::TYPE_CUSTOMER);
+    }
+
+    /**
+     * Test create Data\TaxClassInterface
+     */
+    public function testUpdateTaxClass()
+    {
+        //Create Tax Class
+        $taxClassDataObject = $this->taxClassBuilder->setClassName(self::SAMPLE_TAX_CLASS_NAME . uniqid())
+            ->setClassType(TaxClassManagementInterface::TYPE_CUSTOMER)
+            ->create();
+        $taxClassId = $this->taxClassRepository->save($taxClassDataObject);
+        $this->assertNotNull($taxClassId);
+
+        //Update Tax Class
+        $updatedTaxClassName = self::SAMPLE_TAX_CLASS_NAME . uniqid();
+        $updatedTaxClassDataObject = $this->taxClassBuilder
+            ->populate($taxClassDataObject)
+            ->setClassName($updatedTaxClassName)
+            ->create();
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/' . $taxClassId,
+                'httpMethod' => RestConfig::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Save',
+            ],
+        ];
+
+        $taxClass = [
+                'class_id' => $updatedTaxClassDataObject->getClassId(),
+                'class_name' => $updatedTaxClassDataObject->getClassName(),
+                'class_type' => $updatedTaxClassDataObject->getClassType(),
+            ];
+
+        $requestData = ['taxClass' => $taxClass, 'ClassId' => $taxClassId];
+
+        $this->assertEquals($taxClassId, $this->_webApiCall($serviceInfo, $requestData));
+
+        //Verify by getting the Data\TaxClassInterface
+        $this->taxClassRegistry->remove($taxClassId);
+        $taxClassData = $this->taxClassRepository->get($taxClassId);
+        $this->assertEquals($taxClassData->getClassName(), $updatedTaxClassName);
+    }
+
+    public function testGetTaxClass()
+    {
+        //Create Tax Class
+        $taxClassName = self::SAMPLE_TAX_CLASS_NAME . uniqid();
+        $taxClassDataObject = $this->taxClassBuilder->setClassName($taxClassName)
+            ->setClassType(TaxClassManagementInterface::TYPE_CUSTOMER)
+            ->create();
+        $taxClassId = $this->taxClassRepository->save($taxClassDataObject);
+        $this->assertNotNull($taxClassId);
+
+        //Verify by getting the Data\TaxClassInterface
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/' . $taxClassId,
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Get',
+            ],
+        ];
+        $requestData = ['taxClassId' => $taxClassId];
+        $taxClassData = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertEquals($taxClassData[Data\TaxClassInterface::KEY_NAME], $taxClassName);
+        $this->assertEquals(
+            $taxClassData[Data\TaxClassInterface::KEY_TYPE],
+            TaxClassManagementInterface::TYPE_CUSTOMER
+        );
+    }
+
+    /**
+     * Test delete Tax class
+     */
+    public function testDeleteTaxClass()
+    {
+        $taxClassDataObject = $this->taxClassBuilder->setClassName(self::SAMPLE_TAX_CLASS_NAME . uniqid())
+            ->setClassType(TaxClassManagementInterface::TYPE_CUSTOMER)
+            ->create();
+        $taxClassId = $this->taxClassRepository->save($taxClassDataObject);
+        $this->assertNotNull($taxClassId);
+
+        //Verify by getting the Data\TaxClassInterface
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/' . $taxClassId,
+                'httpMethod' => RestConfig::HTTP_METHOD_DELETE,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'DeleteById',
+            ],
+        ];
+        $requestData = ['taxClassId' => $taxClassId];
+        $result = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertTrue($result);
+
+        try {
+            $this->taxClassRegistry->remove($taxClassId);
+            $this->taxClassRepository->get($taxClassId);
+            $this->fail("Tax class was not expected to be returned after being deleted.");
+        } catch (NoSuchEntityException $e) {
+            $this->assertEquals('No such entity with class_id = ' . $taxClassId, $e->getMessage());
+        }
+    }
+
+    /**
+     * Test with a single filter
+     */
+    public function testSearchTaxClass()
+    {
+        $taxClassName = 'Retail Customer';
+        $taxClassNameField = Data\TaxClassInterface::KEY_NAME;
+        $filter = $this->filterBuilder->setField($taxClassNameField)
+            ->setValue($taxClassName)
+            ->create();
+        $this->searchCriteriaBuilder->addFilter([$filter]);
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/search',
+                'httpMethod' => RestConfig::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'GetList',
+            ],
+        ];
+        $searchData = $this->searchCriteriaBuilder->create()->__toArray();
+        $requestData = ['searchCriteria' => $searchData];
+        $searchResults = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertEquals(1, $searchResults['total_count']);
+        $this->assertEquals($taxClassName, $searchResults['items'][0][$taxClassNameField]);
+    }
+
+    /**
+     * Test using multiple filters
+     */
+    public function testSearchTaxClassMultipleFilterGroups()
+    {
+        $productTaxClass = [
+            Data\TaxClassInterface::KEY_NAME => 'Taxable Goods',
+            Data\TaxClassInterface::KEY_TYPE => 'PRODUCT',
+        ];
+        $customerTaxClass = [Data\TaxClassInterface::KEY_NAME => 'Retail Customer',
+            Data\TaxClassInterface::KEY_TYPE => 'CUSTOMER', ];
+
+        $filter1 = $this->filterBuilder->setField(Data\TaxClassInterface::KEY_NAME)
+            ->setValue($productTaxClass[Data\TaxClassInterface::KEY_NAME])
+            ->create();
+        $filter2 = $this->filterBuilder->setField(Data\TaxClassInterface::KEY_NAME)
+            ->setValue($customerTaxClass[Data\TaxClassInterface::KEY_NAME])
+            ->create();
+        $filter3 = $this->filterBuilder->setField(Data\TaxClassInterface::KEY_TYPE)
+            ->setValue($productTaxClass[Data\TaxClassInterface::KEY_TYPE])
+            ->create();
+        $filter4 = $this->filterBuilder->setField(Data\TaxClassInterface::KEY_TYPE)
+            ->setValue($customerTaxClass[Data\TaxClassInterface::KEY_TYPE])
+            ->create();
+
+        /**
+         * (class_name == 'Retail Customer' || class_name == 'Taxable Goods)
+         * && ( class_type == 'CUSTOMER' || class_type == 'PRODUCT')
+         */
+        $this->searchCriteriaBuilder->addFilter([$filter1, $filter2]);
+        $this->searchCriteriaBuilder->addFilter([$filter3, $filter4]);
+        $searchCriteria = $this->searchCriteriaBuilder->setCurrentPage(1)->setPageSize(10)->create();
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/search',
+                'httpMethod' => RestConfig::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'GetList',
+            ],
+        ];
+        $searchData = $searchCriteria->__toArray();
+        $requestData = ['searchCriteria' => $searchData];
+        $searchResults = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertEquals(2, $searchResults['total_count']);
+        $this->assertEquals($productTaxClass[Data\TaxClassInterface::KEY_NAME],
+            $searchResults['items'][0][Data\TaxClassInterface::KEY_NAME]);
+        $this->assertEquals($customerTaxClass[Data\TaxClassInterface::KEY_NAME],
+            $searchResults['items'][1][Data\TaxClassInterface::KEY_NAME]);
+
+        /** class_name == 'Retail Customer' && ( class_type == 'CUSTOMER' || class_type == 'PRODUCT') */
+        $this->searchCriteriaBuilder->addFilter([$filter2]);
+        $this->searchCriteriaBuilder->addFilter([$filter3, $filter4]);
+        $searchCriteria = $this->searchCriteriaBuilder->create();
+        $searchData = $searchCriteria->__toArray();
+        $requestData = ['searchCriteria' => $searchData];
+        $searchResults = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertEquals(1, $searchResults['total_count']);
+        $this->assertEquals($customerTaxClass[Data\TaxClassInterface::KEY_NAME],
+            $searchResults['items'][0][Data\TaxClassInterface::KEY_NAME]);
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Tax/Api/TaxRateRepositoryTest.php b/dev/tests/api-functional/testsuite/Magento/Tax/Api/TaxRateRepositoryTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..0122bdfa3d7d1e751f1aa709e35f13f3cdca0639
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Tax/Api/TaxRateRepositoryTest.php
@@ -0,0 +1,660 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+namespace Magento\Tax\Api;
+
+use Magento\Framework\Api\FilterBuilder;
+use Magento\Framework\Api\SearchCriteria;
+use Magento\Framework\Api\SearchCriteriaBuilder;
+use Magento\Framework\Api\SortOrderBuilder;
+use Magento\Tax\Api\Data\TaxRateInterface as TaxRate;
+use Magento\TestFramework\Helper\Bootstrap;
+use Magento\TestFramework\TestCase\WebapiAbstract;
+
+class TaxRateRepositoryTest extends WebapiAbstract
+{
+    const SERVICE_NAME = "taxTaxRateRepositoryV1";
+    const SERVICE_VERSION = "V1";
+    const RESOURCE_PATH = "/V1/taxRate";
+
+    /** @var \Magento\Tax\Model\Calculation\Rate[] */
+    private $fixtureTaxRates;
+
+    /** @var \Magento\Tax\Model\ClassModel[] */
+    private $fixtureTaxClasses;
+
+    /** @var \Magento\Tax\Model\Calculation\Rule[] */
+    private $fixtureTaxRules;
+
+    /**
+     * @var \Magento\Tax\Api\TaxRateRepositoryInterface
+     */
+    private $taxRateService;
+
+    /** @var FilterBuilder */
+    private $filterBuilder;
+
+    /** @var SearchCriteriaBuilder */
+    private $searchCriteriaBuilder;
+
+    /** @var  SortOrderBuilder */
+    private $sortOrderBuilder;
+
+    /**
+     * Other rates created during tests, to be deleted in tearDown()
+     *
+     * @var \Magento\Tax\Model\Calculation\Rate[]
+     */
+    private $otherRates = [];
+
+    /**
+     * Execute per test initialization.
+     */
+    public function setUp()
+    {
+        $objectManager = Bootstrap::getObjectManager();
+        $this->taxRateService = $objectManager->get('Magento\Tax\Api\TaxRateRepositoryInterface');
+        $this->searchCriteriaBuilder = $objectManager->create(
+            'Magento\Framework\Api\SearchCriteriaBuilder'
+        );
+        $this->filterBuilder = $objectManager->create(
+            'Magento\Framework\Api\FilterBuilder'
+        );
+        $this->sortOrderBuilder = $objectManager->create(
+            'Magento\Framework\Api\SortOrderBuilder'
+        );
+        /** Initialize tax classes, tax rates and tax rules defined in fixture Magento/Tax/_files/tax_classes.php */
+        $this->getFixtureTaxRates();
+        $this->getFixtureTaxClasses();
+        $this->getFixtureTaxRules();
+    }
+
+    public function tearDown()
+    {
+        $taxRules = $this->getFixtureTaxRules();
+        if (count($taxRules)) {
+            $taxRates = $this->getFixtureTaxRates();
+            $taxClasses = $this->getFixtureTaxClasses();
+            foreach ($taxRules as $taxRule) {
+                $taxRule->delete();
+            }
+            foreach ($taxRates as $taxRate) {
+                $taxRate->delete();
+            }
+            foreach ($taxClasses as $taxClass) {
+                $taxClass->delete();
+            }
+        }
+        if (count($this->otherRates)) {
+            foreach ($this->otherRates as $taxRate) {
+                $taxRate->delete();
+            }
+        }
+    }
+
+    public function testCreateTaxRateExistingCode()
+    {
+        $data = [
+            'tax_rate' => [
+                'tax_country_id' => 'US',
+                'tax_region_id' => 12,
+                'tax_postcode' => '*',
+                'code' => 'US-CA-*-Rate 1',
+                'rate' => '8.2501',
+            ],
+        ];
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH,
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_POST,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Save',
+            ],
+        ];
+        try {
+            $this->_webApiCall($serviceInfo, $data);
+            $this->fail('Expected exception was not raised');
+        } catch (\Exception $e) {
+            $expectedMessage = 'Code already exists.';
+
+            $this->assertContains(
+                $expectedMessage,
+                $e->getMessage(),
+                "Exception does not contain expected message."
+            );
+        }
+    }
+
+    public function testCreateTaxRate()
+    {
+        $data = [
+            'tax_rate' => [
+                'tax_country_id' => 'US',
+                'tax_region_id' => 12,
+                'tax_postcode' => '*',
+                'code' => 'Test Tax Rate ' . microtime(),
+                'rate' => '8.2501',
+            ],
+        ];
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH,
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_POST,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Save',
+            ],
+        ];
+        $result = $this->_webApiCall($serviceInfo, $data);
+        $this->assertArrayHasKey('id', $result);
+        $taxRateId = $result['id'];
+        /** Ensure that tax rate was actually created in DB */
+        /** @var \Magento\Tax\Model\Calculation\Rate $taxRate */
+        $taxRate = Bootstrap::getObjectManager()->create('Magento\Tax\Model\Calculation\Rate');
+        $this->assertEquals($taxRateId, $taxRate->load($taxRateId)->getId(), 'Tax rate was not created in  DB.');
+        $taxRate->delete();
+    }
+
+    public function testCreateTaxRateWithZipRange()
+    {
+        $data = [
+            'tax_rate' => [
+                'tax_country_id' => 'US',
+                'tax_region_id' => 12,
+                'code' => 'Test Tax Rate ' . microtime(),
+                'rate' => '8.2501',
+                'zip_is_range' => 1,
+                'zip_from' => 17,
+                'zip_to' => 25,
+            ],
+        ];
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH,
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_POST,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Save',
+            ],
+        ];
+        $result = $this->_webApiCall($serviceInfo, $data);
+        $this->assertArrayHasKey('id', $result);
+        $taxRateId = $result['id'];
+        /** Ensure that tax rate was actually created in DB */
+        /** @var \Magento\Tax\Model\Calculation\Rate $taxRate */
+        $taxRate = Bootstrap::getObjectManager()->create('Magento\Tax\Model\Calculation\Rate');
+        $this->assertEquals($taxRateId, $taxRate->load($taxRateId)->getId(), 'Tax rate was not created in  DB.');
+        $this->assertEquals('17-25', $taxRate->getTaxPostcode(), 'Zip range is not saved in DB.');
+        $taxRate->delete();
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Tax/_files/tax_classes.php
+     */
+    public function testUpdateTaxRate()
+    {
+        $fixtureRate = $this->getFixtureTaxRates()[0];
+
+        $data = [
+            'tax_rate' => [
+                'id' => $fixtureRate->getId(),
+                'tax_region_id' => 43,
+                'tax_country_id' => 'US',
+                'tax_postcode' => '07400',
+                'code' => 'Test Tax Rate ' . microtime(),
+                'rate' => 3.456,
+            ],
+        ];
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH,
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Save',
+            ],
+        ];
+        $this->_webApiCall($serviceInfo, $data);
+        $expectedRateData = $data['tax_rate'];
+        /** Ensure that tax rate was actually updated in DB */
+        /** @var \Magento\Tax\Model\Calculation\Rate $taxRate */
+        $taxRate = Bootstrap::getObjectManager()->create('Magento\Tax\Model\Calculation\Rate');
+        $taxRateModel = $taxRate->load($fixtureRate->getId());
+        $this->assertEquals($expectedRateData['id'], $taxRateModel->getId(), 'Tax rate was not updated in  DB.');
+        $this->assertEquals(
+            $expectedRateData['tax_region_id'],
+            $taxRateModel->getTaxRegionId(),
+            'Tax rate was not updated in  DB.'
+        );
+        $this->assertEquals(
+            $expectedRateData['tax_country_id'],
+            $taxRateModel->getTaxCountryId(),
+            'Tax rate was not updated in  DB.'
+        );
+        $this->assertEquals(
+            $expectedRateData['tax_postcode'],
+            $taxRateModel->getTaxPostcode(),
+            'Tax rate was not updated in  DB.'
+        );
+        $this->assertEquals($expectedRateData['code'], $taxRateModel->getCode(), 'Tax rate was not updated in  DB.');
+        $this->assertEquals(
+            $expectedRateData['rate'],
+            $taxRateModel->getRate(),
+            'Tax rate was not updated in  DB.'
+        );
+    }
+
+    public function testUpdateTaxRateNotExisting()
+    {
+        $data = [
+            'tax_rate' => [
+                'id' => 555,
+                'tax_region_id' => 43,
+                'tax_country_id' => 'US',
+                'tax_postcode' => '07400',
+                'code' => 'Test Tax Rate ' . microtime(),
+                'rate' => 3.456,
+            ],
+        ];
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH,
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Save',
+            ],
+        ];
+        try {
+            $this->_webApiCall($serviceInfo, $data);
+            $this->fail('Expected exception was not raised');
+        } catch (\Exception $e) {
+            $expectedMessage = 'No such entity with %fieldName = %fieldValue';
+
+            $this->assertContains(
+                $expectedMessage,
+                $e->getMessage(),
+                "Exception does not contain expected message."
+            );
+        }
+    }
+
+    public function testGetTaxRate()
+    {
+        $taxRateId = 2;
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . "/$taxRateId",
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Get',
+            ],
+        ];
+
+        $result = $this->_webApiCall($serviceInfo, ['rateId' => $taxRateId]);
+        $expectedRateData = [
+            'id' => 2,
+            'tax_country_id' => 'US',
+            'tax_region_id' => 43,
+            'tax_postcode' => '*',
+            'code' => 'US-NY-*-Rate 1',
+            'rate' => 8.375,
+            'titles' => [],
+            'region_name' => 'NY',
+        ];
+        $this->assertEquals($expectedRateData, $result);
+    }
+
+    public function testGetTaxRateNotExist()
+    {
+        $taxRateId = 37865;
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . "/$taxRateId",
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Get',
+            ],
+        ];
+        try {
+            $this->_webApiCall($serviceInfo, ['rateId' => $taxRateId]);
+            $this->fail('Expected exception was not raised');
+        } catch (\Exception $e) {
+            $expectedMessage = 'No such entity with %fieldName = %fieldValue';
+
+            $this->assertContains(
+                $expectedMessage,
+                $e->getMessage(),
+                "Exception does not contain expected message."
+            );
+        }
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Tax/_files/tax_classes.php
+     */
+    public function testDeleteTaxRate()
+    {
+        /** Tax rules must be deleted since tax rate cannot be deleted if there are any tax rules associated with it */
+        $taxRules = $this->getFixtureTaxRules();
+        foreach ($taxRules as $taxRule) {
+            $taxRule->delete();
+        }
+
+        $fixtureRate = $this->getFixtureTaxRates()[0];
+        $taxRateId = $fixtureRate->getId();
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . "/$taxRateId",
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_DELETE,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'DeleteById',
+            ],
+        ];
+
+        $result = $this->_webApiCall($serviceInfo, ['rateId' => $taxRateId]);
+        $this->assertTrue($result);
+        /** Ensure that tax rate was actually removed from DB */
+        /** @var \Magento\Tax\Model\Calculation\Rate $taxRate */
+        $taxRate = Bootstrap::getObjectManager()->create('Magento\Tax\Model\Calculation\Rate');
+        $this->assertNull($taxRate->load($taxRateId)->getId(), 'Tax rate was not deleted from DB.');
+    }
+
+    /**
+     * Insure that tax rate cannot be deleted if it is used for a tax rule.
+     *
+     * @magentoApiDataFixture Magento/Tax/_files/tax_classes.php
+     */
+    public function testCannotDeleteTaxRate()
+    {
+        $fixtureRate = $this->getFixtureTaxRates()[0];
+        $taxRateId = $fixtureRate->getId();
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . "/$taxRateId",
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_DELETE,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'DeleteById',
+            ],
+        ];
+        try {
+            $this->_webApiCall($serviceInfo, ['rateId' => $taxRateId]);
+            $this->fail('Expected exception was not raised');
+        } catch (\Exception $e) {
+            $expectedMessage = 'The tax rate cannot be removed. It exists in a tax rule.';
+
+            $this->assertContains(
+                $expectedMessage,
+                $e->getMessage(),
+                "Exception does not contain expected message."
+            );
+        }
+    }
+
+    public function testSearchTaxRates()
+    {
+        $rates = $this->setupTaxRatesForSearch();
+
+        // Find rates whose code is 'codeUs12'
+        $filter = $this->filterBuilder->setField(TaxRate::KEY_CODE)
+            ->setValue('codeUs12')
+            ->create();
+
+        $this->searchCriteriaBuilder->addFilter([$filter]);
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/search',
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'GetList',
+            ],
+        ];
+        $searchData = $this->searchCriteriaBuilder->create()->__toArray();
+        $requestData = ['searchCriteria' => $searchData];
+
+        /** @var \Magento\Framework\Api\SearchResults $searchResults */
+        $searchResults = $this->_webApiCall($serviceInfo, $requestData);
+
+        $this->assertEquals(1, $searchResults['total_count']);
+
+        $expectedRuleData = [
+            [
+                'id' => (int)$rates['codeUs12']->getId(),
+                'tax_country_id' => $rates['codeUs12']->getTaxCountryId(),
+                'tax_region_id' => (int)$rates['codeUs12']->getTaxRegionId(),
+                'region_name' => 'CA',
+                'tax_postcode' => $rates['codeUs12']->getTaxPostcode(),
+                'code' =>  $rates['codeUs12']->getCode(),
+                'rate' => ((float) $rates['codeUs12']->getRate()),
+                'titles' => [],
+            ],
+        ];
+        $this->assertEquals($expectedRuleData, $searchResults['items']);
+    }
+
+    public function testSearchTaxRatesCz()
+    {
+        // TODO: This test fails in SOAP, a generic bug searching in SOAP
+        $this->_markTestAsRestOnly();
+        $rates = $this->setupTaxRatesForSearch();
+
+        // Find rates which country id 'CZ'
+        $filter = $this->filterBuilder->setField(TaxRate::KEY_COUNTRY_ID)
+            ->setValue('CZ')
+            ->create();
+        $sortOrder = $this->sortOrderBuilder
+            ->setField(TaxRate::KEY_POSTCODE)
+            ->setDirection(SearchCriteria::SORT_DESC)
+            ->create();
+        // Order them by descending postcode (not the default order)
+        $this->searchCriteriaBuilder->addFilter([$filter])
+            ->addSortOrder($sortOrder);
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/search',
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'GetList',
+            ],
+        ];
+        $searchData = $this->searchCriteriaBuilder->create()->__toArray();
+        $requestData = ['searchCriteria' => $searchData];
+
+        /** @var \Magento\Framework\Api\SearchResults $searchResults */
+        $searchResults = $this->_webApiCall($serviceInfo, $requestData);
+
+        $this->assertEquals(2, $searchResults['total_count']);
+
+        $expectedRuleData = [
+            [
+                'id' => (int)$rates['codeCz2']->getId(),
+                'tax_country_id' => $rates['codeCz2']->getTaxCountryId(),
+                'tax_postcode' => $rates['codeCz2']->getTaxPostcode(),
+                'code' =>  $rates['codeCz2']->getCode(),
+                'rate' =>  ((float) $rates['codeCz2']->getRate()),
+                'tax_region_id' => 0,
+                'titles' => [],
+            ],
+            [
+                'id' => (int)$rates['codeCz1']->getId(),
+                'tax_country_id' => $rates['codeCz1']->getTaxCountryId(),
+                'tax_postcode' => $rates['codeCz1']->getTaxPostcode(),
+                'code' => $rates['codeCz1']->getCode(),
+                'rate' => ((float) $rates['codeCz1']->getRate()),
+                'tax_region_id' => 0,
+                'titles' => [],
+            ],
+        ];
+        $this->assertEquals($expectedRuleData, $searchResults['items']);
+    }
+
+    /**
+     * Get tax rates created in Magento\Tax\_files\tax_classes.php
+     *
+     * @return \Magento\Tax\Model\Calculation\Rate[]
+     */
+    private function getFixtureTaxRates()
+    {
+        if (is_null($this->fixtureTaxRates)) {
+            $this->fixtureTaxRates = [];
+            if ($this->getFixtureTaxRules()) {
+                $taxRateIds = (array)$this->getFixtureTaxRules()[0]->getRates();
+                foreach ($taxRateIds as $taxRateId) {
+                    /** @var \Magento\Tax\Model\Calculation\Rate $taxRate */
+                    $taxRate = Bootstrap::getObjectManager()->create('Magento\Tax\Model\Calculation\Rate');
+                    $this->fixtureTaxRates[] = $taxRate->load($taxRateId);
+                }
+            }
+        }
+        return $this->fixtureTaxRates;
+    }
+
+    /**
+     * Get tax classes created in Magento\Tax\_files\tax_classes.php
+     *
+     * @return \Magento\Tax\Model\ClassModel[]
+     */
+    private function getFixtureTaxClasses()
+    {
+        if (is_null($this->fixtureTaxClasses)) {
+            $this->fixtureTaxClasses = [];
+            if ($this->getFixtureTaxRules()) {
+                $taxClassIds = array_merge(
+                    (array)$this->getFixtureTaxRules()[0]->getCustomerTaxClasses(),
+                    (array)$this->getFixtureTaxRules()[0]->getProductTaxClasses()
+                );
+                foreach ($taxClassIds as $taxClassId) {
+                    /** @var \Magento\Tax\Model\ClassModel $taxClass */
+                    $taxClass = Bootstrap::getObjectManager()->create('Magento\Tax\Model\ClassModel');
+                    $this->fixtureTaxClasses[] = $taxClass->load($taxClassId);
+                }
+            }
+        }
+        return $this->fixtureTaxClasses;
+    }
+
+    /**
+     * Get tax rule created in Magento\Tax\_files\tax_classes.php
+     *
+     * @return \Magento\Tax\Model\Calculation\Rule[]
+     */
+    private function getFixtureTaxRules()
+    {
+        if (is_null($this->fixtureTaxRules)) {
+            $this->fixtureTaxRules = [];
+            $taxRuleCodes = ['Test Rule Duplicate', 'Test Rule'];
+            foreach ($taxRuleCodes as $taxRuleCode) {
+                /** @var \Magento\Tax\Model\Calculation\Rule $taxRule */
+                $taxRule = Bootstrap::getObjectManager()->create('Magento\Tax\Model\Calculation\Rule');
+                $taxRule->load($taxRuleCode, 'code');
+                if ($taxRule->getId()) {
+                    $this->fixtureTaxRules[] = $taxRule;
+                }
+            }
+        }
+        return $this->fixtureTaxRules;
+    }
+
+    /**
+     * Creates rates for search tests.
+     *
+     * @return \Magento\Tax\Model\Calculation\Rate[]
+     */
+    private function setupTaxRatesForSearch()
+    {
+        $objectManager = Bootstrap::getObjectManager();
+
+        $taxRateUs12 = [
+            'tax_country_id' => 'US',
+            'tax_region_id' => 12,
+            'tax_postcode' => '*',
+            'code' => 'codeUs12',
+            'rate' => 22,
+            'region_name' => 'CA',
+        ];
+        $rates['codeUs12'] = $objectManager->create('Magento\Tax\Model\Calculation\Rate')
+            ->setData($taxRateUs12)
+            ->save();
+
+        $taxRateUs14 = [
+            'tax_country_id' => 'US',
+            'tax_region_id' => 14,
+            'tax_postcode' => '*',
+            'code' => 'codeUs14',
+            'rate' => 22,
+        ];
+        $rates['codeUs14'] = $objectManager->create('Magento\Tax\Model\Calculation\Rate')
+            ->setData($taxRateUs14)
+            ->save();
+        $taxRateBr13 = [
+            'tax_country_id' => 'BR',
+            'tax_region_id' => 13,
+            'tax_postcode' => '*',
+            'code' => 'codeBr13',
+            'rate' => 7.5,
+        ];
+        $rates['codeBr13'] = $objectManager->create('Magento\Tax\Model\Calculation\Rate')
+            ->setData($taxRateBr13)
+            ->save();
+
+        $taxRateCz1 = [
+            'tax_country_id' => 'CZ',
+            'tax_postcode' => '110 00',
+            'code' => 'codeCz1',
+            'rate' => 1.1,
+        ];
+        $rates['codeCz1'] = $objectManager->create('Magento\Tax\Model\Calculation\Rate')
+            ->setData($taxRateCz1)
+            ->save();
+        $taxRateCz2 = [
+            'tax_country_id' => 'CZ',
+            'tax_postcode' => '250 00',
+            'code' => 'codeCz2',
+            'rate' => 2.2,
+        ];
+        $rates['codeCz2'] = $objectManager->create('Magento\Tax\Model\Calculation\Rate')
+            ->setData($taxRateCz2)
+            ->save();
+
+        // Set class variable so rates will be deleted on tearDown()
+        $this->otherRates = $rates;
+        return $rates;
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Tax/Api/TaxRuleRepositoryInterfaceTest.php b/dev/tests/api-functional/testsuite/Magento/Tax/Api/TaxRuleRepositoryInterfaceTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..277b0611d7acbb12ad62fe8c5763d3eaf088fd4e
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Tax/Api/TaxRuleRepositoryInterfaceTest.php
@@ -0,0 +1,606 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+namespace Magento\Tax\Api;
+
+use Magento\Framework\Api\FilterBuilder;
+use Magento\Framework\Api\SearchCriteriaBuilder;
+use Magento\TestFramework\Helper\Bootstrap;
+use Magento\TestFramework\TestCase\WebapiAbstract;
+use Magento\Webapi\Model\Rest\Config as HttpConstants;
+
+class TaxRuleRepositoryInterfaceTest extends WebapiAbstract
+{
+    const SERVICE_NAME = "taxTaxRuleRepositoryV1";
+    const SERVICE_VERSION = "V1";
+    const RESOURCE_PATH = "/V1/taxRules";
+
+    /** @var \Magento\Tax\Model\Calculation\Rate[] */
+    private $fixtureTaxRates;
+
+    /** @var \Magento\Tax\Model\ClassModel[] */
+    private $fixtureTaxClasses;
+
+    /** @var \Magento\Tax\Model\Calculation\Rule[] */
+    private $fixtureTaxRules;
+
+    /** @var FilterBuilder */
+    private $filterBuilder;
+
+    /** @var SearchCriteriaBuilder */
+    private $searchCriteriaBuilder;
+
+    /**
+     * Execute per test initialization.
+     */
+    public function setUp()
+    {
+        $this->searchCriteriaBuilder = Bootstrap::getObjectManager()->create(
+            'Magento\Framework\Api\SearchCriteriaBuilder'
+        );
+        $this->filterBuilder = Bootstrap::getObjectManager()->create(
+            'Magento\Framework\Api\FilterBuilder'
+        );
+        $objectManager = Bootstrap::getObjectManager();
+
+        $this->searchCriteriaBuilder = $objectManager->create(
+            'Magento\Framework\Api\SearchCriteriaBuilder'
+        );
+        $this->filterBuilder = $objectManager->create(
+            'Magento\Framework\Api\FilterBuilder'
+        );
+
+        /** Initialize tax classes, tax rates and tax rules defined in fixture Magento/Tax/_files/tax_classes.php */
+        $this->getFixtureTaxRates();
+        $this->getFixtureTaxClasses();
+        $this->getFixtureTaxRules();
+    }
+
+    public function tearDown()
+    {
+        $taxRules = $this->getFixtureTaxRules();
+        if (count($taxRules)) {
+            $taxRates = $this->getFixtureTaxRates();
+            $taxClasses = $this->getFixtureTaxClasses();
+            foreach ($taxRules as $taxRule) {
+                $taxRule->delete();
+            }
+            foreach ($taxRates as $taxRate) {
+                $taxRate->delete();
+            }
+            foreach ($taxClasses as $taxClass) {
+                $taxClass->delete();
+            }
+        }
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Tax/_files/tax_classes.php
+     */
+    public function testDeleteTaxRule()
+    {
+        $fixtureRule = $this->getFixtureTaxRules()[0];
+        $taxRuleId = $fixtureRule->getId();
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . "/$taxRuleId",
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_DELETE,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'DeleteById',
+            ],
+        ];
+        $requestData = ['ruleId' => $taxRuleId];
+        $result = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertTrue($result);
+        /** Ensure that tax rule was actually removed from DB */
+        /** @var \Magento\Tax\Model\Calculation\Rule $taxRule */
+        $taxRate = Bootstrap::getObjectManager()->create('Magento\Tax\Model\Calculation\Rate');
+        $this->assertNull($taxRate->load($taxRuleId)->getId(), 'Tax rule was not deleted from DB.');
+    }
+
+    public function testCreateTaxRule()
+    {
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH,
+                'httpMethod' => HttpConstants::HTTP_METHOD_POST,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Save',
+            ],
+        ];
+        $requestData = [
+            'rule' => [
+                'code' => 'Test Rule ' . microtime(),
+                'position' => 10,
+                'priority' => 5,
+                'customer_tax_class_ids' => [3],
+                'product_tax_class_ids' => [2],
+                'tax_rate_ids' => [1, 2],
+                'calculate_subtotal' => 1,
+            ],
+        ];
+        $taxRuleData = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertArrayHasKey('id', $taxRuleData, "Tax rule ID is expected");
+        $this->assertGreaterThan(0, $taxRuleData['id']);
+        $taxRuleId = $taxRuleData['id'];
+        unset($taxRuleData['id']);
+        $this->assertEquals($requestData['rule'], $taxRuleData, "Tax rule is created with invalid data.");
+        /** Ensure that tax rule was actually created in DB */
+        /** @var \Magento\Tax\Model\Calculation\Rule $taxRule */
+        $taxRule = Bootstrap::getObjectManager()->create('Magento\Tax\Model\Calculation\Rule');
+        $this->assertEquals($taxRuleId, $taxRule->load($taxRuleId)->getId(), 'Tax rule was not created in DB.');
+        $taxRule->delete();
+    }
+
+    public function testCreateTaxRuleInvalidTaxClassIds()
+    {
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH,
+                'httpMethod' => HttpConstants::HTTP_METHOD_POST,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Save',
+            ],
+        ];
+        $requestData = [
+            'rule' => [
+                'code' => 'Test Rule ' . microtime(),
+                'position' => 10,
+                'priority' => 5,
+                'customer_tax_class_ids' => [2],
+                'product_tax_class_ids' => [3],
+                'tax_rate_ids' => [1, 2],
+                'calculate_subtotal' => 1,
+            ],
+        ];
+
+        try {
+            $this->_webApiCall($serviceInfo, $requestData);
+            $this->fail('Did not throw expected InputException');
+        } catch (\SoapFault $e) {
+            $this->assertContains('No such entity with customer_tax_class_ids = %fieldValue', $e->getMessage());
+        } catch (\Exception $e) {
+            $this->assertContains('No such entity with customer_tax_class_ids = %fieldValue', $e->getMessage());
+        }
+    }
+
+    public function testCreateTaxRuleExistingCode()
+    {
+        $requestData = [
+            'rule' => [
+                'code' => 'Test Rule ' . microtime(),
+                'position' => 10,
+                'priority' => 5,
+                'customer_tax_class_ids' => [3],
+                'product_tax_class_ids' => [2],
+                'tax_rate_ids' => [1, 2],
+                'calculate_subtotal' => 0,
+            ],
+        ];
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH,
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_POST,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Save',
+            ],
+        ];
+        $newTaxRuleData = $this->_webApiCall($serviceInfo, $requestData);
+        try {
+            $this->_webApiCall($serviceInfo, $requestData);
+            $this->fail('Expected exception was not raised');
+        } catch (\Exception $e) {
+            $expectedMessage = 'Code already exists.';
+            $this->assertContains(
+                $expectedMessage,
+                $e->getMessage(),
+                "Exception does not contain expected message."
+            );
+        }
+
+        // Clean up the new tax rule so it won't affect other tests
+        /** @var \Magento\Tax\Model\Calculation\Rule $taxRule */
+        $taxRule = Bootstrap::getObjectManager()->create('Magento\Tax\Model\Calculation\Rule');
+        $taxRule->load($newTaxRuleData['id']);
+        $taxRule->delete();
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Tax/_files/tax_classes.php
+     */
+    public function testGetTaxRule()
+    {
+        $fixtureRule = $this->getFixtureTaxRules()[0];
+        $taxRuleId = $fixtureRule->getId();
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . "/$taxRuleId",
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Get',
+            ],
+        ];
+
+        $expectedRuleData = [
+            'id' => $taxRuleId,
+            'code' => 'Test Rule Duplicate',
+            'priority' => '0',
+            'position' => '0',
+            'customer_tax_class_ids' => array_values(array_unique($fixtureRule->getCustomerTaxClasses())),
+            'product_tax_class_ids' => array_values(array_unique($fixtureRule->getProductTaxClasses())),
+            'tax_rate_ids' => array_values(array_unique($fixtureRule->getRates())),
+            'calculate_subtotal' => false,
+        ];
+        $requestData = ['ruleId' => $taxRuleId];
+        $result = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertEquals($expectedRuleData, $result);
+    }
+    /**
+     * @magentoApiDataFixture Magento/Tax/_files/tax_classes.php
+     */
+    public function testSearchTaxRulesSimple()
+    {
+        // Find rules whose code is 'Test Rule'
+        $filter = $this->filterBuilder->setField('code')
+            ->setValue('Test Rule')
+            ->create();
+
+        $this->searchCriteriaBuilder->addFilter([$filter]);
+
+        $fixtureRule = $this->getFixtureTaxRules()[1];
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/search',
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'GetList',
+            ],
+        ];
+        $searchData = $this->searchCriteriaBuilder->create()->__toArray();
+        $requestData = ['searchCriteria' => $searchData];
+
+        /** @var \Magento\Framework\Api\SearchResults $searchResults */
+        $searchResults = $this->_webApiCall($serviceInfo, $requestData);
+
+        $this->assertEquals(1, $searchResults['total_count']);
+
+        $expectedRuleData = [
+            [
+                'id' => $fixtureRule->getId(),
+                'code' => 'Test Rule',
+                'priority' => 0,
+                'position' => 0,
+                'calculate_subtotal' => 0,
+                'customer_tax_class_ids' => array_values(array_unique($fixtureRule->getCustomerTaxClasses())),
+                'product_tax_class_ids' => array_values(array_unique($fixtureRule->getProductTaxClasses())),
+                'tax_rate_ids' => array_values(array_unique($fixtureRule->getRates())),
+            ],
+        ];
+        $this->assertEquals($expectedRuleData, $searchResults['items']);
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Tax/_files/tax_classes.php
+     */
+    public function testSearchTaxRulesCodeLike()
+    {
+        // Find rules whose code starts with 'Test Rule'
+        $filter = $this->filterBuilder
+            ->setField('code')
+            ->setValue('Test Rule%')
+            ->setConditionType('like')
+            ->create();
+
+        $sortFilter = $this->filterBuilder
+            ->setField('position')
+            ->setValue(0)
+            ->create();
+
+        $this->searchCriteriaBuilder->addFilter([$filter, $sortFilter]);
+
+        $fixtureRule = $this->getFixtureTaxRules()[1];
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/search',
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'GetList',
+            ],
+        ];
+        $searchData = $this->searchCriteriaBuilder->create()->__toArray();
+        $requestData = ['searchCriteria' => $searchData];
+
+        /** @var \Magento\Framework\Api\SearchResults $searchResults */
+        $searchResults = $this->_webApiCall($serviceInfo, $requestData);
+
+        $this->assertEquals(2, $searchResults['total_count']);
+
+        $expectedRuleData = [
+            [
+                'id' => $fixtureRule->getId(),
+                'code' => 'Test Rule',
+                'priority' => 0,
+                'position' => 0,
+                'calculate_subtotal' => 0,
+                'customer_tax_class_ids' => array_values(array_unique($fixtureRule->getCustomerTaxClasses())),
+                'product_tax_class_ids' => array_values(array_unique($fixtureRule->getProductTaxClasses())),
+                'tax_rate_ids' => array_values(array_unique($fixtureRule->getRates())),
+            ],
+            [
+                'id' => $this->getFixtureTaxRules()[0]->getId(),
+                'code' => 'Test Rule Duplicate',
+                'priority' => 0,
+                'position' => 0,
+                'calculate_subtotal' => 0,
+                'customer_tax_class_ids' => array_values(array_unique($fixtureRule->getCustomerTaxClasses())),
+                'product_tax_class_ids' => array_values(array_unique($fixtureRule->getProductTaxClasses())),
+                'tax_rate_ids' => array_values(array_unique($fixtureRule->getRates()))
+            ],
+        ];
+        $this->assertEquals($expectedRuleData, $searchResults['items']);
+    }
+
+    public function testGetTaxRuleNotExist()
+    {
+        $taxRuleId = 37865;
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . "/$taxRuleId",
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Get',
+            ],
+        ];
+        $requestData = ['ruleId' => $taxRuleId];
+        try {
+            $this->_webApiCall($serviceInfo, $requestData);
+            $this->fail('Expected exception was not raised');
+        } catch (\Exception $e) {
+            $expectedMessage = 'No such entity with %fieldName = %fieldValue';
+
+            $this->assertContains(
+                $expectedMessage,
+                $e->getMessage(),
+                "Exception does not contain expected message."
+            );
+        }
+    }
+    /**
+     * @magentoApiDataFixture Magento/Tax/_files/tax_classes.php
+     */
+    public function testUpdateTaxRule()
+    {
+        $fixtureRule = $this->getFixtureTaxRules()[0];
+        $requestData = [
+            'rule' => [
+                'id' => $fixtureRule->getId(),
+                'code' => 'Test Rule ' . microtime(),
+                'position' => 10,
+                'priority' => 5,
+                'customer_tax_class_ids' => [3],
+                'product_tax_class_ids' => [2],
+                'tax_rate_ids' => [1, 2],
+                'calculate_subtotal' => 1,
+            ],
+        ];
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH,
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Save',
+            ],
+        ];
+        $this->_webApiCall($serviceInfo, $requestData);
+        $expectedRuleData = $requestData['rule'];
+        /** Ensure that tax rule was actually updated in DB */
+        /** @var \Magento\Tax\Model\Calculation $taxCalculation */
+        $taxCalculation = Bootstrap::getObjectManager()->create('Magento\Tax\Model\Calculation');
+        /** @var \Magento\Tax\Model\Calculation\Rule $taxRule */
+        $taxRule = Bootstrap::getObjectManager()->create(
+            'Magento\Tax\Model\Calculation\Rule',
+            ['calculation' => $taxCalculation]
+        );
+        $taxRuleModel = $taxRule->load($fixtureRule->getId());
+        $this->assertEquals($expectedRuleData['id'], $taxRuleModel->getId(), 'Tax rule was not updated in DB.');
+        $this->assertEquals(
+            $expectedRuleData['code'],
+            $taxRuleModel->getCode(),
+            'Tax rule code was updated incorrectly.'
+        );
+        $this->assertEquals(
+            $expectedRuleData['position'],
+            $taxRuleModel->getPosition(),
+            'Tax rule sort order was updated incorrectly.'
+        );
+        $this->assertEquals(
+            $expectedRuleData['priority'],
+            $taxRuleModel->getPriority(),
+            'Tax rule priority was updated incorrectly.'
+        );
+        $this->assertEquals(
+            $expectedRuleData['customer_tax_class_ids'],
+            array_values(array_unique($taxRuleModel->getCustomerTaxClasses())),
+            'Customer Tax classes were updated incorrectly'
+        );
+        $this->assertEquals(
+            $expectedRuleData['product_tax_class_ids'],
+            array_values(array_unique($taxRuleModel->getProductTaxClasses())),
+            'Product Tax classes were updated incorrectly.'
+        );
+        $this->assertEquals(
+            $expectedRuleData['tax_rate_ids'],
+            array_values(array_unique($taxRuleModel->getRates())),
+            'Tax rates were updated incorrectly.'
+        );
+    }
+    public function testUpdateTaxRuleNotExisting()
+    {
+        $requestData = [
+            'rule' => [
+                'id' => 12345,
+                'code' => 'Test Rule ' . microtime(),
+                'position' => 10,
+                'priority' => 5,
+                'customer_tax_class_ids' => [3],
+                'product_tax_class_ids' => [2],
+                'tax_rate_ids' => [1, 2],
+            ],
+        ];
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH,
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'Save',
+            ],
+        ];
+        try {
+            $this->_webApiCall($serviceInfo, $requestData);
+            $this->fail('Expected exception was not raised');
+        } catch (\Exception $e) {
+            $expectedMessage = 'No such entity with %fieldName = %fieldValue';
+
+            $this->assertContains(
+                $expectedMessage,
+                $e->getMessage(),
+                "Exception does not contain expected message."
+            );
+        }
+    }
+
+    /**
+     * @magentoApiDataFixture Magento/Tax/_files/tax_classes.php
+     */
+    public function testSearchTaxRule()
+    {
+        $fixtureRule = $this->getFixtureTaxRules()[0];
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => self::RESOURCE_PATH . '/search',
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_PUT,
+            ],
+            'soap' => [
+                'service' => self::SERVICE_NAME,
+                'serviceVersion' => self::SERVICE_VERSION,
+                'operation' => self::SERVICE_NAME . 'GetList',
+            ],
+        ];
+
+        $filter = $this->filterBuilder->setField('code')
+            ->setValue($fixtureRule->getCode())
+            ->create();
+        $this->searchCriteriaBuilder->addFilter([$filter]);
+        $searchData = $this->searchCriteriaBuilder->create()->__toArray();
+        $requestData = ['searchCriteria' => $searchData];
+        $searchResults = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertEquals(1, $searchResults['total_count']);
+        $this->assertEquals($fixtureRule->getId(), $searchResults['items'][0]["id"]);
+        $this->assertEquals($fixtureRule->getCode(), $searchResults['items'][0]['code']);
+    }
+
+    /**
+     * Get tax rates created in Magento\Tax\_files\tax_classes.php
+     *
+     * @return \Magento\Tax\Model\Calculation\Rate[]
+     */
+    private function getFixtureTaxRates()
+    {
+        if (is_null($this->fixtureTaxRates)) {
+            $this->fixtureTaxRates = [];
+            if ($this->getFixtureTaxRules()) {
+                $taxRateIds = (array)$this->getFixtureTaxRules()[0]->getRates();
+                foreach ($taxRateIds as $taxRateId) {
+                    /** @var \Magento\Tax\Model\Calculation\Rate $taxRate */
+                    $taxRate = Bootstrap::getObjectManager()->create('Magento\Tax\Model\Calculation\Rate');
+                    $this->fixtureTaxRates[] = $taxRate->load($taxRateId);
+                }
+            }
+        }
+        return $this->fixtureTaxRates;
+    }
+
+    /**
+     * Get tax classes created in Magento\Tax\_files\tax_classes.php
+     *
+     * @return \Magento\Tax\Model\ClassModel[]
+     */
+    private function getFixtureTaxClasses()
+    {
+        if (is_null($this->fixtureTaxClasses)) {
+            $this->fixtureTaxClasses = [];
+            if ($this->getFixtureTaxRules()) {
+                $taxClassIds = array_merge(
+                    (array)$this->getFixtureTaxRules()[0]->getCustomerTaxClasses(),
+                    (array)$this->getFixtureTaxRules()[0]->getProductTaxClasses()
+                );
+                foreach ($taxClassIds as $taxClassId) {
+                    /** @var \Magento\Tax\Model\ClassModel $taxClass */
+                    $taxClass = Bootstrap::getObjectManager()->create('Magento\Tax\Model\ClassModel');
+                    $this->fixtureTaxClasses[] = $taxClass->load($taxClassId);
+                }
+            }
+        }
+        return $this->fixtureTaxClasses;
+    }
+
+    /**
+     * Get tax rule created in Magento\Tax\_files\tax_classes.php
+     *
+     * @return \Magento\Tax\Model\Calculation\Rule[]
+     */
+    private function getFixtureTaxRules()
+    {
+        if (is_null($this->fixtureTaxRules)) {
+            $this->fixtureTaxRules = [];
+            $taxRuleCodes = ['Test Rule Duplicate', 'Test Rule'];
+            foreach ($taxRuleCodes as $taxRuleCode) {
+                /** @var \Magento\Tax\Model\Calculation\Rule $taxRule */
+                $taxRule = Bootstrap::getObjectManager()->create('Magento\Tax\Model\Calculation\Rule');
+                $taxRule->load($taxRuleCode, 'code');
+                if ($taxRule->getId()) {
+                    $this->fixtureTaxRules[] = $taxRule;
+                }
+            }
+        }
+        return $this->fixtureTaxRules;
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Webapi/Authentication/RestTest.php b/dev/tests/api-functional/testsuite/Magento/Webapi/Authentication/RestTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..3f8bd45445a6e5c43b3969d391bf47920e3245a6
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Webapi/Authentication/RestTest.php
@@ -0,0 +1,203 @@
+<?php
+/**
+ * Test authentication mechanisms in REST.
+ *
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Webapi\Authentication;
+
+/**
+ * @magentoApiDataFixture consumerFixture
+ */
+class RestTest extends \Magento\TestFramework\TestCase\WebapiAbstract
+{
+    /** @var \Magento\TestFramework\Authentication\Rest\OauthClient[] */
+    protected $_oAuthClients = [];
+
+    /** @var \Magento\Integration\Model\Oauth\Consumer */
+    protected static $_consumer;
+
+    /** @var \Magento\Integration\Model\Oauth\Token */
+    protected static $_token;
+
+    /** @var string */
+    protected static $_consumerKey;
+
+    /** @var string */
+    protected static $_consumerSecret;
+
+    /** @var string */
+    protected static $_verifier;
+
+    protected function setUp()
+    {
+        $this->_markTestAsRestOnly();
+        parent::setUp();
+    }
+
+    /**
+     * Create a consumer
+     */
+    public static function consumerFixture($date = null)
+    {
+        /** Clear the credentials because during the fixture generation, any previous credentials are invalidated */
+        \Magento\TestFramework\Authentication\OauthHelper::clearApiAccessCredentials();
+
+        $consumerCredentials = \Magento\TestFramework\Authentication\OauthHelper::getConsumerCredentials($date);
+        self::$_consumerKey = $consumerCredentials['key'];
+        self::$_consumerSecret = $consumerCredentials['secret'];
+        self::$_verifier = $consumerCredentials['verifier'];
+        self::$_consumer = $consumerCredentials['consumer'];
+        self::$_token = $consumerCredentials['token'];
+    }
+
+    protected function tearDown()
+    {
+        parent::tearDown();
+        $this->_oAuthClients = [];
+        if (isset(self::$_consumer)) {
+            self::$_consumer->delete();
+            self::$_token->delete();
+        }
+    }
+
+    public function testGetRequestToken()
+    {
+        /** @var $oAuthClient \Magento\TestFramework\Authentication\Rest\OauthClient */
+        $oAuthClient = $this->_getOauthClient(self::$_consumerKey, self::$_consumerSecret);
+        $requestToken = $oAuthClient->requestRequestToken();
+
+        $this->assertNotEmpty($requestToken->getRequestToken(), "Request token value is not set");
+        $this->assertNotEmpty($requestToken->getRequestTokenSecret(), "Request token secret is not set");
+
+        $this->assertEquals(
+            \Magento\Framework\Oauth\Helper\Oauth::LENGTH_TOKEN,
+            strlen($requestToken->getRequestToken()),
+            "Request token value length should be " . \Magento\Framework\Oauth\Helper\Oauth::LENGTH_TOKEN
+        );
+        $this->assertEquals(
+            \Magento\Framework\Oauth\Helper\Oauth::LENGTH_TOKEN_SECRET,
+            strlen($requestToken->getRequestTokenSecret()),
+            "Request token secret length should be " . \Magento\Framework\Oauth\Helper\Oauth::LENGTH_TOKEN_SECRET
+        );
+    }
+
+    /**
+     * @expectedException \Exception
+     * @expectedExceptionMessage HTTP/1.1 401
+     */
+    public function testGetRequestTokenExpiredConsumer()
+    {
+        $this::consumerFixture('2012-01-01 00:00:00');
+        /** @var $oAuthClient \Magento\TestFramework\Authentication\Rest\OauthClient */
+        $oAuthClient = $this->_getOauthClient(self::$_consumerKey, self::$_consumerSecret);
+        $oAuthClient->requestRequestToken();
+    }
+
+    /**
+     * @expectedException \Exception
+     * @expectedExceptionMessage HTTP/1.1 401
+     */
+    public function testGetRequestTokenInvalidConsumerKey()
+    {
+        $oAuthClient = $this->_getOauthClient('invalid_key', self::$_consumerSecret);
+        $oAuthClient->requestRequestToken();
+    }
+
+    /**
+     * @expectedException \Exception
+     * @expectedExceptionMessage HTTP/1.1 401
+     */
+    public function testGetRequestTokenInvalidConsumerSecret()
+    {
+        $oAuthClient = $this->_getOauthClient(self::$_consumerKey, 'invalid_secret');
+        $oAuthClient->requestRequestToken();
+    }
+
+    public function testGetAccessToken()
+    {
+        $oAuthClient = $this->_getOauthClient(self::$_consumerKey, self::$_consumerSecret);
+        $requestToken = $oAuthClient->requestRequestToken();
+        $accessToken = $oAuthClient->requestAccessToken(
+            $requestToken->getRequestToken(),
+            self::$_verifier,
+            $requestToken->getRequestTokenSecret()
+        );
+        $this->assertNotEmpty($accessToken->getAccessToken(), "Access token value is not set.");
+        $this->assertNotEmpty($accessToken->getAccessTokenSecret(), "Access token secret is not set.");
+
+        $this->assertEquals(
+            \Magento\Framework\Oauth\Helper\Oauth::LENGTH_TOKEN,
+            strlen($accessToken->getAccessToken()),
+            "Access token value length should be " . \Magento\Framework\Oauth\Helper\Oauth::LENGTH_TOKEN
+        );
+        $this->assertEquals(
+            \Magento\Framework\Oauth\Helper\Oauth::LENGTH_TOKEN_SECRET,
+            strlen($accessToken->getAccessTokenSecret()),
+            "Access token secret length should be " . \Magento\Framework\Oauth\Helper\Oauth::LENGTH_TOKEN_SECRET
+        );
+    }
+
+    /**
+     * @expectedException \Exception
+     * @expectedExceptionMessage HTTP/1.1 401
+     */
+    public function testGetAccessTokenInvalidVerifier()
+    {
+        $oAuthClient = $this->_getOauthClient(self::$_consumerKey, self::$_consumerSecret);
+        $requestToken = $oAuthClient->requestRequestToken();
+        $oAuthClient->requestAccessToken(
+            $requestToken->getRequestToken(),
+            'invalid verifier',
+            $requestToken->getRequestTokenSecret()
+        );
+    }
+
+    /**
+     * @expectedException \Exception
+     * @expectedExceptionMessage HTTP/1.1 401
+     */
+    public function testGetAccessTokenConsumerMismatch()
+    {
+        $oAuthClientA = $this->_getOauthClient(self::$_consumerKey, self::$_consumerSecret);
+        $requestTokenA = $oAuthClientA->requestRequestToken();
+        $oauthVerifierA = self::$_verifier;
+
+        self::consumerFixture();
+        $oAuthClientB = $this->_getOauthClient(self::$_consumerKey, self::$_consumerSecret);
+
+        $oAuthClientB->requestAccessToken(
+            $requestTokenA->getRequestToken(),
+            $oauthVerifierA,
+            $requestTokenA->getRequestTokenSecret()
+        );
+    }
+
+    /**
+     * @expectedException \Exception
+     * @expectedExceptionMessage HTTP/1.1 401
+     */
+    public function testAccessApiInvalidAccessToken()
+    {
+        $oAuthClient = $this->_getOauthClient(self::$_consumerKey, self::$_consumerSecret);
+        $requestToken = $oAuthClient->requestRequestToken();
+        $accessToken = $oAuthClient->requestAccessToken(
+            $requestToken->getRequestToken(),
+            self::$_verifier,
+            $requestToken->getRequestTokenSecret()
+        );
+        $accessToken->setAccessToken('invalid');
+        $oAuthClient->validateAccessToken($accessToken);
+    }
+
+    protected function _getOauthClient($consumerKey, $consumerSecret)
+    {
+        if (!isset($this->_oAuthClients[$consumerKey])) {
+            $credentials = new \OAuth\Common\Consumer\Credentials($consumerKey, $consumerSecret, TESTS_BASE_URL);
+            $this->_oAuthClients[$consumerKey] = new \Magento\TestFramework\Authentication\Rest\OauthClient(
+                $credentials
+            );
+        }
+        return $this->_oAuthClients[$consumerKey];
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Webapi/DataObjectSerialization/CustomAttributeSerializationMSCTest.php b/dev/tests/api-functional/testsuite/Magento/Webapi/DataObjectSerialization/CustomAttributeSerializationMSCTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..7b1f33148d999c81ac87d6444edf593408cbe1a1
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Webapi/DataObjectSerialization/CustomAttributeSerializationMSCTest.php
@@ -0,0 +1,244 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+namespace Magento\Webapi\DataObjectSerialization;
+
+use Magento\Framework\Reflection\DataObjectProcessor;
+use Magento\TestFramework\Helper\Bootstrap;
+use Magento\TestModuleMSC\Api\Data\ItemDataBuilder;
+use Magento\Webapi\Controller\Rest\Response\DataObjectConverter;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+/**
+ * api-functional/testsuite/Magento/Webapi/DataObjectSerialization/CustomAttributeSerializationMSCTest.php
+ * Class to test if custom attributes are serialized correctly for the new Module Service Contract approach
+ */
+class CustomAttributeSerializationMSCTest extends \Magento\Webapi\Routing\BaseService
+{
+    /**
+     * @var string
+     */
+    protected $_version;
+    /**
+     * @var string
+     */
+    protected $_restResourcePath;
+    /**
+     * @var string
+     */
+    protected $_soapService = 'testModuleMSCAllSoapAndRest';
+
+    /**
+     * @var ItemDataBuilder
+     */
+    protected $itemDataBuilder;
+
+    /**
+     * @var \Magento\TestModuleMSC\Api\Data\CustomAttributeNestedDataObjectDataBuilder
+     */
+    protected $customAttributeNestedDataObjectDataBuilder;
+
+    /**
+     * @var \Magento\TestModuleMSC\Api\Data\CustomAttributeDataObjectDataBuilder
+     */
+    protected $customAttributeDataObjectDataBuilder;
+
+    /**
+     * @var DataObjectProcessor $dataObjectProcessor
+     */
+    protected $dataObjectProcessor;
+
+    /**
+     * @var DataObjectConverter $dataObjectConverter
+     */
+    protected $dataObjectConverter;
+
+    /**
+     * Set up custom attribute related data objects
+     */
+    protected function setUp()
+    {
+        $this->markTestSkipped('This test become irrelevant according to new API Contract');
+        $this->_version = 'V1';
+        $this->_soapService = 'testModuleMSCAllSoapAndRestV1';
+        $this->_restResourcePath = "/{$this->_version}/testmoduleMSC/";
+
+        $this->itemDataBuilder = Bootstrap::getObjectManager()->create(
+            'Magento\TestModuleMSC\Api\Data\ItemDataBuilder'
+        );
+
+        $this->customAttributeNestedDataObjectDataBuilder = Bootstrap::getObjectManager()->create(
+            'Magento\TestModuleMSC\Api\Data\CustomAttributeNestedDataObjectDataBuilder'
+        );
+
+        $this->customAttributeDataObjectDataBuilder = Bootstrap::getObjectManager()->create(
+            'Magento\TestModuleMSC\Api\Data\CustomAttributeDataObjectDataBuilder'
+        );
+
+        $this->dataObjectProcessor = Bootstrap::getObjectManager()->create(
+            'Magento\Framework\Reflection\DataObjectProcessor'
+        );
+
+        $this->dataObjectConverter = Bootstrap::getObjectManager()->create(
+            'Magento\Webapi\Controller\Rest\Response\DataObjectConverter'
+        );
+    }
+
+    public function testSimpleAndNonExistentCustomAttributes()
+    {
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => $this->_restResourcePath . 'itemAnyType',
+                'httpMethod' => RestConfig::HTTP_METHOD_POST,
+            ],
+            'soap' => ['service' => $this->_soapService, 'operation' => $this->_soapService . 'ItemAnyType'],
+        ];
+        $requestData = [
+            'item_id' => 1,
+            'name' => 'testProductAnyType',
+            'custom_attributes' => [
+                    'non_existent' => [
+                            'attribute_code' => 'non_existent',
+                            'value' => 'test',
+                        ],
+                    'custom_attribute_string' => [
+                            'attribute_code' => 'custom_attribute_string',
+                            'value' => 'someStringValue',
+                        ],
+                ],
+        ];
+        $result = $this->_webApiCall($serviceInfo, ['entityItem' => $requestData]);
+
+        //The non_existent custom attribute should be dropped since its not a defined custom attribute
+        $expectedResponse = [
+            'item_id' => 1,
+            'name' => 'testProductAnyType',
+            'custom_attributes' => [
+                    [
+                        'attribute_code' => 'custom_attribute_string',
+                        'value' => 'someStringValue',
+                    ],
+                ],
+        ];
+
+        //\Magento\TestModuleMSC\Api\AllSoapAndRest::itemAnyType just return the input data back as response
+        $this->assertEquals($expectedResponse, $result);
+    }
+
+    public function testDataObjectCustomAttributes()
+    {
+        if (TESTS_WEB_API_ADAPTER == self::ADAPTER_SOAP) {
+            $this->markTestIncomplete('MAGETWO-31016: incompatible with ZF 1.12.9');
+        }
+        $customAttributeDataObject = $this->customAttributeDataObjectDataBuilder
+            ->setName('nameValue')
+            ->setCustomAttribute('custom_attribute_int', 1)
+            ->create();
+
+        $item = $this->itemDataBuilder
+            ->setItemId(1)
+            ->setName('testProductAnyType')
+            ->setCustomAttribute('custom_attribute_data_object', $customAttributeDataObject)
+            ->setCustomAttribute('custom_attribute_string', 'someStringValue')
+            ->create();
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => $this->_restResourcePath . 'itemAnyType',
+                'httpMethod' => RestConfig::HTTP_METHOD_POST,
+            ],
+            'soap' => ['service' => $this->_soapService, 'operation' => $this->_soapService . 'ItemAnyType'],
+        ];
+        $requestData = $this->dataObjectProcessor->buildOutputDataArray($item, get_class($item));
+        $result = $this->_webApiCall($serviceInfo, ['entityItem' => $requestData]);
+
+        $expectedResponse = $this->dataObjectConverter->processServiceOutput(
+            $item,
+            '\Magento\TestModuleMSC\Api\AllSoapAndRestInterface',
+            'itemAnyType'
+        );
+        //\Magento\TestModuleMSC\Api\AllSoapAndRest::itemAnyType just return the input data back as response
+        $this->assertEquals($expectedResponse, $result);
+    }
+
+    public function testDataObjectCustomAttributesPreconfiguredItem()
+    {
+        if (TESTS_WEB_API_ADAPTER == self::ADAPTER_SOAP) {
+            $this->markTestIncomplete('MAGETWO-31016: incompatible with ZF 1.12.9');
+        }
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => $this->_restResourcePath . 'itemPreconfigured',
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+            'soap' => ['service' => $this->_soapService, 'operation' => $this->_soapService . 'GetPreconfiguredItem'],
+        ];
+
+        $result = $this->_webApiCall($serviceInfo, []);
+
+        $customAttributeDataObject = $this->customAttributeDataObjectDataBuilder
+            ->setName('nameValue')
+            ->setCustomAttribute('custom_attribute_int', 1)
+            ->create();
+
+        $item = $this->itemDataBuilder
+            ->setItemId(1)
+            ->setName('testProductAnyType')
+            ->setCustomAttribute('custom_attribute_data_object', $customAttributeDataObject)
+            ->setCustomAttribute('custom_attribute_string', 'someStringValue')
+            ->create();
+
+        $expectedResponse = $this->dataObjectConverter->processServiceOutput(
+            $item,
+            '\Magento\TestModuleMSC\Api\AllSoapAndRestInterface',
+            'getPreconfiguredItem'
+        );
+        $this->assertEquals($expectedResponse, $result);
+    }
+
+    public function testNestedDataObjectCustomAttributes()
+    {
+        if (TESTS_WEB_API_ADAPTER == self::ADAPTER_SOAP) {
+            $this->markTestIncomplete('MAGETWO-31016: incompatible with ZF 1.12.9');
+        }
+        $customAttributeNestedDataObject = $this->customAttributeNestedDataObjectDataBuilder
+            ->setName('nestedNameValue')
+            ->create();
+
+        $customAttributeDataObject = $this->customAttributeDataObjectDataBuilder
+            ->setName('nameValue')
+            ->setCustomAttribute('custom_attribute_nested', $customAttributeNestedDataObject)
+            ->setCustomAttribute('custom_attribute_int', 1)
+            ->create();
+
+        $item = $this->itemDataBuilder
+            ->setItemId(1)
+            ->setName('testProductAnyType')
+            ->setCustomAttribute('custom_attribute_data_object', $customAttributeDataObject)
+            ->setCustomAttribute('custom_attribute_string', 'someStringValue')
+            ->create();
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => $this->_restResourcePath . 'itemAnyType',
+                'httpMethod' => RestConfig::HTTP_METHOD_POST,
+            ],
+            'soap' => ['service' => $this->_soapService, 'operation' => $this->_soapService . 'ItemAnyType'],
+        ];
+        $requestData = $this->dataObjectProcessor->buildOutputDataArray(
+            $item,
+            '\Magento\TestModuleMSC\Api\Data\ItemInterface'
+        );
+        $result = $this->_webApiCall($serviceInfo, ['entityItem' => $requestData]);
+
+        $expectedResponse = $this->dataObjectConverter->processServiceOutput(
+            $item,
+            '\Magento\TestModuleMSC\Api\AllSoapAndRestInterface',
+            'itemAnyType'
+        );
+        //\Magento\TestModuleMSC\Api\AllSoapAndRest::itemAnyType just return the input data back as response
+        $this->assertEquals($expectedResponse, $result);
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Webapi/DataObjectSerialization/CustomAttributeSerializationTest.php b/dev/tests/api-functional/testsuite/Magento/Webapi/DataObjectSerialization/CustomAttributeSerializationTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..4baca07992c41b68ac509b10a5d646550b414d06
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Webapi/DataObjectSerialization/CustomAttributeSerializationTest.php
@@ -0,0 +1,228 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+/**
+ * Class to test if custom attributes are serialized correctly
+ */
+namespace Magento\Webapi\DataObjectSerialization;
+
+use Magento\TestFramework\Helper\Bootstrap;
+use Magento\TestModule1\Service\V1\Entity\ItemBuilder;
+use Magento\Webapi\Controller\Rest\Response\DataObjectConverter;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+class CustomAttributeSerializationTest extends \Magento\Webapi\Routing\BaseService
+{
+    /**
+     * @var string
+     */
+    protected $_version;
+    /**
+     * @var string
+     */
+    protected $_restResourcePath;
+    /**
+     * @var string
+     */
+    protected $_soapService = 'testModule1AllSoapAndRest';
+
+    /**
+     * @var ItemBuilder
+     */
+    protected $itemBuilder;
+
+    /**
+     * @var \Magento\TestModule1\Service\V1\Entity\CustomAttributeNestedDataObjectBuilder
+     */
+    protected $customAttributeNestedDataObjectBuilder;
+
+    /**
+     * @var \Magento\TestModule1\Service\V1\Entity\CustomAttributeDataObjectBuilder
+     */
+    protected $customAttributeDataObjectBuilder;
+
+    /**
+     * @var DataObjectConverter $dataObjectConverter
+     */
+    protected $dataObjectConverter;
+
+    /**
+     * Set up custom attribute related data objects
+     */
+    protected function setUp()
+    {
+        $this->_version = 'V1';
+        $this->_soapService = 'testModule1AllSoapAndRestV1';
+        $this->_restResourcePath = "/{$this->_version}/testmodule1/";
+
+        $this->itemBuilder = Bootstrap::getObjectManager()->create(
+            'Magento\TestModule1\Service\V1\Entity\ItemBuilder'
+        );
+
+        $this->customAttributeNestedDataObjectBuilder = Bootstrap::getObjectManager()->create(
+            'Magento\TestModule1\Service\V1\Entity\CustomAttributeNestedDataObjectBuilder'
+        );
+
+        $this->customAttributeDataObjectBuilder = Bootstrap::getObjectManager()->create(
+            'Magento\TestModule1\Service\V1\Entity\CustomAttributeDataObjectBuilder'
+        );
+
+        $this->dataObjectConverter = Bootstrap::getObjectManager()->create(
+            'Magento\Webapi\Controller\Rest\Response\DataObjectConverter'
+        );
+    }
+
+    public function testSimpleAndNonExistentCustomAttributes()
+    {
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => $this->_restResourcePath . 'itemAnyType',
+                'httpMethod' => RestConfig::HTTP_METHOD_POST,
+            ],
+            'soap' => ['service' => $this->_soapService, 'operation' => $this->_soapService . 'ItemAnyType'],
+        ];
+        $requestData = [
+            'item_id' => 1,
+            'name' => 'testProductAnyType',
+            'custom_attributes' => [
+                    'non_existent' => [
+                            'attribute_code' => 'non_existent',
+                            'value' => 'test',
+                        ],
+                    'custom_attribute_string' => [
+                            'attribute_code' => 'custom_attribute_string',
+                            'value' => 'someStringValue',
+                        ],
+                ],
+        ];
+        $result = $this->_webApiCall($serviceInfo, ['entityItem' => $requestData]);
+
+        //The non_existent custom attribute should be dropped since its not a defined custom attribute
+        $expectedResponse = [
+            'item_id' => 1,
+            'name' => 'testProductAnyType',
+            'custom_attributes' => [
+                    [
+                        'attribute_code' => 'custom_attribute_string',
+                        'value' => 'someStringValue',
+                    ],
+                ],
+        ];
+
+        //\Magento\TestModule1\Service\V1\AllSoapAndRest::itemAnyType just return the input data back as response
+        $this->assertEquals($expectedResponse, $result);
+    }
+
+    public function testDataObjectCustomAttributes()
+    {
+        if (TESTS_WEB_API_ADAPTER == self::ADAPTER_SOAP) {
+            $this->markTestIncomplete('MAGETWO-31016: incompatible with ZF 1.12.9');
+        }
+        $customAttributeDataObject = $this->customAttributeDataObjectBuilder
+            ->setName('nameValue')
+            ->setCustomAttribute('custom_attribute_int', 1)
+            ->create();
+
+        $item = $this->itemBuilder
+            ->setItemId(1)
+            ->setName('testProductAnyType')
+            ->setCustomAttribute('custom_attribute_data_object', $customAttributeDataObject)
+            ->setCustomAttribute('custom_attribute_string', 'someStringValue')
+            ->create();
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => $this->_restResourcePath . 'itemAnyType',
+                'httpMethod' => RestConfig::HTTP_METHOD_POST,
+            ],
+            'soap' => ['service' => $this->_soapService, 'operation' => $this->_soapService . 'ItemAnyType'],
+        ];
+        $requestData = $item->__toArray();
+        $result = $this->_webApiCall($serviceInfo, ['entityItem' => $requestData]);
+
+        $expectedResponse = $this->dataObjectConverter->processServiceOutput(
+            $item,
+            '\Magento\TestModule1\Service\V1\AllSoapAndRestInterface',
+            'itemAnyType'
+        );
+        //\Magento\TestModule1\Service\V1\AllSoapAndRest::itemAnyType just return the input data back as response
+        $this->assertEquals($expectedResponse, $result);
+    }
+
+    public function testDataObjectCustomAttributesPreconfiguredItem()
+    {
+        if (TESTS_WEB_API_ADAPTER == self::ADAPTER_SOAP) {
+            $this->markTestIncomplete('MAGETWO-31016: incompatible with ZF 1.12.9');
+        }
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => $this->_restResourcePath . 'itemPreconfigured',
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+            'soap' => ['service' => $this->_soapService, 'operation' => $this->_soapService . 'GetPreconfiguredItem'],
+        ];
+
+        $result = $this->_webApiCall($serviceInfo, []);
+
+        $customAttributeDataObject = $this->customAttributeDataObjectBuilder
+            ->setName('nameValue')
+            ->setCustomAttribute('custom_attribute_int', 1)
+            ->create();
+
+        $item = $this->itemBuilder
+            ->setItemId(1)
+            ->setName('testProductAnyType')
+            ->setCustomAttribute('custom_attribute_data_object', $customAttributeDataObject)
+            ->setCustomAttribute('custom_attribute_string', 'someStringValue')
+            ->create();
+        $expectedResponse = $this->dataObjectConverter->processServiceOutput(
+            $item,
+            '\Magento\TestModule1\Service\V1\AllSoapAndRestInterface',
+            'getPreconfiguredItem'
+        );
+        $this->assertEquals($expectedResponse, $result);
+    }
+
+    public function testNestedDataObjectCustomAttributes()
+    {
+        if (TESTS_WEB_API_ADAPTER == self::ADAPTER_SOAP) {
+            $this->markTestIncomplete('MAGETWO-31016: incompatible with ZF 1.12.9');
+        }
+        $customAttributeNestedDataObject = $this->customAttributeNestedDataObjectBuilder
+            ->setName('nestedNameValue')
+            ->create();
+
+        $customAttributeDataObject = $this->customAttributeDataObjectBuilder
+            ->setName('nameValue')
+            ->setCustomAttribute('custom_attribute_nested', $customAttributeNestedDataObject)
+            ->setCustomAttribute('custom_attribute_int', 1)
+            ->create();
+
+        $item = $this->itemBuilder
+            ->setItemId(1)
+            ->setName('testProductAnyType')
+            ->setCustomAttribute('custom_attribute_data_object', $customAttributeDataObject)
+            ->setCustomAttribute('custom_attribute_string', 'someStringValue')
+            ->create();
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => $this->_restResourcePath . 'itemAnyType',
+                'httpMethod' => RestConfig::HTTP_METHOD_POST,
+            ],
+            'soap' => ['service' => $this->_soapService, 'operation' => $this->_soapService . 'ItemAnyType'],
+        ];
+        $requestData = $item->__toArray();
+        $result = $this->_webApiCall($serviceInfo, ['entityItem' => $requestData]);
+
+        $expectedResponse = $this->dataObjectConverter->processServiceOutput(
+            $item,
+            '\Magento\TestModule1\Service\V1\AllSoapAndRestInterface',
+            'itemAnyType'
+        );
+        //\Magento\TestModule1\Service\V1\AllSoapAndRest::itemAnyType just return the input data back as response
+        $this->assertEquals($expectedResponse, $result);
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Webapi/DataObjectSerialization/ServiceSerializationTest.php b/dev/tests/api-functional/testsuite/Magento/Webapi/DataObjectSerialization/ServiceSerializationTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..ce7aa7ea452fcb1622e2963a514b182c17b66a31
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Webapi/DataObjectSerialization/ServiceSerializationTest.php
@@ -0,0 +1,110 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Webapi\DataObjectSerialization;
+
+class ServiceSerializationTest extends \Magento\TestFramework\TestCase\WebapiAbstract
+{
+    /**
+     * @var string
+     */
+    protected $_version;
+
+    /**
+     * @var string
+     */
+    protected $_restResourcePath;
+
+    protected function setUp()
+    {
+        $this->_markTestAsRestOnly();
+        $this->_version = 'V1';
+        $this->_restResourcePath = "/{$this->_version}/testmodule4/";
+    }
+
+    /**
+     *  Test simple request data
+     */
+    public function testGetServiceCall()
+    {
+        $itemId = 1;
+        $name = 'Test';
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => $this->_restResourcePath . $itemId,
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_GET,
+            ],
+        ];
+        $item = $this->_webApiCall($serviceInfo, []);
+        $this->assertEquals($itemId, $item['entity_id'], 'id field returned incorrectly');
+        $this->assertEquals($name, $item['name'], 'name field returned incorrectly');
+    }
+
+    /**
+     *  Test multiple params with Data Object
+     */
+    public function testUpdateServiceCall()
+    {
+        $itemId = 1;
+        $name = 'Test';
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => $this->_restResourcePath . $itemId,
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_POST,
+            ],
+        ];
+        $item = $this->_webApiCall($serviceInfo, ['request' => ['name' => $name]]);
+        $this->assertEquals($itemId, $item['entity_id'], 'id field returned incorrectly');
+        $this->assertEquals($name, $item['name'], 'name field returned incorrectly');
+    }
+
+    /**
+     *  Test nested Data Object
+     */
+    public function testNestedDataObjectCall()
+    {
+        $itemId = 1;
+        $name = 'Test';
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => $this->_restResourcePath . $itemId . '/nested',
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_POST,
+            ],
+        ];
+        $item = $this->_webApiCall($serviceInfo, ['request' => ['details' => ['name' => $name]]]);
+        $this->assertEquals($itemId, $item['entity_id'], 'id field returned incorrectly');
+        $this->assertEquals($name, $item['name'], 'name field returned incorrectly');
+    }
+
+    public function testScalarResponse()
+    {
+        $id = 2;
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => "{$this->_restResourcePath}scalar/{$id}",
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_GET,
+            ],
+        ];
+        $this->assertEquals($id, $this->_webApiCall($serviceInfo), 'Scalar service output is serialized incorrectly.');
+    }
+
+    public function testExtensibleCall()
+    {
+        $id = 2;
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => "{$this->_restResourcePath}extensibleDataObject/{$id}",
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_POST,
+            ],
+        ];
+
+        $name = 'Magento';
+        $requestData = [
+          'name' => $name,
+        ];
+        $item = $this->_webApiCall($serviceInfo, ['request' => $requestData]);
+        $this->assertEquals($id, $item['entity_id'], 'id field returned incorrectly');
+        $this->assertEquals($name, $item['name'], 'name field returned incorrectly');
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Webapi/DeserializationTest.php b/dev/tests/api-functional/testsuite/Magento/Webapi/DeserializationTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..06a701a2ec6f117789b75b9dc150732c1ec4e6db
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Webapi/DeserializationTest.php
@@ -0,0 +1,79 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+namespace Magento\Webapi;
+
+use Magento\TestFramework\TestCase\Webapi\Adapter\Rest\CurlClient;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+class DeserializationTest extends \Magento\TestFramework\TestCase\WebapiAbstract
+{
+    /**
+     * @var string
+     */
+    protected $_version;
+
+    /**
+     * @var string
+     */
+    protected $_restResourcePath;
+
+    protected function setUp()
+    {
+        $this->_version = 'V1';
+        $this->_restResourcePath = "/{$this->_version}/TestModule5/";
+    }
+
+    /**
+     *  Test POST request with empty body
+     */
+    public function testPostRequestWithEmptyBody()
+    {
+        $this->_markTestAsRestOnly();
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => $this->_restResourcePath,
+                'httpMethod' => RestConfig::HTTP_METHOD_POST,
+            ],
+        ];
+        $expectedMessage = '{"message":"%fieldName is a required field.","parameters":{"fieldName":"item"}}';
+        try {
+            $this->_webApiCall($serviceInfo, CurlClient::EMPTY_REQUEST_BODY);
+        } catch (\Exception $e) {
+            $this->assertEquals(\Magento\Webapi\Exception::HTTP_BAD_REQUEST, $e->getCode());
+            $this->assertContains(
+                $expectedMessage,
+                $e->getMessage(),
+                "Response does not contain expected message."
+            );
+        }
+    }
+
+    /**
+     *  Test PUT request with empty body
+     */
+    public function testPutRequestWithEmptyBody()
+    {
+        $this->_markTestAsRestOnly();
+        $itemId = 1;
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => $this->_restResourcePath . $itemId,
+                'httpMethod' => RestConfig::HTTP_METHOD_PUT,
+            ],
+        ];
+        $expectedMessage = '{"message":"%fieldName is a required field.","parameters":{"fieldName":"entityItem"}}';
+        try {
+            $this->_webApiCall($serviceInfo, CurlClient::EMPTY_REQUEST_BODY);
+        } catch (\Exception $e) {
+            $this->assertEquals(\Magento\Webapi\Exception::HTTP_BAD_REQUEST, $e->getCode());
+            $this->assertContains(
+                $expectedMessage,
+                $e->getMessage(),
+                "Response does not contain expected message."
+            );
+        }
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Webapi/PartialResponseTest.php b/dev/tests/api-functional/testsuite/Magento/Webapi/PartialResponseTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..36b79d9157bf635865c040218576b9339ccb49fb
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Webapi/PartialResponseTest.php
@@ -0,0 +1,99 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+namespace Magento\Webapi;
+
+use Magento\Customer\Api\AccountManagementTest;
+use Magento\TestFramework\Helper\Bootstrap;
+use Magento\TestFramework\Helper\Customer as CustomerHelper;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+class PartialResponseTest extends \Magento\TestFramework\TestCase\WebapiAbstract
+{
+    /** @var CustomerHelper */
+    protected $customerHelper;
+
+    /** @var string */
+    protected $customerData;
+
+    protected function setUp()
+    {
+        $this->_markTestAsRestOnly('Partial response functionality available in REST mode only.');
+
+        $this->customerHelper = Bootstrap::getObjectManager()
+            ->get('Magento\TestFramework\Helper\Customer');
+
+        $this->customerData = $this->customerHelper->createSampleCustomer();
+    }
+
+    public function testCustomerWithEmailFilter()
+    {
+        $filter = 'email';
+        $expected = ['email' => $this->customerData['email']];
+        $result = $this->_getCustomerWithFilter($filter, $this->customerData['id']);
+        $this->assertEquals($expected, $result);
+    }
+
+    public function testCustomerWithEmailAndAddressFilter()
+    {
+        $filter = 'email,addresses[city]';
+        $expected = [
+            'email' => $this->customerData['email'],
+            'addresses' => [
+                ['city' => CustomerHelper::ADDRESS_CITY1],
+                ['city' => CustomerHelper::ADDRESS_CITY2],
+            ],
+        ];
+        $result = $this->_getCustomerWithFilter($filter, $this->customerData['id']);
+        $this->assertEquals($expected, $result);
+    }
+
+    public function testCustomerWithNestedAddressFilter()
+    {
+        $filter = 'addresses[region[region_code]]';
+        $expected = [
+            'addresses' => [
+                ['region' => ['region_code' => CustomerHelper::ADDRESS_REGION_CODE1]],
+                ['region' => ['region_code' => CustomerHelper::ADDRESS_REGION_CODE2]],
+            ],
+        ];
+        $result = $this->_getCustomerWithFilter($filter, $this->customerData['id']);
+        $this->assertEquals($expected, $result);
+    }
+
+    public function testCustomerInvalidFilter()
+    {
+        // Invalid filter should return an empty result
+        $result = $this->_getCustomerWithFilter('invalid', $this->customerData['id']);
+        $this->assertEmpty($result);
+    }
+
+    public function testFilterForCustomerApiWithSimpleResponse()
+    {
+        $result = $this->_getCustomerWithFilter('customers', $this->customerData['id'], '/permissions/readonly');
+        // assert if filter is ignored and a normal response is returned
+        $this->assertFalse($result);
+    }
+
+    protected function _getCustomerWithFilter($filter, $customerId, $path = '')
+    {
+        $resourcePath = sprintf(
+            '%s/%d%s?fields=%s',
+            AccountManagementTest::RESOURCE_PATH,
+            $customerId,
+            $path,
+            $filter
+        );
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => $resourcePath,
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+        ];
+
+        return $this->_webApiCall($serviceInfo);
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Webapi/Routing/BaseService.php b/dev/tests/api-functional/testsuite/Magento/Webapi/Routing/BaseService.php
new file mode 100644
index 0000000000000000000000000000000000000000..5312762a3ae8634e5d0a98a1fcebe27636160f4f
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Webapi/Routing/BaseService.php
@@ -0,0 +1,113 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Webapi\Routing;
+
+use Magento\Framework\Exception\AuthorizationException;
+use Magento\Webapi\Exception as WebapiException;
+
+/**
+ * Base class for all Service based routing tests
+ */
+abstract class BaseService extends \Magento\TestFramework\TestCase\WebapiAbstract
+{
+    /**
+     * Check a particular adapter and assert unauthorized access
+     *
+     * @param array      $serviceInfo
+     * @param array|null $requestData
+     */
+    protected function assertUnauthorizedException($serviceInfo, $requestData = null)
+    {
+        if (TESTS_WEB_API_ADAPTER == self::ADAPTER_SOAP) {
+            $this->_assertSoapException($serviceInfo, $requestData, 'Consumer is not authorized to access %resources');
+        } elseif (TESTS_WEB_API_ADAPTER == self::ADAPTER_REST) {
+            $this->_assertRestUnauthorizedException($serviceInfo, $requestData);
+        }
+    }
+
+    /**
+     * Invoke the REST api and assert access is unauthorized
+     *
+     * @param array      $serviceInfo
+     * @param array|null $requestData
+     */
+    protected function _assertRestUnauthorizedException($serviceInfo, $requestData = null)
+    {
+        try {
+            $this->_webApiCall($serviceInfo, $requestData);
+        } catch (\Exception $e) {
+            $this->assertContains(
+                '{"message":"' . AuthorizationException::NOT_AUTHORIZED . '"',
+                $e->getMessage(),
+                sprintf(
+                    'REST routing did not fail as expected for the method "%s" of service "%s"',
+                    $serviceInfo['rest']['httpMethod'],
+                    $serviceInfo['rest']['resourcePath']
+                )
+            );
+            $this->assertEquals(WebapiException::HTTP_UNAUTHORIZED, $e->getCode());
+        }
+    }
+
+    /**
+     * Check a particular adapter and assert the exception
+     *
+     * @param array      $serviceInfo
+     * @param array|null $requestData
+     */
+    protected function _assertNoRouteOrOperationException($serviceInfo, $requestData = null)
+    {
+        if (TESTS_WEB_API_ADAPTER == self::ADAPTER_SOAP) {
+            $this->_assertSoapException($serviceInfo, $requestData);
+        } elseif (TESTS_WEB_API_ADAPTER == self::ADAPTER_REST) {
+            $this->_assertNoRestRouteException($serviceInfo, $requestData);
+        }
+    }
+
+    /**
+     * Invoke the REST api and assert for test cases that no such REST route exist
+     *
+     * @param array      $serviceInfo
+     * @param array|null $requestData
+     */
+    protected function _assertNoRestRouteException($serviceInfo, $requestData = null)
+    {
+        try {
+            $this->_webApiCall($serviceInfo, $requestData);
+        } catch (\Exception $e) {
+            $error = json_decode($e->getMessage(), true);
+            $this->assertEquals('Request does not match any route.', $error['message']);
+            $this->assertEquals(WebapiException::HTTP_NOT_FOUND, $e->getCode());
+        }
+    }
+
+    /**
+     * Invoke the SOAP api and assert for the NoWebApiXmlTestTest test cases that no such SOAP route exists
+     *
+     * @param array      $serviceInfo
+     * @param array|null $requestData
+     * @param string     $expectedMessage
+     */
+    protected function _assertSoapException($serviceInfo, $requestData = null, $expectedMessage = '')
+    {
+        try {
+            $this->_webApiCall($serviceInfo, $requestData);
+        } catch (\Exception $e) {
+            if (get_class($e) !== 'SoapFault') {
+                $this->fail(
+                    sprintf(
+                        'Expected SoapFault exception not generated for Service - "%s" and Operation - "%s"',
+                        $serviceInfo['soap']['service'],
+                        $serviceInfo['soap']['operation']
+                    )
+                );
+            }
+
+            if ($expectedMessage) {
+                $this->assertEquals($expectedMessage, $e->getMessage());
+            }
+        }
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Webapi/Routing/CoreRoutingTest.php b/dev/tests/api-functional/testsuite/Magento/Webapi/Routing/CoreRoutingTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..e1959e4f59fc9a54e46a4ad472fc8916471f2f4e
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Webapi/Routing/CoreRoutingTest.php
@@ -0,0 +1,75 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+/**
+ * Class to test Core Web API routing
+ */
+namespace Magento\Webapi\Routing;
+
+class CoreRoutingTest extends \Magento\Webapi\Routing\BaseService
+{
+    public function testBasicRoutingExplicitPath()
+    {
+        $itemId = 1;
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/testmodule1/' . $itemId,
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => 'testModule1AllSoapAndRestV1',
+                'operation' => 'testModule1AllSoapAndRestV1Item',
+            ],
+        ];
+        $requestData = ['itemId' => $itemId];
+        $item = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertEquals('testProduct1', $item['name'], "Item was retrieved unsuccessfully");
+    }
+
+    public function testDisabledIntegrationAuthorizationException()
+    {
+        $itemId = 1;
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/testmodule1/' . $itemId,
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => 'testModule1AllSoapAndRestV1',
+                'operation' => 'testModule1AllSoapAndRestV1Item',
+            ],
+        ];
+        $requestData = ['itemId' => $itemId];
+
+        /** Disable integration associated with active OAuth credentials. */
+        $credentials = \Magento\TestFramework\Authentication\OauthHelper::getApiAccessCredentials();
+        /** @var \Magento\Integration\Model\Integration $integration */
+        $integration = $credentials['integration'];
+        $originalStatus = $integration->getStatus();
+        $integration->setStatus(\Magento\Integration\Model\Integration::STATUS_INACTIVE)->save();
+
+        try {
+            $this->assertUnauthorizedException($serviceInfo, $requestData);
+        } catch (\Exception $e) {
+            /** Restore original status of integration associated with active OAuth credentials */
+            $integration->setStatus($originalStatus)->save();
+            throw $e;
+        }
+        $integration->setStatus($originalStatus)->save();
+    }
+
+    public function testExceptionSoapInternalError()
+    {
+        $this->_markTestAsSoapOnly();
+        $serviceInfo = [
+            'soap' => [
+                'service' => 'testModule3ErrorV1',
+                'operation' => 'testModule3ErrorV1ServiceException',
+            ],
+        ];
+        $this->setExpectedException('SoapFault', 'Generic service exception');
+        $this->_webApiCall($serviceInfo);
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Webapi/Routing/GettersTest.php b/dev/tests/api-functional/testsuite/Magento/Webapi/Routing/GettersTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..5dfc7fb4a6e240be896190d2319dfb2c943e6299
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Webapi/Routing/GettersTest.php
@@ -0,0 +1,55 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Webapi\Routing;
+
+use Magento\TestModule5\Service\V1\Entity\AllSoapAndRest;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+class GettersTest extends \Magento\Webapi\Routing\BaseService
+{
+    /**
+     * @var string
+     */
+    protected $_version;
+
+    /**
+     * @var string
+     */
+    protected $_restResourcePath;
+
+    /**
+     * @var string
+     */
+    protected $_soapService = 'testModule5AllSoapAndRest';
+
+    protected function setUp()
+    {
+        $this->_version = 'V1';
+        $this->_soapService = "testModule5AllSoapAndRest{$this->_version}";
+        $this->_restResourcePath = "/{$this->_version}/TestModule5/";
+    }
+
+    public function testGetters()
+    {
+        $itemId = 1;
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => $this->_restResourcePath . $itemId,
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => $this->_soapService,
+                'operation' => $this->_soapService . 'Item',
+            ],
+        ];
+        $requestData = [AllSoapAndRest::ID => $itemId];
+        $item = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertEquals($itemId, $item[AllSoapAndRest::ID], 'Item was retrieved unsuccessfully');
+        $isEnabled = isset($item[AllSoapAndRest::ENABLED]) && $item[AllSoapAndRest::ENABLED] === true;
+        $this->assertTrue($isEnabled, 'Getter with "is" prefix is processed incorrectly.');
+        $hasOrder = isset($item[AllSoapAndRest::HAS_ORDERS]) && $item[AllSoapAndRest::HAS_ORDERS] === true;
+        $this->assertTrue($hasOrder, 'Getter with "has" prefix is processed incorrectly.');
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Webapi/Routing/NoWebApiXmlTest.php b/dev/tests/api-functional/testsuite/Magento/Webapi/Routing/NoWebApiXmlTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..819429fb00302902ff55d6960ff64b5ee9218705
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Webapi/Routing/NoWebApiXmlTest.php
@@ -0,0 +1,106 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Webapi\Routing;
+
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+/**
+ * Class to test routing with a service that has no webapi.xml
+ */
+class NoWebApiXmlTest extends \Magento\Webapi\Routing\BaseService
+{
+    /**
+     * @var string
+     */
+    private $_version;
+
+    /**
+     * @var string
+     */
+    private $_restResourcePath;
+
+    protected function setUp()
+    {
+        $this->_version = 'V1';
+        $this->_restResourcePath = "/{$this->_version}/testModule2NoWebApiXml/";
+    }
+
+    /**
+     *  Test get item
+     */
+    public function testItem()
+    {
+        $this->_markTestAsRestOnly();
+        $itemId = 1;
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => $this->_restResourcePath . $itemId,
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+        ];
+        $requestData = ['id' => $itemId];
+        $this->_assertNoRestRouteException($serviceInfo, $requestData);
+    }
+
+    /**
+     * Test fetching all items
+     */
+    public function testItems()
+    {
+        $this->_markTestAsRestOnly();
+        $serviceInfo = [
+            'rest' => ['resourcePath' => $this->_restResourcePath, 'httpMethod' => RestConfig::HTTP_METHOD_GET],
+        ];
+        $this->_assertNoRestRouteException($serviceInfo);
+    }
+
+    /**
+     *  Test create item
+     */
+    public function testCreate()
+    {
+        $this->_markTestAsRestOnly();
+        $createdItemName = 'createdItemName';
+        $serviceInfo = [
+            'rest' => ['resourcePath' => $this->_restResourcePath, 'httpMethod' => RestConfig::HTTP_METHOD_POST],
+        ];
+        $requestData = ['name' => $createdItemName];
+        $this->_assertNoRestRouteException($serviceInfo, $requestData);
+    }
+
+    /**
+     *  Test update item
+     */
+    public function testUpdate()
+    {
+        $this->_markTestAsRestOnly();
+        $itemId = 1;
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => $this->_restResourcePath . $itemId,
+                'httpMethod' => RestConfig::HTTP_METHOD_PUT,
+            ],
+        ];
+        $requestData = ['id' => $itemId];
+        $this->_assertNoRestRouteException($serviceInfo, $requestData);
+    }
+
+    /**
+     *  Test remove item
+     */
+    public function testRemove()
+    {
+        $this->_markTestAsRestOnly();
+        $itemId = 1;
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => $this->_restResourcePath . $itemId,
+                'httpMethod' => RestConfig::HTTP_METHOD_DELETE,
+            ],
+        ];
+        $requestData = ['id' => $itemId];
+        $this->_assertNoRestRouteException($serviceInfo, $requestData);
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Webapi/Routing/RequestIdOverrideTest.php b/dev/tests/api-functional/testsuite/Magento/Webapi/Routing/RequestIdOverrideTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..6ed32b0349fe1a14417e294732777634d0553431
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Webapi/Routing/RequestIdOverrideTest.php
@@ -0,0 +1,160 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+namespace Magento\Webapi\Routing;
+
+use Magento\TestFramework\Helper\Bootstrap;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+/**
+ * Class to test overriding request body identifier property with id passed in url path parameter
+ *
+ * Refer to \Magento\Webapi\Controller\Rest\Request::overrideRequestBodyIdWithPathParam
+ */
+class RequestIdOverrideTest extends \Magento\Webapi\Routing\BaseService
+{
+    /**
+     * @var string
+     */
+    protected $_version;
+
+    /**
+     * @var string
+     */
+    protected $_restResourcePath;
+
+    /**
+     * @var \Magento\TestModule5\Service\V1\Entity\AllSoapAndRestBuilder
+     */
+    protected $itemBuilder;
+
+    /**
+     * @var string
+     */
+    protected $_soapService = 'testModule5AllSoapAndRest';
+
+    protected function setUp()
+    {
+        $this->_markTestAsRestOnly('Request Id overriding is a REST based feature.');
+        $this->_version = 'V1';
+        $this->_restResourcePath = "/{$this->_version}/TestModule5/";
+        $this->itemBuilder = Bootstrap::getObjectManager()
+            ->create('Magento\TestModule5\Service\V1\Entity\AllSoapAndRestBuilder');
+    }
+
+    public function testOverride()
+    {
+        $itemId = 1;
+        $incorrectItemId = 2;
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => $this->_restResourcePath . $itemId,
+                'httpMethod' => RestConfig::HTTP_METHOD_PUT,
+            ],
+        ];
+        $item = $this->itemBuilder
+            ->setEntityId($incorrectItemId)
+            ->setName('test')
+            ->create();
+        $requestData = ['entityItem' => $item->__toArray()];
+        $item = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertEquals(
+            $itemId,
+            $item[\Magento\TestModule5\Service\V1\Entity\AllSoapAndRest::ID],
+            'Identifier overriding failed.'
+        );
+    }
+
+    public function testOverrideNested()
+    {
+        $firstItemId = 1;
+        $secondItemId = 11;
+        $incorrectItemId = 2;
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => $this->_restResourcePath . $firstItemId . '/nestedResource/' . $secondItemId,
+                'httpMethod' => RestConfig::HTTP_METHOD_PUT,
+            ],
+        ];
+        $item = $this->itemBuilder
+            ->setEntityId($incorrectItemId)
+            ->setName('test')
+            ->create();
+        $requestData = ['entityItem' => $item->__toArray()];
+        $item = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertEquals(
+            $secondItemId,
+            $item[\Magento\TestModule5\Service\V1\Entity\AllSoapAndRest::ID],
+            'Identifier overriding failed for nested resource request.'
+        );
+    }
+
+    public function testOverrideAdd()
+    {
+        $itemId = 1;
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => $this->_restResourcePath . $itemId,
+                'httpMethod' => RestConfig::HTTP_METHOD_PUT,
+            ],
+        ];
+        $item = $this->itemBuilder
+            ->setName('test')
+            ->create();
+        $requestData = ['entityItem' => $item->__toArray()];
+        $item = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertEquals(
+            $itemId,
+            $item[\Magento\TestModule5\Service\V1\Entity\AllSoapAndRest::ID],
+            'Identifier replacing failed.'
+        );
+    }
+
+    /**
+     * Test if the framework works if camelCase path parameters are provided instead of valid snake case ones.
+     * Webapi Framework currently accepts both cases due to shortcoming in Serialization (MAGETWO-29833).
+     * Unless it is fixed this use case is valid.
+     */
+    public function testAddCaseMismatch()
+    {
+        $itemId = 1;
+        $incorrectItemId = 2;
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => $this->_restResourcePath . $itemId,
+                'httpMethod' => RestConfig::HTTP_METHOD_PUT,
+            ],
+        ];
+        $requestData = ['entityItem' => ['entityId' => $incorrectItemId, 'name' => 'test']];
+        $item = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertEquals(
+            $itemId,
+            $item[\Magento\TestModule5\Service\V1\Entity\AllSoapAndRest::ID],
+            'Identifier overriding failed.'
+        );
+    }
+
+    public function testOverrideWithScalarValues()
+    {
+        $firstItemId = 1;
+        $secondItemId = 11;
+        $incorrectItemId = 2;
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => "/{$this->_version}/TestModule5/OverrideService/" . $firstItemId
+                    . '/nestedResource/' . $secondItemId,
+                'httpMethod' => RestConfig::HTTP_METHOD_PUT,
+            ],
+        ];
+
+        $requestData = ['entity_id' => $incorrectItemId, 'name' => 'test', 'orders' => true];
+        $item = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertEquals(
+            $secondItemId,
+            $item[\Magento\TestModule5\Service\V1\Entity\AllSoapAndRest::ID],
+            'Identifier overriding failed.'
+        );
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Webapi/Routing/RestErrorHandlingTest.php b/dev/tests/api-functional/testsuite/Magento/Webapi/Routing/RestErrorHandlingTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..18d169911f9fd95a5112de353d85d9fcb5565073
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Webapi/Routing/RestErrorHandlingTest.php
@@ -0,0 +1,160 @@
+<?php
+/**
+ * Test Web API error codes.
+ *
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Webapi\Routing;
+
+use Magento\TestFramework\Helper\Bootstrap;
+use Magento\Webapi\Exception as WebapiException;
+
+class RestErrorHandlingTest extends \Magento\TestFramework\TestCase\WebapiAbstract
+{
+    /**
+     * @var string
+     */
+    protected $mode;
+
+    protected function setUp()
+    {
+        $this->_markTestAsRestOnly();
+        $this->mode = Bootstrap::getObjectManager()->get('Magento\Framework\App\State')->getMode();
+        parent::setUp();
+    }
+
+    public function testSuccess()
+    {
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/errortest/success',
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_GET,
+            ],
+        ];
+
+        $item = $this->_webApiCall($serviceInfo);
+
+        // TODO: check Http Status = 200, cannot do yet due to missing header info returned
+
+        $this->assertEquals('a good id', $item['value'], 'Success case is correct');
+    }
+
+    public function testNotFound()
+    {
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/errortest/notfound',
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_GET,
+            ],
+        ];
+
+        // \Magento\Framework\Api\ResourceNotFoundException
+        $this->_errorTest(
+            $serviceInfo,
+            ['resource_id' => 'resourceY'],
+            WebapiException::HTTP_NOT_FOUND,
+            'Resource with ID "%resource_id" not found.'
+        );
+    }
+
+    public function testUnauthorized()
+    {
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/errortest/unauthorized',
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_GET,
+            ],
+        ];
+
+        // \Magento\Framework\Api\AuthorizationException
+        $this->_errorTest(
+            $serviceInfo,
+            [],
+            WebapiException::HTTP_UNAUTHORIZED,
+            'Consumer is not authorized to access %resources',
+            ['resources' => 'resourceN']
+        );
+    }
+
+    public function testOtherException()
+    {
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => '/V1/errortest/otherexception',
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_GET,
+            ],
+        ];
+
+        /* TODO : Fix as part MAGETWO-31330
+        $expectedMessage = $this->mode == \Magento\Framework\App\State::MODE_DEVELOPER
+            ? 'Non service exception'
+            : 'Internal Error. Details are available in Magento log file. Report ID: webapi-XXX';
+        */
+        $expectedMessage = 'Internal Error. Details are available in Magento log file. Report ID: webapi-XXX';
+        $this->_errorTest(
+            $serviceInfo,
+            [],
+            WebapiException::HTTP_INTERNAL_ERROR,
+            $expectedMessage,
+            null,
+            'Magento\TestModule3\Service\V1\Error->otherException()' // Check if trace contains proper error source
+        );
+    }
+
+    /**
+     * Perform a negative REST api call test case and compare the results with expected values.
+     *
+     * @param string $serviceInfo - REST Service information (i.e. resource path and HTTP method)
+     * @param array $data - Data for the cal
+     * @param int $httpStatus - Expected HTTP status
+     * @param string|array $errorMessage - \Exception error message
+     * @param array $parameters - Optional parameters array, or null if no parameters
+     * @param string $traceString - Optional trace string to verify
+     */
+    protected function _errorTest(
+        $serviceInfo,
+        $data,
+        $httpStatus,
+        $errorMessage,
+        $parameters = [],
+        $traceString = null
+    ) {
+        try {
+            $this->_webApiCall($serviceInfo, $data);
+        } catch (\Exception $e) {
+            $this->assertEquals($httpStatus, $e->getCode(), 'Checking HTTP status code');
+
+            $body = json_decode($e->getMessage(), true);
+
+            $errorMessages = is_array($errorMessage) ? $errorMessage : [$errorMessage];
+            $actualMessage = $body['message'];
+            $matches = [];
+            //Report ID was created dynamically, so we need to replace it with some static value in order to test
+            if (preg_match('/.*Report\sID\:\s([a-zA-Z0-9\-]*)/', $actualMessage, $matches)) {
+                $actualMessage = str_replace($matches[1], 'webapi-XXX', $actualMessage);
+            }
+            //make sure that the match for a report with an id is found if Internal error was reported
+            //Refer : \Magento\Webapi\Controller\ErrorProcessor::INTERNAL_SERVER_ERROR_MSG
+            if (count($matches) > 1) {
+                $this->assertTrue(!empty($matches[1]), 'Report id missing for internal error.');
+            }
+            $this->assertContains(
+                $actualMessage,
+                $errorMessages,
+                "Message is invalid. Actual: '{$actualMessage}'. Expected one of: {'" . implode(
+                    "', '",
+                    $errorMessages
+                ) . "'}"
+            );
+
+            if ($parameters) {
+                $this->assertEquals($parameters, $body['parameters'], 'Checking body parameters');
+            }
+
+            if ($this->mode == \Magento\Framework\App\State::MODE_DEVELOPER && $traceString) {
+                // TODO : Fix as part MAGETWO-31330
+                //$this->assertContains($traceString, $body['trace'], 'Trace information is incorrect.');
+            }
+        }
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Webapi/Routing/ServiceVersionV1Test.php b/dev/tests/api-functional/testsuite/Magento/Webapi/Routing/ServiceVersionV1Test.php
new file mode 100644
index 0000000000000000000000000000000000000000..d0e97d268f3473b8464dc1a6925e8d99bc773215
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Webapi/Routing/ServiceVersionV1Test.php
@@ -0,0 +1,267 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+/**
+ * Class to test routing based on Service Versioning(for V1 version of a service)
+ */
+namespace Magento\Webapi\Routing;
+
+use Magento\Framework\Api\AttributeValue;
+use Magento\TestFramework\Authentication\OauthHelper;
+use Magento\TestFramework\Helper\Bootstrap;
+use Magento\TestModule1\Service\V1\Entity\ItemBuilder;
+use Magento\Webapi\Model\Rest\Config as RestConfig;
+
+class ServiceVersionV1Test extends \Magento\Webapi\Routing\BaseService
+{
+    /**
+     * @var string
+     */
+    protected $_version;
+    /**
+     * @var string
+     */
+    protected $_restResourcePath;
+    /**
+     * @var string
+     */
+    protected $_soapService = 'testModule1AllSoapAndRest';
+
+    /** @var \Magento\Framework\Api\AttributeDataBuilder */
+    protected $valueBuilder;
+
+    /** @var ItemBuilder */
+    protected $itemBuilder;
+
+    protected function setUp()
+    {
+        $this->_version = 'V1';
+        $this->_soapService = 'testModule1AllSoapAndRestV1';
+        $this->_restResourcePath = "/{$this->_version}/testmodule1/";
+
+        $this->valueBuilder = Bootstrap::getObjectManager()->create(
+            'Magento\Framework\Api\AttributeDataBuilder'
+        );
+
+        $this->itemBuilder = Bootstrap::getObjectManager()->create(
+            'Magento\TestModule1\Service\V1\Entity\ItemBuilder'
+        );
+    }
+
+    /**
+     *  Test get item
+     */
+    public function testItem()
+    {
+        $itemId = 1;
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => $this->_restResourcePath . $itemId,
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+            'soap' => ['service' => $this->_soapService, 'operation' => $this->_soapService . 'Item'],
+        ];
+        $requestData = ['itemId' => $itemId];
+        $item = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertEquals('testProduct1', $item['name'], 'Item was retrieved unsuccessfully');
+    }
+
+    /**
+     *  Test get item with any type
+     */
+    public function testItemAnyType()
+    {
+        $this->_markTestAsRestOnly('Test will fail for SOAP because attribute values get converted to strings.');
+        $customerAttributes = [
+            ItemBuilder::CUSTOM_ATTRIBUTE_1 => [
+                AttributeValue::ATTRIBUTE_CODE => ItemBuilder::CUSTOM_ATTRIBUTE_1,
+                AttributeValue::VALUE => '12345',
+            ],
+            ItemBuilder::CUSTOM_ATTRIBUTE_2 => [
+                AttributeValue::ATTRIBUTE_CODE => ItemBuilder::CUSTOM_ATTRIBUTE_2,
+                AttributeValue::VALUE => 12345,
+            ],
+            ItemBuilder::CUSTOM_ATTRIBUTE_3 => [
+                AttributeValue::ATTRIBUTE_CODE => ItemBuilder::CUSTOM_ATTRIBUTE_3,
+                AttributeValue::VALUE => true,
+            ],
+        ];
+
+        $attributeValue1 = $this->valueBuilder
+            ->setAttributeCode(ItemBuilder::CUSTOM_ATTRIBUTE_1)
+            ->setValue('12345')
+            ->create();
+        $attributeValue2 = $this->valueBuilder
+            ->setAttributeCode(ItemBuilder::CUSTOM_ATTRIBUTE_2)
+            ->setValue(12345)
+            ->create();
+        $attributeValue3 = $this->valueBuilder
+            ->setAttributeCode(ItemBuilder::CUSTOM_ATTRIBUTE_3)
+            ->setValue(true)
+            ->create();
+
+        $item = $this->itemBuilder
+            ->setItemId(1)
+            ->setName('testProductAnyType')
+            ->setCustomAttributes([$attributeValue1, $attributeValue2, $attributeValue3])
+            ->create();
+
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => $this->_restResourcePath . 'itemAnyType',
+                'httpMethod' => RestConfig::HTTP_METHOD_POST,
+            ],
+            'soap' => ['service' => $this->_soapService, 'operation' => $this->_soapService . 'ItemAnyType'],
+        ];
+        $requestData = $item->__toArray();
+        $item = $this->_webApiCall($serviceInfo, ['entityItem' => $requestData]);
+
+        $this->assertSame(
+            $attributeValue1->getValue(),
+            $item['custom_attributes'][0]['value'],
+            'Serialized attribute value type does\'t match pre-defined type.'
+        ); // string '12345' is expected
+
+        $this->assertSame(
+            $attributeValue2->getValue(),
+            $item['custom_attributes'][1]['value'],
+            'Serialized attribute value type does\'t match pre-defined type.'
+        ); // integer 12345 is expected
+
+        $this->assertSame(
+            $attributeValue3->getValue(),
+            $item['custom_attributes'][2]['value'],
+            'Serialized attribute value type does\'t match pre-defined type.'
+        ); // boolean true is expected
+    }
+
+    /**
+     * Test fetching all items
+     */
+    public function testItems()
+    {
+        $itemArr = [['item_id' => 1, 'name' => 'testProduct1'], ['item_id' => 2, 'name' => 'testProduct2']];
+        $serviceInfo = [
+            'rest' => ['resourcePath' => $this->_restResourcePath, 'httpMethod' => RestConfig::HTTP_METHOD_GET],
+            'soap' => ['service' => $this->_soapService, 'operation' => $this->_soapService . 'Items'],
+        ];
+        $item = $this->_webApiCall($serviceInfo);
+        $this->assertEquals($itemArr, $item, 'Items were not retrieved');
+    }
+
+    /**
+     *  Test create item
+     */
+    public function testCreate()
+    {
+        $createdItemName = 'createdItemName';
+        $serviceInfo = [
+            'rest' => ['resourcePath' => $this->_restResourcePath, 'httpMethod' => RestConfig::HTTP_METHOD_POST],
+            'soap' => ['service' => $this->_soapService, 'operation' => $this->_soapService . 'Create'],
+        ];
+        $requestData = ['name' => $createdItemName];
+        $item = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertEquals($createdItemName, $item['name'], 'Item creation failed');
+    }
+
+    /**
+     *  Test create item with missing proper resources
+     */
+    public function testCreateWithoutResources()
+    {
+        $createdItemName = 'createdItemName';
+        $serviceInfo = [
+            'rest' => ['resourcePath' => $this->_restResourcePath, 'httpMethod' => RestConfig::HTTP_METHOD_POST],
+            'soap' => ['service' => $this->_soapService, 'operation' => $this->_soapService . 'Create'],
+        ];
+        $requestData = ['name' => $createdItemName];
+
+        // getting new credentials that do not match the api resources
+        OauthHelper::clearApiAccessCredentials();
+        OauthHelper::getApiAccessCredentials([]);
+        try {
+            $this->assertUnauthorizedException($serviceInfo, $requestData);
+        } catch (\Exception $e) {
+            OauthHelper::clearApiAccessCredentials();
+            throw $e;
+        }
+        // to allow good credentials to be restored (this is statically stored on OauthHelper)
+        OauthHelper::clearApiAccessCredentials();
+    }
+
+    /**
+     *  Test update item
+     */
+    public function testUpdate()
+    {
+        $itemId = 1;
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => $this->_restResourcePath . $itemId,
+                'httpMethod' => RestConfig::HTTP_METHOD_PUT,
+            ],
+            'soap' => ['service' => $this->_soapService, 'operation' => $this->_soapService . 'Update'],
+        ];
+        $requestData = ['entityItem' => ['itemId' => $itemId, 'name' => 'testName']];
+        $item = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertEquals('Updated' . $requestData['entityItem']['name'], $item['name'], 'Item update failed');
+    }
+
+    /**
+     *  Negative Test: Invoking non-existent delete api which is only available in V2
+     */
+    public function testDelete()
+    {
+        $itemId = 1;
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => $this->_restResourcePath . $itemId,
+                'httpMethod' => RestConfig::HTTP_METHOD_DELETE,
+            ],
+            'soap' => ['service' => $this->_soapService, 'operation' => $this->_soapService . 'Delete'],
+        ];
+        $requestData = ['itemId' => $itemId, 'name' => 'testName'];
+        $this->_assertNoRouteOrOperationException($serviceInfo, $requestData);
+    }
+
+    public function testOverwritten()
+    {
+        $this->_markTestAsRestOnly();
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => $this->_restResourcePath . 'overwritten',
+                'httpMethod' => RestConfig::HTTP_METHOD_GET,
+            ],
+        ];
+        $item = $this->_webApiCall($serviceInfo, []);
+        $this->assertEquals(['item_id' => -55, 'name' => 'testProduct1'], $item);
+    }
+
+    public function testDefaulted()
+    {
+        $this->_markTestAsRestOnly();
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => $this->_restResourcePath . 'testOptionalParam',
+                'httpMethod' => RestConfig::HTTP_METHOD_POST,
+            ],
+        ];
+        $item = $this->_webApiCall($serviceInfo, []);
+        $this->assertEquals(['item_id' => 3, 'name' => 'Default Name'], $item);
+    }
+
+    public function testDefaultedWithValue()
+    {
+        $this->_markTestAsRestOnly();
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => $this->_restResourcePath . 'testOptionalParam',
+                'httpMethod' => RestConfig::HTTP_METHOD_POST,
+            ],
+        ];
+        $item = $this->_webApiCall($serviceInfo, ['name' => 'Ms. LaGrange']);
+        $this->assertEquals(['item_id' => 3, 'name' => 'Ms. LaGrange'], $item);
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Webapi/Routing/ServiceVersionV2Test.php b/dev/tests/api-functional/testsuite/Magento/Webapi/Routing/ServiceVersionV2Test.php
new file mode 100644
index 0000000000000000000000000000000000000000..44707fb779da6960c671bb81e0d9fab78fee2a85
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Webapi/Routing/ServiceVersionV2Test.php
@@ -0,0 +1,142 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+namespace Magento\Webapi\Routing;
+
+class ServiceVersionV2Test extends \Magento\Webapi\Routing\BaseService
+{
+    protected function setUp()
+    {
+        $this->_version = 'V2';
+        $this->_soapService = 'testModule1AllSoapAndRestV2';
+        $this->_restResourcePath = "/{$this->_version}/testmodule1/";
+    }
+
+    /**
+     *  Test to assert overriding of the existing 'Item' api in V2 version of the same service
+     */
+    public function testItem()
+    {
+        $itemId = 1;
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => $this->_restResourcePath . $itemId,
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_GET,
+            ],
+            'soap' => ['service' => $this->_soapService, 'operation' => $this->_soapService . 'Item'],
+        ];
+        $requestData = ['id' => $itemId];
+        $item = $this->_webApiCall($serviceInfo, $requestData);
+        // Asserting for additional attribute returned by the V2 api
+        $this->assertEquals(1, $item['price'], 'Item was retrieved unsuccessfully from V2');
+    }
+
+    /**
+     * Test fetching all items
+     */
+    public function testItems()
+    {
+        $itemArr = [
+            ['id' => 1, 'name' => 'testProduct1', 'price' => '1'],
+            ['id' => 2, 'name' => 'testProduct2', 'price' => '2'],
+        ];
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => $this->_restResourcePath,
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_GET,
+            ],
+            'soap' => ['service' => $this->_soapService, 'operation' => $this->_soapService . 'Items'],
+        ];
+        $item = $this->_webApiCall($serviceInfo);
+        $this->assertEquals($itemArr, $item, 'Items were not retrieved');
+    }
+
+    /**
+     * Test fetching items when filters are applied
+     *
+     * @param string[] $filters
+     * @param array $expectedResult
+     * @dataProvider itemsWithFiltersDataProvider
+     */
+    public function testItemsWithFilters($filters, $expectedResult)
+    {
+        $restFilter = '';
+        foreach ($filters as $filterItemKey => $filterMetadata) {
+            foreach ($filterMetadata as $filterMetaKey => $filterMetaValue) {
+                $paramsDelimiter = empty($restFilter) ? '?' : '&';
+                $restFilter .= "{$paramsDelimiter}filters[{$filterItemKey}][{$filterMetaKey}]={$filterMetaValue}";
+            }
+        }
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => $this->_restResourcePath . $restFilter,
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_GET,
+            ],
+            'soap' => [
+                'service' => $this->_soapService,
+                'operation' => $this->_soapService . 'Items',
+            ],
+        ];
+        $requestData = [];
+        if (!empty($filters)) {
+            $requestData['filters'] = $filters;
+        }
+        $item = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertEquals($expectedResult, $item, 'Filtration does not seem to work correctly.');
+    }
+
+    public function itemsWithFiltersDataProvider()
+    {
+        $firstItem = ['id' => 1, 'name' => 'testProduct1', 'price' => 1];
+        $secondItem = ['id' => 2, 'name' => 'testProduct2', 'price' => 2];
+        return [
+            'Both items filter' => [
+                [
+                    ['field' => 'id', 'conditionType' => 'eq','value' => 1],
+                    ['field' => 'id', 'conditionType' => 'eq','value' => 2],
+                ],
+                [$firstItem, $secondItem],
+            ],
+            'First item filter' => [[['field' => 'id', 'conditionType' => 'eq','value' => 1]], [$firstItem]],
+            'Second item filter' => [[['field' => 'id', 'conditionType' => 'eq','value' => 2]], [$secondItem]],
+            'Empty filter' => [[], [$firstItem, $secondItem]],
+        ];
+    }
+
+    /**
+     *  Test update item
+     */
+    public function testUpdate()
+    {
+        $itemId = 1;
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => $this->_restResourcePath . $itemId,
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_PUT,
+            ],
+            'soap' => ['service' => $this->_soapService, 'operation' => $this->_soapService . 'Update'],
+        ];
+        $requestData = ['entityItem' => ['id' => $itemId, 'name' => 'testName', 'price' => '4']];
+        $item = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertEquals('Updated' . $requestData['entityItem']['name'], $item['name'], 'Item update failed');
+    }
+
+    /**
+     *  Test to assert presence of new 'delete' api added in V2 version of the same service
+     */
+    public function testDelete()
+    {
+        $itemId = 1;
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => $this->_restResourcePath . $itemId,
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_DELETE,
+            ],
+            'soap' => ['service' => $this->_soapService, 'operation' => $this->_soapService . 'Delete'],
+        ];
+        $requestData = ['id' => $itemId, 'name' => 'testName'];
+        $item = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertEquals($itemId, $item['id'], "Item delete failed");
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Webapi/Routing/SoapErrorHandlingTest.php b/dev/tests/api-functional/testsuite/Magento/Webapi/Routing/SoapErrorHandlingTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..d84f5fe8198b26cf1913e59d06e678ed0d93898e
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Webapi/Routing/SoapErrorHandlingTest.php
@@ -0,0 +1,163 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+namespace Magento\Webapi\Routing;
+
+use Magento\Framework\Exception\AuthorizationException;
+
+/**
+ * SOAP error handling test.
+ */
+class SoapErrorHandlingTest extends \Magento\TestFramework\TestCase\WebapiAbstract
+{
+    protected function setUp()
+    {
+        $this->_markTestAsSoapOnly();
+        parent::setUp();
+    }
+
+    public function testWebapiException()
+    {
+        $serviceInfo = [
+            'soap' => [
+                'service' => 'testModule3ErrorV1',
+                'operation' => 'testModule3ErrorV1WebapiException',
+            ],
+        ];
+        try {
+            $this->_webApiCall($serviceInfo);
+            $this->fail("SoapFault was not raised as expected.");
+        } catch (\SoapFault $e) {
+            $this->checkSoapFault(
+                $e,
+                'Service not found',
+                'env:Sender'
+            );
+        }
+    }
+
+    public function testUnknownException()
+    {
+        $serviceInfo = [
+            'soap' => [
+                'service' => 'testModule3ErrorV1',
+                'operation' => 'testModule3ErrorV1OtherException',
+            ],
+        ];
+        try {
+            $this->_webApiCall($serviceInfo);
+            $this->fail("SoapFault was not raised as expected.");
+        } catch (\SoapFault $e) {
+            /** In developer mode message is masked, so checks should be different in two modes */
+            if (strpos($e->getMessage(), 'Internal Error') === false) {
+                $this->checkSoapFault(
+                    $e,
+                    'Non service exception',
+                    'env:Receiver',
+                    null,
+                    null,
+                    'Magento\TestModule3\Service\V1\Error->otherException()'
+                );
+            } else {
+                $this->checkSoapFault(
+                    $e,
+                    'Internal Error. Details are available in Magento log file. Report ID:',
+                    'env:Receiver'
+                );
+            }
+        }
+    }
+
+    public function testEmptyInputException()
+    {
+        $parameters = [];
+        $this->_testWrappedError($parameters);
+    }
+
+    public function testSingleWrappedErrorException()
+    {
+        $parameters = [
+            ['fieldName' => 'key1', 'value' => 'value1'],
+        ];
+        $this->_testWrappedError($parameters);
+    }
+
+    public function testMultipleWrappedErrorException()
+    {
+        $parameters = [
+            ['fieldName' => 'key1', 'value' => 'value1'],
+            ['fieldName' => 'key2', 'value' => 'value2'],
+        ];
+        $this->_testWrappedError($parameters);
+    }
+
+    public function testUnauthorized()
+    {
+        $serviceInfo = [
+            'soap' => [
+                'service' => 'testModule3ErrorV1',
+                'operation' => 'testModule3ErrorV1AuthorizationException',
+                'token' => 'invalidToken',
+            ],
+        ];
+
+        $expectedException = new AuthorizationException(
+            AuthorizationException::NOT_AUTHORIZED,
+            ['resources' => 'Magento_TestModule3::resource1, Magento_TestModule3::resource2']
+        );
+
+        try {
+            $this->_webApiCall($serviceInfo);
+            $this->fail("SoapFault was not raised as expected.");
+        } catch (\SoapFault $e) {
+            $this->checkSoapFault(
+                $e,
+                $expectedException->getRawMessage(),
+                'env:Sender',
+                $expectedException->getParameters() // expected error parameters
+            );
+        }
+    }
+
+    protected function _testWrappedError($parameters)
+    {
+        $serviceInfo = [
+            'soap' => [
+                'service' => 'testModule3ErrorV1',
+                'operation' => 'testModule3ErrorV1InputException',
+            ],
+        ];
+
+        $expectedException = new \Magento\Framework\Exception\InputException();
+        foreach ($parameters as $error) {
+            $expectedException->addError(\Magento\Framework\Exception\InputException::INVALID_FIELD_VALUE, $error);
+        }
+
+        $arguments = [
+            'wrappedErrorParameters' => $parameters,
+        ];
+
+        $expectedErrors = [];
+        foreach ($expectedException->getErrors() as $key => $error) {
+            $expectedErrors[$key] = [
+                'message' => $error->getRawMessage(),
+                'params' => $error->getParameters(),
+            ];
+        }
+
+        try {
+            $this->_webApiCall($serviceInfo, $arguments);
+            $this->fail("SoapFault was not raised as expected.");
+        } catch (\SoapFault $e) {
+            $this->checkSoapFault(
+                $e,
+                $expectedException->getRawMessage(),
+                'env:Sender',
+                $expectedException->getParameters(), // expected error parameters
+                $expectedErrors                      // expected wrapped errors
+            );
+        }
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Webapi/Routing/SubsetTest.php b/dev/tests/api-functional/testsuite/Magento/Webapi/Routing/SubsetTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..e9b3aac72be7cab13a84317d72235c88c4b2c625
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Webapi/Routing/SubsetTest.php
@@ -0,0 +1,75 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+/**
+ * Class to test routing based on a Service that exposes subset of operations
+ */
+namespace Magento\Webapi\Routing;
+
+class SubsetTest extends \Magento\Webapi\Routing\BaseService
+{
+    /**
+     * @var string
+     */
+    protected $_version;
+
+    /**
+     * @var string
+     */
+    protected $_restResourcePath;
+
+    /**
+     * @var string
+     */
+    protected $_soapService;
+
+    /**
+     * @Override
+     */
+    protected function setUp()
+    {
+        $this->_version = 'V1';
+        $this->_restResourcePath = "/{$this->_version}/testModule2SubsetRest/";
+        $this->_soapService = 'testModule2SubsetRestV1';
+    }
+
+    /**
+     * @Override
+     * Test get item
+     */
+    public function testItem()
+    {
+        $itemId = 1;
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => $this->_restResourcePath . $itemId,
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_GET,
+            ],
+            'soap' => ['service' => $this->_soapService, 'operation' => $this->_soapService . 'Item'],
+        ];
+        $requestData = ['id' => $itemId];
+        $item = $this->_webApiCall($serviceInfo, $requestData);
+        $this->assertEquals($itemId, $item['id'], 'Item was retrieved unsuccessfully');
+    }
+
+    /**
+     * @Override
+     * Test fetching all items
+     */
+    public function testItems()
+    {
+        $itemArr = [['id' => 1, 'name' => 'testItem1'], ['id' => 2, 'name' => 'testItem2']];
+        $serviceInfo = [
+            'rest' => [
+                'resourcePath' => $this->_restResourcePath,
+                'httpMethod' => \Magento\Webapi\Model\Rest\Config::HTTP_METHOD_GET,
+            ],
+            'soap' => ['service' => $this->_soapService, 'operation' => $this->_soapService . 'Items'],
+        ];
+
+        $item = $this->_webApiCall($serviceInfo);
+        $this->assertEquals($itemArr, $item, 'Items were not retrieved');
+    }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/Webapi/WsdlGenerationFromDataObjectTest.php b/dev/tests/api-functional/testsuite/Magento/Webapi/WsdlGenerationFromDataObjectTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..3f27b6179ec55ddb06d290b61c6e6ded4dc1c943
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/Webapi/WsdlGenerationFromDataObjectTest.php
@@ -0,0 +1,735 @@
+<?php
+/**
+ * @copyright Copyright (c) 2014 X.commerce, Inc. (http://www.magentocommerce.com)
+ */
+
+namespace Magento\Webapi;
+
+use Magento\TestFramework\Helper\Bootstrap;
+
+/**
+ * Test WSDL generation mechanisms.
+ */
+class WsdlGenerationFromDataObjectTest extends \Magento\TestFramework\TestCase\WebapiAbstract
+{
+    /** @var string */
+    protected $_baseUrl = TESTS_BASE_URL;
+
+    /** @var string */
+    protected $_storeCode;
+
+    /** @var string */
+    protected $_soapUrl;
+
+    protected function setUp()
+    {
+        $this->_markTestAsSoapOnly("WSDL generation tests are intended to be executed for SOAP adapter only.");
+        $this->_storeCode = Bootstrap::getObjectManager()->get('Magento\Store\Model\StoreManagerInterface')
+            ->getStore()->getCode();
+        $this->_soapUrl = "{$this->_baseUrl}/soap/{$this->_storeCode}?services%3DtestModule5AllSoapAndRestV1%2CtestModule5AllSoapAndRestV2";
+        parent::setUp();
+    }
+
+    public function testMultiServiceWsdl()
+    {
+        if (TESTS_WEB_API_ADAPTER == self::ADAPTER_SOAP) {
+            $this->markTestIncomplete('MAGETWO-31016: incompatible with ZF 1.12.9');
+        }
+        $wsdlUrl = $this->_getBaseWsdlUrl() . 'testModule5AllSoapAndRestV1,testModule5AllSoapAndRestV2';
+        $wsdlContent = $this->_convertXmlToString($this->_getWsdlContent($wsdlUrl));
+
+        $this->_checkTypesDeclaration($wsdlContent);
+        $this->_checkPortTypeDeclaration($wsdlContent);
+        $this->_checkBindingDeclaration($wsdlContent);
+        $this->_checkServiceDeclaration($wsdlContent);
+        $this->_checkMessagesDeclaration($wsdlContent);
+        $this->_checkFaultsDeclaration($wsdlContent);
+    }
+
+    public function testInvalidWsdlUrlNoServices()
+    {
+        $responseContent = $this->_getWsdlContent($this->_getBaseWsdlUrl());
+        $this->assertContains("Requested services are missing.", $responseContent);
+    }
+
+    public function testInvalidWsdlUrlInvalidParameter()
+    {
+        $wsdlUrl = $this->_getBaseWsdlUrl() . '&invalid';
+        $responseContent = $this->_getWsdlContent($wsdlUrl);
+        $this->assertContains("Not allowed parameters", $responseContent);
+    }
+
+    /**
+     * Remove unnecessary spaces and line breaks from xml string.
+     *
+     * @param string $xml
+     * @return string
+     */
+    protected function _convertXmlToString($xml)
+    {
+        return str_replace(['    ', "\n", "\r", "&#13;", "&#10;"], '', $xml);
+    }
+
+    /**
+     * Retrieve WSDL content.
+     *
+     * @param string $wsdlUrl
+     * @return string|boolean
+     */
+    protected function _getWsdlContent($wsdlUrl)
+    {
+        $connection = curl_init($wsdlUrl);
+        curl_setopt($connection, CURLOPT_RETURNTRANSFER, 1);
+        $responseContent = curl_exec($connection);
+        $responseDom = new \DOMDocument();
+        $this->assertTrue(
+            $responseDom->loadXML($responseContent),
+            "Valid XML is always expected as a response for WSDL request."
+        );
+        return $responseContent;
+    }
+
+    /**
+     * Generate base WSDL URL (without any services specified)
+     *
+     * @return string
+     */
+    protected function _getBaseWsdlUrl()
+    {
+        /** @var \Magento\TestFramework\TestCase\Webapi\Adapter\Soap $soapAdapter */
+        $soapAdapter = $this->_getWebApiAdapter(self::ADAPTER_SOAP);
+        $wsdlUrl = $soapAdapter->generateWsdlUrl([]);
+        return $wsdlUrl;
+    }
+
+    /**
+     * Ensure that types section has correct structure.
+     *
+     * @param string $wsdlContent
+     */
+    protected function _checkTypesDeclaration($wsdlContent)
+    {
+        // @codingStandardsIgnoreStart
+        $typesSectionDeclaration = <<< TYPES_SECTION_DECLARATION
+<types>
+    <xsd:schema targetNamespace="{$this->_soapUrl}">
+TYPES_SECTION_DECLARATION;
+        // @codingStandardsIgnoreEnd
+        $this->assertContains(
+            $this->_convertXmlToString($typesSectionDeclaration),
+            $wsdlContent,
+            'Types section declaration is invalid'
+        );
+        $this->_checkElementsDeclaration($wsdlContent);
+        $this->_checkComplexTypesDeclaration($wsdlContent);
+    }
+
+    /**
+     * @param string $wsdlContent
+     */
+    protected function _checkElementsDeclaration($wsdlContent)
+    {
+        $requestElement = <<< REQUEST_ELEMENT
+<xsd:element name="testModule5AllSoapAndRestV1ItemRequest" type="tns:TestModule5AllSoapAndRestV1ItemRequest"/>
+REQUEST_ELEMENT;
+        $this->assertContains(
+            $this->_convertXmlToString($requestElement),
+            $wsdlContent,
+            'Request element declaration in types section is invalid'
+        );
+        $responseElement = <<< RESPONSE_ELEMENT
+<xsd:element name="testModule5AllSoapAndRestV1ItemResponse" type="tns:TestModule5AllSoapAndRestV1ItemResponse"/>
+RESPONSE_ELEMENT;
+        $this->assertContains(
+            $this->_convertXmlToString($responseElement),
+            $wsdlContent,
+            'Response element declaration in types section is invalid'
+        );
+    }
+
+    /**
+     * @param string $wsdlContent
+     */
+    protected function _checkComplexTypesDeclaration($wsdlContent)
+    {
+        // @codingStandardsIgnoreStart
+        $requestType = <<< REQUEST_TYPE
+<xsd:complexType name="TestModule5AllSoapAndRestV1ItemRequest">
+    <xsd:annotation>
+        <xsd:documentation>Retrieve an item.</xsd:documentation>
+        <xsd:appinfo xmlns:inf="{$this->_soapUrl}"/>
+    </xsd:annotation>
+    <xsd:sequence>
+        <xsd:element name="entityId" minOccurs="1" maxOccurs="1" type="xsd:int">
+            <xsd:annotation>
+                <xsd:documentation></xsd:documentation>
+                <xsd:appinfo xmlns:inf="{$this->_soapUrl}">
+                    <inf:min/>
+                    <inf:max/>
+                    <inf:callInfo>
+                        <inf:callName>testModule5AllSoapAndRestV1Item</inf:callName>
+                        <inf:requiredInput>Yes</inf:requiredInput>
+                    </inf:callInfo>
+                </xsd:appinfo>
+            </xsd:annotation>
+        </xsd:element>
+    </xsd:sequence>
+</xsd:complexType>
+REQUEST_TYPE;
+        // @codingStandardsIgnoreEnd
+        $this->assertContains(
+            $this->_convertXmlToString($requestType),
+            $wsdlContent,
+            'Request type declaration in types section is invalid'
+        );
+        // @codingStandardsIgnoreStart
+        $responseType = <<< RESPONSE_TYPE
+<xsd:complexType name="TestModule5AllSoapAndRestV1ItemResponse">
+    <xsd:annotation>
+        <xsd:documentation>
+            Response container for the testModule5AllSoapAndRestV1Item call.
+        </xsd:documentation>
+        <xsd:appinfo xmlns:inf="{$this->_soapUrl}"/>
+    </xsd:annotation>
+    <xsd:sequence>
+        <xsd:element name="result" minOccurs="1" maxOccurs="1" type="tns:TestModule5V1EntityAllSoapAndRest">
+            <xsd:annotation>
+                <xsd:documentation></xsd:documentation>
+                <xsd:appinfo xmlns:inf="{$this->_soapUrl}">
+                    <inf:callInfo>
+                        <inf:callName>testModule5AllSoapAndRestV1Item</inf:callName>
+                        <inf:returned>Always</inf:returned>
+                    </inf:callInfo>
+                </xsd:appinfo>
+            </xsd:annotation>
+        </xsd:element>
+    </xsd:sequence>
+</xsd:complexType>
+RESPONSE_TYPE;
+        // @codingStandardsIgnoreEnd
+        $this->assertContains(
+            $this->_convertXmlToString($responseType),
+            $wsdlContent,
+            'Response type declaration in types section is invalid'
+        );
+        $this->_checkReferencedTypeDeclaration($wsdlContent);
+    }
+
+    /**
+     * Ensure that complex type generated from Data Object is correct.
+     *
+     * @param string $wsdlContent
+     */
+    protected function _checkReferencedTypeDeclaration($wsdlContent)
+    {
+        // @codingStandardsIgnoreStart
+        $referencedType = <<< RESPONSE_TYPE
+<xsd:complexType name="TestModule5V1EntityAllSoapAndRest">
+    <xsd:annotation>
+        <xsd:documentation>Some Data Object short description. Data Object long multi line description.</xsd:documentation>
+        <xsd:appinfo xmlns:inf="{$this->_soapUrl}"/>
+    </xsd:annotation>
+    <xsd:sequence>
+        <xsd:element name="entityId" minOccurs="1" maxOccurs="1" type="xsd:int">
+            <xsd:annotation>
+                <xsd:documentation>Item ID</xsd:documentation>
+                <xsd:appinfo xmlns:inf="{$this->_soapUrl}">
+                    <inf:min/>
+                    <inf:max/>
+                    <inf:callInfo>
+                        <inf:callName>testModule5AllSoapAndRestV1Item</inf:callName>
+                        <inf:callName>testModule5AllSoapAndRestV1Create</inf:callName>
+                        <inf:callName>testModule5AllSoapAndRestV1Update</inf:callName>
+                        <inf:callName>testModule5AllSoapAndRestV1NestedUpdate</inf:callName>
+                        <inf:returned>Always</inf:returned>
+                    </inf:callInfo>
+                    <inf:callInfo>
+                        <inf:callName>testModule5AllSoapAndRestV1Create</inf:callName>
+                        <inf:callName>testModule5AllSoapAndRestV1Update</inf:callName>
+                        <inf:callName>testModule5AllSoapAndRestV1NestedUpdate</inf:callName>
+                        <inf:requiredInput>Yes</inf:requiredInput>
+                    </inf:callInfo>
+                </xsd:appinfo>
+            </xsd:annotation>
+        </xsd:element>
+        <xsd:element name="name" minOccurs="0" maxOccurs="1" type="xsd:string">
+            <xsd:annotation>
+                <xsd:documentation>Item name</xsd:documentation>
+                <xsd:appinfo xmlns:inf="{$this->_soapUrl}">
+                    <inf:maxLength/>
+                    <inf:callInfo>
+                        <inf:callName>testModule5AllSoapAndRestV1Item</inf:callName>
+                        <inf:callName>testModule5AllSoapAndRestV1Create</inf:callName>
+                        <inf:callName>testModule5AllSoapAndRestV1Update</inf:callName>
+                        <inf:callName>testModule5AllSoapAndRestV1NestedUpdate</inf:callName>
+                        <inf:returned>Conditionally</inf:returned>
+                    </inf:callInfo>
+                    <inf:callInfo>
+                        <inf:callName>testModule5AllSoapAndRestV1Create</inf:callName>
+                        <inf:callName>testModule5AllSoapAndRestV1Update</inf:callName>
+                        <inf:callName>testModule5AllSoapAndRestV1NestedUpdate</inf:callName>
+                        <inf:requiredInput>No</inf:requiredInput>
+                    </inf:callInfo>
+                </xsd:appinfo>
+            </xsd:annotation>
+        </xsd:element>
+        <xsd:element name="enabled" minOccurs="1" maxOccurs="1" type="xsd:boolean">
+            <xsd:annotation>
+                <xsd:documentation></xsd:documentation>
+                <xsd:appinfo xmlns:inf="{$this->_soapUrl}">
+                    <inf:default>false</inf:default>
+                    <inf:callInfo>
+                        <inf:callName>testModule5AllSoapAndRestV1Item</inf:callName>
+                        <inf:callName>testModule5AllSoapAndRestV1Create</inf:callName>
+                        <inf:callName>testModule5AllSoapAndRestV1Update</inf:callName>
+                        <inf:callName>testModule5AllSoapAndRestV1NestedUpdate</inf:callName>
+                        <inf:returned>Conditionally</inf:returned>
+                    </inf:callInfo>
+                    <inf:callInfo>
+                        <inf:callName>testModule5AllSoapAndRestV1Create</inf:callName>
+                        <inf:callName>testModule5AllSoapAndRestV1Update</inf:callName>
+                        <inf:callName>testModule5AllSoapAndRestV1NestedUpdate</inf:callName>
+                        <inf:requiredInput>No</inf:requiredInput>
+                    </inf:callInfo>
+                </xsd:appinfo>
+            </xsd:annotation>
+        </xsd:element>
+        <xsd:element name="orders" minOccurs="1" maxOccurs="1" type="xsd:boolean">
+            <xsd:annotation>
+                <xsd:documentation></xsd:documentation>
+                <xsd:appinfo xmlns:inf="{$this->_soapUrl}">
+                    <inf:default>false</inf:default>
+                    <inf:callInfo>
+                        <inf:callName>testModule5AllSoapAndRestV1Item</inf:callName>
+                        <inf:callName>testModule5AllSoapAndRestV1Create</inf:callName>
+                        <inf:callName>testModule5AllSoapAndRestV1Update</inf:callName>
+                        <inf:callName>testModule5AllSoapAndRestV1NestedUpdate</inf:callName>
+                        <inf:returned>Conditionally</inf:returned>
+                    </inf:callInfo>
+                    <inf:callInfo>
+                        <inf:callName>testModule5AllSoapAndRestV1Create</inf:callName>
+                        <inf:callName>testModule5AllSoapAndRestV1Update</inf:callName>
+                        <inf:callName>testModule5AllSoapAndRestV1NestedUpdate</inf:callName>
+                        <inf:requiredInput>No</inf:requiredInput>
+                    </inf:callInfo>
+                </xsd:appinfo>
+            </xsd:annotation>
+        </xsd:element>
+        <xsd:element name="customAttributes" type="tns:ArrayOfFrameworkAttributeInterface" minOccurs="0">
+            <xsd:annotation>
+                <xsd:documentation></xsd:documentation>
+                <xsd:appinfo xmlns:inf="{$this->_soapUrl}">
+                    <inf:natureOfType>array</inf:natureOfType>
+                    <inf:callInfo>
+                        <inf:callName>testModule5AllSoapAndRestV1Item</inf:callName>
+                        <inf:callName>testModule5AllSoapAndRestV1Create</inf:callName>
+                        <inf:callName>testModule5AllSoapAndRestV1Update</inf:callName>
+                        <inf:callName>testModule5AllSoapAndRestV1NestedUpdate</inf:callName>
+                        <inf:returned>Conditionally</inf:returned>
+                    </inf:callInfo>
+                    <inf:callInfo>
+                        <inf:callName>testModule5AllSoapAndRestV1Create</inf:callName>
+                        <inf:callName>testModule5AllSoapAndRestV1Update</inf:callName>
+                        <inf:callName>testModule5AllSoapAndRestV1NestedUpdate</inf:callName>
+                        <inf:requiredInput>No</inf:requiredInput>
+                    </inf:callInfo>
+                </xsd:appinfo>
+            </xsd:annotation>
+    </xsd:element>
+    </xsd:sequence>
+</xsd:complexType>
+RESPONSE_TYPE;
+        // @codingStandardsIgnoreEnd
+        $this->assertContains(
+            $this->_convertXmlToString($referencedType),
+            $wsdlContent,
+            'Declaration of complex type generated from Data Object, which is referenced in response, is invalid'
+        );
+    }
+
+    /**
+     * Ensure that port type sections have correct structure.
+     *
+     * @param string $wsdlContent
+     */
+    protected function _checkPortTypeDeclaration($wsdlContent)
+    {
+        $firstPortType = <<< FIRST_PORT_TYPE
+<portType name="testModule5AllSoapAndRestV1PortType">
+FIRST_PORT_TYPE;
+        $this->assertContains(
+            $this->_convertXmlToString($firstPortType),
+            $wsdlContent,
+            'Port type declaration is missing or invalid'
+        );
+        $secondPortType = <<< SECOND_PORT_TYPE
+<portType name="testModule5AllSoapAndRestV2PortType">
+SECOND_PORT_TYPE;
+        $this->assertContains(
+            $this->_convertXmlToString($secondPortType),
+            $wsdlContent,
+            'Port type declaration is missing or invalid'
+        );
+        $operationDeclaration = <<< OPERATION_DECLARATION
+<operation name="testModule5AllSoapAndRestV2Item">
+    <input message="tns:testModule5AllSoapAndRestV2ItemRequest"/>
+    <output message="tns:testModule5AllSoapAndRestV2ItemResponse"/>
+    <fault name="GenericFault" message="tns:GenericFault"/>
+</operation>
+<operation name="testModule5AllSoapAndRestV2Items">
+    <input message="tns:testModule5AllSoapAndRestV2ItemsRequest"/>
+    <output message="tns:testModule5AllSoapAndRestV2ItemsResponse"/>
+    <fault name="GenericFault" message="tns:GenericFault"/>
+</operation>
+OPERATION_DECLARATION;
+        $this->assertContains(
+            $this->_convertXmlToString($operationDeclaration),
+            $wsdlContent,
+            'Operation in port type is invalid'
+        );
+    }
+
+    /**
+     * Ensure that binding sections have correct structure.
+     *
+     * @param string $wsdlContent
+     */
+    protected function _checkBindingDeclaration($wsdlContent)
+    {
+        $firstBinding = <<< FIRST_BINDING
+<binding name="testModule5AllSoapAndRestV1Binding" type="tns:testModule5AllSoapAndRestV1PortType">
+    <soap12:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
+FIRST_BINDING;
+        $this->assertContains(
+            $this->_convertXmlToString($firstBinding),
+            $wsdlContent,
+            'Binding declaration is missing or invalid'
+        );
+        $secondBinding = <<< SECOND_BINDING
+<binding name="testModule5AllSoapAndRestV2Binding" type="tns:testModule5AllSoapAndRestV2PortType">
+    <soap12:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
+SECOND_BINDING;
+        $this->assertContains(
+            $this->_convertXmlToString($secondBinding),
+            $wsdlContent,
+            'Binding declaration is missing or invalid'
+        );
+        $operationDeclaration = <<< OPERATION_DECLARATION
+<operation name="testModule5AllSoapAndRestV1Item">
+    <soap:operation soapAction="testModule5AllSoapAndRestV1Item"/>
+    <input>
+        <soap12:body use="literal"/>
+    </input>
+    <output>
+        <soap12:body use="literal"/>
+    </output>
+    <fault name="GenericFault">
+        <soap12:fault use="literal" name="GenericFault"/>
+    </fault>
+</operation>
+<operation name="testModule5AllSoapAndRestV1Items">
+    <soap:operation soapAction="testModule5AllSoapAndRestV1Items"/>
+    <input>
+        <soap12:body use="literal"/>
+    </input>
+    <output>
+        <soap12:body use="literal"/>
+    </output>
+    <fault name="GenericFault">
+        <soap12:fault use="literal" name="GenericFault"/>
+    </fault>
+</operation>
+OPERATION_DECLARATION;
+        $this->assertContains(
+            $this->_convertXmlToString($operationDeclaration),
+            $wsdlContent,
+            'Operation in binding is invalid'
+        );
+    }
+
+    /**
+     * Ensure that service sections have correct structure.
+     *
+     * @param string $wsdlContent
+     */
+    protected function _checkServiceDeclaration($wsdlContent)
+    {
+        // @codingStandardsIgnoreStart
+        $firstServiceDeclaration = <<< FIRST_SERVICE_DECLARATION
+<service name="testModule5AllSoapAndRestV1Service">
+    <port name="testModule5AllSoapAndRestV1Port" binding="tns:testModule5AllSoapAndRestV1Binding">
+        <soap:address location="{$this->_baseUrl}/soap/{$this->_storeCode}?services=testModule5AllSoapAndRestV1%2CtestModule5AllSoapAndRestV2"/>
+    </port>
+</service>
+FIRST_SERVICE_DECLARATION;
+        // @codingStandardsIgnoreEnd
+        $this->assertContains(
+            $this->_convertXmlToString($firstServiceDeclaration),
+            $wsdlContent,
+            'First service section is invalid'
+        );
+
+        // @codingStandardsIgnoreStart
+        $secondServiceDeclaration = <<< SECOND_SERVICE_DECLARATION
+<service name="testModule5AllSoapAndRestV2Service">
+    <port name="testModule5AllSoapAndRestV2Port" binding="tns:testModule5AllSoapAndRestV2Binding">
+        <soap:address location="{$this->_baseUrl}/soap/{$this->_storeCode}?services=testModule5AllSoapAndRestV1%2CtestModule5AllSoapAndRestV2"/>
+    </port>
+</service>
+SECOND_SERVICE_DECLARATION;
+        // @codingStandardsIgnoreEnd
+        $this->assertContains(
+            $this->_convertXmlToString($secondServiceDeclaration),
+            $wsdlContent,
+            'Second service section is invalid'
+        );
+    }
+
+    /**
+     * Ensure that messages sections have correct structure.
+     *
+     * @param string $wsdlContent
+     */
+    protected function _checkMessagesDeclaration($wsdlContent)
+    {
+        $itemMessagesDeclaration = <<< MESSAGES_DECLARATION
+<message name="testModule5AllSoapAndRestV2ItemRequest">
+    <part name="messageParameters" element="tns:testModule5AllSoapAndRestV2ItemRequest"/>
+</message>
+<message name="testModule5AllSoapAndRestV2ItemResponse">
+    <part name="messageParameters" element="tns:testModule5AllSoapAndRestV2ItemResponse"/>
+</message>
+MESSAGES_DECLARATION;
+        $this->assertContains(
+            $this->_convertXmlToString($itemMessagesDeclaration),
+            $wsdlContent,
+            'Messages section for "item" operation is invalid'
+        );
+        $itemsMessagesDeclaration = <<< MESSAGES_DECLARATION
+<message name="testModule5AllSoapAndRestV2ItemsRequest">
+    <part name="messageParameters" element="tns:testModule5AllSoapAndRestV2ItemsRequest"/>
+</message>
+<message name="testModule5AllSoapAndRestV2ItemsResponse">
+    <part name="messageParameters" element="tns:testModule5AllSoapAndRestV2ItemsResponse"/>
+</message>
+MESSAGES_DECLARATION;
+        $this->assertContains(
+            $this->_convertXmlToString($itemsMessagesDeclaration),
+            $wsdlContent,
+            'Messages section for "items" operation is invalid'
+        );
+    }
+
+    /**
+     * Ensure that SOAP faults are declared properly.
+     *
+     * @param string $wsdlContent
+     */
+    protected function _checkFaultsDeclaration($wsdlContent)
+    {
+        $this->_checkFaultsPortTypeSection($wsdlContent);
+        $this->_checkFaultsBindingSection($wsdlContent);
+        $this->_checkFaultsMessagesSection($wsdlContent);
+        $this->_checkFaultsComplexTypeSection($wsdlContent);
+    }
+
+    /**
+     * @param string $wsdlContent
+     */
+    protected function _checkFaultsPortTypeSection($wsdlContent)
+    {
+        $faultsInPortType = <<< FAULT_IN_PORT_TYPE
+<fault name="GenericFault" message="tns:GenericFault"/>
+FAULT_IN_PORT_TYPE;
+        $this->assertContains(
+            $this->_convertXmlToString($faultsInPortType),
+            $wsdlContent,
+            'SOAP Fault section in port type section is invalid'
+        );
+    }
+
+    /**
+     * @param string $wsdlContent
+     */
+    protected function _checkFaultsBindingSection($wsdlContent)
+    {
+        $faultsInBinding = <<< FAULT_IN_BINDING
+<fault name="GenericFault">
+    <soap12:fault use="literal" name="GenericFault"/>
+</fault>
+FAULT_IN_BINDING;
+        $this->assertContains(
+            $this->_convertXmlToString($faultsInBinding),
+            $wsdlContent,
+            'SOAP Fault section in binding section is invalid'
+        );
+    }
+
+    /**
+     * @param string $wsdlContent
+     */
+    protected function _checkFaultsMessagesSection($wsdlContent)
+    {
+        $genericFaultMessage = <<< GENERIC_FAULT_IN_MESSAGES
+<message name="GenericFault">
+    <part name="messageParameters" element="tns:GenericFault"/>
+</message>
+GENERIC_FAULT_IN_MESSAGES;
+        $this->assertContains(
+            $this->_convertXmlToString($genericFaultMessage),
+            $wsdlContent,
+            'Generic SOAP Fault declaration in messages section is invalid'
+        );
+    }
+
+    /**
+     * @param string $wsdlContent
+     */
+    protected function _checkFaultsComplexTypeSection($wsdlContent)
+    {
+        $this->assertContains(
+            $this->_convertXmlToString('<xsd:element name="GenericFault" type="tns:GenericFault"/>'),
+            $wsdlContent,
+            'Default SOAP Fault complex type element declaration is invalid'
+        );
+
+        // @codingStandardsIgnoreStart
+        $genericFaultType = <<< GENERIC_FAULT_COMPLEX_TYPE
+<xsd:complexType name="GenericFault">
+    <xsd:sequence>
+        <xsd:element name="Trace" minOccurs="0" maxOccurs="1" type="xsd:string">
+            <xsd:annotation>
+                <xsd:documentation>Exception calls stack trace.</xsd:documentation>
+                <xsd:appinfo xmlns:inf="{$this->_soapUrl}">
+                    <inf:maxLength/>
+                </xsd:appinfo>
+            </xsd:annotation>
+        </xsd:element>
+        <xsd:element name="Parameters" type="tns:ArrayOfGenericFaultParameter" minOccurs="0">
+            <xsd:annotation>
+                <xsd:documentation>Additional exception parameters.</xsd:documentation>
+                <xsd:appinfo xmlns:inf="{$this->_soapUrl}">
+                    <inf:natureOfType>array</inf:natureOfType>
+                </xsd:appinfo>
+            </xsd:annotation>
+        </xsd:element>
+        <xsd:element name="WrappedErrors" type="tns:ArrayOfWrappedError" minOccurs="0">
+            <xsd:annotation>
+                <xsd:documentation>Additional wrapped errors.</xsd:documentation>
+                <xsd:appinfo xmlns:inf="{$this->_soapUrl}">
+                    <inf:natureOfType>array</inf:natureOfType>
+                </xsd:appinfo>
+            </xsd:annotation>
+        </xsd:element>
+    </xsd:sequence>
+</xsd:complexType>
+GENERIC_FAULT_COMPLEX_TYPE;
+        $this->assertContains(
+            $this->_convertXmlToString($genericFaultType),
+            $wsdlContent,
+            'Default SOAP Fault complex types declaration is invalid'
+        );
+
+        $detailsParameterType = <<< PARAM_COMPLEX_TYPE
+<xsd:complexType name="GenericFaultParameter">
+    <xsd:sequence>
+        <xsd:element name="key" minOccurs="1" maxOccurs="1" type="xsd:string">
+            <xsd:annotation>
+                <xsd:documentation></xsd:documentation>
+                <xsd:appinfo xmlns:inf="{$this->_soapUrl}">
+                    <inf:maxLength/>
+                </xsd:appinfo>
+            </xsd:annotation>
+        </xsd:element>
+        <xsd:element name="value" minOccurs="1" maxOccurs="1" type="xsd:string">
+            <xsd:annotation>
+                <xsd:documentation></xsd:documentation>
+                <xsd:appinfo xmlns:inf="{$this->_soapUrl}">
+                    <inf:maxLength/>
+                </xsd:appinfo>
+            </xsd:annotation>
+        </xsd:element>
+    </xsd:sequence>
+</xsd:complexType>
+PARAM_COMPLEX_TYPE;
+        $this->assertContains(
+            $this->_convertXmlToString($detailsParameterType),
+            $wsdlContent,
+            'Details parameter complex types declaration is invalid.'
+        );
+
+        $detailsWrappedErrorType = <<< WRAPPED_ERROR_COMPLEX_TYPE
+<xsd:complexType name="WrappedError">
+    <xsd:sequence>
+        <xsd:element name="message" minOccurs="1" maxOccurs="1" type="xsd:string">
+            <xsd:annotation>
+                <xsd:documentation></xsd:documentation>
+                <xsd:appinfo xmlns:inf="{$this->_baseUrl}/soap/{$this->_storeCode}?services%3DtestModule5AllSoapAndRestV1%2CtestModule5AllSoapAndRestV2">
+                    <inf:maxLength/>
+                </xsd:appinfo>
+            </xsd:annotation>
+        </xsd:element>
+        <xsd:element name="parameters" type="tns:ArrayOfGenericFaultParameter" minOccurs="0">
+            <xsd:annotation>
+                <xsd:documentation>Message parameters.</xsd:documentation>
+                <xsd:appinfo xmlns:inf="{$this->_baseUrl}/soap/{$this->_storeCode}?services%3DtestModule5AllSoapAndRestV1%2CtestModule5AllSoapAndRestV2">
+                    <inf:natureOfType>array</inf:natureOfType>
+                </xsd:appinfo>
+            </xsd:annotation>
+        </xsd:element>
+    </xsd:sequence>
+</xsd:complexType>
+WRAPPED_ERROR_COMPLEX_TYPE;
+        $this->assertContains(
+            $this->_convertXmlToString($detailsWrappedErrorType),
+            $wsdlContent,
+            'Details wrapped error complex types declaration is invalid.'
+        );
+
+        $detailsParametersType = <<< PARAMETERS_COMPLEX_TYPE
+<xsd:complexType name="ArrayOfGenericFaultParameter">
+    <xsd:annotation>
+        <xsd:documentation>An array of GenericFaultParameter items.</xsd:documentation>
+        <xsd:appinfo xmlns:inf="{$this->_soapUrl}"/>
+    </xsd:annotation>
+    <xsd:sequence>
+        <xsd:element name="item" minOccurs="0" maxOccurs="unbounded" type="tns:GenericFaultParameter">
+            <xsd:annotation>
+                <xsd:documentation>An item of ArrayOfGenericFaultParameter.</xsd:documentation>
+                <xsd:appinfo xmlns:inf="{$this->_soapUrl}"/>
+            </xsd:annotation>
+        </xsd:element>
+    </xsd:sequence>
+</xsd:complexType>
+PARAMETERS_COMPLEX_TYPE;
+        // @codingStandardsIgnoreEnd
+        $this->assertContains(
+            $this->_convertXmlToString($detailsParametersType),
+            $wsdlContent,
+            'Details parameters (array of parameters) complex types declaration is invalid.'
+        );
+
+        $detailsWrappedErrorsType = <<< WRAPPED_ERRORS_COMPLEX_TYPE
+<xsd:complexType name="ArrayOfWrappedError">
+    <xsd:annotation>
+        <xsd:documentation>An array of WrappedError items.</xsd:documentation>
+        <xsd:appinfo xmlns:inf="{$this->_baseUrl}/soap/{$this->_storeCode}?services%3DtestModule5AllSoapAndRestV1%2CtestModule5AllSoapAndRestV2"/>
+    </xsd:annotation>
+    <xsd:sequence>
+        <xsd:element name="item" minOccurs="0" maxOccurs="unbounded" type="tns:WrappedError">
+            <xsd:annotation>
+                <xsd:documentation>An item of ArrayOfWrappedError.</xsd:documentation>
+                <xsd:appinfo xmlns:inf="{$this->_baseUrl}/soap/{$this->_storeCode}?services%3DtestModule5AllSoapAndRestV1%2CtestModule5AllSoapAndRestV2"/>
+            </xsd:annotation>
+        </xsd:element>
+    </xsd:sequence>
+</xsd:complexType>
+WRAPPED_ERRORS_COMPLEX_TYPE;
+        // @codingStandardsIgnoreEnd
+        $this->assertContains(
+            $this->_convertXmlToString($detailsWrappedErrorsType),
+            $wsdlContent,
+            'Details wrapped errors (array of wrapped errors) complex types declaration is invalid.'
+        );
+    }
+}
diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/_files/blacklist/reference.txt b/dev/tests/static/testsuite/Magento/Test/Integrity/_files/blacklist/reference.txt
index 1d55de7b2756da549f1f0657a43e5146a24b7ea3..c6bac2ba9f3b3dd5970b717f88acea5427bb38bb 100644
--- a/dev/tests/static/testsuite/Magento/Test/Integrity/_files/blacklist/reference.txt
+++ b/dev/tests/static/testsuite/Magento/Test/Integrity/_files/blacklist/reference.txt
@@ -37,3 +37,17 @@ Model3
 \Magento\Wonderland\Model\Data\FakeRegion
 \Magento\Wonderland\Model\Data\FakeAddress
 \Magento\Framework\Error\Processor
+\Magento\TestModule3\Service\V1\Entity\Parameter
+\Magento\TestModule3\Service\V1\Entity\ParameterBuilder
+\Magento\TestModuleMSC\Api\Data\ItemInterface
+\Magento\TestModule5\Service\V1\Entity\AllSoapAndRest
+\Magento\TestModule5\Service\V2\Entity\AllSoapAndRest
+\Magento\TestModule5\Service\V2\Entity\AllSoapAndRestBuilder
+\Magento\TestModule1\Service\V1\Entity\Item
+\Magento\TestModule1\Service\V1\Entity\ItemBuilder
+\Magento\TestModule1\Service\V2\Entity\Item
+\Magento\TestModule1\Service\V2\Entity\ItemBuilder
+\Magento\TestModule2\Service\V1\Entity\Item
+\Magento\TestModule2\Service\V1\Entity\ItemBuilder
+\Magento\TestModule4\Service\V1\Entity\DataObjectResponse
+\Magento\TestModule4\Service\V1\Entity\DataObjectRequest