Create functionality and e2e tests for project 77/47177/1
authorthuva4 <tharma.thuva@gmail.com>
Tue, 14 Nov 2017 10:08:56 +0000 (15:38 +0530)
committerthuva4 <tharma.thuva@gmail.com>
Tue, 14 Nov 2017 10:08:56 +0000 (15:38 +0530)
Implemented the create function for the projects.
Wrote the e2e tests for the create function.

Change-Id: Iceac650573ca31b6246350c4d60033b42e0ffb0f
Signed-off-by: thuva4 <tharma.thuva@gmail.com>
15 files changed:
testapi/3rd_party/static/testapi-ui/Gruntfile.js
testapi/3rd_party/static/testapi-ui/app.js
testapi/3rd_party/static/testapi-ui/index.html
testapi/3rd_party/static/testapi-ui/shared/header/header.html
testapi/opnfv_testapi/common/check.py
testapi/opnfv_testapi/common/message.py
testapi/opnfv_testapi/handlers/base_handlers.py
testapi/opnfv_testapi/tests/UI/e2e/podsControllerSpec.js
testapi/opnfv_testapi/tests/UI/e2e/projectControllerSpec.js [new file with mode: 0644]
testapi/opnfv_testapi/tests/unit/handlers/test_base.py
testapi/opnfv_testapi/tests/unit/handlers/test_project.py
testapi/opnfv_testapi/tests/unit/handlers/test_result.py
testapi/opnfv_testapi/tests/unit/handlers/test_testcase.py
testapi/opnfv_testapi/ui/projects/projects.html [new file with mode: 0644]
testapi/opnfv_testapi/ui/projects/projectsController.js [new file with mode: 0644]

index 8ff2802..f82269e 100644 (file)
@@ -30,6 +30,12 @@ module.exports = function (grunt) {
                          dest: 'testapi-ui/assets',
                        },
                        components: {
+                               expand: true,
+                               cwd: '../../../opnfv_testapi/ui',
+                               src: '**',
+                               dest: 'components',
+                       },
+                       copyComponents: {
                                expand: true,
                                cwd: 'components',
                                src: '**',
@@ -82,6 +88,12 @@ module.exports = function (grunt) {
                              async: true
                            }
                        },
