Hi, Please find the attached patch which covers test cases for the backup module (RM #3206).
1. Unit test cases 2. End to end regression test cases 3. Feature test cases Thanks, Khushboo
diff --git a/web/pgadmin/feature_tests/pg_utilities_backup_test.py b/web/pgadmin/feature_tests/pg_utilities_backup_test.py new file mode 100644 index 0000000..feca43a --- /dev/null +++ b/web/pgadmin/feature_tests/pg_utilities_backup_test.py @@ -0,0 +1,82 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2018, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +import time +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.common.by import By +from selenium.webdriver.support import expected_conditions as EC +from regression.feature_utils.base_feature_test import BaseFeatureTest +from regression.python_test_utils import test_utils + + +class PGUtilitiesBackupFeatureTest(BaseFeatureTest): + """ This class test PG utilities - Backup test scenarios """ + + scenarios = [ + ("Test for PG utilities", dict()) + ] + + def before(self): + connection = test_utils.get_db_connection( + self.server['db'], + self.server['username'], + self.server['db_password'], + self.server['host'], + self.server['port'], + self.server['sslmode'] + ) + test_utils.drop_database(connection, "acceptance_test_db") + + test_utils.create_database(self.server, "acceptance_test_db") + test_utils.create_table(self.server, "acceptance_test_db", "acceptance_test_table") + self.page.add_server(self.server) + + self.wait = WebDriverWait(self.page.driver, 10) + + def runTest(self): + self.page.toggle_open_server(self.server['name']) + self.page.toggle_open_tree_item('Databases') + self.page.toggle_open_tree_item('acceptance_test_db') + self.driver.find_element_by_link_text("Tools").click() + + self.page.find_by_partial_link_text("Backup...").click() + + time.sleep(0.5) + + self.page.fill_input_by_field_name("file", "test_backup_file") + self.page.find_by_xpath("//button[contains(@class,'fa-save') and contains(.,'Backup')]").click() + + self.page.wait_for_element_to_disappear( + lambda driver: driver.find_element_by_css_selector(".ajs-modal") + ) + + status = self.page.find_by_xpath("//div[contains(@class,'bg-success')]").text + + self.assertEquals(status, "Successfully completed.") + self.page.find_by_xpath("//span[contains(string(), 'Click here for details.')]").click() + command = self.page.find_by_xpath("//p[contains(@class, 'bg-detailed-desc')]").text + + self.assertIn(self.server['name'], str(command)) + self.assertIn("from database 'acceptance_test_db'", str(command)) + self.assertIn("test_backup_file", str(command)) + self.assertIn("pg_dump", str(command)) + + self.page.find_by_xpath("//div[contains(@class,'wcFloatingFocus')]//div[contains(@class,'fa-close')]").click() + + def after(self): + self.page.remove_server(self.server) + connection = test_utils.get_db_connection( + self.server['db'], + self.server['username'], + self.server['db_password'], + self.server['host'], + self.server['port'], + self.server['sslmode'] + ) + test_utils.drop_database(connection, "acceptance_test_db") diff --git a/web/pgadmin/tools/backup/tests/__init__.py b/web/pgadmin/tools/backup/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/web/pgadmin/tools/backup/tests/test_backup_message.py b/web/pgadmin/tools/backup/tests/test_backup_message.py new file mode 100644 index 0000000..780106e --- /dev/null +++ b/web/pgadmin/tools/backup/tests/test_backup_message.py @@ -0,0 +1,148 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2018, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## +import sys + +from flask import Response +import simplejson as json + +from pgadmin.tools.backup import BackupMessage, BACKUP +from pgadmin.utils.route import BaseTestGenerator + +if sys.version_info < (3, 3): + from mock import patch, MagicMock +else: + from unittest.mock import patch, MagicMock + + +class BackupMessageTest(BaseTestGenerator): + """Test the BackupMessage class""" + scenarios = [ + ('When backup server', + dict( + class_params=dict( + type=BACKUP.SERVER, + sid=1, + name='test_backup_server', + port=5444, + host='localhost', + database='postgres', + bfile='test_backup', + args=[ + '--file', + "backup_file", + '--host', + "localhost", + '--port', + "5444", + '--username', + "postgres", + '--no-password', + '--database', + "postgres" + ], + cmd = "/test_path/pg_dump" + ), + extected_msg="Backing up the server 'test_backup_server (localhost:5444)'...", + expetced_details_cmd='/test_path/pg_dump --file "backup_file" --host "localhost"' + ' --port "5444" --username "postgres" --no-password --database "postgres"' + + )), + ('When backup global', + dict( + class_params=dict( + type=BACKUP.GLOBALS, + sid=1, + name='test_backup_server', + port=5444, + host='localhost', + database='postgres', + bfile='test_backup', + args=[ + '--file', + 'backup_file', + '--host', + 'localhost', + '--port', + '5444', + '--username', + 'postgres', + '--no-password', + '--database', + 'postgres' + ], + cmd="/test_path/pg_dump" + ), + extected_msg="Backing up the global objects on the server 'test_backup_server (localhost:5444)'...", + expetced_details_cmd='/test_path/pg_dump --file "backup_file" --host "localhost"' + ' --port "5444" --username "postgres" --no-password --database "postgres"' + + )), + ('When backup object', + dict( + class_params=dict( + type=BACKUP.OBJECT, + sid=1, + name='test_backup_server', + port=5444, + host='localhost', + database='postgres', + bfile='test_backup', + args=[ + '--file', + 'backup_file', + '--host', + 'localhost', + '--port', + '5444', + '--username', + 'postgres', + '--no-password', + '--database', + 'postgres' + ], + cmd="/test_path/pg_dump" + ), + extected_msg="Backing up an object on the server 'test_backup_server (localhost:5444)'" + " from database 'postgres'...", + expetced_details_cmd='/test_path/pg_dump --file "backup_file" --host "localhost"' + ' --port "5444" --username "postgres" --no-password --database "postgres"' + + )) + ] + + @patch('pgadmin.tools.backup.Server') + @patch('pgadmin.tools.backup.current_user') + def runTest(self, current_user_mock, server_mock): + current_user_mock = 1 + + class TestMockServer(): + def __init__(self, name, host, port): + self.name = name + self.host = host + self.port = port + mock_obj = TestMockServer(self.class_params['name'], + self.class_params['host'], + self.class_params['port']) + mock_result = server_mock.query.filter_by.return_value + mock_result.first.return_value = mock_obj + + backup_obj = BackupMessage( + self.class_params['type'], + self.class_params['sid'], + self.class_params['bfile'], + *self.class_params['args'], + **{'database': self.class_params['database']} + ) + + # Check the expected message returned + assert backup_obj.message == self.extected_msg + + # Check the command + obj_details = backup_obj.details(self.class_params['cmd'], self.class_params['args']) + self.assertIn(self.expetced_details_cmd, obj_details) diff --git a/web/pgadmin/tools/backup/tests/test_batch_process.py b/web/pgadmin/tools/backup/tests/test_batch_process.py new file mode 100644 index 0000000..aab5fd1 --- /dev/null +++ b/web/pgadmin/tools/backup/tests/test_batch_process.py @@ -0,0 +1,216 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2018, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## +import sys + +from flask import Response +import simplejson as json + +from pgadmin.misc.bgprocess.processes import BatchProcess, IProcessDesc +from pgadmin.tools.backup import BackupMessage, BACKUP +from pgadmin.utils.route import BaseTestGenerator +from pickle import dumps, loads + +if sys.version_info < (3, 3): + from mock import patch, MagicMock +else: + from unittest.mock import patch, MagicMock + + +class BatchProcessTest(BaseTestGenerator): + """Test the BatchProcess class""" + scenarios = [ + ('When backup server', + dict( + class_params=dict( + type=BACKUP.SERVER, + sid=1, + name='test_backup_server', + port=5444, + host='localhost', + database='postgres', + username='postgres', + bfile='test_backup', + args=[ + '--file', + "backup_file", + '--host', + "localhost", + '--port', + "5444", + '--username', + "postgres", + '--no-password', + '--database', + "postgres" + ], + cmd='backup_server' + ) + )), + ('When backup globals', + dict( + class_params=dict( + type=BACKUP.GLOBALS, + sid=1, + name='test_backup_server', + port=5444, + host='localhost', + database='postgres', + username='postgres', + bfile='test_backup', + args=[ + '--file', + "backup_file", + '--host', + "localhost", + '--port', + "5444", + '--username', + "postgres", + '--no-password', + '--database', + "postgres" + ], + cmd='backup' + ) + )), + ('When backup object', + dict( + class_params=dict( + type=BACKUP.OBJECT, + sid=1, + name='test_backup_server', + port=5444, + host='localhost', + database='postgres', + username='postgres', + bfile='test_backup', + args=[ + '--file', + "backup_file", + '--host', + "localhost", + '--port', + "5444", + '--username', + "postgres", + '--no-password', + '--database', + "postgres" + ], + cmd='backup' + ) + )) + ] + + @patch('pgadmin.misc.bgprocess.processes.Popen') + @patch('pgadmin.misc.bgprocess.processes.current_app') + @patch('pgadmin.misc.bgprocess.processes.db') + @patch('pgadmin.tools.backup.Server') + @patch('pgadmin.tools.backup.current_user') + @patch('pgadmin.misc.bgprocess.processes.current_user') + def runTest(self, current_user_mock, current_user, server_mock, db_mock, + current_app_mock, popen_mock): + current_user.id = 1 + current_user_mock.id = 1 + current_app_mock.PGADMIN_RUNTIME = False + + class TestMockServer(): + def __init__(self, name, host, port): + self.name = name + self.host = host + self.port = port + + def db_session_add_mock(j): + cmd_obj = loads(j.desc) + assert isinstance(cmd_obj, IProcessDesc) + self.assertEquals(cmd_obj.backup_type, self.class_params['type']) + self.assertEquals(cmd_obj.bfile, self.class_params['bfile']) + self.assertEquals(cmd_obj.database, self.class_params['database']) + self.assertEquals(cmd_obj.cmd, ' --file "backup_file" --host "{0}" ' + '--port "{1}" --username "{2}" ' + '--no-password --database "{3}"'.format( + self.class_params['host'], self.class_params['port'], + self.class_params['username'], self.class_params['database'] + )) + + mock_obj = TestMockServer(self.class_params['name'], + self.class_params['host'], + self.class_params['port']) + mock_result = server_mock.query.filter_by.return_value + mock_result.first.return_value = mock_obj + + db_mock.session.add.side_effect = db_session_add_mock + + backup_obj = BackupMessage( + self.class_params['type'], + self.class_params['sid'], + self.class_params['bfile'], + *self.class_params['args'], + **{'database': self.class_params['database']} + ) + + p = BatchProcess( + desc=backup_obj, + cmd=self.class_params['cmd'], + args=self.class_params['args'] + ) + + # Check that _create_process has been called + assert db_mock.session.add.called + + # Check start method + self._check_start(popen_mock, p) + + # Check list method + self._check_list(p, backup_obj) + + def _check_start(self, popen_mock, p): + cmd_test = self.class_params['cmd'] + + class popenMockSideEffect(): + def __init__(self, cmd, **kwargs): + assert cmd_test in cmd + assert 'env' in kwargs + + def poll(self): + pass + + popen_mock.side_effect = popenMockSideEffect + p.start() + + assert popen_mock.called + + @patch('pgadmin.misc.bgprocess.processes.Process') + @patch('pgadmin.misc.bgprocess.processes.BatchProcess.update_process_info') + def _check_list(self, p, backup_obj, update_process_info_mock, process_mock): + class TestMockProcess(): + def __init__(self, desc, args, cmd): + self.pid = 1 + self.exit_code = 1 + self.start_time = '2018-04-17 06:18:56.315445 +0000' + self.end_time = None + self.desc = dumps(desc) + self.arguments = " ".join(args) + self.command = cmd + self.acknowledge = None + + process_mock.query.filter_by.return_value = [TestMockProcess(backup_obj, + self.class_params['args'], + self.class_params['cmd'])] + + update_process_info_mock.return_value = [True, True] + + ret_value = p.list() + self.assertEqual(1, len(ret_value)) + assert 'details' in ret_value[0] + assert 'desc' in ret_value[0] + + + + diff --git a/web/pgadmin/tools/backup/tests/test_create_backup_job.py b/web/pgadmin/tools/backup/tests/test_create_backup_job.py new file mode 100644 index 0000000..6c6e58e --- /dev/null +++ b/web/pgadmin/tools/backup/tests/test_create_backup_job.py @@ -0,0 +1,371 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2018, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## +import sys +import time +import random +import config + +from flask import Response +import simplejson as json +from pickle import dumps, loads + + +from pgadmin.misc.bgprocess.processes import BatchProcess, IProcessDesc +from pgadmin.tools.backup import BackupMessage, BACKUP +from pgadmin.utils.route import BaseTestGenerator +from regression import parent_node_dict +from regression.python_test_utils import test_utils as utils +from pgadmin.utils import server_utils as server_utils +from pgadmin.browser.server_groups.servers.databases.tests import utils as \ + database_utils + +from flask import current_app + +if sys.version_info < (3, 3): + from mock import patch, MagicMock +else: + from unittest.mock import patch, MagicMock + + +class BackupJobTest(BaseTestGenerator): + """Backup api test cases""" + scenarios = [ + ('When backup the object with the default options', + dict( + params=dict( + file='test_backup_file', + format='custom', + verbose=True, + blobs=True, + schemas=[], + tables=[], + database='postgres' + ), + url='/backup/job/{0}/object', + expected_cmd_opts=['--verbose', '--format=c', '--blobs'], + not_expected_cmd_opts=[], + expected_exit_code=0 + )), + ('When backup the object with option sections to all data', + dict( + params=dict( + file='test_backup_file', + format='custom', + verbose=True, + schemas=[], + tables=[], + database='postgres', + data=True, + pre_data=True, + post_data=True + ), + url='/backup/job/{0}/object', + expected_cmd_opts=['--verbose', '--format=c', + '--section=pre-data', '--section=data', + '--section=post-data'], + not_expected_cmd_opts=[], + expected_exit_code=0 + + )), + ('When backup the object with option only_data', + dict( + params=dict( + file='test_backup_file', + format='plain', + verbose=True, + schemas=[], + tables=[], + database='postgres', + only_data=True, + only_schema=False + ), + url='/backup/job/{0}/object', + expected_cmd_opts=['--verbose', '--format=p', '--data-only'], + not_expected_cmd_opts=[], + expected_exit_code=0 + )), + ('When backup the object with option only_data', + dict( + params=dict( + file='test_backup_file', + format='plain', + verbose=True, + schemas=[], + tables=[], + database='postgres', + only_data=True, + only_schema=True, + dns_owner=True + ), + url='/backup/job/{0}/object', + expected_cmd_opts=['--verbose', '--format=p', '--data-only'], + not_expected_cmd_opts=['--schema-only', '--no-owner'], + expected_exit_code=0 + )), + ('When backup the object with option only_schema', + dict( + params=dict( + file='test_backup_file', + format='plain', + verbose=True, + schemas=[], + tables=[], + database='postgres', + only_data=False, + only_schema=True + ), + url='/backup/job/{0}/object', + expected_cmd_opts=['--verbose', '--format=p', '--schema-only'], + not_expected_cmd_opts=[], + expected_exit_code=0 + )), + ('When backup the object with option - format plain and dns_owner', + dict( + params=dict( + file='test_backup_file', + format='plain', + verbose=True, + schemas=[], + tables=[], + database='postgres', + dns_owner=True + ), + url='/backup/job/{0}/object', + expected_cmd_opts=['--verbose', '--format=p', '--no-owner'], + not_expected_cmd_opts=[], + expected_exit_code=0 + )), + ('When backup the object with option - Do not save privilege,' + ' tablespace, unlogged table data', + dict( + params=dict( + file='test_backup_file', + format='custom', + verbose=True, + schemas=[], + tables=[], + database='postgres', + dns_privilege=True, + dns_unlogged_tbl_data=True, + dns_tablespace=True + ), + url='/backup/job/{0}/object', + expected_cmd_opts=['--no-privileges', + '--no-tablespaces', + '--no-unlogged-table-data'], + not_expected_cmd_opts=[], + expected_exit_code=0 + )), + ('When backup the object with option - all queries', + dict( + params=dict( + file='test_backup_file', + format='plain', + verbose=True, + schemas=[], + tables=[], + database='postgres', + use_column_inserts=True, + include_create_database=True, + use_insert_commands=True, + include_drop_database=True + ), + url='/backup/job/{0}/object', + expected_cmd_opts=['--create', '--clean', '--inserts', + '--column-inserts'], + not_expected_cmd_opts=[], + expected_exit_code=0 + )), + ('When backup the object with option - all queries and format custom', + dict( + params=dict( + file='test_backup_file', + format='custom', + verbose=True, + schemas=[], + tables=[], + database='postgres', + use_column_inserts=True, + include_create_database=True, + use_insert_commands=True, + include_drop_database=True + ), + url='/backup/job/{0}/object', + expected_cmd_opts=['--inserts', + '--column-inserts'], + not_expected_cmd_opts=['--create', '--clean'], + expected_exit_code=0 + )), + ('When backup the object with option - miscellaneous', + dict( + params=dict( + file='test_backup_file', + format='custom', + verbose=True, + schemas=[], + tables=[], + database='postgres', + disable_quoting=True, + use_set_session_auth=True, + with_oids=True, + dqoute=True + ), + url='/backup/job/{0}/object', + expected_cmd_opts=['--verbose', '--quote-all', + '--disable-dollar-quoting', '--oids', + '--use-set-session-authorization'], + not_expected_cmd_opts=[], + expected_exit_code=0 + )), + ('When backup the object with format tar', + dict( + params=dict( + file='test_backup_file', + format='tar', + verbose=True, + schemas=[], + tables=[], + database='postgres', + blobs=True, + ), + url='/backup/job/{0}/object', + expected_cmd_opts=['--verbose', + '--blobs', + '--format=t'], + not_expected_cmd_opts=[], + expected_exit_code=0 + )), + ('When backup the server', + dict( + params=dict( + file='test_backup_server_file', + dqoute=False, + verbose=True, + type='server' + ), + url='/backup/job/{0}', + expected_cmd_opts=['--verbose'], + not_expected_cmd_opts=[], + expected_exit_code=None + )), + ('When backup globals', + dict( + params=dict( + file='test_backup_global_file', + dqoute=False, + verbose=True, + type='globals' + ), + url='/backup/job/{0}', + expected_cmd_opts=['--verbose'], + not_expected_cmd_opts=[], + expected_exit_code=None + )) + ] + + def runTest(self): + self.db_name = '' + self.server_id = parent_node_dict["server"][-1]["server_id"] + server_response = server_utils.connect_server(self, self.server_id) + if server_response["info"] == "Server connected.": + db_owner = server_response['data']['user']['name'] + self.data = database_utils.get_db_data(db_owner) + self.db_name = self.data['name'] + + url = self.url.format(self.server_id) + + # Create the backup job + response = self.tester.post(url, + data=json.dumps(self.params), + content_type='html/json') + self.assertEquals(response.status_code, 200) + response_data = json.loads(response.data.decode('utf-8')) + job_id = response_data['data']['job_id'] + + cnt = 0 + while 1: + if cnt > 1: + break + # Check the process list + response1 = self.tester.get('/misc/bgprocess/?_='.format(random.randint(1, 9999999))) + self.assertEquals(response1.status_code, 200) + process_list = json.loads(response1.data.decode('utf-8')) + + if len(process_list) > 0 and 'execution_time' in process_list[0]: + break + time.sleep(0.5) + cnt += 1 + + assert 'execution_time' in process_list[0] + assert 'stime' in process_list[0] + assert 'exit_code' in process_list[0] + self.assertEqual(self.expected_exit_code, process_list[0]['exit_code']) + if self.expected_cmd_opts: + for opt in self.expected_cmd_opts: + self.assertIn(opt, process_list[0]['details']) + if self.not_expected_cmd_opts: + for opt in self.not_expected_cmd_opts: + self.assertNotIn(opt, process_list[0]['details']) + + + # Check the process details + p_details = self.tester.get('/misc/bgprocess/{0}?_='.format( + job_id, random.randint(1, 9999999)) + ) + self.assertEquals(p_details.status_code, 200) + p_details_data = json.loads(p_details.data.decode('utf-8')) + + p_details = self.tester.get('/misc/bgprocess/{0}/{1}/{2}/?_='.format( + job_id, 0, 0, random.randint(1, 9999999)) + ) + self.assertEquals(p_details.status_code, 200) + p_details_data = json.loads(p_details.data.decode('utf-8')) + + # Retrieve the backup job process logs + while 1: + out, err, status = BackupJobTest.get_params(p_details_data) + if status: + break + + p_details = self.tester.get('/misc/bgprocess/{0}/{1}/{2}/?_={3}'.format( + job_id, out, err, random.randint(1, 9999999)) + ) + self.assertEquals(p_details.status_code, 200) + p_details_data = json.loads(p_details.data.decode('utf-8')) + + time.sleep(1) + + # Check the job is complete. + backup_ack = self.tester.put('/misc/bgprocess/{0}'.format(job_id)) + self.assertEquals(backup_ack.status_code, 200) + backup_ack_res = json.loads(backup_ack.data.decode('utf-8')) + + self.assertEquals(backup_ack_res['success'], 1) + + @staticmethod + def get_params(data): + out = 0 + out_done = False + err = 0 + err_done = False + if 'out' in data: + out = data['out'] and data['out']['pos'] + + if 'done' in data['out']: + out_done = data['out']['done'] + + if 'err' in data: + err = data['err'] and data['err']['pos'] + + if 'done' in data['err']: + err_done = data['err']['done'] + + return out, err, (out_done and err_done) + + diff --git a/web/regression/runtests.py b/web/regression/runtests.py index 1de9823..99846c1 100644 --- a/web/regression/runtests.py +++ b/web/regression/runtests.py @@ -72,6 +72,13 @@ config.UPGRADE_CHECK_ENABLED = False pgadmin_credentials = test_setup.config_data +config.DEFAULT_BINARY_PATHS = { + "pg": pgadmin_credentials['DEFAULT_BINARY_PATHS']['pg'], + "ppas": pgadmin_credentials['DEFAULT_BINARY_PATHS']['ppas'], + "gpdb": "" +} + + # Set environment variables for email and password os.environ['PGADMIN_SETUP_EMAIL'] = '' os.environ['PGADMIN_SETUP_PASSWORD'] = '' @@ -112,6 +119,9 @@ test_client = app.test_client() driver = None app_starter = None handle_cleanup = None +app.PGADMIN_RUNTIME = True +if config.SERVER_MODE is True: + app.PGADMIN_RUNTIME = False setattr(unit_test.result.TestResult, "passed", []) diff --git a/web/regression/test_config.json.in b/web/regression/test_config.json.in index 47f8499..41f4dd0 100644 --- a/web/regression/test_config.json.in +++ b/web/regression/test_config.json.in @@ -28,5 +28,9 @@ { "comment": "This is test update comment" } - ] + ], + "DEFAULT_BINARY_PATHS": { + "pg": "", + "ppas": "" + } }