Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add External integration tests #756

Merged
merged 4 commits into from
Dec 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
176 changes: 176 additions & 0 deletions integration_test/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
# Created by https://www.toptal.com/developers/gitignore/api/python
# Edit at https://www.toptal.com/developers/gitignore?templates=python

### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
.pybuilder/
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version

# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock

# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock

# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml

# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

# pytype static type analyzer
.pytype/

# Cython debug symbols
cython_debug/

# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

### Python Patch ###
# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
poetry.toml

# ruff
.ruff_cache/

# LSP config files
pyrightconfig.json

# End of https://www.toptal.com/developers/gitignore/api/python
17 changes: 17 additions & 0 deletions integration_test/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Use an official Python runtime as a parent image
FROM python:3.9-slim

# Set the working directory in the container
WORKDIR /app

# Copy the requirements file into the container
COPY requirements.txt .

# Install dependencies
RUN pip install --no-cache-dir -r requirements.txt

# Copy the rest of the application into the container
COPY . .

# Default command to run tests
CMD ["pytest"]
23 changes: 23 additions & 0 deletions integration_test/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Integration Tests

Integration tests for fEMR. This project will bring up the entire fEMR stack with docker then run tests against it.

Assumes that the femr docker container's name is `femr-femr`, otherwise you will need to re-build it with the new name.

## Run with Docker

Ensure that the femr container is built with name `femr-femr`, before running the tests.

In the `integration-test` directory, run the tests with:

```bash
docker compose up --build
```

## Build Image

In the root of the fEMR repository:

```bash
docker compose build
```
77 changes: 77 additions & 0 deletions integration_test/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
from testcontainers.mysql import MySqlContainer
from testcontainers.selenium import BrowserWebDriverContainer
from selenium.webdriver import DesiredCapabilities
from testcontainers.core.container import DockerContainer
from testcontainers.core.network import Network
from testcontainers.core.waiting_utils import wait_for_logs

from testcontainers.core.image import DockerImage
import socket
from contextlib import closing
import requests
import time
import docker
import os
import re

client = docker.from_env()

try:
femr_image = os.getenv("FEMR_IMAGE_NAME", "femr-femr")

# Verify Image exists
client.images.get(femr_image)
except:
femr_image = None

assert femr_image is not None, "FEMR image not found, build image to 'femr-femr' or set FEMR_IMAGE_NAME environment variable to the correct image name"

sql_container_spec = MySqlContainer('mysql:9.1.0', "femr", "password", "password", "femr_db", 3306)\
.with_network_aliases("mysql")\
.with_command("mysqld --log-bin-trust-function-creators=1")


import pytest

@pytest.fixture(scope='module', autouse=True)
def run_before_and_after_tests(request):
"""Fixture to execute asserts before and after a test is run"""

network_name = "femr_test_network" + str(time.time())
network = client.networks.create(network_name, driver="bridge")

def cleanup():
network.remove()

request.addfinalizer(cleanup)

with sql_container_spec as mysql:
network.connect(mysql._container.id, aliases=["db"])

femr_container_spec = DockerContainer(femr_image)\
.with_bind_ports("9000", "9000")\
.with_env("DB_URL", f'jdbc:mysql://db:3306/femr_db?characterEncoding=UTF-8&useSSL=false&allowPublicKeyRetrieval=true')\
.with_env("DB_USER", 'femr')\
.with_env("DB_PASS", 'password')\
.with_env("IS_DOCKER",'true')



with femr_container_spec as femr_container:
network.connect(femr_container_spec._container.id, aliases=["femr"])

wait_for_logs(femr_container, re.compile(".*Listening for HTTP on.*", flags=re.DOTALL | re.MULTILINE).search)

print("Femr Started")

femr_address = f"http://{femr_container.get_container_host_ip()}:{femr_container.get_exposed_port(9000)}"
os.environ["FEMR_ADDRESS"] = femr_address

yield


@pytest.fixture(scope='session', autouse=True)
def setup_selenium_container():
with BrowserWebDriverContainer(DesiredCapabilities.CHROME) as selenium_container:
os.environ["SELENIUM_ADDRESS"] = selenium_container.get_connection_url()
yield
4 changes: 4 additions & 0 deletions integration_test/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[tool.pytest.ini_options]
addopts = [
"--import-mode=importlib",
]
3 changes: 3 additions & 0 deletions integration_test/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
testcontainers==4.9.0
pytest==8.3.4
selenium==4.27.1
Empty file.
47 changes: 47 additions & 0 deletions integration_test/tests/test_basic_functionality.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import pytest
import time
import json
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
import os
import requests


def test_femr_is_alive():
femr_address = os.getenv("FEMR_ADDRESS")

assert femr_address is not None, "FEMR_ADDRESS environment variable not set"

response = requests.get(femr_address)
assert response.status_code == 200

def test_can_login_and_logout_to_admin():
femr_address = os.getenv("FEMR_ADDRESS")

assert femr_address is not None, "FEMR_ADDRESS environment variable not set"

driver_address = os.getenv("SELENIUM_ADDRESS")

assert driver_address is not None, "SELENIUM_ADDRESS environment variable not set"

driver = webdriver.Remote(command_executor=driver_address, options=webdriver.ChromeOptions())

driver.get(f"{femr_address}/")

# Test Login
driver.set_window_size(1361, 1157)
driver.find_element(By.NAME, "email").click()
driver.find_element(By.NAME, "email").send_keys("admin")
driver.find_element(By.NAME, "password").send_keys("admin")
driver.find_element(By.CSS_SELECTOR, "input:nth-child(4)").click()
assert "Welcome to fEMR" in driver.find_element(By.ID, "home_index_h2_Welcome").text

# Test Logout
driver.find_element(By.CSS_SELECTOR, ".glyphicon-log-out").click()
assert driver.find_element(By.CSS_SELECTOR, "h1").text == "Please sign in"

1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
testcontainers==4.8.2
Loading