+                       deleteFiles: {
+                               command: 'rm -r testapi-ui && rm -r components',
+                               options: {
+                             async: false
+                           }
+                       },
                        options: {
                        stdout: false,
                        stderr: false
@@ -90,8 +102,8 @@ module.exports = function (grunt) {
                instrument: {
                files: ['components/**/*.js'],
                options: {
-               lazy: false,
-                   basePath: "./testapi-ui/"
+                       lazy: false,
+                               basePath: "./testapi-ui/"
                }
            },
                karma: {
@@ -105,7 +117,8 @@ module.exports = function (grunt) {
                        noColor: false,
                        coverageDir: '../../../opnfv_testapi/tests/UI/coverage',
                        args: {
-                           specs: ['../../../opnfv_testapi/tests/UI/e2e/podsControllerSpec.js']
+                                       specs: ['../../../opnfv_testapi/tests/UI/e2e/podsControllerSpec.js',
+                                                       '../../../opnfv_testapi/tests/UI/e2e/projectControllerSpec.js']
                        }
                    },
                    local: {
@@ -119,18 +132,7 @@ module.exports = function (grunt) {
                options: {
                    print: 'detail'
                }
-           },
-               protractor: {
-                       e2e: {
-                               options: {
-                                       args: {
-                                               specs: ['../../../opnfv_testapi/tests/UI/e2e/podsControllerSpec.js']
-                                       },
-                                       configFile: '../../../opnfv_testapi/tests/UI/protractor-conf.js',
-                                       keepAlive: true
-                               }
-                       }
-               }
+           }
        });
        grunt.registerTask('test', [
                'karma:unit'
@@ -138,6 +140,7 @@ module.exports = function (grunt) {
        grunt.registerTask('e2e', [
                'copy:assets',
                'copy:components',
+               'copy:copyComponents',
                'copy:shared',
                'copy:filesPng',
                'copy:filesIco',
@@ -149,7 +152,8 @@ module.exports = function (grunt) {
                'shell:startSelenium',
                'wait:default',
                'protractor_coverage',
-               'makeReport'
-               // 'protractor'
+               'makeReport',
+               'shell:deleteFiles'
+
        ]);
 }
index 5f5b861..0b35162 100644 (file)
                 templateUrl: 'testapi-ui/components/pods/pods.html',
                 controller: 'PodsController as ctrl'
             }).
+            state('projects', {
+                url: '/projects',
+                templateUrl: 'testapi-ui/components/projects/projects.html',
+                controller: 'ProjectsController as ctrl'
+            }).
             state('communityResults', {
                 url: '/community_results',
                 templateUrl: 'testapi-ui/components/results/results.html',
         $rootScope.auth.doSignIn = doSignIn;
         $rootScope.auth.doSignOut = doSignOut;
         $rootScope.auth.doSignCheck = doSignCheck;
+        $rootScope.auth.doSubmitterCheck = doSubmitterCheck;
 
         var sign_in_url = testapiApiUrl + '/auth/signin';
         var sign_out_url = testapiApiUrl + '/auth/signout';
         function doSignOut() {
             $rootScope.auth.currentUser = null;
             $rootScope.auth.isAuthenticated = false;
+            $rootScope.auth.projectNames = [];
             $window.location.href = sign_out_url;
         }
 
                 success(function (data) {
                     $rootScope.auth.currentUser = data;
                     $rootScope.auth.isAuthenticated = true;
+                    $rootScope.auth.projectNames = $rootScope.auth.doSubmitterCheck(data.groups);
                 }).
                 error(function () {
                     $rootScope.auth.currentUser = null;
                     $rootScope.auth.isAuthenticated = false;
+                    $rootScope.auth.projectNames  = [];
                 });
         }
 
+        function doSubmitterCheck(groups){
+            var projectNames = []
+            for(var index=0;index<groups.length; index++){
+                if(groups[index].indexOf('-submitters')>=0){
+                    projectNames.push(groups[index].split('-')[2])
+                }
+            }
+            return projectNames;
+        }
+
         $rootScope.auth.doSignCheck();
     }
 
index 2d7399f..45162dc 100644 (file)
@@ -46,6 +46,7 @@
         <script src="testapi-ui/components/profile/profileController.js"></script>
         <script src="testapi-ui/components/auth-failure/authFailureController.js"></script>
         <script src="testapi-ui/components/logout/logoutController.js"></script>
+        <script src="testapi-ui/components/projects/projectsController.js"></script>
 
         <!-- Filters -->
         <script src="testapi-ui/shared/filters.js"></script>
index f5b2414..4b3f8dd 100644 (file)
@@ -19,6 +19,7 @@ TestAPI
             <li ng-class="{ active: header.isActive('/about')}"><a ui-sref="about">About</a></li>
             <li ng-class="{ active: header.isActive('/pods')}"><a ui-sref="pods">Pods</a></li>
             <li ng-class="{ active: header.isActive('/community_results')}"><a ui-sref="communityResults">Community Results</a></li>
+            <li ng-class="{ active: header.isActive('/projects')}"><a ui-sref="projects">Projects</a></li>
             <!--
             <li ng-class="{ active: header.isCatalogActive('public')}" class="dropdown" uib-dropdown>
                 <a role="button" class="dropdown-toggle" uib-dropdown-toggle>
index 667578f..432a6c1 100644 (file)
@@ -21,7 +21,7 @@ from opnfv_testapi.db import api as dbapi
 def is_authorized(method):
     @functools.wraps(method)
     def wrapper(self, *args, **kwargs):
-        if CONF.api_authenticate and self.table in ['pods']:
+        if CONF.api_authenticate and self.table in ['pods', 'projects']:
             testapi_id = self.get_secure_cookie(constants.TESTAPI_ID)
             if not testapi_id:
                 raises.Unauthorized(message.not_login())
@@ -29,6 +29,12 @@ def is_authorized(method):
             if not user_info:
                 raises.Unauthorized(message.not_lfid())
             kwargs['owner'] = testapi_id
+            if self.table in ['projects']:
+                query = kwargs.get('query')
+                query_data = query()
+                group = "opnfv-gerrit-" + query_data['name'] + "-submitters"
+                if group not in user_info['groups']:
+                    raises.Unauthorized(message.no_permission())
         ret = yield gen.coroutine(method)(self, *args, **kwargs)
         raise gen.Return(ret)
     return wrapper
index 3e14f72..b92b7f0 100644 (file)
@@ -60,3 +60,7 @@ def no_update():
 
 def must_int(name):
     return '{} must be int'.format(name)
+
+
+def no_permission():
+    return 'You do not have permission to perform this action'
index a8ee3db..df7f520 100644 (file)
@@ -75,10 +75,10 @@ class GenericApiHandler(web.RequestHandler):
 
     @web.asynchronous
     @gen.coroutine
-    @check.is_authorized
     @check.valid_token
     @check.no_body
     @check.miss_fields
+    @check.is_authorized
     @check.values_check
     @check.carriers_exist
     @check.new_not_exists
index 66a57f2..8cf7467 100644 (file)
@@ -12,7 +12,9 @@ describe('testing the Pods page for anonymous user', function () {
                        },
                        response: {
                                data: {
-                                       pods: [{role: "community-ci", name: "test", owner: "testUser", details: "DemoDetails", mode: "metal", _id: "59f02f099a07c84bfc5c7aed", creation_date: "2017-10-25 11:58:25.926168"}]
+                                       pods: [{role: "community-ci", name: "test", owner: "testUser",
+                                       details: "DemoDetails", mode: "metal", _id: "59f02f099a07c84bfc5c7aed",
+                                       creation_date: "2017-10-25 11:58:25.926168"}]
                                }
                        }
                  }]);
