diff options
22 files changed, 1028 insertions, 154 deletions
diff --git a/reporting/reporting/bottlenecks/reporting-status.py b/reporting/reporting/bottlenecks/reporting-status.py index 225227a..8966d06 100644 --- a/reporting/reporting/bottlenecks/reporting-status.py +++ b/reporting/reporting/bottlenecks/reporting-status.py @@ -37,14 +37,10 @@ for version in VERSIONS: # For all the installers for installer in INSTALLERS: # get scenarios results data - if version != 'master': - new_version = "stable/{}".format(version) - else: - new_version = version scenario_results = rp_utils.getScenarios("bottlenecks", "posca_factor_ping", installer, - new_version) + version) LOGGER.info("scenario_results: %s", scenario_results) scenario_stats = rp_utils.getScenarioStats(scenario_results) diff --git a/testapi/3rd_party/static/testapi-ui/assets/css/style.css b/testapi/3rd_party/static/testapi-ui/assets/css/style.css index 222911c..2b20d6b 100644 --- a/testapi/3rd_party/static/testapi-ui/assets/css/style.css +++ b/testapi/3rd_party/static/testapi-ui/assets/css/style.css @@ -303,7 +303,7 @@ json-tree .leaf-value{ border-radius: 10px; padding: 16px; position: fixed; - z-index: 1; + z-index: 9999; left: 50%; bottom: 30px; } diff --git a/testapi/docker/Dockerfile b/testapi/docker/Dockerfile index bbf12fc..86999d9 100644 --- a/testapi/docker/Dockerfile +++ b/testapi/docker/Dockerfile @@ -40,7 +40,7 @@ python-pip \ crudini \ --no-install-recommends -RUN pip install --upgrade pip requests +RUN pip install --upgrade requests RUN git config --global http.sslVerify false RUN git clone https://gerrit.opnfv.org/gerrit/releng-testresults /home/releng-testresults diff --git a/testapi/docs/developer/devguide/images/CAS-sequence.jpg b/testapi/docs/developer/devguide/images/CAS-sequence.jpg Binary files differnew file mode 100644 index 0000000..a624871 --- /dev/null +++ b/testapi/docs/developer/devguide/images/CAS-sequence.jpg diff --git a/testapi/docs/developer/devguide/testapi-client-import.rst b/testapi/docs/developer/devguide/testapi-client-import.rst new file mode 100644 index 0000000..69cb6ad --- /dev/null +++ b/testapi/docs/developer/devguide/testapi-client-import.rst @@ -0,0 +1,65 @@ +.. This work is licensed under a Creative Commons Attribution 4.0 International License. +.. http://creativecommons.org/licenses/by/4.0 +.. (c) 2017 ZTE Corp. + +===================== +TestAPI client import +===================== + +**Python module to communicate with the TestAPI Server** + +This project aims to provide a python module which can +communicate with the TestAPI Server. The user can use this client +to fetch/post/modify the resources on the TestAPI Server. + +Usage +----- + +Pod +^^^ + +GET +""" + +User will get the json Pod objects with the get request. + +.. code-block:: shell + + from testapiclient.client import pods + + pod_client = pods.PodsClient() + pod_client.get() + +User can use search parameters to get pods + +.. code-block:: shell + + from testapiclient.client import pods + + pod_client = pods.PodsClient() + pod_client.get(name='pod1') + +GET ONE +""""""" + +User will get the json Pod objects with the get request. + +.. code-block:: shell + + from testapiclient.client import pods + + pod_client = pods.PodsClient() + pod_client.get_one('name') + +CREATE +"""""" +User has to authenticate before running the function. + +.. code-block:: shell + + from testapiclient.client import pods + + pod_client = pods.PodsClient(user='test', password='pass') + pod_client.create({'name': 'test-api', 'mode':'metal', + 'role':'community_ci', 'details':''} + diff --git a/testapi/docs/developer/devguide/testapi-client.rst b/testapi/docs/developer/devguide/testapi-client.rst index ab4c8e8..ea08ad9 100644 --- a/testapi/docs/developer/devguide/testapi-client.rst +++ b/testapi/docs/developer/devguide/testapi-client.rst @@ -6,5 +6,427 @@ TestAPI client ============== -.. toctree:: - :maxdepth: 2 +TestAPIClient is a command-line client for TestAPI that +brings the command set for pod, project, testcase, results, +deploy result and scenario together in a single shell with a uniform command +structure. + + +Installation +------------ + +User can install this client from the source. + +.. code-block:: shell + + python install testapi/testapi-client/setup.py install + +After the installation, user has to set the environment variables + +.. code-block:: shell + + source testapi/testapi-client/etc/client.creds + + +Authentication Process +---------------------- + +User needs to provide the username and the password with the testapi +command. + +.. code-block:: shell + + $ testapi -u [username] -p [password] + (testapi) pod create + + +or + +.. code-block:: shell + + testapi pode create -u [username] -p [password] [pod-schema] + +First one, user can continue the progress after the authentication. +cli will create a new session to handle the request. + +In second one, cli won't create a session. one time command. + +Token is also used for the authorization purpose. User has to obtain the +valid token from the TestAPI comminity and set it in the following file +: **testapi/testapi-client/etc/client.creds** + +.. code-block:: shell + + source testapi/testapi-client/etc/client.creds + + +Command Structure +----------------- + +TestAPIClient follows a common command Structure. + +.. code-block:: shell + + testapi [resource-name] [function] [-u] [username] [-p] [password] [command-arguments] + +.. NOTE:: + resource-name : include first order parent name and resource name. + + example: + scenario installer, scenario version, scenario project, scenario custom, + scenario trustindicator, scenario score, pod , project, testcase, result, + deployresult and scenario. + +.. NOTE:: + -u and -p are optional commands. The user can decide on them. + +There are many arguments for each commands. User can get them using help command in the +cli. + +.. code-block:: shell + + pod create --help/-h + +Pod +^^^ + +Create +"""""" + +Authentication required + +.. code-block:: shell + + testapi pod create [-u] [username] [-p] [password] [pod-schema] + +or + +.. code-block:: shell + + $ testapi [-u] [username] [-p] [password] + (testapi) pod create [pod-schema] + +.. NOTE:: + pod-schema - '{"role": "", "name": "", "details": "", "mode": ""}' + +Get +""" + +Authentication is not required + +.. code-block:: shell + + testapi pod get [-name] [key-word] + +.. NOTE:: + -name is not mandatory. The user can use the -name option to reduce the + search result otherwise they will get the details about all pods. + +Get one +""""""" + +Authentication is not required + +.. code-block:: shell + + testapi pod getone [name-keyword] + +.. NOTE:: + name-keyword is mandatory. + + +Delete +"""""" + +Authentication is required + +.. code-block:: shell + + testapi pod delete [-u] [username] [-p] [password] [pod-name] + +or + +.. code-block:: shell + + $ testapi [-u] [username] [-p] [password] + (testapi) pod delete [pod-name] + +.. NOTE:: + pod-name is mandatory. + + +Project +^^^^^^^ + +Create +"""""" + +Authentication required + +.. code-block:: shell + + testapi project create [-u] [username] [-p] [password] [project-schema] + +or + +.. code-block:: shell + + $ testapi [-u] [username] [-p] [password] + (testapi) project create [project-schema] + +.. NOTE:: + project-schema - '{"description": "", "name": ""}' + + +Get +""" + +Authentication is not required + +.. code-block:: shell + + testapi project get [-name] [key-word] + +.. NOTE:: + -name is not mandatory. The user can use the -name option to reduce the + search result otherwise they will get the details about all projects. + +Get one +""""""" + +Authentication is not required + +.. code-block:: shell + + testapi project getone [name-keyword] + +.. NOTE:: + name-keyword is mandatory. + +Update +"""""" + +Authentication required + +.. code-block:: shell + + testapi project put [-u] [username] [-p] [password] [project-name] + [project-schema] + +or + +.. code-block:: shell + + $ testapi [-u] [username] [-p] [password] + (testapi) project put [project-name] [project-schema] + +.. NOTE:: + project-schema - '{"name": "", "description": ""}' + + +Testcase +^^^^^^^^ + +Create +"""""" + +Authentication required + +.. code-block:: shell + + testapi testcase create [-u] [username] [-p] [password] + [--project-name] [testcase-schema] + +or + +.. code-block:: shell + + $ testapi [-u] [username] [-p] [password] + (testapi) testcase create [--project-name] [testcase-schema] + +.. NOTE:: + testcase-schema - '{"run": "", "name": "", "ci_loop": "", "tags": "", + "url": "", "catalog_description": "", "tier": "", + "dependencies": "", "version": "", "criteria": "", + "domains": "", "trust": "", "blocking": "", + "description": ""}' + + +Get +""" + +Authentication is not required + +.. code-block:: shell + + testapi testcase get [--project-name] + +.. NOTE:: + --project-name is mandatory. + +Get one +""""""" + +Authentication is not required + +.. code-block:: shell + + testapi testcase getone [--project-name] [name] + +.. NOTE:: + name and project-name are mandatory. + +Update +"""""" + +Authentication required + +.. code-block:: shell + + testapi testcase put [-u] [username] [-p] [password] [--project-name] + [name] [testcase-schema] + +or + +.. code-block:: shell + + $ testapi [-u] [username] [-p] [password] + (testapi) testcase put [--project-name] [name] [testcase-schema] + +.. NOTE:: + testcase-schema - '{"run": "", "name": "", "ci_loop": "", "tags": "", + "url": "", "catalog_description": "", "tier": "", + "dependencies": "", "version": "", "criteria": "", + "domains": "", "trust": "", "blocking": "", + "description": ""} + + +Result +^^^^^^^ + +Create +"""""" + +Token is required. Set token as an environment variable. + +.. code-block:: shell + + testapi result create [-u] [username] [-p] [password] [result-schema] + +or + +.. code-block:: shell + + $ testapi [-u] [username] [-p] [password] + (testapi) result create [result-schema] + +.. NOTE:: + result-schema - '{"project_name": "", "scenario": "", "stop_date": "", + "case_name": "", "build_tag": "", "version": "", + "pod_name": "", "criteria": "", "installer": "", + "start_date": "", "details": ""}' + + +Get +""" + +Authentication is not required + +.. code-block:: shell + + testapi result get [-cli-arguments] [arguments-value] + +.. NOTE:: + List of commandline arguments + + * -case : Search results using tesetcase + * -build-tag : Search results using build tag + * -from : Search results using from date + * -last : Search results using last date + * -scenario : Search results using scenario + * -period : Search results using period + * -project : Search results using project + * -to : Search results using to + * ---version : Search results using version + * -criteria : Search results using criteria + * -installer : Search results using installer + * -pod : Search results using pod + * -page : Search results using page + +Get one +""""""" + +Token is required. Set token as an environment variable. + +.. code-block:: shell + + testapi result getone [result_id] + +.. NOTE:: + result_id is mandatory. + +Deploy Result +^^^^^^^^^^^^^ + +Create +"""""" + +Token is required. Set token as an environment variable. + + +.. code-block:: shell + + testapi deployresult [-u] [username] [-p] [password] + [--project-name] [deployresult-schema] + +or + +.. code-block:: shell + + $ testapi [-u] [username] [-p] [password] + (testapi) deployresult create [deployresult-schema] + +.. NOTE:: + deployresult-schema - '{"run": "", "name": "", "ci_loop": "", "tags": "", + "url": "", "catalog_description": "", "tier": "", + "dependencies": "", "version": "", "criteria": "", + "domains": "", "trust": "", "blocking": "", + "description": ""}' + + +Get +""" + +Authentication is not required + +.. code-block:: shell + + testapi deployresult get [-cli-arguments] [arguments-value] + +.. NOTE:: + List of command line arguments + + * -job-name : Search results using job + * -build-id : Search results using build id + * -from : Search results using from date + * -last : Search results using last date + * -scenario : Search results using scenario + * -period : Search results using period + * -to : Search results using to + * ---version : Search results using version + * -criteria : Search results using criteria + * -installer : Search results using installer + * -pod-name : Search results using pod + * -page : Search results using page + +Get one +""""""" + +Authentication is not required + +.. code-block:: shell + + testapi deployresult getone [deployresult_id] + +.. NOTE:: + deployresult_id is mandatory. diff --git a/testapi/docs/developer/devguide/web-portal.rst b/testapi/docs/developer/devguide/web-portal.rst index 62b2f17..8c4bc6c 100644 --- a/testapi/docs/developer/devguide/web-portal.rst +++ b/testapi/docs/developer/devguide/web-portal.rst @@ -6,5 +6,115 @@ Web portal ========== -.. toctree:: - :maxdepth: 2 +**Web-portal of OPNFV Testapi**: + +This project aims to provide the web interface for the Testapi framework. It uses the Restful APIs +of the testapi framework to provide front-end functionalities. + +If you are interested in how TestAPI looks like, please visit OPNFV's official `TestAPI Server`__ + +.. __: http://testresults.opnfv.org/test + +Pre-requsites +============= + +In the web portal, we are using AngularJS(1.3.15) as the frontend framework with Bootstrap(v3) CSS. + +Running locally +=============== + +Installation +^^^^^^^^^^^^ + +Web portal will be installed with the testapi framework. No extra installation. + +.. code-block:: shell + + python setup.py install + +Start Server +^^^^^^^^^^^^ + +.. code-block:: shell + + *opnfv-testapi [--config-file <config.ini>]* + +If --config-file is provided, the specified configuration file will be employed +when starting the server, or else /etc/opnfv_testapi/config.ini will be utilized +by default. + +After executing the command successfully, a TestAPI server will be started on +port 8000, to visit web portal, please access http://hostname:8000 + +Test +=============== + +There are few external requirements for the testing. +They are + +1. npm : node package manager + you can get the installation package for nodejs from the official `website`__ + + .. __: https://nodejs.org/en/ + +2. grunt cli : Automation tool + +.. code-block:: shell + + npm install -g grunt-cli + +After installing global dependencies, you have to install the required local node modules. + +.. code-block:: shell + + npm install + +**Running tests** + +.. code-block:: shell + + grunt e2e + +Authentication +============== + +The web portal is using Linux identity server as the Central Authentication Service. The following +diagram will explain the authentication process. + +.. image:: /images/CAS-sequence.jpg + :width: 600 + :alt: Workflow of the Athentication + +When a new user initially logs into an application they won't have established a +session with the application. Instead of displaying a login form asking for the username and +password, the application (via the CAS Client) will redirect the browser to the linux foundation +login page. Linux foundation identity server then authenticates the user. If the authentication +fails, the Linux foundation login page is displayed again with an error message. So until +authentication succeeds, the user will not be returned to the application. + +Authorization +============= + +TestAPI has 3 level authorization layer. They are + +**Public** + +The public can view the resources(pod, project, testcase, result, deploy result, scenario). +They do not have the access to create, delete or modify the resources. + +**User - Contributors** + +Contributors level user can view all the resources(pod, project, testcase, result, deploy result, +scenario). They can create/delete/modify pod and scenario. + +They do not have the access to create project or testcase. + +**User - Submitter** + +Submitter level user can view all the resources(pod, project, testcase, result, deploy result, +scenario). They can create/delete/modify pod and scenario. + +If user want to create/modify/delete a project or testcase then user has to be in the Submitter +group for that specific project. + +Currently, we can't create/modify/delete results or deploy results from the web portal.
\ No newline at end of file diff --git a/testapi/opnfv_testapi/tests/UI/e2e/scenarioControllerSpec.js b/testapi/opnfv_testapi/tests/UI/e2e/scenarioControllerSpec.js index 7777721..37b42dc 100644 --- a/testapi/opnfv_testapi/tests/UI/e2e/scenarioControllerSpec.js +++ b/testapi/opnfv_testapi/tests/UI/e2e/scenarioControllerSpec.js @@ -682,8 +682,11 @@ describe('testing the scenarios page for user', function () { name.sendKeys('test'); var buttonOk = element(by.xpath('//*[@id="ng-app"]/body/div[3]/div/div/div/div[2]/button[1]')) buttonOk.click() - expect(element(by.cssContainingText(".alert","Installers are successfully updated.")) + browser.ignoreSynchronization = true; + expect(element(by.cssContainingText(".success.show","Installers are successfully updated.")) .isDisplayed()).toBe(true); + browser.sleep(500); + browser.ignoreSynchronization = false; }); @@ -720,8 +723,11 @@ describe('testing the scenarios page for user', function () { .isDisplayed()).toBe(true); var buttonOK = element(by.buttonText('Ok')); buttonOK.click(); - expect(element(by.cssContainingText(".alert","Installer is successfully deleted.")) + browser.ignoreSynchronization = true; + expect(element(by.cssContainingText(".success.show","Installer is successfully deleted.")) .isDisplayed()).toBe(true); + browser.sleep(500); + browser.ignoreSynchronization = false; }); it( 'Expand installer by user', function() { @@ -753,8 +759,11 @@ describe('testing the scenarios page for user', function () { owner.sendKeys('testOwner'); var buttonOk = element(by.xpath('//*[@id="ng-app"]/body/div[3]/div/div/div/div[2]/button[1]')) buttonOk.click() - expect(element(by.cssContainingText(".alert","Versions are successfully updated.")) + browser.ignoreSynchronization = true; + expect(element(by.cssContainingText(".success.show","Versions are successfully updated.")) .isDisplayed()).toBe(true); + browser.sleep(500); + browser.ignoreSynchronization = false; }); it( 'Expand versions by user', function() { @@ -793,8 +802,11 @@ describe('testing the scenarios page for user', function () { .isDisplayed()).toBe(true); var buttonOK = element(by.buttonText('Ok')); buttonOK.click(); - expect(element(by.cssContainingText(".alert","Versions are successfully deleted.")) + browser.ignoreSynchronization = true; + expect(element(by.cssContainingText(".success.show","Versions are successfully deleted.")) .isDisplayed()).toBe(true); + browser.sleep(500); + browser.ignoreSynchronization = false; }); it( 'Expand version by user', function() { @@ -832,8 +844,11 @@ describe('testing the scenarios page for user', function () { project.sendKeys('testP'); var buttonOk = element(by.xpath('//*[@id="ng-app"]/body/div[3]/div/div/div/div[2]/button[1]')) buttonOk.click() - expect(element(by.cssContainingText(".alert","Projects are successfully updated.")) + browser.ignoreSynchronization = true; + expect(element(by.cssContainingText(".success.show","Projects are successfully updated.")) .isDisplayed()).toBe(true); + browser.sleep(500); + browser.ignoreSynchronization = false; }); it( 'Expand projects by user', function() { @@ -878,8 +893,11 @@ describe('testing the scenarios page for user', function () { .isDisplayed()).toBe(true); var buttonOK = element(by.buttonText('Ok')); buttonOK.click(); - expect(element(by.cssContainingText(".alert","Projects are successfully Deleted.")) + browser.ignoreSynchronization = true; + expect(element(by.cssContainingText(".success.show","Projects are successfully Deleted.")) .isDisplayed()).toBe(true); + browser.sleep(500); + browser.ignoreSynchronization = false; }); it( 'Expand project by user', function() { @@ -1011,8 +1029,11 @@ describe('testing the scenarios page for user', function () { custom.sendKeys('testC'); var buttonOk = element(by.xpath('//*[@id="ng-app"]/body/div[3]/div/div/div/div[2]/button[1]')) buttonOk.click() - expect(element(by.cssContainingText(".alert","Customs are successfully updated.")) + browser.ignoreSynchronization = true; + expect(element(by.cssContainingText(".success.show","Customs are successfully updated.")) .isDisplayed()).toBe(true); + browser.sleep(500); + browser.ignoreSynchronization = false; }); it( 'Add multiple Customs by user', function() { @@ -1043,8 +1064,11 @@ describe('testing the scenarios page for user', function () { custom.sendKeys('testC,testD,'); var buttonOk = element(by.xpath('//*[@id="ng-app"]/body/div[3]/div/div/div/div[2]/button[1]')) buttonOk.click() - expect(element(by.cssContainingText(".alert","Customs are successfully updated.")) + browser.ignoreSynchronization = true; + expect(element(by.cssContainingText(".success.show","Customs are successfully updated.")) .isDisplayed()).toBe(true); + browser.sleep(500); + browser.ignoreSynchronization = false; }); it( 'Delete Customs by user', function() { @@ -1074,8 +1098,11 @@ describe('testing the scenarios page for user', function () { .isDisplayed()).toBe(true); var buttonOk = element(by.xpath('//*[@id="ng-app"]/body/div[3]/div/div/div[3]/button[1]')) buttonOk.click() - expect(element(by.cssContainingText(".alert","Customs are successfully deleted.")) + browser.ignoreSynchronization = true; + expect(element(by.cssContainingText(".success.show","Customs are successfully deleted.")) .isDisplayed()).toBe(true); + browser.sleep(500); + browser.ignoreSynchronization = false; }); });
\ No newline at end of file diff --git a/testapi/opnfv_testapi/tests/UI/e2e/scenariosControllerSpec.js b/testapi/opnfv_testapi/tests/UI/e2e/scenariosControllerSpec.js index e7c5fb0..55922ad 100644 --- a/testapi/opnfv_testapi/tests/UI/e2e/scenariosControllerSpec.js +++ b/testapi/opnfv_testapi/tests/UI/e2e/scenariosControllerSpec.js @@ -119,7 +119,7 @@ describe('testing the scenarios page for anonymous user', function () { it('Sort scenarios', function () { browser.get(baseURL+"#/scenarios"); - var sort = element(by.xpath('//*[@id="ng-app"]/body/div/div[4]/div/table/thead/tr/th[2]/a[2]/span')) + var sort = element(by.xpath('//*[@id="ng-app"]/body/div/div[5]/div/table/thead/tr/th[2]/a[2]/span')) sort.click(); var row = element.all(by.repeater('(index, scenario) in ctrl.data.scenarios')).first(); var cells = row.all(by.tagName('td')); @@ -284,8 +284,11 @@ describe('testing the scenarios page for user', function () { name.sendKeys('test'); var buttonOK = element(by.buttonText('Ok')); buttonOK.click(); - expect(element(by.cssContainingText(".alert","Scenario is successfully created.")) + browser.ignoreSynchronization = true; + expect(element(by.cssContainingText(".success.show","Scenario is successfully created.")) .isDisplayed()).toBe(true); + browser.sleep(500); + browser.ignoreSynchronization = false; }); it('create scenarrio by user with installers ', function () { @@ -304,8 +307,11 @@ describe('testing the scenarios page for user', function () { buttonOK.click(); var buttonOK = element(by.buttonText('Ok')); buttonOK.click(); - expect(element(by.cssContainingText(".alert","Scenario is successfully created.")) + browser.ignoreSynchronization = true; + expect(element(by.cssContainingText(".success.show","Scenario is successfully created.")) .isDisplayed()).toBe(true); + browser.sleep(500); + browser.ignoreSynchronization = false; }); it('create scenarrio by user with installers with versions ', function () { @@ -333,8 +339,11 @@ describe('testing the scenarios page for user', function () { buttonOK.click(); var buttonOK = element(by.buttonText('Ok')); buttonOK.click(); - expect(element(by.cssContainingText(".alert","Scenario is successfully created.")) + browser.ignoreSynchronization = true; + expect(element(by.cssContainingText(".success.show","Scenario is successfully created.")) .isDisplayed()).toBe(true); + browser.sleep(500); + browser.ignoreSynchronization = false; }); it('create scenarrio by user with installers with versions with project', function () { @@ -369,8 +378,11 @@ describe('testing the scenarios page for user', function () { buttonOK.click(); var buttonOK = element(by.buttonText('Ok')); buttonOK.click(); - expect(element(by.cssContainingText(".alert","Scenario is successfully created.")) + browser.ignoreSynchronization = true; + expect(element(by.cssContainingText(".success.show","Scenario is successfully created.")) .isDisplayed()).toBe(true); + browser.sleep(500); + browser.ignoreSynchronization = false; }); it('create scenarrio by user with installers with versions with project with custom', function () { @@ -412,8 +424,11 @@ describe('testing the scenarios page for user', function () { buttonOK.click(); var buttonOK = element(by.buttonText('Ok')); buttonOK.click(); - expect(element(by.cssContainingText(".alert","Scenario is successfully created.")) + browser.ignoreSynchronization = true; + expect(element(by.cssContainingText(".success.show","Scenario is successfully created.")) .isDisplayed()).toBe(true); + browser.sleep(500); + browser.ignoreSynchronization = false; }); it('view scenarrio by user ', function () { @@ -443,8 +458,11 @@ describe('testing the scenarios page for user', function () { .isDisplayed()).toBe(true); var buttonOK = element(by.buttonText('Ok')); buttonOK.click(); - expect(element(by.cssContainingText(".alert","Scenario is successfully deleted.")) + browser.ignoreSynchronization = true; + expect(element(by.cssContainingText(".success.show","Scenario is successfully deleted.")) .isDisplayed()).toBe(true); + browser.sleep(500); + browser.ignoreSynchronization = false; }); it('Batch Delete the scenarios ', function () { @@ -455,8 +473,11 @@ describe('testing the scenarios page for user', function () { buttonDelete.click(); var buttonOK = element(by.buttonText('Ok')); buttonOK.click(); - expect(element(by.cssContainingText(".alert","Scenario is successfully deleted.")) + browser.ignoreSynchronization = true; + expect(element(by.cssContainingText(".success.show","Scenario is successfully deleted.")) .isDisplayed()).toBe(true); + browser.sleep(500); + browser.ignoreSynchronization = false; }); it('Edit the scenarios ', function () { @@ -469,8 +490,11 @@ describe('testing the scenarios page for user', function () { name.sendKeys('test2'); var buttonOK = element(by.buttonText('Ok')); buttonOK.click() - expect(element(by.cssContainingText(".alert","Scenario is successfully Updated.")) + browser.ignoreSynchronization = true; + expect(element(by.cssContainingText(".success.show","Scenario is successfully Updated.")) .isDisplayed()).toBe(true); + browser.sleep(500); + browser.ignoreSynchronization = false; }); });
\ No newline at end of file diff --git a/testapi/opnfv_testapi/tests/UI/e2e/testCasesControllerSpec.js b/testapi/opnfv_testapi/tests/UI/e2e/testCasesControllerSpec.js index 53e7bdf..38e0f24 100644 --- a/testapi/opnfv_testapi/tests/UI/e2e/testCasesControllerSpec.js +++ b/testapi/opnfv_testapi/tests/UI/e2e/testCasesControllerSpec.js @@ -550,8 +550,11 @@ describe('testing the test cases page for user who is in submitter group', funct name.sendKeys('test'); var buttonOK = element(by.buttonText('Ok')); buttonOK.click(); - expect(element(by.cssContainingText(".alert","Testcase is successfully created.")) + browser.ignoreSynchronization = true; + expect(element(by.cssContainingText(".success.show","Testcase is successfully created.")) .isDisplayed()).toBe(true); + browser.sleep(500); + browser.ignoreSynchronization = false; }); it('Showing error when creating with a empty name ', function () { @@ -565,8 +568,11 @@ describe('testing the test cases page for user who is in submitter group', funct browser.wait(EC.visibilityOf(name), 5000); var buttonOK = element(by.buttonText('Ok')); buttonOK.click(); - expect(element(by.cssContainingText(".alert","Name is missing.")) + browser.ignoreSynchronization = true; + expect(element(by.cssContainingText(".error.show","Name is missing.")) .isDisplayed()).toBe(true); + browser.sleep(500); + browser.ignoreSynchronization = false; }); it('cancel the delete confimation modal of the test case ', function () { @@ -590,8 +596,11 @@ describe('testing the test cases page for user who is in submitter group', funct deleteOperation.click(); var buttonOK = element(by.buttonText('Ok')); buttonOK.click(); - expect(element(by.cssContainingText(".alert","Test case is successfully deleted")) + browser.ignoreSynchronization = true; + expect(element(by.cssContainingText(".success.show","Test case is successfully deleted")) .isDisplayed()).toBe(true); + browser.sleep(500); + browser.ignoreSynchronization = false; }); it('cancel the Edit modal of the test case ', function () { @@ -621,8 +630,11 @@ describe('testing the test cases page for user who is in submitter group', funct name.sendKeys('test1'); var buttonOK = element(by.buttonText('Ok')); buttonOK.click(); - expect(element(by.cssContainingText(".alert","Test case is successfully updated")) + browser.ignoreSynchronization = true; + expect(element(by.cssContainingText(".success.show","Test case is successfully updated")) .isDisplayed()).toBe(true); + browser.sleep(500); + browser.ignoreSynchronization = false; }); it('view the test case ', function () { @@ -646,7 +658,10 @@ describe('testing the test cases page for user who is in submitter group', funct buttonDelete.click(); var buttonOK = element(by.buttonText('Ok')); buttonOK.click(); - expect(element(by.cssContainingText(".alert","Test case is successfully deleted")) + browser.ignoreSynchronization = true; + expect(element(by.cssContainingText(".success.show","Test case is successfully deleted")) .isDisplayed()).toBe(true); + browser.sleep(500); + browser.ignoreSynchronization = false; }); }) diff --git a/testapi/opnfv_testapi/ui/components/projects/project/testCases/testCases.html b/testapi/opnfv_testapi/ui/components/projects/project/testCases/testCases.html index 395db03..04baa9c 100644 --- a/testapi/opnfv_testapi/ui/components/projects/project/testCases/testCases.html +++ b/testapi/opnfv_testapi/ui/components/projects/project/testCases/testCases.html @@ -11,16 +11,12 @@ <i class="fa fa-plus"></i> Create</button> </div> </div> - <div class='clo-md-12'> - <div ng-show="testCasesCtrl.showError" class="alert alert-danger" role="alert"> - <span class="pull-right"> {{testCasesCtrl.error}}</span> - <span class="glyphicon glyphicon-exclamation-sign pull-right" aria-hidden="true" >Error:</span> - </div> - <div ng-show="testCasesCtrl.showSuccess" class="alert alert-success" role="alert"> - <span class="pull-right"> {{testCasesCtrl.successMessage}}</span> - <span class="glyphicon glyphicon-ok pull-right" aria-hidden="true"></span> - </div> - </div> + <div ng-class="{'show': testCasesCtrl.showError}" id="toast" class="error"> + <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true" ></span> + {{testCasesCtrl.error}}</div> + <div ng-class="{'show': testCasesCtrl.showSuccess}" id="toast" class="success"> + <span class="glyphicon glyphicon-ok" aria-hidden="true"></span> + {{testCasesCtrl.success}}</div> <div class='clo-md-12' style="padding-right:0px"> <div class="table-responsive"> <table class="table table-bordered table-hover" ng-data="testCasesCtrl.data.testcases"> diff --git a/testapi/opnfv_testapi/ui/components/projects/project/testCases/testCasesController.js b/testapi/opnfv_testapi/ui/components/projects/project/testCases/testCasesController.js index 4d6153e..ea0498a 100644 --- a/testapi/opnfv_testapi/ui/components/projects/project/testCases/testCasesController.js +++ b/testapi/opnfv_testapi/ui/components/projects/project/testCases/testCasesController.js @@ -21,7 +21,7 @@ TestCasesController.$inject = [ '$scope', '$http', '$filter', '$state', '$window', '$uibModal', 'testapiApiUrl','raiseAlert', - 'confirmModal', 'authenticate' + 'confirmModal', 'authenticate', '$timeout' ]; /** @@ -31,7 +31,7 @@ * in them. */ function TestCasesController($scope, $http, $filter, $state, $window, $uibModal, testapiApiUrl, - raiseAlert, confirmModal, authenticate) { + raiseAlert, confirmModal, authenticate, $timeout) { var ctrl = this; ctrl.loadDetails = loadDetails; ctrl.name = $state.params['name']; @@ -49,29 +49,40 @@ ctrl.checkBox = []; ctrl.checkBoxList = []; + ctrl.toastError = toastError + ctrl.toastSuccess = toastSuccess + + function toastError() { + ctrl.showError = true + $timeout(function(){ ctrl.showError = false;}, 3000); + } + + function toastSuccess() { + ctrl.showSuccess = true + $timeout(function(){ ctrl.showSuccess = false;}, 3000); + } /** * This will contact the TestAPI to create a new test case. */ function createTestCase(name, testcase) { - ctrl.showError = false; - ctrl.showSuccess = false; if(testcase.name != "" && testcase.name!=null){ var testCase_url = ctrl.requestUrl; - ctrl.testCasesRequest = - $http.post(testCase_url, testcase).success(function (data){ - ctrl.showSuccess = true ; - ctrl.successMessage = "Testcase is successfully created." + ctrl.testCasesRequest = $http.post(testCase_url, testcase) + ctrl.testCasesRequest.success(function (data){ + ctrl.success = "Testcase is successfully created." loadDetails(); + ctrl.toastSuccess() }) .catch(function (data) { - ctrl.showError = true; ctrl.error = data.statusText; + ctrl.toastError(); }); + return ctrl.testCasesRequest; } else{ - ctrl.showError = true; ctrl.error = 'Name is missing.' + ctrl.toastError(); } } @@ -114,24 +125,23 @@ * This will contact the TestAPI to update an existing test case. */ function updateTestCase(name, testCase) { - ctrl.showError = false; - ctrl.showSuccess = false; if(testCase.name != ""){ var testCase_url = ctrl.requestUrl + '/' + name; - ctrl.testCasesRequest = - $http.put(testCase_url, testCase).success(function (data){ - ctrl.showSuccess = true ; - ctrl.successMessage = "Test case is successfully updated" + ctrl.testCasesRequest = $http.put(testCase_url, testCase) + ctrl.testCasesRequest.success(function (data){ + ctrl.success = "Test case is successfully updated" loadDetails(); + ctrl.toastSuccess(); }) .catch(function (data) { - ctrl.showError = true; ctrl.error = data.statusText; + ctrl.toastError() }); + return ctrl.testCasesRequest; } else{ - ctrl.showError = true; ctrl.error = 'Name is missing.' + ctrl.toastError() } } @@ -139,16 +149,14 @@ * This will contact the TestAPI to delete an existing test case. */ function deleteTestCase(name) { - ctrl.showError = false; - ctrl.showSuccess = false; ctrl.testCasesRequest = $http.delete(ctrl.requestUrl+"/"+name).success(function (data) { loadDetails(); - ctrl.showSuccess = true ; - ctrl.successMessage = "Test case is successfully deleted" + ctrl.success = "Test case is successfully deleted"; + ctrl.toastSuccess(); }).catch(function (error) { - ctrl.showError = true; - ctrl.error = data.statusText; + ctrl.error = error.statusText; + ctrl.toastError(); }); } @@ -218,8 +226,8 @@ ctrl.data = data; }).catch(function (error) { ctrl.data = null; - ctrl.showError = true; ctrl.error = error.statusText; + ctrl.toastError() }); } ctrl.loadDetails(); @@ -271,9 +279,15 @@ * inputs. */ function confirm() { - $uibModalInstance.close(); if (angular.isDefined(ctrl.data.successHandler)) { - ctrl.data.successHandler(ctrl.name, ctrl.testcase); + if(ctrl.testcase.name){ + ctrl.data.successHandler(ctrl.name, ctrl.testcase).success( function(){ + $uibModalInstance.close(); + }) + } + else{ + ctrl.data.successHandler(ctrl.name, ctrl.testcase) + } } } diff --git a/testapi/opnfv_testapi/ui/components/scenarios/scenario/scenario.html b/testapi/opnfv_testapi/ui/components/scenarios/scenario/scenario.html index 4f0a580..d6d4257 100644 --- a/testapi/opnfv_testapi/ui/components/scenarios/scenario/scenario.html +++ b/testapi/opnfv_testapi/ui/components/scenarios/scenario/scenario.html @@ -225,14 +225,10 @@ </div> </div> <div class="row" style="margin-bottom:24px;"></div> -<div class='clo-md-12'> - <div ng-show="ctrl.showError" class="alert alert-danger" role="alert"> - <span class="pull-right"> {{ctrl.error}}</span> - <span class="glyphicon glyphicon-exclamation-sign pull-right" aria-hidden="true" >Error:</span> - </div> - <div ng-show="ctrl.showSuccess" class="alert alert-success" role="alert"> - <span class="pull-right"> {{ctrl.success}}</span> - <span class="glyphicon glyphicon-ok pull-right" aria-hidden="true"></span> - </div> -</div> +<div ng-class="{'show': ctrl.showError}" id="toast" class="error"> + <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true" ></span> + {{ctrl.error}}</div> +<div ng-class="{'show': ctrl.showSuccess}" id="toast" class="success"> + <span class="glyphicon glyphicon-ok" aria-hidden="true"></span> + {{ctrl.success}}</div> <div class="row" style="margin-bottom:24px;"></div>
\ No newline at end of file diff --git a/testapi/opnfv_testapi/ui/components/scenarios/scenario/scenarioController.js b/testapi/opnfv_testapi/ui/components/scenarios/scenario/scenarioController.js index a0cd5eb..e17718f 100644 --- a/testapi/opnfv_testapi/ui/components/scenarios/scenario/scenarioController.js +++ b/testapi/opnfv_testapi/ui/components/scenarios/scenario/scenarioController.js @@ -21,7 +21,7 @@ ScenarioController.$inject = [ '$scope', '$http', '$filter', '$state', '$window', '$uibModal', 'testapiApiUrl','raiseAlert', - 'confirmModal', 'authenticate' + 'confirmModal', 'authenticate', '$timeout' ]; /** @@ -30,7 +30,7 @@ * through Scenario declared in TestAPI. */ function ScenarioController($scope, $http, $filter, $state, $window, $uibModal, testapiApiUrl, - raiseAlert, confirmModal, authenticate) { + raiseAlert, confirmModal, authenticate, $timeout) { var ctrl = this; ctrl.name = $state.params['name']; ctrl.url = testapiApiUrl + '/scenarios?name=' + ctrl.name; @@ -73,7 +73,18 @@ ctrl.buttonInstaller = true ctrl.buttonVersion = true ctrl.buttonProject = true + ctrl.toastError = toastError + ctrl.toastSuccess = toastSuccess + function toastError() { + ctrl.showError = true + $timeout(function(){ ctrl.showError = false;}, 3000); + } + + function toastSuccess() { + ctrl.showSuccess = true + $timeout(function(){ ctrl.showSuccess = false;}, 3000); + } /** * This will contact the TestAPI to get a listing of declared projects. */ @@ -84,8 +95,8 @@ ctrl.data = data; }).catch(function (error) { ctrl.data = null; - ctrl.showError = true; ctrl.error = error.statusText + ctrl.toastError() }); } @@ -203,13 +214,13 @@ function deleteInstaller(data){ ctrl.installerReqest = testapiApiUrl+ "/scenarios/"+ ctrl.name + "/installers" $http.delete(ctrl.installerReqest, {data: data.installers, headers: {'Content-Type': 'application/json'}}).success(function (data){ - ctrl.showSuccess = true ; ctrl.success = "Installer is successfully deleted." + ctrl.toastSuccess(); ctrl.loadDetails(); }) .catch(function (data) { - ctrl.showError = true; ctrl.error = data.statusText; + ctrl.toastError() }); } @@ -225,16 +236,18 @@ function addInstaller(installer){ var installers = [] installers.push(installer) - ctrl.installerReqest = testapiApiUrl+ "/scenarios/"+ ctrl.name + "/installers" - $http.post(ctrl.installerReqest, installers).success(function (data){ - ctrl.showSuccess = true ; + ctrl.installerRequestUrl = testapiApiUrl+ "/scenarios/"+ ctrl.name + "/installers" + ctrl.installerRequest = $http.post(ctrl.installerRequestUrl, installers) + ctrl.installerRequest.success(function (data){ ctrl.success = "Installers are successfully updated." ctrl.loadDetails(); + ctrl.toastSuccess(); }) .catch(function (data) { - ctrl.showError = true; ctrl.error = data.statusText; + ctrl.toastError(); }); + return ctrl.installerRequest } function openAddInstaller(){ @@ -254,16 +267,18 @@ } function addVersion(versions, installer){ - ctrl.versionReqest = testapiApiUrl+ "/scenarios/"+ ctrl.name + "/versions?installer="+installer - $http.post(ctrl.versionReqest, versions).success(function (data){ - ctrl.showSuccess = true ; + ctrl.versionRequestUrl = testapiApiUrl+ "/scenarios/"+ ctrl.name + "/versions?installer="+installer + ctrl.versionRequest = $http.post(ctrl.versionRequestUrl, versions) + ctrl.versionRequest.success(function (data){ ctrl.success = "Versions are successfully updated." ctrl.loadDetails(); + ctrl.toastSuccess() }) .catch(function (data) { - ctrl.showError = true; ctrl.error = data.statusText; + ctrl.toastError() }); + return ctrl.versionRequest; } function openDeleteVersionModal(version, installer){ @@ -279,13 +294,13 @@ function deleteVersion(data){ ctrl.versionReqest = testapiApiUrl+ "/scenarios/"+ ctrl.name + "/versions?installer="+data.installer $http.delete(ctrl.versionReqest, {data: data.version, headers: {'Content-Type': 'application/json'}}).success(function (data){ - ctrl.showSuccess = true ; ctrl.success = "Versions are successfully deleted." ctrl.loadDetails(); + ctrl.toastSuccess(); }) .catch(function (data) { - ctrl.showError = true; ctrl.error = data.statusText; + ctrl.toastError(); }); } @@ -307,16 +322,18 @@ } function addProject(project, version, installer){ - ctrl.projectReqest = testapiApiUrl+ "/scenarios/"+ ctrl.name + "/projects?installer="+installer+"&version="+version - $http.post(ctrl.projectReqest, project).success(function (data){ - ctrl.showSuccess = true ; + ctrl.projectRequestUrl = testapiApiUrl+ "/scenarios/"+ ctrl.name + "/projects?installer="+installer+"&version="+version + ctrl.projectRequest= $http.post(ctrl.projectRequestUrl, project) + ctrl.projectRequest.success(function (data){ ctrl.success = "Projects are successfully updated." ctrl.loadDetails(); + ctrl.toastSuccess(); }) .catch(function (data) { - ctrl.showError = true; ctrl.error = data.statusText; + ctrl.toastError(); }); + return ctrl.projectRequest; } function openAddProjectModal(version, installer){ @@ -338,16 +355,18 @@ } function addCustom(custom,project,version,installer){ - ctrl.customReqest = testapiApiUrl+ "/scenarios/"+ ctrl.name + "/customs?installer="+installer+"&version="+version+"&project="+ project - $http.post(ctrl.customReqest, custom).success(function (data){ - ctrl.showSuccess = true ; + ctrl.customRequestUrl = testapiApiUrl+ "/scenarios/"+ ctrl.name + "/customs?installer="+installer+"&version="+version+"&project="+ project + ctrl.customRequest = $http.post(ctrl.customRequestUrl, custom) + ctrl.customRequest.success(function (data){ ctrl.success = "Customs are successfully updated." ctrl.loadDetails(); + ctrl.toastSuccess(); }) .catch(function (data) { - ctrl.showError = true; ctrl.error = data.statusText; + ctrl.toastError(); }); + return ctrl.customRequest } function openDeleteCustomModal(custom,project,version,installer){ @@ -365,13 +384,13 @@ function deleteCustom(data){ ctrl.customReqest = testapiApiUrl+ "/scenarios/"+ ctrl.name + "/customs?installer="+data.installer+"&version="+data.version+"&project="+ data.project $http.delete(ctrl.customReqest, {data: data.customs, headers: {'Content-Type': 'application/json'}}).success(function (data){ - ctrl.showSuccess = true ; ctrl.success = "Customs are successfully deleted." ctrl.loadDetails(); + ctrl.toastSuccess(); }) .catch(function (data) { - ctrl.showError = true; ctrl.error = data.statusText; + ctrl.toastError(); }); } @@ -408,13 +427,13 @@ function deleteProject(data){ ctrl.projectReqest = testapiApiUrl+ "/scenarios/"+ ctrl.name + "/projects?installer="+data.installer+"&version="+data.version $http.delete(ctrl.projectReqest, {data: data.projects, headers: {'Content-Type': 'application/json'}}).success(function (data){ - ctrl.showSuccess = true ; - ctrl.success = "Projects are successfully Deleted." + ctrl.success = "Projects are successfully Deleted."; + ctrl.toastSuccess(); ctrl.loadDetails(); }) .catch(function (data) { - ctrl.showError = true; ctrl.error = data.statusText; + ctrl.toastError(); }); } @@ -443,8 +462,11 @@ ctrl.customs = custom.split(/[ ,]+/).filter(Boolean); } console.log(ctrl.customs) - ctrl.data.successHandler(ctrl.customs,ctrl.data.project,ctrl.data.version,ctrl.data.installer); - $uibModalInstance.dismiss('cancel'); + ctrl.data.successHandler( + ctrl.customs, ctrl.data.project, + ctrl.data.version,ctrl.data.installer).success(function(){ + $uibModalInstance.dismiss('cancel'); + }); } @@ -486,8 +508,10 @@ */ function confirm() { ctrl.projects.push(ctrl.project) - ctrl.data.successHandler(ctrl.projects, ctrl.data.version, ctrl.data.installer); - $uibModalInstance.dismiss('cancel'); + ctrl.data.successHandler( + ctrl.projects, ctrl.data.version, ctrl.data.installer).success( function(){ + $uibModalInstance.dismiss('cancel'); + }); } @@ -545,8 +569,9 @@ */ function confirm() { ctrl.versions.push(ctrl.version) - ctrl.data.successHandler(ctrl.versions, ctrl.data.installer); - $uibModalInstance.dismiss('cancel'); + ctrl.data.successHandler(ctrl.versions, ctrl.data.installer).success(function(){ + $uibModalInstance.dismiss('cancel'); + }); } diff --git a/testapi/opnfv_testapi/ui/components/scenarios/scenarios.html b/testapi/opnfv_testapi/ui/components/scenarios/scenarios.html index bde946c..8d23449 100644 --- a/testapi/opnfv_testapi/ui/components/scenarios/scenarios.html +++ b/testapi/opnfv_testapi/ui/components/scenarios/scenarios.html @@ -10,16 +10,12 @@ <i class="fa fa-plus"></i>Create</button> </div> </div> -<div class='clo-md-12'> - <div ng-show="ctrl.showError" class="alert alert-danger" role="alert"> - <span class="pull-right"> {{ctrl.error}}</span> - <span class="glyphicon glyphicon-exclamation-sign pull-right" aria-hidden="true" >Error:</span> - </div> - <div ng-show="ctrl.showCreateSuccess" class="alert alert-success" role="alert"> - <span class="pull-right"> {{ctrl.success}}</span> - <span class="glyphicon glyphicon-ok pull-right" aria-hidden="true"></span> - </div> -</div> +<div ng-class="{'show': ctrl.showError}" id="toast" class="error"> + <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true" ></span> + {{ctrl.error}}</div> +<div ng-class="{'show': ctrl.showSuccess}" id="toast" class="success"> + <span class="glyphicon glyphicon-ok" aria-hidden="true"></span> + {{ctrl.success}}</div> <div class='clo-md-12' style="padding-right:0px"> <div class="table-responsive"> <table class="table table-bordered table-hover" ng-data="ctrl.data.scenarios"> diff --git a/testapi/opnfv_testapi/ui/components/scenarios/scenariosController.js b/testapi/opnfv_testapi/ui/components/scenarios/scenariosController.js index 240287c..0aa5bf0 100644 --- a/testapi/opnfv_testapi/ui/components/scenarios/scenariosController.js +++ b/testapi/opnfv_testapi/ui/components/scenarios/scenariosController.js @@ -21,7 +21,7 @@ ScenariosController.$inject = [ '$scope', '$http', '$filter', '$state', '$window', '$uibModal', 'testapiApiUrl', - 'raiseAlert', 'confirmModal', 'sortService' + 'raiseAlert', 'confirmModal', 'sortService', '$timeout' ]; /** @@ -30,7 +30,7 @@ * through projects declared in TestAPI. */ function ScenariosController($scope, $http, $filter, $state, $window, $uibModal, testapiApiUrl, - raiseAlert, confirmModal, sortService) { + raiseAlert, confirmModal, sortService, $timeout) { var ctrl = this; ctrl.url = testapiApiUrl + '/scenarios'; @@ -47,6 +47,18 @@ ctrl.sortBy = sortBy ctrl.checkBox = []; ctrl.sortName = false + ctrl.toastError = toastError + ctrl.toastSuccess = toastSuccess + + function toastError() { + ctrl.showError = true + $timeout(function(){ ctrl.showError = false;}, 3000); + } + + function toastSuccess() { + ctrl.showSuccess = true + $timeout(function(){ ctrl.showSuccess = false;}, 3000); + } function openUpdateModal(name){ $uibModal.open({ @@ -78,12 +90,12 @@ var scenarioURL = ctrl.url+"/"+name; ctrl.scenarioRequest = $http.delete(scenarioURL).success(function (data){ - ctrl.showCreateSuccess = true; ctrl.success = "Scenario is successfully deleted."; ctrl.listScenarios(); + ctrl.toastSuccess(); }).catch(function (data) { - ctrl.showError = true; ctrl.error = data.statusText; + ctrl.toastError() }); } @@ -117,15 +129,21 @@ var body = { "name": newName } - ctrl.scenarioRequest = - $http.put(scenarioURL, body).success(function (data){ - ctrl.showCreateSuccess = true; + if(newName){ + ctrl.scenarioRequest = $http.put(scenarioURL, body) + ctrl.scenarioRequest.success(function (data){ ctrl.success = "Scenario is successfully Updated." - ctrl.listScenarios() + ctrl.listScenarios(); + ctrl.toastSuccess(); }).catch(function (data) { - ctrl.showError = true; ctrl.error = data.statusText; + ctrl.toastError(); }); + return ctrl.scenarioRequest + }else{ + ctrl.error = "Name is missing"; + ctrl.toastError(); + } } function viewScenario(name){ @@ -133,14 +151,17 @@ } function createScenario(scenario) { - ctrl.scenarioRequest = - $http.post(ctrl.url, scenario).success(function (data){ - ctrl.showCreateSuccess = true; - ctrl.success = "Scenario is successfully created." + ctrl.scenarioRequest = $http.post(ctrl.url, scenario) + + ctrl.scenarioRequest.success(function (data){ + ctrl.success = "Scenario is successfully created."; + ctrl.toastSuccess(); }).catch(function (data) { - ctrl.showError = true; ctrl.error = data.statusText; + ctrl.toastError(); }); + + return ctrl.scenarioRequest; } function listScenarios() { @@ -151,8 +172,8 @@ ctrl.sortBy() }).catch(function (data) { ctrl.data = null; - ctrl.showError = true; ctrl.error = data.statusText; + ctrl.toastError(); }); } @@ -183,8 +204,8 @@ * edit the project's details */ angular.module('testapiApp').controller('scenarioModalController', scenarioModalController); - scenarioModalController.$inject = ['$scope', '$uibModal', '$uibModalInstance', 'data']; - function scenarioModalController($scope, $uibModal, $uibModalInstance, data) { + scenarioModalController.$inject = ['$scope', '$uibModal', '$uibModalInstance', 'data', '$q']; + function scenarioModalController($scope, $uibModal, $uibModalInstance, data, $q) { var ctrl = this; ctrl.confirm = confirm; ctrl.cancel = cancel; @@ -201,8 +222,9 @@ * inputs. */ function confirm() { - ctrl.data.successHandler(ctrl.scenario); - $uibModalInstance.dismiss('cancel'); + ctrl.data.successHandler(ctrl.scenario).success(function(){ + $uibModalInstance.dismiss('cancel'); + }); } @@ -215,6 +237,9 @@ function handleModalData(installer){ ctrl.scenario.installers.push(installer) + var deferred = $q.defer(); + deferred.resolve(); + return deferred.promise; } function openInstallerModal(){ @@ -259,8 +284,9 @@ * inputs. */ function confirm() { - ctrl.data.successHandler(ctrl.installer); - $uibModalInstance.dismiss('cancel'); + ctrl.data.successHandler(ctrl.installer).success(function(){ + $uibModalInstance.dismiss('cancel'); + }); } @@ -472,9 +498,9 @@ * inputs. */ function confirm() { - ctrl.data.successHandler(ctrl.name,ctrl.data.name); - $uibModalInstance.dismiss('cancel'); - + ctrl.data.successHandler(ctrl.name,ctrl.data.name).success( function() { + $uibModalInstance.dismiss('cancel'); + }) } /** diff --git a/testapi/testapi-client/testapiclient/cli/pods.py b/testapi/testapi-client/testapiclient/cli/pods.py index df63737..a7706f6 100644 --- a/testapi/testapi-client/testapiclient/cli/pods.py +++ b/testapi/testapi-client/testapiclient/cli/pods.py @@ -3,6 +3,7 @@ import json from testapiclient.client import pods from testapiclient.utils import command from testapiclient.utils import urlparse +from testapiclient.models import pods as pm def pods_url(): @@ -61,8 +62,10 @@ class PodCreate(command.ShowOne): parser.add_argument('pod', type=json.loads, help='Pod create request format :\n' - '\'{"role": "", "name": "", "details": "", ' - '"mode": ""}\',\n role should be either ' + '\'{}\''.format(json.dumps( + pm.PodCreateRequest().__dict__ + )) + + '\n role should be either ' '"community-ci" or "production-ci", and ' 'mode should be either "metal" or "virtual.') return parser diff --git a/testapi/testapi-client/testapiclient/client/pods.py b/testapi/testapi-client/testapiclient/client/pods.py index 4254da7..d08114f 100644 --- a/testapi/testapi-client/testapiclient/client/pods.py +++ b/testapi/testapi-client/testapiclient/client/pods.py @@ -1,4 +1,7 @@ +import json + from testapiclient.client import base +from testapiclient.utils import urlparse class PodsClient(base.Client): @@ -9,3 +12,20 @@ class PodsClient(base.Client): def create(self, pod_req): return self.clientmanager.post(self.url, pod_req) + + def get(self, **queries): + if queries: + return json.dumps( + self.clientmanager.get( + urlparse.query_join(self.url, **queries))['pods']) + else: + return json.dumps( + self.clientmanager.get(self.url)['pods']) + + def get_one(self, name): + return json.dumps(self.clientmanager.get( + urlparse.path_join(self.url, name))) + + def delete(self, name): + return self.clientmanager.delete( + urlparse.path_join(self.url, name)) diff --git a/testapi/testapi-client/testapiclient/models/pods.py b/testapi/testapi-client/testapiclient/models/pods.py index 27ea311..4fa42e7 100644 --- a/testapi/testapi-client/testapiclient/models/pods.py +++ b/testapi/testapi-client/testapiclient/models/pods.py @@ -1,5 +1,5 @@ class PodCreateRequest(object): - def __init__(self, name='', mode='', details='', role=""): + def __init__(self, name='', mode='', details='', role=''): self.name = name self.mode = mode self.details = details diff --git a/testapi/testapi-client/testapiclient/tests/unit/test_app.py b/testapi/testapi-client/testapiclient/tests/unit/test_app.py new file mode 100644 index 0000000..c20b27f --- /dev/null +++ b/testapi/testapi-client/testapiclient/tests/unit/test_app.py @@ -0,0 +1,50 @@ +import urllib + +from mock import mock + +from testapiclient import main +from testapiclient.tests.unit import utils +from testapiclient.tests.unit import fakes + + +class MainTest(utils.TestCommand): + def setUp(self): + super(MainTest, self).setUp() + self.app = main.TestAPIClient() + self.cas_sever = '{}{}{}'.format( + self.env_variables['testapi_cas_auth_url'], + urllib.quote(self.env_variables['testapi_url']), + self.env_variables['testapi_cas_signin_return']) + self.headers = { + 'Content-type': 'application/json', + 'Accept': 'text/plain'} + + def test_auth_success(self): + self.post_mock.return_value = fakes.FakeResponse( + data={'text': "success"}) + self.app.run( + ['-u', 'user', '-p', 'pass', 'pod', 'create', + '{"name": "asfad"}']) + self.post_mock.assert_called_with( + 'http://localhost:8000/api/v1/pods', + data='{"name": "asfad"}', + headers=self.headers) + + def test_auth_failure(self): + self.post_mock.return_value = fakes.FakeResponse( + data={'text': "login"}) + self.app.run( + ['-u', 'user', '-p', 'pass', 'pod', 'create', + '{"name": "asfad"}']) + self.post_mock.assert_called_once_with( + self.cas_sever, + {'pass': 'pass', 'name': 'user', 'form_id': 'user_login'} + ) + + def test_auth_not_called(self): + self.auth_post = mock.patch( + 'testapiclient.utils.clientmanager.ClientManager.auth').start() + self.app.run(['pod', 'get']) + self.auth_post.assert_not_called() + self.get_mock.assert_called_once_with( + 'http://localhost:8000/api/v1/pods', headers=self.headers) diff --git a/testapi/testapi-client/testapiclient/tests/unit/test_pod_client.py b/testapi/testapi-client/testapiclient/tests/unit/test_pod_client.py new file mode 100644 index 0000000..1df5660 --- /dev/null +++ b/testapi/testapi-client/testapiclient/tests/unit/test_pod_client.py @@ -0,0 +1,89 @@ +import json + +from six.moves.urllib import parse +import testtools + +from testapiclient.client import pods +from testapiclient.tests.unit import fakes +from testapiclient.tests.unit import utils +from testapiclient.utils import clientmanager + + +class PodClientTest(utils.TestCommand): + def setUp(self): + super(PodClientTest, self).setUp() + self.base_url = parse.urljoin(self.api_url, 'pods') + self.pod_json = { + 'role': 'community-ci', + 'name': 'test_pod', + 'details': '', + 'mode': 'metal' + } + self.pod_client = pods.PodsClient() + self.pod_string = json.dumps(self.pod_json) + + +class PodClientGetTest(PodClientTest): + + def setUp(self): + super(PodClientGetTest, self).setUp() + self.pods_rsp = {'pods': [self.pod_json]} + + def test_get(self): + self.get_mock.return_value = fakes.FakeResponse(data=self.pods_rsp) + self.pod_client.get() + self.get_mock.assert_called_once_with( + self.base_url, + headers=clientmanager.ClientManager.headers) + + def test_get_search(self): + self.get_mock.return_value = fakes.FakeResponse(data=self.pods_rsp) + self.pod_client.get(name='pod1') + self.get_mock.assert_called_once_with( + self.base_url + '?name=pod1', + headers=clientmanager.ClientManager.headers) + + def test_get_one(self): + self.get_mock.return_value = fakes.FakeResponse(data=self.pod_json) + self.pod_client.get_one('def') + self.get_mock.assert_called_once_with( + self.base_url + '/def', + headers=clientmanager.ClientManager.headers) + + +class PodClientCreateTest(PodClientTest): + + def setUp(self): + super(PodClientCreateTest, self).setUp() + self.succ_rsp = { + 'href': '{}/{}'.format(self.base_url, self.pod_json.get('name')) + } + + def test_create_success(self): + self.post_mock.return_value = fakes.FakeResponse(data=self.succ_rsp) + self.pod_client.create(self.pod_json) + self.post_mock.assert_called_once() + + def test_create_failure(self): + with testtools.ExpectedException(Exception, 'Create failed: Error'): + self.post_mock.return_value = utils.FAKE_FAILURE + self.pod_client.create(self.pod_json) + + +class PodClientDeleteTest(PodClientTest): + + def setUp(self): + super(PodClientDeleteTest, self).setUp() + + def test_delete_success(self): + self.delete_mock.return_value = fakes.FakeResponse() + self.pod_client.delete('def') + self.delete_mock.assert_called_once_with( + self.base_url + '/def', + data=None, + headers=clientmanager.ClientManager.headers) + + def test_delete_failure(self): + with testtools.ExpectedException(Exception, 'Delete failed: Error'): + self.delete_mock.return_value = utils.FAKE_FAILURE + self.pod_client.delete('def') diff --git a/testapi/testapi-client/testapiclient/tests/unit/utils.py b/testapi/testapi-client/testapiclient/tests/unit/utils.py index 21f98c4..c59aadd 100644 --- a/testapi/testapi-client/testapiclient/tests/unit/utils.py +++ b/testapi/testapi-client/testapiclient/tests/unit/utils.py @@ -19,7 +19,7 @@ class TestCommand(testtools.TestCase): def setUp(self): super(TestCommand, self).setUp() - env_variables = { + self.env_variables = { 'testapi_url': 'http://localhost:8000/api/v1', 'testapi_cas_auth_url': ( @@ -29,7 +29,7 @@ class TestCommand(testtools.TestCase): 'testapi_cas_signin_return': '/auth/signin_return' } self.config_mock = mock.patch.dict( - 'os.environ', env_variables).start() + 'os.environ', self.env_variables).start() self.fake_stdout = fakes.FakeStdout() self.fake_log = fakes.FakeLog() self.app = fakes.FakeApp(self.fake_stdout, self.fake_log) |