diff --git a/web/pgadmin/feature_tests/datatype_test.json b/web/pgadmin/feature_tests/datatype_test.json new file mode 100644 index 000000000..8ded72896 --- /dev/null +++ b/web/pgadmin/feature_tests/datatype_test.json @@ -0,0 +1,173 @@ +[ + { + "datatype": [ + "smallint", + "smallint", + "integer", + "integer", + "bigint", + "bigint", + "decimal", + "decimal", + "numeric", + "numeric", + "float[]", + "float[]", + "real", + "real[]", + "bytea", + "bytea[]" + ], + "input":[ + "-32767", + "32767", + "-2147483647", + "2147483647", + "-9223372036854775807", + "9223372036854775807", + "922337203685.4775807", + "92203685.477", + "922337203685.922337203685", + "-92233720368547758.08", + "ARRAY[1, 2, 3]", + "ARRAY['nan', 'nan', 'nan']", + "'Infinity'", + "'{Infinity}'", + "'E\\\\xDEADBEEF'", + "ARRAY['E\\\\xDEADBEEF', 'E\\\\xDEADBEEF']" + ], + "output":[ + "-32767", + "32767", + "-2147483647", + "2147483647", + "-9223372036854775807", + "9223372036854775807", + "922337203685.4775807", + "92203685.477", + "922337203685.922337203685", + "-92233720368547758.08", + "{1,2,3}", + "{NaN,NaN,NaN}", + "Infinity", + "{Infinity}", + "[binary data]", + "[binary data[]]" + ] + }, + { + "datatype": [ + "int4range", + "int8range", + "numrange", + "daterange", + "tsrange", + "tstzrange", + "int4range[]", + "int8range[]", + "numrange[]", + "daterange[]", + "tsrange[]", + "tstzrange[]", + "int8range[]", + "daterange[]", + "tstzrange[]", + "", + "" + ], + "input":[ + "'(1,2147483647)'", + "'(2,9223372036854775807)'", + "'(3,922337203685.922337203685]'", + "'(2010-01-01, 2010-02-01]'", + "'[2010-01-01 14:00, 2010-04-01 15:00)'", + "'[2010-01-01 14:00:00{tz}, 2010-02-01 15:00:00{tz})'", + "'{\"(1,2147483647)\", \"(2,2147483647)\"}'", + "'{\"(2,9223372036854775807)\", \"(2,9223372036854775807)\"}'", + "'{\"(3,922337203685.922337203685]\", \"(5,922337203685.922337203685]\"}'", + "'{\"(2010-01-01, 2010-02-01]\", \"(2010-01-01, 2010-02-01]\"}'", + "'{\"[2010-01-01 14:00, 2010-04-01 15:00)\", \"[2010-01-01 14:00, 2010-04-01 15:00)\"}'", + "'{{\"[2010-01-01 14:00:00{tz}, 2010-02-01 15:00:00{tz})\", \"[2017-01-12 14:00:00{tz}, 2017-02-28 15:00:00{tz})\"}}'", + "'{{\"(2,9223372036854775807)\", \"(2,9223372036854775807)\"},{\"(2,9223372036854775807)\", \"(2,9223372036854775807)\"}}'", + "'{{\"(2010-01-01, 2010-02-01]\", \"(2010-01-01, 2010-02-01]\"},{\"(2010-01-01, 2010-02-01]\", \"(2010-01-01, 2010-02-01]\"}}'", + "'{{{{\"[2010-01-01 14:00:00{tz}, 2010-02-01 15:00:00{tz})\", \"[2017-01-12 14:00:00{tz}, 2017-02-28 15:00:00{tz})\"}}, {{\"[2010-01-01 14:00:00{tz}, 2010-02-01 15:00:00{tz})\", \"[2017-01-12 14:00:00{tz}, 2017-02-28 15:00:00{tz})\"}}}}'", + "enum_range(NULL::rainbow)", + "ARRAY[enum_range(NULL::rainbow), enum_range(NULL::rainbow)]" + ], + "output":[ + "[2,2147483647)", + "[3,9223372036854775807)", + "(3,922337203685.922337203685]", + "[2010-01-02,2010-02-02)", + "[\"2010-01-01 14:00:00\",\"2010-04-01 15:00:00\")", + "[\"2010-01-01 14:00:00{tz}\",\"2010-02-01 15:00:00{tz}\")", + "{\"[2,2147483647)\",\"[3,2147483647)\"}", + "{\"[3,9223372036854775807)\",\"[3,9223372036854775807)\"}", + "{\"(3,922337203685.922337203685]\",\"(5,922337203685.922337203685]\"}", + "{\"[2010-01-02,2010-02-02)\",\"[2010-01-02,2010-02-02)\"}", + "{\"[\\\"2010-01-01 14:00:00\\\",\\\"2010-04-01 15:00:00\\\")\",\"[\\\"2010-01-01 14:00:00\\\",\\\"2010-04-01 15:00:00\\\")\"}", + "{{\"[\\\"2010-01-01 14:00:00{tz}\\\",\\\"2010-02-01 15:00:00{tz}\\\")\",\"[\\\"2017-01-12 14:00:00{tz}\\\",\\\"2017-02-28 15:00:00{tz}\\\")\"}}", + "{{\"[3,9223372036854775807)\",\"[3,9223372036854775807)\"},{\"[3,9223372036854775807)\",\"[3,9223372036854775807)\"}}", + "{{\"[2010-01-02,2010-02-02)\",\"[2010-01-02,2010-02-02)\"},{\"[2010-01-02,2010-02-02)\",\"[2010-01-02,2010-02-02)\"}}", + "{{{{\"[\\\"2010-01-01 14:00:00{tz}\\\",\\\"2010-02-01 15:00:00{tz}\\\")\",\"[\\\"2017-01-12 14:00:00{tz}\\\",\\\"2017-02-28 15:00:00{tz}\\\")\"}},{{\"[\\\"2010-01-01 14:00:00{tz}\\\",\\\"2010-02-01 15:00:00{tz}\\\")\",\"[\\\"2017-01-12 14:00:00{tz}\\\",\\\"2017-02-28 15:00:00{tz}\\\")\"}}}}", + "{red,orange,yellow,green,blue,purple}", + "{{red,orange,yellow,green,blue,purple},{red,orange,yellow,green,blue,purple}}" + ] + }, + { + "datatype": [ + "inet", + "inet[]", + "inet[]", + "cidr", + "cidr[]", + "cidr[]", + "uuid", + "uuid[]", + "xml", + "xml[]", + "bit", + "bit[]", + "varbit", + "varbit[]", + "macaddr", + "macaddr[]" + ], + "input":[ + "'::2'", + "'{\"::2\",\"192.168.1.1/16\",\"FFF0:0:007a::\"}'", + "'{{\"::2\",\"192.168.1.1/16\",\"FFF0:0:007a::\"},{\"::2\",\"192.168.1.1/16\",\"FFF0:0:007a::\"}}'", + "'::1'", + "'{\"::1\", \"192.168.100.128/25\", \"FFF0:0:007a::\"}'", + "'{{\"::1\", \"192.168.100.128/25\", \"FFF0:0:007a::\"},{\"::1\", \"192.168.100.128/25\", \"FFF0:0:007a::\"}}'", + "'e1ab7b6d-a62d-4bee-b0ce-b8488f83d89c'", + "'{55f8e502-e0b4-11e7-80c1-9a214cf093ae, e1ab7b6d-a62d-4bee-b0ce-b8488f83d89c}'", + "'pgAdmin 4'", + "'{\"pgAdmin 4\", \"pgAdmin 4\"}'", + "'1'", + "'{0,1}'", + "'1001'", + "'{10010,1011}'", + "'08:00:2b:01:02:03'", + "'{08:00:2b:01:02:03, 08-00-2b-01-02-03, 08002b:010203, 08002b-010203, 0800.2b01.0203, 0800-2b01-0203, 08002b010203}'" + ], + "output":[ + "::2", + "{::2,192.168.1.1/16,fff0:0:7a::}", + "{{::2,192.168.1.1/16,fff0:0:7a::},{::2,192.168.1.1/16,fff0:0:7a::}}", + "::1/128", + "{::1/128,192.168.100.128/25,fff0:0:7a::/128}", + "{{::1/128,192.168.100.128/25,fff0:0:7a::/128},{::1/128,192.168.100.128/25,fff0:0:7a::/128}}", + "e1ab7b6d-a62d-4bee-b0ce-b8488f83d89c", + "{55f8e502-e0b4-11e7-80c1-9a214cf093ae,e1ab7b6d-a62d-4bee-b0ce-b8488f83d89c}", + "pgAdmin 4", + "{\"pgAdmin 4\",\"pgAdmin 4\"}", + "1", + "{0,1}", + "1001", + "{10010,1011}", + "08:00:2b:01:02:03", + "{08:00:2b:01:02:03,08:00:2b:01:02:03,08:00:2b:01:02:03,08:00:2b:01:02:03,08:00:2b:01:02:03,08:00:2b:01:02:03,08:00:2b:01:02:03}" + ] + } +] \ No newline at end of file diff --git a/web/pgadmin/feature_tests/pg_datatype_validation_test.py b/web/pgadmin/feature_tests/pg_datatype_validation_test.py index 75993a491..b1b10a81c 100644 --- a/web/pgadmin/feature_tests/pg_datatype_validation_test.py +++ b/web/pgadmin/feature_tests/pg_datatype_validation_test.py @@ -7,14 +7,27 @@ # ########################################################################## +import os +import json +import time from selenium.common.exceptions import TimeoutException from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By +from selenium.webdriver import ActionChains from regression.python_test_utils import test_utils from regression.feature_utils.base_feature_test import BaseFeatureTest +CURRENT_PATH = os.path.dirname(os.path.realpath(__file__)) + +try: + with open(CURRENT_PATH + '/datatype_test.json') as data_file: + config_data = json.load(data_file) +except Exception as e: + print(str(e)) + + class PGDataypeFeatureTest(BaseFeatureTest): """ This feature test will test the different Postgres @@ -32,9 +45,74 @@ class PGDataypeFeatureTest(BaseFeatureTest): self.server['host'], self.server['port'], self.server['sslmode']) + + self.timezone = int(test_utils.get_timezone_without_dst(connection)) + + if abs(self.timezone) % 3600 > 0: + hh_mm = '%H:%M' + else: + hh_mm = '%H' + + self.timezone_hh_mm = time.strftime( + hh_mm, time.gmtime(abs(self.timezone))) + + if self.timezone < 0: + self.timezone_hh_mm = '-{}'.format(self.timezone_hh_mm) + else: + self.timezone_hh_mm = '+{}'.format(self.timezone_hh_mm) + test_utils.drop_database(connection, "acceptance_test_db") test_utils.create_database(self.server, "acceptance_test_db") + # For this test case we need to set "Insert bracket pairs?" + # SQL Editor preference to 'false' to avoid codemirror + # to add matching closing bracket by it self. + self._update_preferences() + + def _update_preferences(self): + self.page.find_by_id("mnu_file").click() + self.page.find_by_id("mnu_preferences").click() + wait = WebDriverWait(self.page.driver, 10) + + wait.until(EC.presence_of_element_located( + (By.XPATH, "//*[contains(string(), 'Show system objects?')]")) + ) + + self.page.find_by_css_selector(".ajs-maximize").click() + + sql_editor = self.page.find_by_xpath( + "//*[contains(@class,'aciTreeLi') and contains(.,'SQL Editor')]") + + sql_editor.find_element_by_xpath( + "//*[contains(@class,'aciTreeText') and contains(.,'Options')]")\ + .click() + + insert_bracket_pairs_control= self.page.find_by_xpath( + "//div[contains(@class,'pgadmin-control-group') and contains(.,'Insert bracket pairs?')]") + + switch_btn = insert_bracket_pairs_control.\ + find_element_by_class_name('bootstrap-switch') + + # check if switch is on then only toggle. + if 'bootstrap-switch-on' in switch_btn.get_attribute('class'): + switch_btn.click() + + # save and close the preference dialog. + self.page.find_by_xpath( + "//*[contains(@class,'btn-primary') and contains(.,'OK')]").click() + + self.page.wait_for_element_to_disappear( + lambda driver: driver.find_element_by_css_selector(".ajs-modal") + ) + + def _create_enum_type(self): + query = """CREATE TYPE public.rainbow AS ENUM ('red', 'orange', + 'yellow','green','blue','purple'); + """ + self.page.fill_codemirror_area_with(query) + self.page.find_by_id("btn-flash").click() + self._clear_query_tool() + def runTest(self): self.page.wait_for_spinner_to_disappear() self.page.add_server(self.server) @@ -60,59 +138,87 @@ class PGDataypeFeatureTest(BaseFeatureTest): self.page.toggle_open_tree_item('acceptance_test_db') def _check_datatype(self): - query = r"SELECT -32767::smallint, 32767::smallint," \ - r"-2147483647::integer, 2147483647::integer," \ - r"9223372036854775807::bigint, 9223372036854775807::bigint," \ - r"922337203685.4775807::decimal, 92203685.477::decimal," \ - r"922337203685.922337203685::numeric, " \ - r"-92233720368547758.08::numeric," \ - r"ARRAY[1, 2, 3]::float[], ARRAY['nan', 'nan', 'nan']::float[]," \ - r"'Infinity'::real, '{Infinity}'::real[]," \ - r"E'\\xDEADBEEF'::bytea, ARRAY[E'\\xDEADBEEF', E'\\xDEADBEEF']::bytea[];" - - expected_output = [ - '-32767', '32767', '-2147483647', '2147483647', - '9223372036854775807', '9223372036854775807', - '922337203685.4775807', '92203685.477', - '922337203685.922337203685', '-92233720368547758.08', - '{1,2,3}', '{NaN,NaN,NaN}', - 'Infinity', '{Infinity}', - 'binary data', 'binary data[]' - ] - + # Slick grid does not render all the column if viewport is not enough + # wide. So execute test as batch of queries. self.page.open_query_tool() - self.page.fill_codemirror_area_with(query) - self.page.find_by_id("btn-flash").click() - wait = WebDriverWait(self.page.driver, 5) + self._create_enum_type() + for batch in config_data: + query = self.construct_select_query(batch) + self.page.fill_codemirror_area_with(query) + self.page.find_by_id("btn-flash").click() + wait = WebDriverWait(self.page.driver, 5) - canvas = wait.until(EC.presence_of_element_located( - (By.CSS_SELECTOR, "#datagrid .slick-viewport .grid-canvas")) - ) - - # For every sample data-type value, check the expected output. - cnt = 2 - cells = canvas.find_elements_by_css_selector('.slick-cell') - # remove first element as it is row number. - cells.pop(0) - for val, cell in zip(expected_output, cells): - try: - source_code = cell.text - - PGDataypeFeatureTest.check_result( - source_code, - expected_output[cnt - 2] - ) - cnt += 1 - except TimeoutException: - assert False, "{0} does not match with {1}".format( - val, expected_output[cnt] - ) - - @staticmethod - def check_result(source_code, string_to_find): - assert source_code.find(string_to_find) != -1,\ - "{0} does not match with {1}".format( - source_code, string_to_find + wait.until(EC.presence_of_element_located( + (By.XPATH, + "//*[contains(@class,'column-type') and contains(.,'{}')]".format(batch['datatype'][0]) + )) ) + canvas = wait.until(EC.presence_of_element_located( + (By.CSS_SELECTOR, "#datagrid .slick-viewport .grid-canvas")) + ) + # For every sample data-type value, check the expected output. + cnt = 2 + cells = canvas.find_elements_by_css_selector('.slick-cell') + # remove first element as it is row number. + cells.pop(0) + for val, cell, datatype in zip(batch['output'], cells, batch['datatype']): + expected_output = batch['output'][cnt - 2] + + if datatype in ('tstzrange', 'tstzrange[]'): + expected_output = expected_output.format( + **dict([('tz', self.timezone_hh_mm)])) + try: + source_code = cell.text + PGDataypeFeatureTest.check_result( + datatype, + source_code, + expected_output + ) + + cnt += 1 + except TimeoutException: + assert False,\ + "for datatype {0}\n{1} does not match with {2}".format( + datatype, val, expected_output + ) + self._clear_query_tool() + + def construct_select_query(self, batch): + query = 'SELECT ' + first = True + for datatype, inputdata in zip(batch['datatype'], batch['input']): + if datatype != '': + dataformatter = '{}::{}' + else: + dataformatter = '{}' + + if datatype in ('tstzrange', 'tstzrange[]'): + inputdata = inputdata.format( + **dict([('tz', self.timezone_hh_mm)])) + if first: + query += dataformatter.format(inputdata, datatype) + else: + query += ','+dataformatter.format(inputdata, datatype) + first = False + return query + ';' + + @staticmethod + def check_result(datatype, source_code, string_to_find): + assert source_code == string_to_find,\ + "for datatype {0}\n{1} does not match with {2}".format( + datatype, source_code, string_to_find + ) + + def _clear_query_tool(self): + self.page.click_element( + self.page.find_by_xpath("//*[@id='btn-clear-dropdown']") + ) + ActionChains(self.driver)\ + .move_to_element(self.page.find_by_xpath("//*[@id='btn-clear']"))\ + .perform() + self.page.click_element( + self.page.find_by_xpath("//*[@id='btn-clear']") + ) + self.page.click_modal('Yes') diff --git a/web/regression/feature_utils/pgadmin_page.py b/web/regression/feature_utils/pgadmin_page.py index 26e10cd23..255842f83 100644 --- a/web/regression/feature_utils/pgadmin_page.py +++ b/web/regression/feature_utils/pgadmin_page.py @@ -226,6 +226,19 @@ class PgadminPage: return self._wait_for("element to exist", element_if_it_exists) + def wait_for_element_to_disappear(self, find_method_with_args): + def element_if_it_disappears(driver): + try: + element = find_method_with_args(driver) + if element.is_displayed() and element.is_enabled(): + return False + + return True + except NoSuchElementException: + return True + + return self._wait_for("element to disappear", element_if_it_disappears) + def wait_for_reloading_indicator_to_disappear(self): def reloading_indicator_has_disappeared(driver): try: diff --git a/web/regression/python_test_utils/test_utils.py b/web/regression/python_test_utils/test_utils.py index af52b9db3..668742575 100644 --- a/web/regression/python_test_utils/test_utils.py +++ b/web/regression/python_test_utils/test_utils.py @@ -712,3 +712,21 @@ class Database: def __exit__(self, type, value, traceback): self.connection.close() drop_database(self.maintenance_connection, self.name) + + +def get_timezone_without_dst(connection): + """ + Returns timezone when daylight savings is not observed. + DST starts at mid of March and ends on first week of November. + So when getting timezone without dst use date (2017-01-01) which do not + fall in dst range. + """ + + timezone_no_dst_sql = """SELECT EXTRACT( + TIMEZONE FROM '2017-01-01 00:00:00'::timestamp with time zone);""" + + pg_cursor = connection.cursor() + + pg_cursor.execute(timezone_no_dst_sql) + + return pg_cursor.fetchone()[0]