@@ -73,7 +75,8 @@ describe('testing the Pods page for anonymous user', function () {
                mock.teardown();
                var buttonFilter = element(by.buttonText('Filter'));
                buttonFilter.click().then(function(){
-                       expect(element(by.css('.alert.alert-danger.ng-binding.ng-scope')).isDisplayed()).toBe(true);
+                       expect(element(by.css('.alert.alert-danger.ng-binding.ng-scope'))
+                       .isDisplayed()).toBe(true);
                });
        });
 
@@ -116,7 +119,9 @@ describe('testing the Pods page for authorized user', function () {
                                },
                                response: {
                                        data: {
-                                               "fullname": "Test User", "_id": "79f82eey9a00c84bfhc7aed", "user": "testUser", "groups": ["opnfv-testapi-users"], "email": "testuser@test.com"
+                                               "fullname": "Test User", "_id": "79f82eey9a00c84bfhc7aed",
+                                               "user": "testUser", "groups": ["opnfv-testapi-users",
+                                               "opnfv-gerrit-functest-submitters"], "email": "testuser@test.com"
                                        }
                                }
                        }
@@ -136,7 +141,8 @@ describe('testing the Pods page for authorized user', function () {
                details.sendKeys('DemoDetails');
                var buttonCreate = element(by.buttonText('Create'));
                buttonCreate.click().then(function(){
-                       expect(element(by.css('.alert.alert-danger.ng-binding.ng-scope')).isDisplayed()).toBe(false);
+                       expect(element(by.css('.alert.alert-danger.ng-binding.ng-scope'))
+                       .isDisplayed()).toBe(false);
                });
        });
 
