Add tests to product page
This patch updates the product page to include tests associated to versions of the product. This allows filtering results based on a product_id. Tests associated with private products will have their product information hidden. Change-Id: Ic5b6b45c9e3d14d9c2cb36a8eba72f2a6e31d2aa
This commit is contained in:
parent
02307f3a1e
commit
5ad18f067f
@ -1,4 +1,5 @@
|
||||
<h3>Cloud Product</h3>
|
||||
<div cg-busy="{promise:ctrl.productRequest,message:'Loading'}"></div>
|
||||
<div ng-show="ctrl.product" class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="pull-left">
|
||||
@ -14,6 +15,8 @@
|
||||
<div ng-include src="'components/products/partials/management.html'"></div>
|
||||
<div class="clearfix"></div>
|
||||
<div ng-include src="'components/products/partials/versions.html'"></div>
|
||||
<hr>
|
||||
<div ng-include src="'components/products/partials/testsTable.html'"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-show="ctrl.showError" class="alert alert-danger" role="alert">
|
||||
|
@ -1,4 +1,5 @@
|
||||
<h3>Distro Product</h3>
|
||||
<div cg-busy="{promise:ctrl.productRequest,message:'Loading'}"></div>
|
||||
<div ng-show="ctrl.product" class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="pull-left">
|
||||
@ -14,6 +15,8 @@
|
||||
<div ng-include src="'components/products/partials/management.html'"></div>
|
||||
<div class="clearfix"></div>
|
||||
<div ng-include src="'components/products/partials/versions.html'"></div>
|
||||
<hr>
|
||||
<div ng-include src="'components/products/partials/testsTable.html'"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-show="ctrl.showError" class="alert alert-danger" role="alert">
|
||||
|
137
refstack-ui/app/components/products/partials/testsTable.html
Normal file
137
refstack-ui/app/components/products/partials/testsTable.html
Normal file
@ -0,0 +1,137 @@
|
||||
<h4><strong>Test Runs on Product</strong></h4>
|
||||
<div cg-busy="{promise:ctrl.testsRequest,message:'Loading'}"></div>
|
||||
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Upload Date</th>
|
||||
<th>Test Run ID</th>
|
||||
<th>Product Version</th>
|
||||
<th>Shared</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr ng-repeat-start="(index, result) in ctrl.testsData">
|
||||
<td>
|
||||
<a ng-if="!result.expanded"
|
||||
class="glyphicon glyphicon-plus"
|
||||
ng-click="result.expanded = true">
|
||||
</a>
|
||||
<a ng-if="result.expanded"
|
||||
class="glyphicon glyphicon-minus"
|
||||
ng-click="result.expanded = false">
|
||||
</a>
|
||||
</td>
|
||||
<td>{{result.created_at}}</td>
|
||||
<td><a ui-sref="resultsDetail({testID: result.id})">{{result.id}}</a></td>
|
||||
<td>{{result.product_version.version}}</td>
|
||||
<td>
|
||||
<span ng-show="result.meta.shared" class="glyphicon glyphicon-share"></span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="result.expanded" ng-repeat-end>
|
||||
<td></td>
|
||||
<td colspan="4">
|
||||
<strong>Publicly Shared:</strong>
|
||||
<span ng-if="result.meta.shared == 'true' && !result.sharedEdit">Yes</span>
|
||||
<span ng-if="!result.meta.shared && !result.sharedEdit">
|
||||
<em>No</em>
|
||||
</span>
|
||||
<select ng-if="result.sharedEdit"
|
||||
ng-model="result.meta.shared"
|
||||
class="form-inline">
|
||||
<option value="true">Yes</option>
|
||||
<option value="">No</option>
|
||||
</select>
|
||||
<a ng-if="!result.sharedEdit"
|
||||
ng-click="result.sharedEdit = true"
|
||||
title="Edit"
|
||||
class="glyphicon glyphicon-pencil"></a>
|
||||
<a ng-if="result.sharedEdit"
|
||||
ng-click="ctrl.associateTestMeta(index,'shared',result.meta.shared)"
|
||||
title="Save"
|
||||
class="glyphicon glyphicon-floppy-disk"></a>
|
||||
<br />
|
||||
|
||||
<strong>Associated Guideline:</strong>
|
||||
<span ng-if="!result.meta.guideline && !result.guidelineEdit">
|
||||
<em>None</em>
|
||||
</span>
|
||||
<span ng-if="result.meta.guideline && !result.guidelineEdit">
|
||||
{{result.meta.guideline.slice(0, -5)}}
|
||||
</span>
|
||||
<select ng-if="result.guidelineEdit"
|
||||
ng-model="result.meta.guideline"
|
||||
ng-options="o as o.slice(0, -5) for o in ctrl.versionList"
|
||||
class="form-inline">
|
||||
<option value="">None</option>
|
||||
</select>
|
||||
<a ng-if="!result.guidelineEdit"
|
||||
ng-click="ctrl.getGuidelineVersionList();result.guidelineEdit = true"
|
||||
title="Edit"
|
||||
class="glyphicon glyphicon-pencil"></a>
|
||||
<a ng-if="result.guidelineEdit"
|
||||
ng-click="ctrl.associateTestMeta(index, 'guideline', result.meta.guideline)"
|
||||
title="Save"
|
||||
class="glyphicon glyphicon-floppy-disk">
|
||||
</a>
|
||||
<br />
|
||||
|
||||
<strong>Associated Target Program:</strong>
|
||||
<span ng-if="!result.meta.target && !result.targetEdit">
|
||||
<em>None</em>
|
||||
</span>
|
||||
<span ng-if="result.meta.target && !result.targetEdit">
|
||||
{{ctrl.targetMappings[result.meta.target]}}</span>
|
||||
<select ng-if="result.targetEdit"
|
||||
ng-model="result.meta.target"
|
||||
class="form-inline">
|
||||
<option value="">None</option>
|
||||
<option value="platform">OpenStack Powered Platform</option>
|
||||
<option value="compute">OpenStack Powered Compute</option>
|
||||
<option value="object">OpenStack Powered Object Storage</option>
|
||||
</select>
|
||||
<a ng-if="!result.targetEdit"
|
||||
ng-click="result.targetEdit = true"
|
||||
title="Edit"
|
||||
class="glyphicon glyphicon-pencil">
|
||||
</a>
|
||||
<a ng-if="result.targetEdit"
|
||||
ng-click="ctrl.associateTestMeta(index, 'target', result.meta.target)"
|
||||
title="Save"
|
||||
class="glyphicon glyphicon-floppy-disk">
|
||||
</a>
|
||||
<br />
|
||||
<br />
|
||||
<small>
|
||||
<a ng-click="ctrl.unassociateTest(index)"
|
||||
confirm="Are you sure you want to unassociate this test result with product: {{ctrl.product.name}}? Test result ownership will be given back to the original owner only.">
|
||||
<span class="glyphicon glyphicon-remove-circle" ></span> Unassociate test result from product
|
||||
</a>
|
||||
</small>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="pages">
|
||||
<uib-pagination
|
||||
total-items="ctrl.totalItems"
|
||||
ng-model="ctrl.currentPage"
|
||||
items-per-page="ctrl.itemsPerPage"
|
||||
max-size="ctrl.maxSize"
|
||||
class="pagination-sm"
|
||||
boundary-links="true"
|
||||
rotate="false"
|
||||
num-pages="ctrl.numPages"
|
||||
ng-change="ctrl.getProductTests()">
|
||||
</uib-pagination>
|
||||
</div>
|
||||
|
||||
<div ng-show="ctrl.showTestsError" class="alert alert-danger" role="alert">
|
||||
<span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
|
||||
<span class="sr-only">Error:</span>
|
||||
{{ctrl.testsError}}
|
||||
</div>
|
@ -37,8 +37,12 @@
|
||||
ctrl.getProductVersions = getProductVersions;
|
||||
ctrl.deleteProduct = deleteProduct;
|
||||
ctrl.deleteProductVersion = deleteProductVersion;
|
||||
ctrl.getProductTests = getProductTests;
|
||||
ctrl.switchProductPublicity = switchProductPublicity;
|
||||
ctrl.associateTestMeta = associateTestMeta;
|
||||
ctrl.getGuidelineVersionList = getGuidelineVersionList;
|
||||
ctrl.addProductVersion = addProductVersion;
|
||||
ctrl.unassociateTest = unassociateTest;
|
||||
ctrl.openVersionModal = openVersionModal;
|
||||
|
||||
/** The product id extracted from the URL route. */
|
||||
@ -49,8 +53,21 @@
|
||||
$state.go('home');
|
||||
}
|
||||
|
||||
/** Mappings of DefCore components to marketing program names. */
|
||||
ctrl.targetMappings = {
|
||||
'platform': 'Openstack Powered Platform',
|
||||
'compute': 'OpenStack Powered Compute',
|
||||
'object': 'OpenStack Powered Object Storage'
|
||||
};
|
||||
|
||||
// Pagination controls.
|
||||
ctrl.currentPage = 1;
|
||||
ctrl.itemsPerPage = 20;
|
||||
ctrl.maxSize = 5;
|
||||
|
||||
ctrl.getProduct();
|
||||
ctrl.getProductVersions();
|
||||
ctrl.getProductTests();
|
||||
|
||||
/**
|
||||
* This will contact the Refstack API to get a product information.
|
||||
@ -147,6 +164,30 @@
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tests runs associated with the current product.
|
||||
*/
|
||||
function getProductTests() {
|
||||
ctrl.showTestsError = false;
|
||||
var content_url = refstackApiUrl + '/results' +
|
||||
'?page=' + ctrl.currentPage + '&product_id='
|
||||
+ ctrl.id;
|
||||
|
||||
ctrl.testsRequest = $http.get(content_url).success(
|
||||
function(data) {
|
||||
ctrl.testsData = data.results;
|
||||
ctrl.totalItems = data.pagination.total_pages *
|
||||
ctrl.itemsPerPage;
|
||||
ctrl.currentPage = data.pagination.current_page;
|
||||
}
|
||||
).error(function(error) {
|
||||
ctrl.showTestsError = true;
|
||||
ctrl.testsError =
|
||||
'Error retrieving tests from server: ' +
|
||||
angular.toJson(error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This will switch public/private property of the product.
|
||||
*/
|
||||
@ -161,6 +202,73 @@
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This will send an API request in order to associate a metadata
|
||||
* key-value pair with the given testId
|
||||
* @param {Number} index - index of the test object in the results list
|
||||
* @param {String} key - metadata key
|
||||
* @param {String} value - metadata value
|
||||
*/
|
||||
function associateTestMeta(index, key, value) {
|
||||
var testId = ctrl.testsData[index].id;
|
||||
var metaUrl = [
|
||||
refstackApiUrl, '/results/', testId, '/meta/', key
|
||||
].join('');
|
||||
|
||||
var editFlag = key + 'Edit';
|
||||
if (value) {
|
||||
ctrl.associateRequest = $http.post(metaUrl, value)
|
||||
.success(function () {
|
||||
ctrl.testsData[index][editFlag] = false;
|
||||
}).error(function (error) {
|
||||
raiseAlert('danger', error.title, error.detail);
|
||||
});
|
||||
}
|
||||
else {
|
||||
ctrl.unassociateRequest = $http.delete(metaUrl)
|
||||
.success(function () {
|
||||
ctrl.testsData[index][editFlag] = false;
|
||||
}).error(function (error) {
|
||||
raiseAlert('danger', error.title, error.detail);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an array of available capability files from the Refstack
|
||||
* API server, sort this array reverse-alphabetically, and store it in
|
||||
* a scoped variable.
|
||||
* Sample API return array: ["2015.03.json", "2015.04.json"]
|
||||
*/
|
||||
function getGuidelineVersionList() {
|
||||
if (ctrl.versionList) {
|
||||
return;
|
||||
}
|
||||
var content_url = refstackApiUrl + '/guidelines';
|
||||
ctrl.versionsRequest =
|
||||
$http.get(content_url).success(function (data) {
|
||||
ctrl.versionList = data.sort().reverse();
|
||||
}).error(function (error) {
|
||||
raiseAlert('danger', error.title,
|
||||
'Unable to retrieve version list');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a PUT request to the API server to unassociate a product with
|
||||
* a test result.
|
||||
*/
|
||||
function unassociateTest(index) {
|
||||
var testId = ctrl.testsData[index].id;
|
||||
var url = refstackApiUrl + '/results/' + testId;
|
||||
ctrl.associateRequest = $http.put(url, {'product_version_id': null})
|
||||
.success(function () {
|
||||
ctrl.testsData.splice(index, 1);
|
||||
}).error(function (error) {
|
||||
raiseAlert('danger', error.title, error.detail);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This will open the modal that will allow a product version
|
||||
* to be managed.
|
||||
|
@ -133,7 +133,8 @@
|
||||
*/
|
||||
function isEditingAllowed() {
|
||||
return Boolean(ctrl.resultsData &&
|
||||
ctrl.resultsData.user_role === 'owner');
|
||||
(ctrl.resultsData.user_role === 'owner' ||
|
||||
ctrl.resultsData.user_role == 'foundation'));
|
||||
}
|
||||
/**
|
||||
* This tells you whether the current results are shared with the
|
||||
|
@ -1107,6 +1107,9 @@ describe('Refstack controllers', function () {
|
||||
'cpid': null,
|
||||
'version': '1.0',
|
||||
'product_id': '1234'}];
|
||||
var fakeTestsResp = {'pagination': {'current_page': 1,
|
||||
'total_pages': 1},
|
||||
'results':[{'id': 'foo-test'}]};
|
||||
var fakeVendorResp = {'id': 'fake-org-id',
|
||||
'type': 3,
|
||||
'can_manage': true,
|
||||
@ -1134,6 +1137,8 @@ describe('Refstack controllers', function () {
|
||||
'/products/1234').respond(fakeProdResp);
|
||||
$httpBackend.when('GET', fakeApiUrl +
|
||||
'/products/1234/versions').respond(fakeVersionResp);
|
||||
$httpBackend.when('GET', fakeApiUrl +
|
||||
'/results?page=1&product_id=1234').respond(fakeTestsResp);
|
||||
$httpBackend.when('GET', fakeApiUrl +
|
||||
'/vendors/fake-org-id').respond(fakeVendorResp);
|
||||
}));
|
||||
@ -1189,6 +1194,26 @@ describe('Refstack controllers', function () {
|
||||
$httpBackend.flush();
|
||||
});
|
||||
|
||||
it('should have a function to get tests on a product',
|
||||
function () {
|
||||
ctrl.getProductTests();
|
||||
$httpBackend.flush();
|
||||
expect(ctrl.testsData).toEqual(fakeTestsResp.results);
|
||||
expect(ctrl.currentPage).toEqual(1);
|
||||
});
|
||||
|
||||
it('should have a function to unassociate a test from a product',
|
||||
function () {
|
||||
ctrl.testsData = [{'id': 'foo-test'}];
|
||||
$httpBackend.expectPUT(
|
||||
fakeApiUrl + '/results/foo-test',
|
||||
{product_version_id: null})
|
||||
.respond(200, {'id': 'foo-test'});
|
||||
ctrl.unassociateTest(0);
|
||||
$httpBackend.flush();
|
||||
expect(ctrl.testsData).toEqual([]);
|
||||
});
|
||||
|
||||
it('should have a function to switch the publicity of a project',
|
||||
function () {
|
||||
ctrl.product = {'public': true};
|
||||
|
@ -21,6 +21,8 @@ CPID = 'cpid'
|
||||
PAGE = 'page'
|
||||
SIGNED = 'signed'
|
||||
VERIFICATION_STATUS = 'verification_status'
|
||||
PRODUCT_ID = 'product_id'
|
||||
ALL_PRODUCT_TESTS = 'all_product_tests'
|
||||
OPENID = 'openid'
|
||||
USER_PUBKEYS = 'pubkeys'
|
||||
|
||||
|
@ -51,7 +51,8 @@ class VersionsController(validation.BaseRestControllerWithValidation):
|
||||
if not product['public'] and not is_admin:
|
||||
pecan.abort(403, 'Forbidden.')
|
||||
|
||||
return db.get_product_versions(id)
|
||||
allowed_keys = ['id', 'product_id', 'version', 'cpid']
|
||||
return db.get_product_versions(id, allowed_keys=allowed_keys)
|
||||
|
||||
@pecan.expose('json')
|
||||
def get_one(self, id, version_id):
|
||||
@ -62,8 +63,8 @@ class VersionsController(validation.BaseRestControllerWithValidation):
|
||||
api_utils.check_user_is_vendor_admin(vendor_id))
|
||||
if not product['public'] and not is_admin:
|
||||
pecan.abort(403, 'Forbidden.')
|
||||
|
||||
return db.get_product_version(version_id)
|
||||
allowed_keys = ['id', 'product_id', 'version', 'cpid']
|
||||
return db.get_product_version(version_id, allowed_keys=allowed_keys)
|
||||
|
||||
@secure(api_utils.is_authenticated)
|
||||
@pecan.expose('json')
|
||||
@ -171,19 +172,21 @@ class ProductsController(validation.BaseRestControllerWithValidation):
|
||||
@pecan.expose('json')
|
||||
def get_one(self, id):
|
||||
"""Get information about product."""
|
||||
product = db.get_product(id)
|
||||
allowed_keys = ['id', 'name', 'description',
|
||||
'product_ref_id', 'product_type',
|
||||
'public', 'properties', 'created_at', 'updated_at',
|
||||
'organization_id', 'created_by_user', 'type']
|
||||
product = db.get_product(id, allowed_keys=allowed_keys)
|
||||
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_ref_id',
|
||||
'type', 'product_type', 'public',
|
||||
'organization_id']
|
||||
admin_only_keys = ['created_by_user', 'created_at', 'updated_at',
|
||||
'properties']
|
||||
for key in product.keys():
|
||||
if key not in allowed_keys:
|
||||
if key in admin_only_keys:
|
||||
product.pop(key)
|
||||
|
||||
product['can_manage'] = is_admin
|
||||
|
@ -112,7 +112,7 @@ class ResultsController(validation.BaseRestControllerWithValidation):
|
||||
test_info = db.get_test(
|
||||
test_id, allowed_keys=['id', 'cpid', 'created_at',
|
||||
'duration_seconds', 'meta',
|
||||
'product_version_id',
|
||||
'product_version',
|
||||
'verification_status']
|
||||
)
|
||||
else:
|
||||
@ -123,6 +123,12 @@ class ResultsController(validation.BaseRestControllerWithValidation):
|
||||
'user_role': user_role})
|
||||
|
||||
if user_role not in (const.ROLE_FOUNDATION, const.ROLE_OWNER):
|
||||
# Don't expose product information if product is not public.
|
||||
if (test_info.get('product_version') and
|
||||
not test_info['product_version']['product_info']['public']):
|
||||
|
||||
test_info['product_version'] = None
|
||||
|
||||
test_info['meta'] = {
|
||||
k: v for k, v in six.iteritems(test_info['meta'])
|
||||
if k in MetadataController.rw_access_keys
|
||||
@ -176,10 +182,22 @@ class ResultsController(validation.BaseRestControllerWithValidation):
|
||||
const.END_DATE,
|
||||
const.CPID,
|
||||
const.SIGNED,
|
||||
const.VERIFICATION_STATUS
|
||||
const.VERIFICATION_STATUS,
|
||||
const.PRODUCT_ID
|
||||
]
|
||||
|
||||
filters = api_utils.parse_input_params(expected_input_params)
|
||||
|
||||
if const.PRODUCT_ID in filters:
|
||||
product = db.get_product(filters[const.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 is_admin:
|
||||
filters[const.ALL_PRODUCT_TESTS] = True
|
||||
elif not product['public']:
|
||||
pecan.abort(403, 'Forbidden.')
|
||||
|
||||
records_count = db.get_test_records_count(filters)
|
||||
page_number, total_pages_number = \
|
||||
api_utils.get_page_number(records_count)
|
||||
@ -187,13 +205,18 @@ class ResultsController(validation.BaseRestControllerWithValidation):
|
||||
try:
|
||||
per_page = CONF.api.results_per_page
|
||||
results = db.get_test_records(page_number, per_page, filters)
|
||||
|
||||
is_foundation = api_utils.check_user_is_foundation_admin()
|
||||
for result in results:
|
||||
# Only show all metadata if the user is the owner or a member
|
||||
# of the Foundation group.
|
||||
if (not api_utils.check_owner(result['id']) and
|
||||
not api_utils.check_user_is_foundation_admin()):
|
||||
|
||||
if not (api_utils.check_owner(result['id']) or is_foundation):
|
||||
|
||||
# Don't expose product info if the product is not public.
|
||||
if (result.get('product_version') and not
|
||||
result['product_version']['product_info']['public']):
|
||||
|
||||
result['product_version'] = None
|
||||
# Only show all metadata if the user is the owner or a
|
||||
# member of the Foundation group.
|
||||
result['meta'] = {
|
||||
k: v for k, v in six.iteritems(result['meta'])
|
||||
if k in MetadataController.rw_access_keys
|
||||
@ -209,8 +232,8 @@ class ResultsController(validation.BaseRestControllerWithValidation):
|
||||
}}
|
||||
except Exception as ex:
|
||||
LOG.debug('An error occurred during '
|
||||
'operation with database: %s' % ex)
|
||||
pecan.abort(400)
|
||||
'operation with database: %s' % str(ex))
|
||||
pecan.abort(500)
|
||||
|
||||
return page
|
||||
|
||||
@ -229,7 +252,8 @@ class ResultsController(validation.BaseRestControllerWithValidation):
|
||||
|
||||
if kw['product_version_id']:
|
||||
# Verify that the user is a member of the product's vendor.
|
||||
version = db.get_product_version(kw['product_version_id'])
|
||||
version = db.get_product_version(kw['product_version_id'],
|
||||
allowed_keys=['product_id'])
|
||||
is_vendor_admin = (
|
||||
api_utils
|
||||
.check_user_is_product_admin(version['product_id'])
|
||||
|
@ -210,9 +210,9 @@ def update_product(product_info):
|
||||
return IMPL.update_product(product_info)
|
||||
|
||||
|
||||
def get_product(id):
|
||||
def get_product(id, allowed_keys=None):
|
||||
"""Get product by id."""
|
||||
return IMPL.get_product(id)
|
||||
return IMPL.get_product(id, allowed_keys=allowed_keys)
|
||||
|
||||
|
||||
def delete_product(id):
|
||||
|
@ -243,6 +243,13 @@ def _apply_filters_for_query(query, filters):
|
||||
query = query.filter(models.Test.verification_status ==
|
||||
verification_status)
|
||||
|
||||
if api_const.PRODUCT_ID in filters:
|
||||
query = (query
|
||||
.join(models.ProductVersion)
|
||||
.filter(models.ProductVersion.product_id ==
|
||||
filters[api_const.PRODUCT_ID]))
|
||||
|
||||
all_product_tests = filters.get(api_const.ALL_PRODUCT_TESTS)
|
||||
signed = api_const.SIGNED in filters
|
||||
# If we only want to get the user's test results.
|
||||
if signed:
|
||||
@ -251,7 +258,9 @@ def _apply_filters_for_query(query, filters):
|
||||
.filter(models.TestMeta.meta_key == api_const.USER)
|
||||
.filter(models.TestMeta.value == filters[api_const.OPENID])
|
||||
)
|
||||
else:
|
||||
elif not all_product_tests:
|
||||
# Get all non-signed (aka anonymously uploaded) test results
|
||||
# along with signed but shared test results.
|
||||
signed_results = (query.session
|
||||
.query(models.TestMeta.test_id)
|
||||
.filter_by(meta_key=api_const.USER))
|
||||
@ -260,6 +269,7 @@ def _apply_filters_for_query(query, filters):
|
||||
.filter_by(meta_key=api_const.SHARED_TEST_RUN))
|
||||
query = (query.filter(models.Test.id.notin_(signed_results))
|
||||
.union(query.filter(models.Test.id.in_(shared_results))))
|
||||
|
||||
return query
|
||||
|
||||
|
||||
@ -505,13 +515,13 @@ def update_product(product_info):
|
||||
return _to_dict(product)
|
||||
|
||||
|
||||
def get_product(id):
|
||||
def get_product(id, allowed_keys=None):
|
||||
"""Get product by id."""
|
||||
session = get_session()
|
||||
product = session.query(models.Product).filter_by(id=id).first()
|
||||
if product is None:
|
||||
raise NotFound('Product with id "%s" not found' % id)
|
||||
return _to_dict(product)
|
||||
return _to_dict(product, allowed_keys=allowed_keys)
|
||||
|
||||
|
||||
def delete_product(id):
|
||||
@ -653,7 +663,8 @@ def get_product_versions(product_id, allowed_keys=None):
|
||||
"""Get all versions for a product."""
|
||||
session = get_session()
|
||||
version_info = (
|
||||
session.query(models.ProductVersion).filter_by(product_id=product_id)
|
||||
session.query(models.ProductVersion)
|
||||
.filter_by(product_id=product_id).all()
|
||||
)
|
||||
return _to_dict(version_info, allowed_keys=allowed_keys)
|
||||
|
||||
|
@ -63,11 +63,12 @@ class Test(BASE, RefStackBase): # pragma: no cover
|
||||
sa.ForeignKey('product_version.id'),
|
||||
nullable=True, unique=False)
|
||||
verification_status = sa.Column(sa.Integer, nullable=False, default=0)
|
||||
product_version = orm.relationship('ProductVersion', backref='test')
|
||||
|
||||
@property
|
||||
def _extra_keys(self):
|
||||
"""Relation should be pointed directly."""
|
||||
return ['results', 'meta']
|
||||
return ['results', 'meta', 'product_version']
|
||||
|
||||
@property
|
||||
def metadata_keys(self):
|
||||
@ -79,7 +80,7 @@ class Test(BASE, RefStackBase): # pragma: no cover
|
||||
def default_allowed_keys(self):
|
||||
"""Default keys."""
|
||||
return ('id', 'created_at', 'duration_seconds', 'meta',
|
||||
'product_version_id', 'verification_status')
|
||||
'verification_status', 'product_version')
|
||||
|
||||
|
||||
class TestResults(BASE, RefStackBase): # pragma: no cover
|
||||
@ -245,9 +246,7 @@ class Product(BASE, RefStackBase): # pragma: no cover
|
||||
@property
|
||||
def default_allowed_keys(self):
|
||||
"""Default keys."""
|
||||
return ('id', 'name', 'description', 'product_ref_id', 'product_type',
|
||||
'public', 'properties', 'created_at', 'updated_at',
|
||||
'organization_id', 'created_by_user', 'type')
|
||||
return ('id', 'name', 'organization_id', 'public')
|
||||
|
||||
|
||||
class ProductVersion(BASE, RefStackBase):
|
||||
@ -266,8 +265,14 @@ class ProductVersion(BASE, RefStackBase):
|
||||
cpid = sa.Column(sa.String(36), nullable=True)
|
||||
created_by_user = sa.Column(sa.String(128), sa.ForeignKey('user.openid'),
|
||||
nullable=False)
|
||||
product_info = orm.relationship('Product', backref='product_version')
|
||||
|
||||
@property
|
||||
def _extra_keys(self):
|
||||
"""Relation should be pointed directly."""
|
||||
return ['product_info']
|
||||
|
||||
@property
|
||||
def default_allowed_keys(self):
|
||||
"""Default keys."""
|
||||
return ('id', 'product_id', 'version', 'cpid')
|
||||
return ('id', 'version', 'cpid', 'product_info')
|
||||
|
@ -135,6 +135,17 @@ class TestProductsEndpoint(api.FunctionalTest):
|
||||
self.get_json,
|
||||
self.URL + post_response.get('id'))
|
||||
|
||||
mock_get_user.return_value = 'foo-open-id'
|
||||
# Make product public.
|
||||
product_info = {'id': post_response.get('id'), 'public': 1}
|
||||
db.update_product(product_info)
|
||||
|
||||
# Test when getting product info when not owner/foundation.
|
||||
get_response = self.get_json(self.URL + post_response.get('id'))
|
||||
self.assertNotIn('created_by_user', get_response)
|
||||
self.assertNotIn('created_at', get_response)
|
||||
self.assertNotIn('updated_at', get_response)
|
||||
|
||||
@mock.patch('refstack.api.utils.get_user_id', return_value='test-open-id')
|
||||
def test_delete(self, mock_get_user):
|
||||
"""Test delete request."""
|
||||
@ -182,7 +193,7 @@ class TestProductsEndpoint(api.FunctionalTest):
|
||||
|
||||
|
||||
class TestProductVersionEndpoint(api.FunctionalTest):
|
||||
"""Test case for the 'product/<product_id>/version' API endpoint."""
|
||||
"""Test case for the 'products/<product_id>/version' API endpoint."""
|
||||
|
||||
def setUp(self):
|
||||
super(TestProductVersionEndpoint, self).setUp()
|
||||
@ -218,7 +229,7 @@ class TestProductVersionEndpoint(api.FunctionalTest):
|
||||
|
||||
response = self.get_json(self.URL)
|
||||
self.assertEqual(2, len(response))
|
||||
self.assertEqual(post_response, response[1])
|
||||
self.assertEqual(post_response['version'], response[1]['version'])
|
||||
|
||||
def test_get_one(self):
|
||||
""""Test get a specific version."""
|
||||
@ -228,7 +239,7 @@ class TestProductVersionEndpoint(api.FunctionalTest):
|
||||
version_id = post_response['id']
|
||||
|
||||
response = self.get_json(self.URL + version_id)
|
||||
self.assertEqual(post_response, response)
|
||||
self.assertEqual(post_response['version'], response['version'])
|
||||
|
||||
# Test nonexistent version.
|
||||
self.assertRaises(webtest.app.AppError, self.get_json,
|
||||
@ -238,14 +249,17 @@ class TestProductVersionEndpoint(api.FunctionalTest):
|
||||
"""Test creating a product version."""
|
||||
version = {'cpid': '123', 'version': '5.0'}
|
||||
post_response = self.post_json(self.URL, params=json.dumps(version))
|
||||
self.assertEqual(version['cpid'], post_response['cpid'])
|
||||
self.assertEqual(version['version'], post_response['version'])
|
||||
self.assertEqual(self.product_id, post_response['product_id'])
|
||||
self.assertIn('id', post_response)
|
||||
|
||||
get_response = self.get_json(self.URL + post_response['id'])
|
||||
self.assertEqual(version['cpid'], get_response['cpid'])
|
||||
self.assertEqual(version['version'], get_response['version'])
|
||||
self.assertEqual(self.product_id, get_response['product_id'])
|
||||
self.assertIn('id', get_response)
|
||||
|
||||
# Test 'version' not in response body.
|
||||
self.assertRaises(webtest.app.AppError, self.get_json,
|
||||
self.URL + '/sdsdsds')
|
||||
response = self.post_json(self.URL, expect_errors=True,
|
||||
params=json.dumps({'cpid': '123'}))
|
||||
self.assertEqual(400, response.status_code)
|
||||
|
||||
def test_put(self):
|
||||
"""Test updating a product version."""
|
||||
|
@ -119,13 +119,13 @@ class TestResultsEndpoint(api.FunctionalTest):
|
||||
self.put_json(url, params=json.dumps(body))
|
||||
get_response = self.get_json(url)
|
||||
self.assertEqual(version_response['id'],
|
||||
get_response['product_version_id'])
|
||||
get_response['product_version']['id'])
|
||||
|
||||
# Test when product_version_id is None.
|
||||
body = {'product_version_id': None}
|
||||
self.put_json(url, params=json.dumps(body))
|
||||
get_response = self.get_json(url)
|
||||
self.assertIsNone(get_response['product_version_id'])
|
||||
self.assertIsNone(get_response['product_version'])
|
||||
|
||||
# Test when test verification preconditions are not met.
|
||||
body = {'verification_status': api_const.TEST_VERIFIED}
|
||||
@ -167,7 +167,7 @@ class TestResultsEndpoint(api.FunctionalTest):
|
||||
self.put_json(url, params=json.dumps(body))
|
||||
get_response = self.get_json(url)
|
||||
self.assertEqual(version_response['id'],
|
||||
get_response['product_version_id'])
|
||||
get_response['product_version']['id'])
|
||||
|
||||
# Test non-Foundation user can't change verification_status.
|
||||
body = {'verification_status': 1}
|
||||
@ -328,6 +328,67 @@ class TestResultsEndpoint(api.FunctionalTest):
|
||||
filtering_results = self.get_json(url)
|
||||
self.assertEqual([], filtering_results['results'])
|
||||
|
||||
@mock.patch('refstack.api.utils.get_user_id')
|
||||
def test_get_with_product_id(self, mock_get_user):
|
||||
user_info = {
|
||||
'openid': 'test-open-id',
|
||||
'email': 'foo@bar.com',
|
||||
'fullname': 'Foo Bar'
|
||||
}
|
||||
db.user_save(user_info)
|
||||
|
||||
mock_get_user.return_value = 'test-open-id'
|
||||
|
||||
fake_product = {
|
||||
'name': 'product name',
|
||||
'description': 'product description',
|
||||
'product_type': api_const.CLOUD,
|
||||
}
|
||||
|
||||
product = json.dumps(fake_product)
|
||||
response = self.post_json('/v1/products/', params=product)
|
||||
product_id = response['id']
|
||||
|
||||
# Create a version.
|
||||
version_url = '/v1/products/' + product_id + '/versions'
|
||||
version = {'cpid': '123', 'version': '6.0'}
|
||||
post_response = self.post_json(version_url, params=json.dumps(version))
|
||||
version_id = post_response['id']
|
||||
|
||||
# Create a test and associate it to the product version and user.
|
||||
results = json.dumps(FAKE_TESTS_RESULT)
|
||||
post_response = self.post_json('/v1/results', params=results)
|
||||
test_id = post_response['test_id']
|
||||
test_info = {'id': test_id, 'product_version_id': version_id}
|
||||
db.update_test(test_info)
|
||||
db.save_test_meta_item(test_id, api_const.USER, 'test-open-id')
|
||||
|
||||
url = self.URL + '?page=1&product_id=' + product_id
|
||||
|
||||
# Test GET.
|
||||
response = self.get_json(url)
|
||||
self.assertEqual(1, len(response['results']))
|
||||
self.assertEqual(test_id, response['results'][0]['id'])
|
||||
|
||||
# Test unauthorized.
|
||||
mock_get_user.return_value = 'test-foo-id'
|
||||
response = self.get_json(url, expect_errors=True)
|
||||
self.assertEqual(403, response.status_code)
|
||||
|
||||
# Make product public.
|
||||
product_info = {'id': product_id, 'public': 1}
|
||||
db.update_product(product_info)
|
||||
|
||||
# Test result is not shared yet, so no tests should return.
|
||||
response = self.get_json(url)
|
||||
self.assertFalse(response['results'])
|
||||
|
||||
# Share the test run.
|
||||
db.save_test_meta_item(test_id, api_const.SHARED_TEST_RUN, 1)
|
||||
response = self.get_json(url)
|
||||
self.assertEqual(1, len(response['results']))
|
||||
self.assertEqual(test_id, response['results'][0]['id'])
|
||||
|
||||
@mock.patch('refstack.api.utils.check_owner')
|
||||
def test_delete(self, mock_check_owner):
|
||||
results = json.dumps(FAKE_TESTS_RESULT)
|
||||
|
@ -141,7 +141,7 @@ class ResultsControllerTestCase(BaseControllerTestCase):
|
||||
mock_get_test.assert_called_once_with(
|
||||
'fake_arg', allowed_keys=['id', 'cpid', 'created_at',
|
||||
'duration_seconds', 'meta',
|
||||
'product_version_id',
|
||||
'product_version',
|
||||
'verification_status']
|
||||
)
|
||||
|
||||
@ -251,6 +251,7 @@ class ResultsControllerTestCase(BaseControllerTestCase):
|
||||
const.CPID,
|
||||
const.SIGNED,
|
||||
const.VERIFICATION_STATUS,
|
||||
const.PRODUCT_ID
|
||||
]
|
||||
page_number = 1
|
||||
total_pages_number = 10
|
||||
|
@ -769,7 +769,7 @@ class DBBackendTestCase(base.BaseTestCase):
|
||||
@mock.patch.object(api, 'get_session',
|
||||
return_value=mock.Mock(name='session'),)
|
||||
@mock.patch('refstack.db.sqlalchemy.models.Product')
|
||||
@mock.patch.object(api, '_to_dict', side_effect=lambda x: x)
|
||||
@mock.patch.object(api, '_to_dict', side_effect=lambda x, allowed_keys: x)
|
||||
def test_product_get(self, mock_to_dict, mock_model, mock_get_session):
|
||||
_id = 12345
|
||||
session = mock_get_session.return_value
|
||||
|
Loading…
Reference in New Issue
Block a user