diff --git a/.github/workflows/testing-pipline.yml b/.github/workflows/testing-pipline.yml new file mode 100644 index 000000000..275a265ea --- /dev/null +++ b/.github/workflows/testing-pipline.yml @@ -0,0 +1,142 @@ +name: Integration-test + +on: + push: + branches: + - main + paths: + - "CodeListLibrary_project/**" + pull_request: + paths: + - "CodeListLibrary_project/**" + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +jobs: + pre-deploy-test: + env: + working-directory: ./concept-library + runs-on: + labels: [self-hosted, linux, x64] + group: heavy + strategy: + max-parallel: 4 + matrix: + python-version: [3.11] + + services: + db: + image: postgres + env: + POSTGRES_DB: concept_library + POSTGRES_USER: clluser_test + POSTGRES_PASSWORD: password + ports: + - 5432:5432 + options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 + + steps: + - uses: actions/checkout@v3 + - run: | + sudo apt-get update + sudo apt-get install -y libgconf-2-4 libatk1.0-0 libatk-bridge2.0-0 libgdk-pixbuf2.0-0 libgtk-3-0 libgbm-dev libnss3-dev libxss-dev libasound2 + wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | sudo apt-key add - + - uses: browser-actions/setup-chrome@v1 + - uses: actions/cache@v3 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('docker/requirements/test-requirements.txt') }} + restore-keys: | + ${{ runner.os }}-pip- + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} + + - name: Install LDAP files + run: | + sudo apt-get install -y -q libsasl2-dev libldap2-dev libssl-dev libpq-dev + + - name: Install Dependencies + run: | + python -m pip install --upgrade pip + pip install --upgrade --upgrade-strategy eager -r docker/requirements/test-requirements.txt + + - name: Prepare Selenium + uses: nanasess/setup-chromedriver@v2 + - name: Run Browser + run: | + chrome --version + - name: Run chromedriver + run: | + export DISPLAY=:99 + chromedriver --url-base=/wd/hub & + sudo Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 & # optional + + - name: Run application + run: | + export $(grep -v '^#' docker/selenium-testing/env/remotetest.compose.env | xargs) + cd CodeListLibrary_project + python manage.py makemigrations + python manage.py migrate + python manage.py runserver 0.0.0.0:8000 > /dev/null & + + - name: Run tests + run: | + export $(grep -v '^#' docker/selenium-testing/env/remotetest.compose.env | xargs) + cd CodeListLibrary_project + pytest -v -s --cov-report xml --cov . --html=report.html --self-contained-html --alluredir=./clinicalcode/tests/allure-results + env: + DISPLAY: ":99" + + - name: Coverage report + if: always() + uses: 5monkeys/cobertura-action@v13 + with: + path: CodeListLibrary_project/coverage.xml + minimum_coverage: 25 + + - name: Upload Test Results + if: always() + uses: actions/upload-artifact@v3 + with: + name: test-results + path: CodeListLibrary_project/report.html + retention-days: 5 + + - name: Get Allure history + uses: actions/checkout@v3 + if: (success() || failure()) && github.event_name != 'pull_request' + continue-on-error: true + with: + ref: gh-pages + path: gh-pages + + - name: Allure report action # Step to generate Allure report + if: (success() || failure()) && github.event_name != 'pull_request' + uses: simple-elf/allure-report-action@master + with: + allure_results: CodeListLibrary_project/clinicalcode/tests/allure-results + allure_report: CodeListLibrary_project/clinicalcode/tests/allure-report + allure_history: allure-history + gh_pages: gh-pages + keep_reports: 10 # Specify the number of previous reports to keep + + - name: Upload an artifact for GitHub Pages + uses: actions/upload-pages-artifact@v2 + if: (success() || failure()) && github.event_name != 'pull_request' + with: + name: allure-report + path: CodeListLibrary_project/clinicalcode/tests/allure-report + + - name: Deploy report to gh-pages branch + if: (success() || failure()) && github.event_name != 'pull_request' + uses: peaceiris/actions-gh-pages@v2 + env: + PERSONAL_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PUBLISH_BRANCH: gh-pages + PUBLISH_DIR: allure-history + keep_files: true diff --git a/.gitignore b/.gitignore index 942ec9476..0479f5296 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,5 @@ docker/CodeListLibrary_project # macOS system files .DS_Stores .idea + +CodeListLibrary_project/clinicalcode/tests/allure-results/ diff --git a/CodeListLibrary_project/clinicalcode/entity_utils/entity_db_utils.py b/CodeListLibrary_project/clinicalcode/entity_utils/entity_db_utils.py index fd68fc948..e9a938f96 100644 --- a/CodeListLibrary_project/clinicalcode/entity_utils/entity_db_utils.py +++ b/CodeListLibrary_project/clinicalcode/entity_utils/entity_db_utils.py @@ -804,3 +804,4 @@ def chk_valid_id(request, set_class, pk, chk_permission=False): ret_id = set_class.objects.get(pk=actual_pk).id return is_valid_id, err, ret_id + diff --git a/CodeListLibrary_project/clinicalcode/tests/conftest.py b/CodeListLibrary_project/clinicalcode/tests/conftest.py new file mode 100644 index 000000000..c6b085575 --- /dev/null +++ b/CodeListLibrary_project/clinicalcode/tests/conftest.py @@ -0,0 +1,125 @@ +from datetime import datetime + +import pytest +from clinicalcode.models import GenericEntity +from django.contrib.auth.models import User, Group +from django.utils.timezone import make_aware + +from selenium.webdriver import Keys +from selenium import webdriver +from selenium.webdriver.common.by import By +from selenium.webdriver.support.wait import WebDriverWait + +from cll.test_settings import WEBAPP_HOST, REMOTE_TEST_HOST, REMOTE_TEST, chrome_options + + +@pytest.fixture +def generate_user(): + su_user = User.objects.create_superuser(username='superuser', password='superpassword', email=None) + nm_user = User.objects.create_user(username='normaluser', password='normalpassword', email=None) + ow_user = User.objects.create_user(username='owneruser', password='ownerpassword', email=None) + gp_user = User.objects.create_user(username='groupuser', password='grouppassword', email=None) + vgp_user = User.objects.create_user(username='viewgroupuser', password='viewgrouppassword', email=None) + egp_user = User.objects.create_user(username='editgroupuser', password='editgrouppassword', email=None) + + users = { + 'super_user': su_user, + 'normal_user': nm_user, + 'owner_user': ow_user, + 'group_user': gp_user, + 'view_group_user': vgp_user, + 'edit_group_user': egp_user, + } + + yield users + + # Clean up the users after the tests are finished + for user in users.values(): + user.delete() + + +@pytest.fixture +def create_groups(): + permitted_group = Group.objects.create(name="permitted_group") + forbidden_group = Group.objects.create(name="forbidden_group") + view_group = Group.objects.create(name="view_group") + edit_group = Group.objects.create(name="edit_group") + + # Yield the created groups so they can be used in tests + yield { + 'permitted_group': permitted_group, + 'forbidden_group': forbidden_group, + 'view_group': view_group, + 'edit_group': edit_group, + } + + # Clean up the groups after the tests are finished + for group in [permitted_group, forbidden_group, view_group, edit_group]: + group.delete() + +@pytest.fixture +def generate_entity(create_groups): + template_data = { + "sex": "3", + "type": "1", + "version": 1, + "phenoflowid": "", + "data_sources": [ + 5 + ], + "coding_system": [], + "agreement_date": "2012-11-23", + "phenotype_uuid": "4", + "event_date_range": "01/01/1999 - 01/07/2016", + "source_reference": "https://portal.caliberresearch.org/phenotypes/archangelidi-heart-rate-6keWsw2mW2TQjDMhNAUETt", + "concept_information": [] + } + generate_entity = GenericEntity.objects.create(name="Test entity", + group=create_groups['permitted_group'], + template_data=template_data,updated=make_aware(datetime.now())) + return generate_entity + + +@pytest.fixture(scope="class") +def setup_webdriver(request): + if REMOTE_TEST: + driver = webdriver.Chrome(options=chrome_options) + else: + driver = webdriver.Remote(command_executor=REMOTE_TEST_HOST, options=chrome_options) + + wait = WebDriverWait(driver, 10) + driver.maximize_window() + request.cls.driver = driver + request.cls.wait = wait + yield + driver.quit() + + +@pytest.fixture(scope="function") +def login(): + def _login(driver, username, password): + driver.get(WEBAPP_HOST + "/account/login/") + username_input = driver.find_element(By.NAME, "username") + password_input = driver.find_element(By.NAME, "password") + + # Input username and password + username_input.send_keys(username) + password_input.send_keys(password) + + # Submit the form by pressing Enter + password_input.send_keys(Keys.ENTER) + + yield _login + + +@pytest.fixture(scope="function") +def logout(): + def _logout(driver): + driver.get(WEBAPP_HOST + "/account/logout/") + yield _logout + + + + + + diff --git a/CodeListLibrary_project/clinicalcode/tests/functional_tests/test_search_filters.py b/CodeListLibrary_project/clinicalcode/tests/functional_tests/test_search_filters.py new file mode 100644 index 000000000..59549de00 --- /dev/null +++ b/CodeListLibrary_project/clinicalcode/tests/functional_tests/test_search_filters.py @@ -0,0 +1,27 @@ +import time + +import pytest +from selenium.webdriver.common.by import By + +from cll.test_settings import WEBAPP_HOST + +@pytest.mark.django_db +@pytest.mark.usefixtures("setup_webdriver") +class TestSearchFilters: + + @pytest.mark.parametrize('user_type', ['super_user']) + def test_tags_filter(self, login, logout, generate_user, user_type): + user = generate_user[user_type] + login(self.driver, user.username, user.password) + + self.driver.get(WEBAPP_HOST +"/phenotypes") + accordian = self.driver.find_element(By.XPATH, "/html/body/main/div/div/aside/div[2]/div[4]") + time.sleep(5) + accordian.click() + checkboxes = self.driver.find_elements(By.XPATH, "//input[(@class='checkbox-item') and (@aria-label = 'Tags')]") + + for checkbox in checkboxes: + assert checkbox.is_enabled() is True + + logout(self.driver) + diff --git a/CodeListLibrary_project/clinicalcode/tests/unit_tests/test_read_only_permmisions.py b/CodeListLibrary_project/clinicalcode/tests/unit_tests/test_read_only_permmisions.py new file mode 100644 index 000000000..12304d903 --- /dev/null +++ b/CodeListLibrary_project/clinicalcode/tests/unit_tests/test_read_only_permmisions.py @@ -0,0 +1,38 @@ +from pytest import mark +from clinicalcode.entity_utils.permission_utils import allowed_to_create, can_user_edit_entity +import pytest +from datetime import datetime + + +@pytest.mark.django_db +class TestReadOnlyPermissions: + + def test_my_user(self, generate_user): + super_user = generate_user['super_user'] + assert super_user.username == 'superuser' + + @pytest.mark.parametrize('user_type', ['super_user']) + def test_genereic_entity(self, generate_entity, generate_user, user_type): + generate_entity.owner = generate_user[user_type] + generate_entity.created_by = generate_user[user_type] + assert generate_entity.name == 'Test entity' + + @pytest.mark.parametrize('user_type', ['super_user', 'owner_user']) + def test_users_to_edit(self, generate_user, generate_entity, user_type): + generate_entity.owner = generate_user[user_type] + generate_entity.created_by = generate_user[user_type] + assert can_user_edit_entity( + None, generate_entity, generate_user[user_type]) == False + + def test_user_not_allowed_to_create(self): + assert allowed_to_create() == False + + def test_wrong_answer(self): + assert allowed_to_create() == True + + def test_wrong_again(): + assert 1 == 2 + + + + diff --git a/CodeListLibrary_project/cll/settings.py b/CodeListLibrary_project/cll/settings.py index a5c946eb9..cc94070a8 100644 --- a/CodeListLibrary_project/cll/settings.py +++ b/CodeListLibrary_project/cll/settings.py @@ -21,6 +21,7 @@ ''' Utilities ''' + def strtobool(val): """ Converts str() to bool() @@ -34,14 +35,17 @@ def strtobool(val): val = str(int(val)) if not isinstance(val, str): - raise ValueError('Invalid paramater %r, expected but got %r' % (val,type(val))) + raise ValueError('Invalid paramater %r, expected but got %r' % (val, type(val))) val = val.lower() if val in ('y', 'yes', 't', 'true', 'on', '1'): return 1 elif val in ('n', 'no', 'f', 'false', 'off', '0'): return 0 - raise ValueError('Invalid truth value %r, expected one of (\'y/n\', \'yes/no\', \'t/f\', \'true/false\', \'on/off\', \'1/0\')' % (val,)) + raise ValueError( + 'Invalid truth value %r, expected one of (\'y/n\', \'yes/no\', \'t/f\', \'true/false\', \'on/off\', \'1/0\')' % ( + val,)) + def GET_SERVER_IP(TARGET_IP='10.255.255.255', PORT=1): """ @@ -58,6 +62,7 @@ def GET_SERVER_IP(TARGET_IP='10.255.255.255', PORT=1): S.close() return IP + def get_env_value(env_variable, cast=None): """ Attempts to get env variable from OS @@ -75,7 +80,8 @@ def get_env_value(env_variable, cast=None): error_msg = 'Set the {} environment variable'.format(env_variable) raise ImproperlyConfigured(error_msg) -#==============================================================================# + +# ==============================================================================# ''' Application base ''' @@ -90,7 +96,7 @@ def get_env_value(env_variable, cast=None): ('Dan', 'd.s.thayer@swansea.ac.uk') ] -#==============================================================================# +# ==============================================================================# ''' Application settings ''' @@ -110,7 +116,7 @@ def get_env_value(env_variable, cast=None): if path_prj not in sys.path: sys.path.append(path_prj) -#==============================================================================# +# ==============================================================================# ''' Application variables ''' @@ -150,7 +156,7 @@ def get_env_value(env_variable, cast=None): ] # This variable was used for dev/admin and no longer maintained -#ENABLE_PUBLISH = True # get_env_value('ENABLE_PUBLISH', cast='bool') +# ENABLE_PUBLISH = True # get_env_value('ENABLE_PUBLISH', cast='bool') SHOWADMIN = get_env_value('SHOWADMIN', cast='bool') BROWSABLEAPI = get_env_value('BROWSABLEAPI', cast='bool') @@ -164,7 +170,7 @@ def get_env_value(env_variable, cast=None): os.environ['DJANGO_SETTINGS_MODULE'] = 'cll.settings' -#==============================================================================# +# ==============================================================================# ''' Site related variables ''' @@ -190,7 +196,7 @@ def get_env_value(env_variable, cast=None): SHOW_COOKIE_ALERT = True -#==============================================================================# +# ==============================================================================# ''' LDAP authentication ''' @@ -202,8 +208,10 @@ def get_env_value(env_variable, cast=None): AUTH_LDAP_BIND_PASSWORD = get_env_value('AUTH_LDAP_BIND_PASSWORD') + AUTH_LDAP_USER_SEARCH = LDAPSearchUnion(LDAPSearch(get_env_value('AUTH_LDAP_USER_SEARCH'), ldap.SCOPE_SUBTREE, '(sAMAccountName=%(user)s)'), ) + # Set up the basic group parameters. AUTH_LDAP_GROUP_SEARCH = LDAPSearch(get_env_value('AUTH_LDAP_GROUP_SEARCH'), ldap.SCOPE_SUBTREE, '(objectClass=group)') @@ -229,7 +237,7 @@ def get_env_value(env_variable, cast=None): AUTH_LDAP_CACHE_GROUPS = True AUTH_LDAP_GROUP_CACHE_TIMEOUT = 3600 -#==============================================================================# +# ==============================================================================# ''' Installed applications ''' @@ -251,13 +259,13 @@ def get_env_value(env_variable, cast=None): 'cll', 'simple_history', 'rest_framework', - #'mod_wsgi.server', + # 'mod_wsgi.server', 'django_extensions', 'markdownify', 'cookielaw', 'django_celery_results', 'django_celery_beat', - #'rest_framework_swagger', + # 'rest_framework_swagger', 'drf_yasg', 'django.contrib.sitemaps', 'svg', @@ -269,7 +277,7 @@ def get_env_value(env_variable, cast=None): 'django_minify_html', ] -#==============================================================================# +# ==============================================================================# ''' Middleware ''' @@ -295,7 +303,7 @@ def get_env_value(env_variable, cast=None): 'clinicalcode.middleware.sessions.SessionExpiryMiddleware', ] -#==============================================================================# +# ==============================================================================# ''' Authentication backends ''' @@ -303,7 +311,7 @@ def get_env_value(env_variable, cast=None): # Don't check AD on development PCs due to network connection if IS_DEVELOPMENT_PC or (not ENABLE_LDAP_AUTH): AUTHENTICATION_BACKENDS = [ - #'django_auth_ldap.backend.LDAPBackend', + # 'django_auth_ldap.backend.LDAPBackend', 'django.contrib.auth.backends.ModelBackend', ] else: @@ -316,23 +324,23 @@ def get_env_value(env_variable, cast=None): AUTH_PASSWORD_VALIDATORS = [ { 'NAME': - 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', }, { 'NAME': - 'django.contrib.auth.password_validation.MinimumLengthValidator', + 'django.contrib.auth.password_validation.MinimumLengthValidator', }, { 'NAME': - 'django.contrib.auth.password_validation.CommonPasswordValidator', + 'django.contrib.auth.password_validation.CommonPasswordValidator', }, { 'NAME': - 'django.contrib.auth.password_validation.NumericPasswordValidator', + 'django.contrib.auth.password_validation.NumericPasswordValidator', }, ] -#==============================================================================# +# ==============================================================================# ''' REST framework settings ''' @@ -363,7 +371,7 @@ def get_env_value(env_variable, cast=None): 'clinicalcode.entity_utils.api_utils.PrettyJsonRenderer', ) -#==============================================================================# +# ==============================================================================# ''' Templating settings ''' @@ -390,7 +398,7 @@ def get_env_value(env_variable, cast=None): }, ] -#==============================================================================# +# ==============================================================================# ''' Database settings ''' @@ -410,7 +418,7 @@ def get_env_value(env_variable, cast=None): if not IS_DEMO and (not IS_DEVELOPMENT_PC): DATABASES['default']['OPTIONS'] = {'sslmode': 'require'} -#==============================================================================# +# ==============================================================================# ''' Caching ''' @@ -432,7 +440,7 @@ def get_env_value(env_variable, cast=None): } } -#==============================================================================# +# ==============================================================================# ''' Static file handling & serving ''' @@ -459,14 +467,14 @@ def get_env_value(env_variable, cast=None): os.path.join(BASE_DIR, 'cll/static'), ] -#==============================================================================# +# ==============================================================================# ''' Media file handling ''' MEDIA_ROOT = os.path.join(BASE_DIR, 'media') MEDIA_URL = '/media/' -#==============================================================================# +# ==============================================================================# ''' Application logging settings ''' @@ -493,7 +501,7 @@ def get_env_value(env_variable, cast=None): 'formatters': { 'verbose': { 'format': - '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s' + '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s' }, 'simple': { 'format': '%(levelname)s %(message)s' @@ -524,7 +532,7 @@ def get_env_value(env_variable, cast=None): }, } -#==============================================================================# +# ==============================================================================# ''' Installed application settings ''' @@ -610,8 +618,8 @@ def get_env_value(env_variable, cast=None): 'WHITELIST_TAGS': [ 'a', 'abbr', 'acronym', 'b', 'blockquote', 'em', 'i', 'li', 'ol', 'p', 'strong', 'ul', 'img', - 'h1', 'h2', 'h3','h4', 'h5', 'h6', 'h7' - #, 'span', 'div', 'code' + 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'h7' + # , 'span', 'div', 'code' ], 'WHITELIST_ATTRS': [ 'href', @@ -637,4 +645,4 @@ def get_env_value(env_variable, cast=None): } } -#==============================================================================# +# ==============================================================================# diff --git a/CodeListLibrary_project/cll/test_settings.py b/CodeListLibrary_project/cll/test_settings.py index 073e56838..5fe3ef881 100644 --- a/CodeListLibrary_project/cll/test_settings.py +++ b/CodeListLibrary_project/cll/test_settings.py @@ -21,91 +21,61 @@ https://docs.djangoproject.com/ """ +import time from selenium import webdriver from .settings import * -import wget -import subprocess -import requests import os -import zipfile - -def download_lates_driver(version, directory_driver, type_os): - url_windows = "https://chromedriver.storage.googleapis.com/" + version + "/chromedriver_win32.zip" - url_linux = "https://chromedriver.storage.googleapis.com/" + version + "/chromedriver_linux64.zip" - - driver_zip = '' - if type_os == 'windows': - driver_zip = wget.download(url_windows, out=directory_driver) - - elif type_os == 'linux': - driver_zip = wget.download(url_linux, out=directory_driver) - - with zipfile.ZipFile(driver_zip, 'r') as downloaded_zip: - downloaded_zip.extractall(path=directory_driver) - - os.remove(driver_zip) - return - - -def check_driver(driver_directory, type_os): - url_latest_release = "https://chromedriver.storage.googleapis.com/LATEST_RELEASE" - response = requests.get(url_latest_release) - online_drive_version = response.text - - path_to_directory = os.path.abspath(driver_directory) - - try: - - cmd_run = subprocess.run(['cmd.exe','/r',path_to_directory + "/chromedriver --version"], capture_output=True, text=True) - if IS_LINUX: - linux_run = cmd_run - linux_run = subprocess.run(path_to_directory + "/chromedriver --version",capture_output=True, text=True) - cmd_run = linux_run - - - except FileNotFoundError: - print("No chromedriver") - download_lates_driver(online_drive_version, driver_directory, type_os) - else: - local_drive_version = cmd_run.stdout.split()[1] - print(f'Local version of chromedrive:{local_drive_version}') - print(f'Web version of chromedrive:{online_drive_version}') - - if local_drive_version == online_drive_version: - return True - else: - download_lates_driver(online_drive_version, driver_directory, type_os) - - -WEBAPP_HOST = '' +WEBAPP_HOST = "" # remote test features -REMOTE_TEST = False # True +REMOTE_TEST = get_env_value('REMOTE_TEST', cast='bool') REMOTE_TEST_HOST = 'http://selenium-hub:4444/wd/hub' IMPLICTLY_WAIT = 10 TEST_SLEEP_TIME = 5 -if REMOTE_TEST: - WEBAPP_HOST = 'http://webapp-test/' - -linux_chromdriver = check_driver('clinicalcode/tests/functional_tests','linux') -windows_chromdriver = check_driver('clinicalcode/tests/functional_tests','windows') - chrome_options = webdriver.ChromeOptions() chrome_options.add_experimental_option("prefs", {'profile.managed_default_content_settings.javascript': 'enable'}) +chrome_options.accept_insecure_certs = True +chrome_options.accept_ssl_certs = True + +# Add your options as needed +options = [ + "--window-size=1200,1200", + "--ignore-certificate-errors", + "--ignore-ssl-errors", + "--window-size=1280,800", + "--verbose", + "--start-maximized", + "--disable-gpu", + "--allow-insecure-localhost", + "--disable-dev-shm-usage", + "--allow-running-insecure-content", + '--headless' #if need debug localy through selenim container comment this line +] + +for option in options: + chrome_options.add_argument(option) + +print(chrome_options.arguments) -chrome_options.add_argument('--headless') -chrome_options.add_argument("--no-sandbox") -chrome_options.add_argument("--disable-dev-shm-usage") +if REMOTE_TEST: + driver = webdriver.Chrome(options=chrome_options) +else: + driver = webdriver.Remote(command_executor=REMOTE_TEST_HOST, options=chrome_options) +driver.get("http://google.com") +print(driver.title) + +if REMOTE_TEST: + WEBAPP_HOST = "http://localhost:8000/" +else: + WEBAPP_HOST = "http://web-test:8000/" -chrome_options.add_argument("--start-maximized") -chrome_options.add_argument("--disable-gpu") -chrome_options.add_argument("--window-size=1280,800") -chrome_options.add_argument("--allow-insecure-localhost") -chrome_options.add_argument("--verbose") +driver.get(WEBAPP_HOST) +print(driver.title) +driver.quit() os.environ["DJANGO_SETTINGS_MODULE"] = "cll.test_settings" @@ -126,7 +96,7 @@ def check_driver(driver_directory, type_os): 'NAME': get_env_value('UNIT_TEST_DB_NAME'), 'USER': get_env_value('UNIT_TEST_DB_USER'), 'PASSWORD': get_env_value('UNIT_TEST_DB_PASSWORD'), - 'HOST': get_env_value('UNIT_TEST_DB_HOST'), + 'HOST': get_env_value('UNIT_TEST_DB_REMOTE_HOST') if REMOTE_TEST else get_env_value('UNIT_TEST_DB_HOST'), 'PORT': '', 'TEST': { 'NAME': get_env_value('UNIT_TEST_DB_NAME') # TODO: check this was cl_testdatabase before! diff --git a/CodeListLibrary_project/pytest.ini b/CodeListLibrary_project/pytest.ini new file mode 100644 index 000000000..865b10e2f --- /dev/null +++ b/CodeListLibrary_project/pytest.ini @@ -0,0 +1,7 @@ +[pytest] +DJANGO_SETTINGS_MODULE = cll.test_settings +addopts = --reuse-db --migrations --ignore-glob=clinicalcode/tests/legacy/ --ignore-glob=clinicalcode/views/tests/test_views.py +python_files = tests.py test_*.py *_tests.py +filterwarnings = + ignore::DeprecationWarning + ignore::PendingDeprecationWarning \ No newline at end of file diff --git a/docker/development/app.Dockerfile b/docker/development/app.Dockerfile index 5e4d821ce..3a66728ca 100644 --- a/docker/development/app.Dockerfile +++ b/docker/development/app.Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.9-slim +FROM python:3.10-slim ENV PYTHONUNBUFFERED 1 ENV LC_ALL=C.UTF-8 @@ -34,11 +34,15 @@ RUN mkdir -p /var/www/concept_lib_sites/v1 COPY ./requirements /var/www/concept_lib_sites/v1/requirements RUN ["chown", "-R" , "www-data:www-data", "/var/www/concept_lib_sites/"] +# Install requirements RUN pip --no-cache-dir install -r /var/www/concept_lib_sites/v1/requirements/local.txt + # Deploy scripts RUN ["chown" , "-R" , "www-data:www-data" , "/var/www/"] COPY ./development/scripts/wait-for-it.sh /bin/wait-for-it.sh RUN ["chmod", "u+x", "/bin/wait-for-it.sh"] RUN ["dos2unix", "/bin/wait-for-it.sh"] + +WORKDIR /var/www/concept_lib_sites/v1/CodeListLibrary_project/ \ No newline at end of file diff --git a/docker/development/chromedriver b/docker/development/chromedriver new file mode 100755 index 000000000..8da9ce45b Binary files /dev/null and b/docker/development/chromedriver differ diff --git a/docker/development/env/app.compose.env b/docker/development/env/app.compose.env index a67624eae..7fc734aca 100644 --- a/docker/development/env/app.compose.env +++ b/docker/development/env/app.compose.env @@ -11,7 +11,7 @@ SHOWADMIN=True BROWSABLEAPI=False IGNORE_CAPTCHA=True SECRET_KEY=abc -ALLOWED_HOSTS=localhost, 127.0.0.1 +ALLOWED_HOSTS=localhost,127.0.0.1 DB_HOST=postgres DB_NAME=concept_library DB_USER=clluser @@ -40,3 +40,10 @@ POSTGRES_DB="concept_library" POSTGRES_USER="clluser" POSTGRES_PASSWORD="password" POSTGRES_PORT=5432 + +UNIT_TEST_DB_HOST=postgres +UNIT_TEST_DB_REMOTE_HOST=localhost +UNIT_TEST_DB_NAME=concept_library_test +UNIT_TEST_DB_USER=clluser_test +UNIT_TEST_DB_PASSWORD=password +REMOTE_TEST=True \ No newline at end of file diff --git a/docker/development/env/postgres.compose.env b/docker/development/env/postgres.compose.env index 6b9fa1cc7..8d202c474 100644 --- a/docker/development/env/postgres.compose.env +++ b/docker/development/env/postgres.compose.env @@ -5,3 +5,6 @@ POSTGRES_USER="clluser" POSTGRES_PASSWORD="password" POSTGRES_PORT=5432 HEALTHCHECK_PORT=81 + +UNIT_TEST_DB_USER=clluser_test +UNIT_TEST_DB_PASSWORD=password \ No newline at end of file diff --git a/docker/development/scripts/init-db.sh b/docker/development/scripts/init-db.sh index 996f3fb31..27fa076ba 100644 --- a/docker/development/scripts/init-db.sh +++ b/docker/development/scripts/init-db.sh @@ -7,7 +7,6 @@ echo "====================================" BACKUP_FILE=$(find /docker-entrypoint-initdb.d/db/ -name '*.backup'| head -1) if [ ! -z $BACKUP_FILE ] && [ -e $BACKUP_FILE ]; then echo "[!>] Found backup, restoring from local" - echo "[!>] Restoring database from local .backup" /usr/bin/pg_restore -U $POSTGRES_USER -d $POSTGRES_DB $BACKUP_FILE; elif [ -e /docker-entrypoint-initdb.d/db/git.token ]; then @@ -28,6 +27,7 @@ elif [ -e /docker-entrypoint-initdb.d/db/git.token ]; then rm -rf /docker-entrypoint-initdb.d/db/backup/ /usr/bin/pg_restore -U $POSTGRES_USER -d $POSTGRES_DB /docker-entrypoint-initdb.d/db/db.backup + else echo "[!>] Cannot restore, failed to find database file after cloning repo" fi diff --git a/docker/docker-compose.selenium.yaml b/docker/docker-compose.selenium.yaml new file mode 100644 index 000000000..ca2f81171 --- /dev/null +++ b/docker/docker-compose.selenium.yaml @@ -0,0 +1,77 @@ +version: '3' + +services: + postgres: + build: + context: . + dockerfile: ./selenium-testing/postgres.Dockerfile + restart: always + ports: + - 5432:5432 + volumes: + - ./selenium-testing/db/:/docker-entrypoint-initdb.d/db/ + env_file: + - ./selenium-testing/env/postgres.compose.env + + web-test: + platform: linux/amd64 + build: + context: . + dockerfile: ./selenium-testing/app.Dockerfile + command: > + sh -c "/bin/wait-for-it.sh -t 0 postgres:5432 -- python /var/www/concept_lib_sites/v1/CodeListLibrary_project/manage.py makemigrations + && python /var/www/concept_lib_sites/v1/CodeListLibrary_project/manage.py migrate + && python /var/www/concept_lib_sites/v1/CodeListLibrary_project/manage.py runserver 0.0.0.0:8000" + volumes: + - ../CodeListLibrary_project:/var/www/concept_lib_sites/v1/CodeListLibrary_project/ + ports: + - 8000:8000 + expose: + - 8000 + links: + - postgres:postgres + tty: true + stdin_open: true + env_file: + - ./selenium-testing/env/app.compose.env + + selenium-hub: + image: selenium/hub:latest + ports: + - "4442:4442" + - "4443:4443" + - "4444:4444" + restart: unless-stopped + + selenium-chrome: + image: selenium/node-chrome:latest + environment: + - SE_EVENT_BUS_HOST=selenium-hub + - SE_EVENT_BUS_PUBLISH_PORT=4442 + - SE_EVENT_BUS_SUBSCRIBE_PORT=4443 + ports: + - "5900:5900" + depends_on: + - selenium-hub + + selenium-edge: + image: selenium/node-edge + depends_on: + - selenium-hub + ports: + - "5901:5901" + environment: + - SE_EVENT_BUS_HOST=selenium-hub + - SE_EVENT_BUS_PUBLISH_PORT=4442 + - SE_EVENT_BUS_SUBSCRIBE_PORT=4443 + + allure: + image: "frankescobar/allure-docker-service" + environment: + CHECK_RESULTS_EVERY_SECONDS: 1 + KEEP_HISTORY: 1 + ports: + - "5050:5050" + volumes: + - ../CodeListLibrary_project/clinicalcode/tests/allure-results:/app/allure-results + - ../CodeListLibrary_project/clinicalcode/tests/allure-reports:/app/allure-reports diff --git a/docker/requirements/base.txt b/docker/requirements/base.txt index bc270c33e..793fce599 100644 --- a/docker/requirements/base.txt +++ b/docker/requirements/base.txt @@ -29,7 +29,6 @@ python-dateutil==2.8.2 python-decouple==3.6 requests==2.31.0 redis==4.5.3 -selenium==4.4.3 swagger-spec-validator==2.7.6 typing==3.7.4.3 tzdata==2022.2 diff --git a/docker/requirements/test-requirements.txt b/docker/requirements/test-requirements.txt new file mode 100644 index 000000000..f9898efbe --- /dev/null +++ b/docker/requirements/test-requirements.txt @@ -0,0 +1,6 @@ +-r base.txt +selenium==4.15.0 +pytest-django==4.5.2 +pytest-html==4.1.1 +pytest-cov==4.1.0 +allure-pytest==2.13.2 diff --git a/docker/selenium-testing/app.Dockerfile b/docker/selenium-testing/app.Dockerfile new file mode 100644 index 000000000..9f6fee458 --- /dev/null +++ b/docker/selenium-testing/app.Dockerfile @@ -0,0 +1,48 @@ +FROM python:3.10-slim + +ENV PYTHONUNBUFFERED 1 +ENV LC_ALL=C.UTF-8 + +# Install and update packages +RUN apt-get update -y -q && \ + apt-get upgrade -y -q && \ + apt-get install dos2unix + +# Install LDAP header files +RUN apt-get install -y -q libsasl2-dev libldap2-dev libssl-dev + +# Install npm +RUN apt-get update && apt-get install -y \ + software-properties-common \ + npm + +# Install esbuild +RUN npm install -g config set user root + +RUN npm install -g esbuild@0.19.0 + +# Set main workdir +WORKDIR /var/www + +# Install & upgrade pip +RUN \ + apt-get install -y -q python3-pip && \ + pip install --upgrade pip + +# Copy & Install requirements +RUN mkdir -p /var/www/concept_lib_sites/v1 +COPY ./requirements /var/www/concept_lib_sites/v1/requirements +RUN ["chown", "-R" , "www-data:www-data", "/var/www/concept_lib_sites/"] + +# Install requirements +RUN pip --no-cache-dir install -r /var/www/concept_lib_sites/v1/requirements/test-requirements.txt + + +# Deploy scripts +RUN ["chown" , "-R" , "www-data:www-data" , "/var/www/"] + +COPY ./selenium-testing/scripts/wait-for-it.sh /bin/wait-for-it.sh +RUN ["chmod", "u+x", "/bin/wait-for-it.sh"] +RUN ["dos2unix", "/bin/wait-for-it.sh"] + +WORKDIR /var/www/concept_lib_sites/v1/CodeListLibrary_project/ \ No newline at end of file diff --git a/docker/selenium-testing/db/example.git.token b/docker/selenium-testing/db/example.git.token new file mode 100644 index 000000000..2c3694e8a --- /dev/null +++ b/docker/selenium-testing/db/example.git.token @@ -0,0 +1 @@ +# Paste git token here and rename to git.token \ No newline at end of file diff --git a/docker/selenium-testing/env/app.compose.env b/docker/selenium-testing/env/app.compose.env new file mode 100644 index 000000000..231eafff9 --- /dev/null +++ b/docker/selenium-testing/env/app.compose.env @@ -0,0 +1,48 @@ +SERVER_NAME=localhost +AWAIT_POSTGRES=True +DEBUG=True +DEBUG_TOOLS=False +IS_DEMO=False +IS_INSIDE_GATEWAY=False +IS_DEVELOPMENT_PC=True +CLL_READ_ONLY=False +ENABLE_PUBLISH=True +SHOWADMIN=True +BROWSABLEAPI=False +IGNORE_CAPTCHA=True +SECRET_KEY=abc +ALLOWED_HOSTS=localhost,127.0.0.1,web-test +DB_HOST=postgres +DB_NAME=concept_library +DB_USER=clluser +DB_PASSWORD=password +DB_PORT=5432 +ENABLE_LDAP_AUTH=False +AUTH_LDAP_SERVER_URI="" +AUTH_LDAP_BIND_DN="" +AUTH_LDAP_BIND_PASSWORD="" +AUTH_LDAP_USER_SEARCH="" +AUTH_LDAP_GROUP_SEARCH="" +AUTH_LDAP_REQUIRE_GROUP="" +GOOGLE_RECAPTCHA_SECRET_KEY="" +EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackend +DEFAULT_FROM_EMAIL="" +EMAIL_USE_TLS=False +EMAIL_HOST="" +EMAIL_PORT=25 +EMAIL_HOST_PASSWORD="" +EMAIL_HOST_USER="" +HELPDESK_EMAIL="" +CELERY_BROKER=redis://redis:6379/0 +CELERY_BACKEND=redis://redis:6379/0 +POSTGRES_HOST="postgres" +POSTGRES_DB="concept_library" +POSTGRES_USER="clluser" +POSTGRES_PASSWORD="password" +POSTGRES_PORT=5432 + +UNIT_TEST_DB_HOST=postgres #localhost when remote +UNIT_TEST_DB_NAME=concept_library_test +UNIT_TEST_DB_USER=clluser_test +UNIT_TEST_DB_PASSWORD=password +REMOTE_TEST=False \ No newline at end of file diff --git a/docker/selenium-testing/env/postgres.compose.env b/docker/selenium-testing/env/postgres.compose.env new file mode 100644 index 000000000..8d202c474 --- /dev/null +++ b/docker/selenium-testing/env/postgres.compose.env @@ -0,0 +1,10 @@ +POSTGRES_RESTORE_REPO="github.com/SwanseaUniversityMedical/concept-library-dev-db.git" +POSTGRES_HOST="localhost" +POSTGRES_DB="concept_library" +POSTGRES_USER="clluser" +POSTGRES_PASSWORD="password" +POSTGRES_PORT=5432 +HEALTHCHECK_PORT=81 + +UNIT_TEST_DB_USER=clluser_test +UNIT_TEST_DB_PASSWORD=password \ No newline at end of file diff --git a/docker/selenium-testing/env/remotetest.compose.env b/docker/selenium-testing/env/remotetest.compose.env new file mode 100644 index 000000000..6a685a768 --- /dev/null +++ b/docker/selenium-testing/env/remotetest.compose.env @@ -0,0 +1,51 @@ +SERVER_NAME=localhost +AWAIT_POSTGRES=True +DEBUG=True +DEBUG_TOOLS=False +IS_DEMO=False +IS_INSIDE_GATEWAY=False +IS_DEVELOPMENT_PC=True +CLL_READ_ONLY=False +ENABLE_PUBLISH=True +SHOWADMIN=True +BROWSABLEAPI=False +IGNORE_CAPTCHA=True +SECRET_KEY=abc +ALLOWED_HOSTS=localhost,127.0.0.1 + +DB_HOST=localhost +DB_NAME=concept_library +DB_USER=clluser_test +DB_PASSWORD=password +DB_PORT=5432 + +ENABLE_LDAP_AUTH=False +AUTH_LDAP_SERVER_URI="" +AUTH_LDAP_BIND_DN="" +AUTH_LDAP_BIND_PASSWORD="" +AUTH_LDAP_USER_SEARCH="" +AUTH_LDAP_GROUP_SEARCH="" +AUTH_LDAP_REQUIRE_GROUP="" +GOOGLE_RECAPTCHA_SECRET_KEY="" +EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackend +DEFAULT_FROM_EMAIL="" +EMAIL_USE_TLS=False +EMAIL_HOST="" +EMAIL_PORT=25 +EMAIL_HOST_PASSWORD="" +EMAIL_HOST_USER="" +HELPDESK_EMAIL="" +CELERY_BROKER=redis://redis:6379/0 +CELERY_BACKEND=redis://redis:6379/0 +POSTGRES_HOST="db" +POSTGRES_DB="concept_library" +POSTGRES_USER="clluser" +POSTGRES_PASSWORD="password" +POSTGRES_PORT=5432 + +UNIT_TEST_DB_HOST=localhost +UNIT_TEST_DB_REMOTE_HOST=localhost +UNIT_TEST_DB_NAME=concept_library_test +UNIT_TEST_DB_USER=clluser_test +UNIT_TEST_DB_PASSWORD=password +REMOTE_TEST=True \ No newline at end of file diff --git a/docker/selenium-testing/postgres.Dockerfile b/docker/selenium-testing/postgres.Dockerfile new file mode 100644 index 000000000..ead412790 --- /dev/null +++ b/docker/selenium-testing/postgres.Dockerfile @@ -0,0 +1,10 @@ +FROM postgres:14.4 + +# Install and upgrade packages +RUN apt-get update -y -q && \ + apt-get install git -y -q && \ + apt-get install dos2unix + +COPY ./selenium-testing/scripts/init-db.sh /docker-entrypoint-initdb.d/init-db.sh +RUN chmod u+x /docker-entrypoint-initdb.d/init-db.sh +RUN dos2unix /docker-entrypoint-initdb.d/init-db.sh diff --git a/docker/selenium-testing/scripts/init-db.sh b/docker/selenium-testing/scripts/init-db.sh new file mode 100644 index 000000000..2a3a4d0eb --- /dev/null +++ b/docker/selenium-testing/scripts/init-db.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env sh + +echo "====================================" +echo "== Attempting to restore database ==" +echo "====================================" + +BACKUP_FILE=$(find /docker-entrypoint-initdb.d/db/ -name '*.backup'| head -1) +if [ ! -z $BACKUP_FILE ] && [ -e $BACKUP_FILE ]; then + echo "[!>] Found backup, restoring from local" + /usr/bin/psql -U $POSTGRES_USER -d $POSTGRES_DB -c "CREATE USER $UNIT_TEST_DB_USER WITH PASSWORD '$UNIT_TEST_DB_PASSWORD'; ALTER USER $UNIT_TEST_DB_USER CREATEDB;" + echo "[!>] Restoring database from local .backup" + /usr/bin/pg_restore -U $POSTGRES_USER -d $POSTGRES_DB $BACKUP_FILE; +elif [ -e /docker-entrypoint-initdb.d/db/git.token ]; then + echo "[!>] Found token, restoring from git" + GIT_TOKEN=`cat /docker-entrypoint-initdb.d/db/git.token` + + if [ -d "/docker-entrypoint-initdb.d/db/backup/" ]; then + echo "[!>] Aborting restore, please remove './docker/development/db/backup/' folder" + else + echo "[!>] Downloading git repository" + mkdir /docker-entrypoint-initdb.d/db/backup/ + git clone https://$GIT_TOKEN@$POSTGRES_RESTORE_REPO /docker-entrypoint-initdb.d/db/backup/ + + BACKUP_FILE=$(find /docker-entrypoint-initdb.d/db/backup/ -name '*.backup'| head -1) + if [ ! -z $BACKUP_FILE ] && [ -e $BACKUP_FILE ]; then + echo "[!>] Restoring database from cloned .backup file" + mv $BACKUP_FILE /docker-entrypoint-initdb.d/db/db.backup + rm -rf /docker-entrypoint-initdb.d/db/backup/ + + /usr/bin/pg_restore -U $POSTGRES_USER -d $POSTGRES_DB /docker-entrypoint-initdb.d/db/db.backup + /usr/bin/psql -U $POSTGRES_USER -d $POSTGRES_DB -c "CREATE USER $UNIT_TEST_DB_USER WITH PASSWORD '$UNIT_TEST_DB_PASSWORD'; ALTER USER $UNIT_TEST_DB_USER CREATEDB;" + + else + echo "[!>] Cannot restore, failed to find database file after cloning repo" + fi + fi +else + echo "[!>] Cannot restore database from git or local backup" +fi diff --git a/docker/selenium-testing/scripts/wait-for-it.sh b/docker/selenium-testing/scripts/wait-for-it.sh new file mode 100644 index 000000000..d990e0d36 --- /dev/null +++ b/docker/selenium-testing/scripts/wait-for-it.sh @@ -0,0 +1,182 @@ +#!/usr/bin/env bash +# Use this script to test if a given TCP host/port are available + +WAITFORIT_cmdname=${0##*/} + +echoerr() { if [[ $WAITFORIT_QUIET -ne 1 ]]; then echo "$@" 1>&2; fi } + +usage() +{ + cat << USAGE >&2 +Usage: + $WAITFORIT_cmdname host:port [-s] [-t timeout] [-- command args] + -h HOST | --host=HOST Host or IP under test + -p PORT | --port=PORT TCP port under test + Alternatively, you specify the host and port as host:port + -s | --strict Only execute subcommand if the test succeeds + -q | --quiet Don't output any status messages + -t TIMEOUT | --timeout=TIMEOUT + Timeout in seconds, zero for no timeout + -- COMMAND ARGS Execute command with args after the test finishes +USAGE + exit 1 +} + +wait_for() +{ + if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then + echoerr "$WAITFORIT_cmdname: waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT" + else + echoerr "$WAITFORIT_cmdname: waiting for $WAITFORIT_HOST:$WAITFORIT_PORT without a timeout" + fi + WAITFORIT_start_ts=$(date +%s) + while : + do + if [[ $WAITFORIT_ISBUSY -eq 1 ]]; then + nc -z $WAITFORIT_HOST $WAITFORIT_PORT + WAITFORIT_result=$? + else + (echo -n > /dev/tcp/$WAITFORIT_HOST/$WAITFORIT_PORT) >/dev/null 2>&1 + WAITFORIT_result=$? + fi + if [[ $WAITFORIT_result -eq 0 ]]; then + WAITFORIT_end_ts=$(date +%s) + echoerr "$WAITFORIT_cmdname: $WAITFORIT_HOST:$WAITFORIT_PORT is available after $((WAITFORIT_end_ts - WAITFORIT_start_ts)) seconds" + break + fi + sleep 1 + done + return $WAITFORIT_result +} + +wait_for_wrapper() +{ + # In order to support SIGINT during timeout: http://unix.stackexchange.com/a/57692 + if [[ $WAITFORIT_QUIET -eq 1 ]]; then + timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --quiet --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT & + else + timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT & + fi + WAITFORIT_PID=$! + trap "kill -INT -$WAITFORIT_PID" INT + wait $WAITFORIT_PID + WAITFORIT_RESULT=$? + if [[ $WAITFORIT_RESULT -ne 0 ]]; then + echoerr "$WAITFORIT_cmdname: timeout occurred after waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT" + fi + return $WAITFORIT_RESULT +} + +# process arguments +while [[ $# -gt 0 ]] +do + case "$1" in + *:* ) + WAITFORIT_hostport=(${1//:/ }) + WAITFORIT_HOST=${WAITFORIT_hostport[0]} + WAITFORIT_PORT=${WAITFORIT_hostport[1]} + shift 1 + ;; + --child) + WAITFORIT_CHILD=1 + shift 1 + ;; + -q | --quiet) + WAITFORIT_QUIET=1 + shift 1 + ;; + -s | --strict) + WAITFORIT_STRICT=1 + shift 1 + ;; + -h) + WAITFORIT_HOST="$2" + if [[ $WAITFORIT_HOST == "" ]]; then break; fi + shift 2 + ;; + --host=*) + WAITFORIT_HOST="${1#*=}" + shift 1 + ;; + -p) + WAITFORIT_PORT="$2" + if [[ $WAITFORIT_PORT == "" ]]; then break; fi + shift 2 + ;; + --port=*) + WAITFORIT_PORT="${1#*=}" + shift 1 + ;; + -t) + WAITFORIT_TIMEOUT="$2" + if [[ $WAITFORIT_TIMEOUT == "" ]]; then break; fi + shift 2 + ;; + --timeout=*) + WAITFORIT_TIMEOUT="${1#*=}" + shift 1 + ;; + --) + shift + WAITFORIT_CLI=("$@") + break + ;; + --help) + usage + ;; + *) + echoerr "Unknown argument: $1" + usage + ;; + esac +done + +if [[ "$WAITFORIT_HOST" == "" || "$WAITFORIT_PORT" == "" ]]; then + echoerr "Error: you need to provide a host and port to test." + usage +fi + +WAITFORIT_TIMEOUT=${WAITFORIT_TIMEOUT:-15} +WAITFORIT_STRICT=${WAITFORIT_STRICT:-0} +WAITFORIT_CHILD=${WAITFORIT_CHILD:-0} +WAITFORIT_QUIET=${WAITFORIT_QUIET:-0} + +# Check to see if timeout is from busybox? +WAITFORIT_TIMEOUT_PATH=$(type -p timeout) +WAITFORIT_TIMEOUT_PATH=$(realpath $WAITFORIT_TIMEOUT_PATH 2>/dev/null || readlink -f $WAITFORIT_TIMEOUT_PATH) + +WAITFORIT_BUSYTIMEFLAG="" +if [[ $WAITFORIT_TIMEOUT_PATH =~ "busybox" ]]; then + WAITFORIT_ISBUSY=1 + # Check if busybox timeout uses -t flag + # (recent Alpine versions don't support -t anymore) + if timeout &>/dev/stdout | grep -q -e '-t '; then + WAITFORIT_BUSYTIMEFLAG="-t" + fi +else + WAITFORIT_ISBUSY=0 +fi + +if [[ $WAITFORIT_CHILD -gt 0 ]]; then + wait_for + WAITFORIT_RESULT=$? + exit $WAITFORIT_RESULT +else + if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then + wait_for_wrapper + WAITFORIT_RESULT=$? + else + wait_for + WAITFORIT_RESULT=$? + fi +fi + +if [[ $WAITFORIT_CLI != "" ]]; then + if [[ $WAITFORIT_RESULT -ne 0 && $WAITFORIT_STRICT -eq 1 ]]; then + echoerr "$WAITFORIT_cmdname: strict mode, refusing to execute subprocess" + exit $WAITFORIT_RESULT + fi + exec "${WAITFORIT_CLI[@]}" +else + exit $WAITFORIT_RESULT +fi