@@ -170,7 +176,9 @@ describe('testing the Pods page for authorized user', function () {
                                },
                                response: {
                                        data: {
-                                               "fullname": "Test User", "_id": "79f82eey9a00c84bfhc7aed", "user": "testUser", "groups": ["opnfv-testapi-users"], "email": "testuser@test.com"
+                                               "fullname": "Test User", "_id": "79f82eey9a00c84bfhc7aed",
+                                               "user": "testUser", "groups": ["opnfv-testapi-users"],
+                                               "email": "testuser@test.com"
                                        }
                                }
                        }
@@ -182,7 +190,8 @@ describe('testing the Pods page for authorized user', function () {
                details.sendKeys('DemoDetails');
                var buttonCreate = element(by.buttonText('Create'));
                buttonCreate.click().then(function(){
-                       expect(element(by.css('.alert.alert-danger.ng-binding.ng-scope')).isDisplayed()).toBe(true);
+                       expect(element(by.css('.alert.alert-danger.ng-binding.ng-scope'))
+                       .isDisplayed()).toBe(true);
                });
-       })
+       });
 });
\ No newline at end of file
diff --git a/testapi/opnfv_testapi/tests/UI/e2e/projectControllerSpec.js b/testapi/opnfv_testapi/tests/UI/e2e/projectControllerSpec.js
new file mode 100644 (file)
index 0000000..921625d
--- /dev/null
@@ -0,0 +1,224 @@
+'use strict';
+
+var mock = require('protractor-http-mock');
+var baseURL = "http://localhost:8000"
+
+describe('testing the Project Link for anonymous user', function () {
+
+       it( 'should not show the Project Link for anonymous user', function() {
+        mock.teardown();
+               browser.get(baseURL);
+        var projectslink = element(by.linkText('Projects'));
+        expect(projectslink.isPresent()).toBe(true);
+    });
+
+    it( 'navigate anonymous user to project page', function() {
+        browser.get(baseURL+'#/projects');
+        var EC = browser.ExpectedConditions;
+        browser.wait(EC.urlContains(baseURL+ '/#/projects'), 10000);
+    });
+
+    it('create button is not visible for anonymous user ', function () {
+        browser.get(baseURL+'#/projects');
+        var buttonCreate = element(by.buttonText('Create'));
+        expect(buttonCreate.isDisplayed()).toBeFalsy();
+    });
+
+});
+
+describe('testing the Project Link for user who is not in submitter group', function () {
+        beforeEach(function(){
+            mock([
+                {
+                    request: {
+                    path: '/api/v1/profile',
+                    method: 'GET'
+                    },
+                    response: {
+                        data: {
+                            "fullname": "Test User", "_id": "79f82eey9a00c84bfhc7aed",
+                            "user": "testUser", "groups": ["opnfv-testapi-users"],
+                            "email": "testuser@test.com"
+                        }
+                    }
+                }
+            ]);
+        });
+
+        it( 'should show the Project Link for user', function() {
+            browser.get(baseURL);
+            var projectslink = element(by.linkText('Projects'));
+            expect(projectslink.isPresent()).toBe(true);
+        });
+
+        it( 'should navigate the user to the Project page', function() {
+            browser.get(baseURL);
+            var projectslink = element(by.linkText('Projects')).click();
+            var EC = browser.ExpectedConditions;
+            browser.wait(EC.urlContains(baseURL+ '/#/projects'), 10000);
+        });
+
+        it('create button is not visible for user', function () {
+            browser.get(baseURL+'#/projects');
+            var buttonCreate = element(by.buttonText('Create'));
+            expect(buttonCreate.isDisplayed()).toBeFalsy();
+        });
+})
+
+describe('testing the Project Link for user who is in submitter group', function () {
+    beforeEach(function(){
+        mock([
+            {
+                request: {
+                    path: '/api/v1/profile',
+                    method: 'GET'
+                },
+                response: {
+                    data: {
+                        "fullname": "Test User", "_id": "79f82eey9a00c84bfhc7aed",
+                        "user": "testUser", "groups": ["opnfv-testapi-users",
+                        "opnfv-gerrit-testProject1-submitters",
+                        "opnfv-gerrit-testProject2-submitters" ],
+                        "email": "testuser@test.com"
+                    }
+                }
+            },
+            {
+                request: {
+                    path: '/api/v1/projects',
+                    method: 'POST'
+                },
+                response: {
+                    data: {
+                        href: baseURL+"/api/v1/projects/testProject1"
+                    }
+                }
+            },
+            {
+                request: {
+                    path: '/api/v1/projects',
+                    method: 'POST',
+                    data: {
+                        name: 'testProject2',
+                        description : 'demoDescription',
+                    }
+                },
+                response: {
+                    status : 403
+                }
+            },
+            {
+                request: {
+                    path: '/api/v1/projects',
+                    method: 'POST',
+                    data: {
+                        name: 'testProject3',
+                        description : 'demoDescription',
+                    }
+                },
+                response: {
+                    status : 403,
+                    data : 'You do not have permission to perform this action'
+                }
+            }
+        ]);
+    });
+
+    it( 'should show the Project Link for user', function() {
+        browser.get(baseURL);
+        var projectslink = element(by.linkText('Projects'));
+        expect(projectslink.isPresent()).toBe(true);
+    });
+
+    it( 'should navigate the user to the Project page', function() {
+        browser.get(baseURL);
+        var projectslink = element(by.linkText('Projects')).click();
+        var EC = browser.ExpectedConditions;
+        browser.wait(EC.urlContains(baseURL+ '/#/projects'), 10000);
+    });
+
+    it('create button is visible for user', function () {
+        browser.get(baseURL+'#/projects');
+        var buttonCreate = element(by.buttonText('Create'));
+        expect(buttonCreate.isDisplayed()).toBe(true);
+    });
+
+       it('Show error when user click the create button with a empty name', function () {
+               browser.get(baseURL+ '/#/projects');
+               var description = element(by.model('ctrl.description'));
+               description.sendKeys('DemoDescription');
+               var buttonCreate = element(by.buttonText('Create'));
+               buttonCreate.click();
+        expect(element(by.cssContainingText(".alert","Name is missing."))
+        .isDisplayed()).toBe(true);
+       });
+
+       it('Show error when user click the create button with an already existing name', function () {
+               browser.get(baseURL+ '/#/projects');
+               var name = element(by.model('ctrl.name'));
+               var details = element(by.model('ctrl.description'));
+               name.sendKeys('testProject2');
+               details.sendKeys('demoDescription');
+               var buttonCreate = element(by.buttonText('Create'));
+               buttonCreate.click();
+        expect(element(by.cssContainingText(".alert",
+        "Error creating the new Project from server:undefined"))
+        .isDisplayed()).toBe(true);
+    });
+
+    it('Show error when user try to create a project which he is not belonged to ', function () {
+               browser.get(baseURL+ '/#/projects');
+               var name = element(by.model('ctrl.name'));
+               var details = element(by.model('ctrl.description'));
+               name.sendKeys('testProject3');
+               details.sendKeys('demoDescription');
+               var buttonCreate = element(by.buttonText('Create'));
+        buttonCreate.click();
+        expect(element(by.cssContainingText(".alert",
+        'Error creating the new Project from server:"You do not have permission to perform this action"')).isDisplayed())
+        .toBe(true);
+    });
+
+       it('Do not show error if input is acceptable', function () {
+               var name = element(by.model('ctrl.name'));
+               var details = element(by.model('ctrl.description'));
+               name.sendKeys('testProject1');
+               details.sendKeys('demoDescription');
+               var buttonCreate = element(by.buttonText('Create'));
+               buttonCreate.click().then(function(){
+            expect(element(by.cssContainingText(".alert",
+            "Create Success"))
+            .isDisplayed()).toBe(true);
+               });
+    });
+
+       it('If backend is not responding then show error when user click the create button',function(){
+               mock.teardown();
+               mock([
+            {
+                request: {
+                    path: '/api/v1/profile',
+                    method: 'GET'
+                },
+                response: {
+                    data: {
+                        "fullname": "Test User", "_id": "79f82eey9a00c84bfhc7aed",
+                        "user": "testUser", "groups": ["opnfv-testapi-users",
+                        "opnfv-gerrit-testProject1-submitters",
+                        "opnfv-gerrit-testProject2-submitters" ],
+                        "email": "testuser@test.com"
+                    }
+                }
+            }
+               ]);
+               browser.get(baseURL+ '/#/projects');
+               var name = element(by.model('ctrl.name'));
+               var details = element(by.model('ctrl.description'));
+               name.sendKeys('testProject1');
+               details.sendKeys('demoDescription');
+               var buttonCreate = element(by.buttonText('Create'));
+               buttonCreate.click().then(function(){
+                       expect(element(by.css(".alert.alert-danger.ng-binding.ng-scope")).isDisplayed()).toBe(true);
+        });
+       });
+})
index b7fabb9..eb147cc 100644 (file)
@@ -16,6 +16,7 @@ from tornado import testing
 
 from opnfv_testapi.models import base_models
 from opnfv_testapi.models import pod_models
