Add products REST API
This patchset add base methods for products REST API as described in spec. Change-Id: I58e07c940886411705c143077b6871522baed9cb Addresses-Spec: https://review.openstack.org/#/c/292526/
This commit is contained in:
parent
ddecd2ccca
commit
9888cf2bc1
184
refstack/api/controllers/products.py
Normal file
184
refstack/api/controllers/products.py
Normal file
@ -0,0 +1,184 @@
|
||||
# Copyright (c) 2015 Mirantis, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""Product controller."""
|
||||
|
||||
import json
|
||||
import uuid
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
import pecan
|
||||
from pecan.secure import secure
|
||||
import six
|
||||
|
||||
from refstack.api import constants as const
|
||||
from refstack.api.controllers import validation
|
||||
from refstack.api import utils as api_utils
|
||||
from refstack.api import validators
|
||||
from refstack import db
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class ProductsController(validation.BaseRestControllerWithValidation):
|
||||
"""/v1/products handler."""
|
||||
|
||||
__validator__ = validators.ProductValidator
|
||||
|
||||
_custom_actions = {
|
||||
"action": ["POST"],
|
||||
}
|
||||
|
||||
@pecan.expose('json')
|
||||
def get(self):
|
||||
"""Get information of all products."""
|
||||
allowed_keys = ['id', 'name', 'description', 'product_id', 'type',
|
||||
'product_type', 'public', 'organization_id']
|
||||
user = api_utils.get_user_id()
|
||||
is_admin = user in db.get_foundation_users()
|
||||
try:
|
||||
if is_admin:
|
||||
products = db.get_products(allowed_keys=allowed_keys)
|
||||
for s in products:
|
||||
s['can_manage'] = True
|
||||
else:
|
||||
result = dict()
|
||||
products = db.get_public_products(allowed_keys=allowed_keys)
|
||||
for s in products:
|
||||
_id = s['id']
|
||||
result[_id] = s
|
||||
result[_id]['can_manage'] = False
|
||||
|
||||
products = db.get_products_by_user(user,
|
||||
allowed_keys=allowed_keys)
|
||||
for s in products:
|
||||
_id = s['id']
|
||||
if _id not in result:
|
||||
result[_id] = s
|
||||
result[_id]['can_manage'] = True
|
||||
products = result.values()
|
||||
except Exception as ex:
|
||||
LOG.exception('An error occurred during '
|
||||
'operation with database: %s' % ex)
|
||||
pecan.abort(400)
|
||||
|
||||
products.sort(key=lambda x: x['name'])
|
||||
return {'products': products}
|
||||
|
||||
@pecan.expose('json')
|
||||
def get_one(self, id):
|
||||
"""Get information about product."""
|
||||
product = db.get_product(id)
|
||||
vendor_id = product['organization_id']
|
||||
is_admin = (api_utils.check_user_is_foundation_admin() or
|
||||
api_utils.check_user_is_vendor_admin(vendor_id))
|
||||
if not is_admin and not product['public']:
|
||||
pecan.abort(403, 'Forbidden.')
|
||||
|
||||
if not is_admin:
|
||||
allowed_keys = ['id', 'name', 'description', 'product_id', 'type',
|
||||
'product_type', 'public', 'organization_id']
|
||||
for key in product.keys():
|
||||
if key not in allowed_keys:
|
||||
product.pop(key)
|
||||
|
||||
product['can_manage'] = is_admin
|
||||
return product
|
||||
|
||||
@secure(api_utils.is_authenticated)
|
||||
@pecan.expose('json')
|
||||
def post(self):
|
||||
"""'secure' decorator doesn't work at store_item. it must be here."""
|
||||
return super(ProductsController, self).post()
|
||||
|
||||
@pecan.expose('json')
|
||||
def store_item(self, product):
|
||||
"""Handler for storing item. Should return new item id."""
|
||||
creator = api_utils.get_user_id()
|
||||
product['type'] = (const.SOFTWARE
|
||||
if product['product_type'] == const.DISTRO
|
||||
else const.CLOUD)
|
||||
if product['type'] == const.SOFTWARE:
|
||||
product['product_id'] = six.text_type(uuid.uuid4())
|
||||
vendor_id = product.pop('organization_id', None)
|
||||
if not vendor_id:
|
||||
# find or create default vendor for new product
|
||||
# TODO(andrey-mp): maybe just fill with info here and create
|
||||
# at DB layer in one transaction
|
||||
default_vendor_name = 'vendor_' + creator
|
||||
vendors = db.get_organizations_by_user(creator)
|
||||
for v in vendors:
|
||||
if v['name'] == default_vendor_name:
|
||||
vendor_id = v['id']
|
||||
break
|
||||
else:
|
||||
vendor = {'name': default_vendor_name}
|
||||
vendor = db.add_organization(vendor, creator)
|
||||
vendor_id = vendor['id']
|
||||
product['organization_id'] = vendor_id
|
||||
product = db.add_product(product, creator)
|
||||
return {'id': product['id']}
|
||||
|
||||
@secure(api_utils.is_authenticated)
|
||||
@pecan.expose('json', method='PUT')
|
||||
def put(self, id, **kw):
|
||||
"""Handler for update item. Should return full info with updates."""
|
||||
product = db.get_product(id)
|
||||
vendor_id = product['organization_id']
|
||||
vendor = db.get_organization(vendor_id)
|
||||
is_admin = (api_utils.check_user_is_foundation_admin()
|
||||
or api_utils.check_user_is_vendor_admin(vendor_id))
|
||||
if not is_admin:
|
||||
pecan.abort(403, 'Forbidden.')
|
||||
|
||||
product_info = {'id': id}
|
||||
if 'name' in kw:
|
||||
product_info['name'] = kw['name']
|
||||
if 'description' in kw:
|
||||
product_info['description'] = kw['description']
|
||||
if 'product_id' in kw:
|
||||
product_info['product_id'] = kw['product_id']
|
||||
if 'public' in kw:
|
||||
# user can mark product as public only if
|
||||
# his/her vendor is public(official)
|
||||
public = api_utils.str_to_bool(kw['public'])
|
||||
if (vendor['type'] not in (const.OFFICIAL_VENDOR, const.FOUNDATION)
|
||||
and public):
|
||||
pecan.abort(403, 'Forbidden.')
|
||||
product_info['public'] = public
|
||||
if 'properties' in kw:
|
||||
product_info['properties'] = json.dumps(kw['properties'])
|
||||
db.update_product(product_info)
|
||||
|
||||
pecan.response.status = 200
|
||||
product = db.get_product(id)
|
||||
product['can_manage'] = True
|
||||
return product
|
||||
|
||||
@secure(api_utils.is_authenticated)
|
||||
@pecan.expose('json')
|
||||
def delete(self, id):
|
||||
"""Delete product."""
|
||||
product = db.get_product(id)
|
||||
vendor_id = product['organization_id']
|
||||
if (not api_utils.check_user_is_foundation_admin() and
|
||||
not api_utils.check_user_is_vendor_admin(vendor_id)):
|
||||
pecan.abort(403, 'Forbidden.')
|
||||
|
||||
db.delete_product(id)
|
||||
pecan.response.status = 204
|
@ -17,6 +17,7 @@
|
||||
|
||||
from refstack.api.controllers import auth
|
||||
from refstack.api.controllers import guidelines
|
||||
from refstack.api.controllers import products
|
||||
from refstack.api.controllers import results
|
||||
from refstack.api.controllers import user
|
||||
from refstack.api.controllers import vendors
|
||||
@ -29,4 +30,5 @@ class V1Controller(object):
|
||||
guidelines = guidelines.GuidelinesController()
|
||||
auth = auth.AuthController()
|
||||
profile = user.ProfileController()
|
||||
products = products.ProductsController()
|
||||
vendors = vendors.VendorsController()
|
||||
|
@ -128,7 +128,7 @@ class VendorsController(validation.BaseRestControllerWithValidation):
|
||||
vendor_info['properties'] = json.dumps(kw['properties'])
|
||||
db.update_organization(vendor_info)
|
||||
|
||||
pecan.response.status = 201
|
||||
pecan.response.status = 200
|
||||
vendor = db.get_organization(vendor_id)
|
||||
vendor['can_manage'] = True
|
||||
return vendor
|
||||
|
@ -92,6 +92,8 @@ def parse_input_params(expected_input_params):
|
||||
|
||||
def str_to_bool(param):
|
||||
"""Check if a string value should be evaluated as True or False."""
|
||||
if isinstance(param, bool):
|
||||
return param
|
||||
return param.lower() in ("true", "yes", "1")
|
||||
|
||||
|
||||
|
@ -16,6 +16,7 @@
|
||||
"""Validators module."""
|
||||
|
||||
import binascii
|
||||
import six
|
||||
import uuid
|
||||
|
||||
import json
|
||||
@ -72,6 +73,17 @@ class BaseValidator(object):
|
||||
raise api_exc.ValidationError(
|
||||
'Request doesn''t correspond to schema', e)
|
||||
|
||||
def check_emptyness(self, body, keys):
|
||||
"""Check that all values are not empty."""
|
||||
for key in keys:
|
||||
value = body[key]
|
||||
if isinstance(value, six.string_types):
|
||||
value = value.strip()
|
||||
if not value:
|
||||
raise api_exc.ValidationError(key + ' should not be empty')
|
||||
elif value is None:
|
||||
raise api_exc.ValidationError(key + ' must be present')
|
||||
|
||||
|
||||
class TestResultValidator(BaseValidator):
|
||||
"""Validator for incoming test results."""
|
||||
@ -195,6 +207,27 @@ class VendorValidator(BaseValidator):
|
||||
super(VendorValidator, self).validate(request)
|
||||
body = json.loads(request.body)
|
||||
|
||||
name = body['name'].strip()
|
||||
if not name:
|
||||
raise api_exc.ValidationError('Name should not be empty.')
|
||||
self.check_emptyness(body, ['name'])
|
||||
|
||||
|
||||
class ProductValidator(BaseValidator):
|
||||
"""Validate uploaded product data."""
|
||||
|
||||
schema = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'name': {'type': 'string'},
|
||||
'description': {'type': 'string'},
|
||||
'product_type': {'type': 'integer'},
|
||||
'organization_id': {'type': 'string', 'format': 'uuid_hex'},
|
||||
},
|
||||
'required': ['name', 'product_type'],
|
||||
'additionalProperties': False
|
||||
}
|
||||
|
||||
def validate(self, request):
|
||||
"""Validate uploaded test results."""
|
||||
super(ProductValidator, self).validate(request)
|
||||
body = json.loads(request.body)
|
||||
|
||||
self.check_emptyness(body, ['name', 'product_type'])
|
||||
|
@ -236,3 +236,18 @@ def get_organizations_by_user(user_openid, allowed_keys=None):
|
||||
"""Get organizations for specified user."""
|
||||
return IMPL.get_organizations_by_user(user_openid,
|
||||
allowed_keys=allowed_keys)
|
||||
|
||||
|
||||
def get_public_products(allowed_keys=None):
|
||||
"""Get all public products."""
|
||||
return IMPL.get_public_products(allowed_keys=allowed_keys)
|
||||
|
||||
|
||||
def get_products(allowed_keys=None):
|
||||
"""Get all products."""
|
||||
return IMPL.get_products(allowed_keys=allowed_keys)
|
||||
|
||||
|
||||
def get_products_by_user(user_openid, allowed_keys=None):
|
||||
"""Get all products that user can manage."""
|
||||
return IMPL.get_products_by_user(user_openid, allowed_keys=allowed_keys)
|
||||
|
@ -0,0 +1,21 @@
|
||||
"""Make product_id nullable in product table.
|
||||
|
||||
Revision ID: 7093ca478d35
|
||||
Revises: 7092392cbb8e
|
||||
Create Date: 2016-05-12 13:10:00
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '7093ca478d35'
|
||||
down_revision = '7092392cbb8e'
|
||||
MYSQL_CHARSET = 'utf8'
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
def upgrade():
|
||||
"""Upgrade DB."""
|
||||
op.alter_column('product', 'product_id', nullable=True,
|
||||
type_=sa.String(36))
|
@ -437,7 +437,7 @@ def add_product(product_info, creator):
|
||||
product = models.Product()
|
||||
product.type = product_info['type']
|
||||
product.product_type = product_info['product_type']
|
||||
product.product_id = product_info['product_id']
|
||||
product.product_id = product_info.get('product_id')
|
||||
product.name = product_info['name']
|
||||
product.description = product_info.get('description')
|
||||
product.organization_id = product_info['organization_id']
|
||||
@ -452,17 +452,17 @@ def add_product(product_info, creator):
|
||||
|
||||
|
||||
def update_product(product_info):
|
||||
"""Update product by product_id."""
|
||||
"""Update product by id."""
|
||||
session = get_session()
|
||||
_id = product_info.get('product_id')
|
||||
product = session.query(models.Product).filter_by(product_id=_id).first()
|
||||
_id = product_info.get('id')
|
||||
product = session.query(models.Product).filter_by(id=_id).first()
|
||||
if product is None:
|
||||
raise NotFound('Product with product_id %s not found' % _id)
|
||||
raise NotFound('Product with id %s not found' % _id)
|
||||
|
||||
product.name = product_info.get('name', product.name)
|
||||
product.description = product_info.get('description', product.description)
|
||||
product.public = product_info.get('public', product.public)
|
||||
product.properties = product_info.get('properties', product.properties)
|
||||
keys = ['name', 'description', 'product_id', 'public', 'properties']
|
||||
for key in keys:
|
||||
if key in product_info:
|
||||
setattr(product, key, product_info[key])
|
||||
|
||||
with session.begin():
|
||||
product.save(session=session)
|
||||
@ -551,3 +551,40 @@ def get_organizations_by_user(user_openid, allowed_keys=None):
|
||||
.order_by(models.Organization.created_at.desc()).all())
|
||||
items = [item[0] for item in items]
|
||||
return _to_dict(items, allowed_keys=allowed_keys)
|
||||
|
||||
|
||||
def get_public_products(allowed_keys=None):
|
||||
"""Get public products."""
|
||||
session = get_session()
|
||||
items = (
|
||||
session.query(models.Product)
|
||||
.filter_by(public=True)
|
||||
.order_by(models.Product.created_at.desc()).all())
|
||||
return _to_dict(items, allowed_keys=allowed_keys)
|
||||
|
||||
|
||||
def get_products(allowed_keys=None):
|
||||
"""Get all products."""
|
||||
session = get_session()
|
||||
items = (
|
||||
session.query(models.Product)
|
||||
.order_by(models.Product.created_at.desc()).all())
|
||||
return _to_dict(items, allowed_keys=allowed_keys)
|
||||
|
||||
|
||||
def get_products_by_user(user_openid, allowed_keys=None):
|
||||
"""Get all products that user can manage."""
|
||||
session = get_session()
|
||||
items = (
|
||||
session.query(models.Product, models.Organization, models.Group,
|
||||
models.UserToGroup)
|
||||
.join(models.Organization,
|
||||
models.Organization.id == models.Product.organization_id)
|
||||
.join(models.Group,
|
||||
models.Group.id == models.Organization.group_id)
|
||||
.join(models.UserToGroup,
|
||||
models.Group.id == models.UserToGroup.group_id)
|
||||
.filter(models.UserToGroup.user_openid == user_openid)
|
||||
.order_by(models.Organization.created_at.desc()).all())
|
||||
items = [item[0] for item in items]
|
||||
return _to_dict(items, allowed_keys=allowed_keys)
|
||||
|
@ -225,7 +225,7 @@ class Product(BASE, RefStackBase): # pragma: no cover
|
||||
|
||||
id = sa.Column(sa.String(36), primary_key=True,
|
||||
default=lambda: six.text_type(uuid.uuid4()))
|
||||
product_id = sa.Column(sa.String(36), nullable=False)
|
||||
product_id = sa.Column(sa.String(36), nullable=True)
|
||||
name = sa.Column(sa.String(80), nullable=False)
|
||||
description = sa.Column(sa.Text())
|
||||
organization_id = sa.Column(sa.String(36),
|
||||
@ -241,5 +241,6 @@ class Product(BASE, RefStackBase): # pragma: no cover
|
||||
@property
|
||||
def default_allowed_keys(self):
|
||||
"""Default keys."""
|
||||
return ('id', 'product_id', 'name', 'description', 'organization_id',
|
||||
'created_by_user', 'properties', 'type', 'product_type')
|
||||
return ('id', 'name', 'description', 'product_id', 'product_type',
|
||||
'public', 'properties', 'created_at', 'updated_at',
|
||||
'organization_id', 'created_by_user', 'type')
|
||||
|
181
refstack/tests/api/test_products.py
Normal file
181
refstack/tests/api/test_products.py
Normal file
@ -0,0 +1,181 @@
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import json
|
||||
import uuid
|
||||
|
||||
import mock
|
||||
from oslo_config import fixture as config_fixture
|
||||
import webtest.app
|
||||
|
||||
from refstack.api import constants as api_const
|
||||
from refstack import db
|
||||
from refstack.tests import api
|
||||
|
||||
FAKE_PRODUCT = {
|
||||
'name': 'product name',
|
||||
'description': 'product description',
|
||||
'product_type': api_const.CLOUD,
|
||||
}
|
||||
|
||||
|
||||
class TestProductsEndpoint(api.FunctionalTest):
|
||||
"""Test case for the 'products' API endpoint."""
|
||||
|
||||
URL = '/v1/products/'
|
||||
|
||||
def setUp(self):
|
||||
super(TestProductsEndpoint, self).setUp()
|
||||
self.config_fixture = config_fixture.Config()
|
||||
self.CONF = self.useFixture(self.config_fixture).conf
|
||||
|
||||
self.user_info = {
|
||||
'openid': 'test-open-id',
|
||||
'email': 'foo@bar.com',
|
||||
'fullname': 'Foo Bar'
|
||||
}
|
||||
db.user_save(self.user_info)
|
||||
|
||||
@mock.patch('refstack.api.utils.get_user_id', return_value='test-open-id')
|
||||
def test_post(self, mock_get_user):
|
||||
"""Test products endpoint with post request."""
|
||||
product = json.dumps(FAKE_PRODUCT)
|
||||
actual_response = self.post_json(self.URL, params=product)
|
||||
self.assertIn('id', actual_response)
|
||||
try:
|
||||
uuid.UUID(actual_response.get('id'), version=4)
|
||||
except ValueError:
|
||||
self.fail("actual_response doesn't contain new item id")
|
||||
|
||||
@mock.patch('refstack.api.utils.get_user_id', return_value='test-open-id')
|
||||
def test_post_with_empty_object(self, mock_get_user):
|
||||
"""Test products endpoint with empty product request."""
|
||||
results = json.dumps(dict())
|
||||
self.assertRaises(webtest.app.AppError,
|
||||
self.post_json,
|
||||
self.URL,
|
||||
params=results)
|
||||
|
||||
@mock.patch('refstack.api.utils.get_user_id', return_value='test-open-id')
|
||||
def test_post_with_invalid_schema(self, mock_get_user):
|
||||
"""Test post request with invalid schema."""
|
||||
products = json.dumps({
|
||||
'foo': 'bar',
|
||||
})
|
||||
self.assertRaises(webtest.app.AppError,
|
||||
self.post_json,
|
||||
self.URL,
|
||||
params=products)
|
||||
|
||||
@mock.patch('refstack.api.utils.get_user_id', return_value='test-open-id')
|
||||
def test_vendor_was_created(self, mock_get_user):
|
||||
"""Test get_one request."""
|
||||
product = json.dumps(FAKE_PRODUCT)
|
||||
post_response = self.post_json(self.URL, params=product)
|
||||
|
||||
get_response = self.get_json(self.URL + post_response.get('id'))
|
||||
vendor_id = get_response.get('organization_id')
|
||||
self.assertIsNotNone(vendor_id)
|
||||
|
||||
# check vendor is present
|
||||
get_response = self.get_json('/v1/vendors/' + vendor_id)
|
||||
|
||||
@mock.patch('refstack.api.utils.get_user_id', return_value='test-open-id')
|
||||
def test_using_default_vendor(self, mock_get_user):
|
||||
"""Test get_one request."""
|
||||
product = json.dumps(FAKE_PRODUCT)
|
||||
post_response = self.post_json(self.URL, params=product)
|
||||
|
||||
get_response = self.get_json(self.URL + post_response.get('id'))
|
||||
vendor_id = get_response.get('organization_id')
|
||||
self.assertIsNotNone(vendor_id)
|
||||
|
||||
# check vendor is present
|
||||
get_response = self.get_json('/v1/vendors/' + vendor_id)
|
||||
|
||||
# create one more product
|
||||
product = json.dumps(FAKE_PRODUCT)
|
||||
post_response = self.post_json(self.URL, params=product)
|
||||
|
||||
@mock.patch('refstack.api.utils.get_user_id', return_value='test-open-id')
|
||||
def test_get_one(self, mock_get_user):
|
||||
"""Test get_one request."""
|
||||
product = json.dumps(FAKE_PRODUCT)
|
||||
post_response = self.post_json(self.URL, params=product)
|
||||
|
||||
get_response = self.get_json(self.URL + post_response.get('id'))
|
||||
# some of these fields are only exposed to the owner/foundation.
|
||||
self.assertIn('created_by_user', get_response)
|
||||
self.assertIn('properties', get_response)
|
||||
self.assertIn('created_at', get_response)
|
||||
self.assertIn('updated_at', get_response)
|
||||
self.assertEqual(FAKE_PRODUCT['name'],
|
||||
get_response['name'])
|
||||
self.assertEqual(FAKE_PRODUCT['description'],
|
||||
get_response['description'])
|
||||
self.assertEqual(api_const.PUBLIC_CLOUD,
|
||||
get_response['type'])
|
||||
self.assertEqual(api_const.CLOUD,
|
||||
get_response['product_type'])
|
||||
|
||||
# reset auth and check return result for anonymous
|
||||
mock_get_user.return_value = None
|
||||
self.assertRaises(webtest.app.AppError,
|
||||
self.get_json,
|
||||
self.URL + post_response.get('id'))
|
||||
|
||||
@mock.patch('refstack.api.utils.get_user_id', return_value='test-open-id')
|
||||
def test_delete(self, mock_get_user):
|
||||
"""Test delete request."""
|
||||
product = json.dumps(FAKE_PRODUCT)
|
||||
post_response = self.post_json(self.URL, params=product)
|
||||
self.delete(self.URL + post_response.get('id'))
|
||||
|
||||
@mock.patch('refstack.api.utils.get_user_id', return_value='test-open-id')
|
||||
def test_update(self, mock_get_user):
|
||||
"""Test put(update) request."""
|
||||
product = json.dumps(FAKE_PRODUCT)
|
||||
post_response = self.post_json(self.URL, params=product)
|
||||
id = post_response.get('id')
|
||||
|
||||
# check update of properties
|
||||
props = {'properties': {'fake01': 'value01'}}
|
||||
post_response = self.put_json(self.URL + id,
|
||||
params=json.dumps(props))
|
||||
get_response = self.get_json(self.URL + id)
|
||||
self.assertEqual(FAKE_PRODUCT['name'],
|
||||
get_response['name'])
|
||||
self.assertEqual(FAKE_PRODUCT['description'],
|
||||
get_response['description'])
|
||||
self.assertEqual(props['properties'],
|
||||
json.loads(get_response['properties']))
|
||||
|
||||
# check second update of properties
|
||||
props = {'properties': {'fake02': 'value03'}}
|
||||
post_response = self.put_json(self.URL + id,
|
||||
params=json.dumps(props))
|
||||
get_response = self.get_json(self.URL + id)
|
||||
self.assertEqual(props['properties'],
|
||||
json.loads(get_response['properties']))
|
||||
|
||||
def test_get_one_invalid_url(self):
|
||||
"""Test get request with invalid url."""
|
||||
self.assertRaises(webtest.app.AppError,
|
||||
self.get_json,
|
||||
self.URL + 'fake_id')
|
||||
|
||||
def test_get_with_empty_database(self):
|
||||
"""Test get(list) request with no items in DB."""
|
||||
results = self.get_json(self.URL)
|
||||
self.assertEqual([], results['products'])
|
@ -707,15 +707,15 @@ class DBBackendTestCase(base.BaseTestCase):
|
||||
query = session.query.return_value
|
||||
filtered = query.filter_by.return_value
|
||||
product = models.Product()
|
||||
product.product_id = '123'
|
||||
product.id = '123'
|
||||
filtered.first.return_value = product
|
||||
|
||||
product_info = {'product_id': '098', 'name': 'a', 'description': 'b',
|
||||
'creator_openid': 'abc', 'organization_id': '1',
|
||||
'type': 0, 'product_type': 0}
|
||||
'type': 0, 'product_type': 0, 'id': '123'}
|
||||
api.update_product(product_info)
|
||||
|
||||
self.assertEqual('123', product.product_id)
|
||||
self.assertEqual('098', product.product_id)
|
||||
self.assertIsNone(product.created_by_user)
|
||||
self.assertIsNone(product.organization_id)
|
||||
self.assertIsNone(product.type)
|
||||
|
Loading…
x
Reference in New Issue
Block a user