+from opnfv_testapi.models import project_models
 from opnfv_testapi.tests.unit import fake_pymongo
 
 
@@ -43,6 +44,12 @@ class TestBase(testing.AsyncHTTPTestCase):
                                     _id=str(ObjectId()),
                                     owner='ValidUser',
                                     create_date=str(datetime.now()))
+        self.project_e = project_models.Project(
+            name='functest',
+            description='functest test',
+            _id=str(ObjectId()),
+            create_date=str(datetime.now()))
+
         self.req_d = None
         self.req_e = None
         self.addCleanup(self._clear)
@@ -53,6 +60,7 @@ class TestBase(testing.AsyncHTTPTestCase):
                                    'groups': [
                                        'opnfv-testapi-users',
                                        'opnfv-gerrit-functest-submitters',
+                                       'opnfv-gerrit-qtip-submitters',
                                        'opnfv-gerrit-qtip-contributors']
                                    })
 
index 939cc0d..2873ab0 100644 (file)
@@ -1,3 +1,11 @@
+##############################################################################
+# Copyright (c) 2016 ZTE Corporation
+# feng.xiaowei@zte.com.cn
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
 import httplib
 import unittest
 
@@ -10,10 +18,10 @@ from opnfv_testapi.tests.unit.handlers import test_base as base
 class TestProjectBase(base.TestBase):
     def setUp(self):
         super(TestProjectBase, self).setUp()
-        self.req_d = project_models.ProjectCreateRequest('vping',
-                                                         'vping-ssh test')
-        self.req_e = project_models.ProjectCreateRequest('doctor',
-                                                         'doctor test')
+        self.req_d = project_models.ProjectCreateRequest('qtip',
+                                                         'qtip-ssh test')
+        self.req_e = project_models.ProjectCreateRequest('functest',
+                                                         'functest test')
         self.get_res = project_models.Project
         self.list_res = project_models.Projects
         self.update_res = project_models.Project
@@ -29,22 +37,32 @@ class TestProjectBase(base.TestBase):
 
 
 class TestProjectCreate(TestProjectBase):
+
+    @executor.create(httplib.BAD_REQUEST, message.not_login())
+    def test_notlogin(self):
+        return self.req_d
+
+    @executor.mock_valid_lfid()
     @executor.create(httplib.BAD_REQUEST, message.no_body())
     def test_withoutBody(self):
         return None
 
+    @executor.mock_valid_lfid()
     @executor.create(httplib.BAD_REQUEST, message.missing('name'))
     def test_emptyName(self):
         return project_models.ProjectCreateRequest('')
 
+    @executor.mock_valid_lfid()
     @executor.create(httplib.BAD_REQUEST, message.missing('name'))
     def test_noneName(self):
         return project_models.ProjectCreateRequest(None)
 
+    @executor.mock_valid_lfid()
     @executor.create(httplib.OK, 'assert_create_body')
     def test_success(self):
         return self.req_d
 
+    @executor.mock_valid_lfid()
     @executor.create(httplib.FORBIDDEN, message.exist_base)
     def test_alreadyExist(self):
         self.create_d()
@@ -52,6 +70,8 @@ class TestProjectCreate(TestProjectBase):
 
 
 class TestProjectGet(TestProjectBase):
+
+    @executor.mock_valid_lfid()
     def setUp(self):
         super(TestProjectGet, self).setUp()
         self.create_d()
@@ -78,6 +98,7 @@ class TestProjectGet(TestProjectBase):
 
 
 class TestProjectUpdate(TestProjectBase):
+    @executor.mock_valid_lfid()
     def setUp(self):
         super(TestProjectUpdate, self).setUp()
         _, d_body = self.create_d()
@@ -115,6 +136,7 @@ class TestProjectUpdate(TestProjectBase):
 
 
 class TestProjectDelete(TestProjectBase):
+    @executor.mock_valid_lfid()
     def setUp(self):
         super(TestProjectDelete, self).setUp()
         self.create_d()
index b9f9ede..bd482a6 100644 (file)
@@ -15,7 +15,6 @@ import urllib
 import unittest
 
 from opnfv_testapi.common import message
-from opnfv_testapi.models import project_models
 from opnfv_testapi.models import result_models
 from opnfv_testapi.models import testcase_models
 from opnfv_testapi.tests.unit import executor
@@ -86,15 +85,12 @@ class TestResultBase(base.TestBase):
         self.list_res = result_models.TestResults
         self.update_res = result_models.TestResult
         self.basePath = '/api/v1/results'
-        self.req_project = project_models.ProjectCreateRequest(
-            self.project,
-            'vping test')
+        fake_pymongo.projects.insert(self.project_e.format())
         self.req_testcase = testcase_models.TestcaseCreateRequest(
             self.case,
             '/cases/vping',
             'vping-ssh test')
         fake_pymongo.pods.insert(self.pod_d.format())
-        self.create_help('/api/v1/projects', self.req_project)
         self.create_help('/api/v1/projects/%s/cases',
                          self.req_testcase,
                          self.project)
index e4c668e..d5e32e3 100644 (file)
@@ -11,15 +11,16 @@ import httplib
 import unittest
 
 from opnfv_testapi.common import message
-from opnfv_testapi.models import project_models
 from opnfv_testapi.models import testcase_models
 from opnfv_testapi.tests.unit import executor
+from opnfv_testapi.tests.unit import fake_pymongo
 from opnfv_testapi.tests.unit.handlers import test_base as base
 
 
 class TestCaseBase(base.TestBase):
     def setUp(self):
         super(TestCaseBase, self).setUp()
+        self.project = 'functest'
         self.req_d = testcase_models.TestcaseCreateRequest('vping_1',
                                                            '/cases/vping_1',
                                                            'vping-ssh test')
@@ -36,7 +37,7 @@ class TestCaseBase(base.TestBase):
         self.list_res = testcase_models.Testcases
         self.update_res = testcase_models.Testcase
         self.basePath = '/api/v1/projects/%s/cases'
-        self.create_project()
+        fake_pymongo.projects.insert(self.project_e.format())
 
     def assert_body(self, case, req=None):
         if not req:
@@ -56,12 +57,6 @@ class TestCaseBase(base.TestBase):
         self.assertIsNotNone(new._id)
         self.assertIsNotNone(new.creation_date)
 
-    def create_project(self):
-        req_p = project_models.ProjectCreateRequest('functest',
-                                                    'vping-ssh test')
-        self.create_help('/api/v1/projects', req_p)
-        self.project = req_p.name
-
     def create_d(self):
         return super(TestCaseBase, self).create_d(self.project)
 
diff --git a/testapi/opnfv_testapi/ui/projects/projects.html b/testapi/opnfv_testapi/ui/projects/projects.html
new file mode 100644 (file)
index 0000000..62a968b
--- /dev/null
@@ -0,0 +1,38 @@
+<h3>Projects</h3>
+<p> </p>
+
+<div class="row" style="margin-bottom:24px;"></div>
+<div class="project-create" ng-class="{ 'hidden': ! (auth.projectNames.length>0) }">
+    <h4>Create</h4>
+    <div class="row">
+        <div ng-repeat="require in ctrl.createRequirements">
+            <div class="create-project" style="margin-left:24px;">
+                <p class="input-group">
+                    <label for="cpid">{{require.label|capitalize}}: </label>
+                    <a ng-if="require.type == 'text'">
+                        <input type="text" dynamic-model="'ctrl.' + require.label"/>
+                    </a>
+                    <a ng-if="require.type == 'textarea'">
+                        <textarea rows="2" cols="50" dynamic-model="'ctrl.' + require.label">
+                        </textarea>
+                    </a>
+                </p>
+            </div>
+        </div>
+
+        <div class="col-md-3" style="margin-top:12px; margin-left:8px;">
+            <button type="submit" class="btn btn-primary" ng-click="ctrl.create()">Create</button>
+        </div>
+    </div>
+</div>
+
+<div ng-show="ctrl.showError" class="alert alert-danger" role="alert">
+    <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
+    <span class="sr-only">Error:</span>
+    {{ctrl.error}}
+</div>
+
+<div ng-show="ctrl.showSuccess" class="alert alert-success" role="alert">
+    <span class="glyphicon glyphicon-ok" aria-hidden="true"></span>
+    Create Success
+</div>
\ No newline at end of file
diff --git a/testapi/opnfv_testapi/ui/projects/projectsController.js b/testapi/opnfv_testapi/ui/projects/projectsController.js
new file mode 100644 (file)
index 0000000..d2640b6
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * 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.
+ */
+
+(function () {
+    'use strict';
+
+    angular
+        .module('testapiApp')
+        .controller('ProjectsController', ProjectsController);
+
+        ProjectsController.$inject = [
+        '$scope', '$http', '$filter', '$state', 'testapiApiUrl','raiseAlert'
+    ];
+
+    /**
+     * TestAPI Project Controller
+     * This controller is for the '/projects' page where a user can browse
+     * through projects declared in TestAPI.
+     */
+    function ProjectsController($scope, $http, $filter, $state, testapiApiUrl,
+        raiseAlert) {
+        var ctrl = this;
+        ctrl.url = testapiApiUrl + '/projects';
+        ctrl.create = create;
+
+        ctrl.createRequirements = [
+            {label: 'name', type: 'text', required: true},
+            {label: 'description', type: 'textarea', required: false}
+        ];
+
+        ctrl.name = '';
+        ctrl.details = '';
+
+        /**
+         * This will contact the TestAPI to create a new project.
+         */
+        function create() {
+            ctrl.showError = false;
+            ctrl.showSuccess = false;
+            if(ctrl.name != ""){
+                var projects_url = ctrl.url;
+                var body = {
+                    name: ctrl.name,
+                    description: ctrl.description
+                };
+                ctrl.projectsRequest =
+                    $http.post(projects_url, body).success(function (data){
+                        ctrl.showSuccess = true ;
+                    })
+                    .error(function (data) {
+                        ctrl.showError = true;
+                        ctrl.error = 'Error creating the new Project from server:' + angular.toJson(data);
+                    });
+                ctrl.name = "";
+                ctrl.description="";
+            }
+            else{
+                ctrl.showError = true;
+                ctrl.error = 'Name is missing.'
+            }
+        }
+    }
+})();