mirror of
https://github.com/bec-project/bec_atlas.git
synced 2025-07-14 07:01:48 +02:00
tests: added login tests
This commit is contained in:
@ -93,7 +93,8 @@ class ScylladbDatasource:
|
||||
|
||||
for account in functional_accounts:
|
||||
# check if the account already exists in the database
|
||||
password_hash = get_password_hash(account.pop("password"))
|
||||
password = account.pop("password")
|
||||
password_hash = get_password_hash(password)
|
||||
result = schema.User.objects.filter(email=account["email"])
|
||||
if result.count() > 0:
|
||||
continue
|
||||
|
@ -6,7 +6,7 @@ from cassandra.cqlengine.models import Model
|
||||
|
||||
class User(Model):
|
||||
email = columns.Text(primary_key=True)
|
||||
user_id = columns.UUID(default=uuid.uuid4())
|
||||
user_id = columns.UUID(default=uuid.uuid4)
|
||||
first_name = columns.Text()
|
||||
last_name = columns.Text()
|
||||
groups = columns.Set(columns.Text)
|
||||
|
@ -12,10 +12,11 @@ CONFIG = {"redis": {"host": "localhost", "port": 6380}, "scylla": {"hosts": ["lo
|
||||
class AtlasApp:
|
||||
API_VERSION = "v1"
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, config=None):
|
||||
self.config = config or CONFIG
|
||||
self.app = FastAPI()
|
||||
self.prefix = f"/api/{self.API_VERSION}"
|
||||
self.datasources = DatasourceManager(config=CONFIG)
|
||||
self.datasources = DatasourceManager(config=self.config)
|
||||
self.register_event_handler()
|
||||
self.add_routers()
|
||||
|
||||
|
@ -1,13 +1,14 @@
|
||||
import sys
|
||||
import time
|
||||
|
||||
from cassandra.cluster import Cluster
|
||||
from cassandra.cluster import Cluster, Session
|
||||
|
||||
SCYLLA_HOST = "scylla"
|
||||
SCYLLA_KEYSPACE = "test_bec_atlas"
|
||||
SCYLLA_PORT = 9042
|
||||
SCYLLA_KEYSPACE = "bec_atlas"
|
||||
|
||||
|
||||
def wait_for_scylladb(scylla_host: str = SCYLLA_HOST):
|
||||
def wait_for_scylladb(scylla_host: str = SCYLLA_HOST, scylla_port: int = SCYLLA_PORT):
|
||||
"""
|
||||
Wait for ScyllaDB to be ready by trying to connect to it.
|
||||
|
||||
@ -15,18 +16,21 @@ def wait_for_scylladb(scylla_host: str = SCYLLA_HOST):
|
||||
scylla_host(str): The ScyllaDB host.
|
||||
"""
|
||||
print("Waiting for ScyllaDB to be ready...")
|
||||
print(f"ScyllaDB host: {scylla_host}")
|
||||
print(f"ScyllaDB port: {scylla_port}")
|
||||
while True:
|
||||
try:
|
||||
cluster = Cluster([scylla_host])
|
||||
cluster = Cluster([(scylla_host, scylla_port)])
|
||||
# cluster = Cluster([scylla_host])
|
||||
session = cluster.connect()
|
||||
print("Connected to ScyllaDB")
|
||||
break
|
||||
return session
|
||||
except Exception as e:
|
||||
print(f"ScyllaDB not ready yet: {e}")
|
||||
time.sleep(5)
|
||||
|
||||
|
||||
def create_keyspace(scylla_host: str = SCYLLA_HOST, keyspace: str = SCYLLA_KEYSPACE):
|
||||
def create_keyspace(session: Session, keyspace: str = SCYLLA_KEYSPACE):
|
||||
"""
|
||||
Create a new keyspace in ScyllaDB if it does not exist.
|
||||
|
||||
@ -36,8 +40,8 @@ def create_keyspace(scylla_host: str = SCYLLA_HOST, keyspace: str = SCYLLA_KEYSP
|
||||
"""
|
||||
print(f"Creating keyspace '{keyspace}' if not exists...")
|
||||
try:
|
||||
cluster = Cluster([scylla_host])
|
||||
session = cluster.connect()
|
||||
# drop the keyspace if it exists
|
||||
session.execute(f"DROP KEYSPACE IF EXISTS {keyspace}")
|
||||
session.execute(
|
||||
f"""
|
||||
CREATE KEYSPACE IF NOT EXISTS {keyspace}
|
||||
@ -50,11 +54,10 @@ def create_keyspace(scylla_host: str = SCYLLA_HOST, keyspace: str = SCYLLA_KEYSP
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def setup_database():
|
||||
wait_for_scylladb()
|
||||
create_keyspace()
|
||||
def setup_database(host: str = SCYLLA_HOST, port: int = SCYLLA_PORT):
|
||||
session = wait_for_scylladb(scylla_host=host, scylla_port=port)
|
||||
create_keyspace(session)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
wait_for_scylladb()
|
||||
create_keyspace()
|
||||
setup_database()
|
98
backend/tests/conftest.py
Normal file
98
backend/tests/conftest.py
Normal file
@ -0,0 +1,98 @@
|
||||
import contextlib
|
||||
import os
|
||||
from typing import Iterator
|
||||
|
||||
import pytest
|
||||
from pytest_docker.plugin import DockerComposeExecutor, Services
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
parser.addoption(
|
||||
"--skip-docker",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Skip spinning up docker containers",
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def docker_compose_file(pytestconfig):
|
||||
test_directory = os.path.dirname(os.path.abspath(__file__))
|
||||
return os.path.join(test_directory, "docker-compose.yml")
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def docker_compose_project_name() -> str:
|
||||
"""Generate a project name using the current process PID. Override this
|
||||
fixture in your tests if you need a particular project name."""
|
||||
|
||||
return "pytest_9070_atlas"
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def get_docker_services(
|
||||
docker_compose_command: str,
|
||||
docker_compose_file: list[str] | str,
|
||||
docker_compose_project_name: str,
|
||||
docker_setup: list[str] | str,
|
||||
docker_cleanup: list[str] | str,
|
||||
) -> Iterator[Services]:
|
||||
docker_compose = DockerComposeExecutor(
|
||||
docker_compose_command, docker_compose_file, docker_compose_project_name
|
||||
)
|
||||
|
||||
try:
|
||||
if docker_cleanup:
|
||||
# Maintain backwards compatibility with the string format.
|
||||
if isinstance(docker_cleanup, str):
|
||||
docker_cleanup = [docker_cleanup]
|
||||
for command in docker_cleanup:
|
||||
docker_compose.execute(command)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# setup containers.
|
||||
if docker_setup:
|
||||
# Maintain backwards compatibility with the string format.
|
||||
if isinstance(docker_setup, str):
|
||||
docker_setup = [docker_setup]
|
||||
for command in docker_setup:
|
||||
docker_compose.execute(command)
|
||||
|
||||
try:
|
||||
# Let test(s) run.
|
||||
yield Services(docker_compose)
|
||||
finally:
|
||||
# Clean up.
|
||||
if docker_cleanup:
|
||||
# Maintain backwards compatibility with the string format.
|
||||
if isinstance(docker_cleanup, str):
|
||||
docker_cleanup = [docker_cleanup]
|
||||
for command in docker_cleanup:
|
||||
docker_compose.execute(command)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def docker_services(
|
||||
docker_compose_command: str,
|
||||
docker_compose_file: list[str] | str,
|
||||
docker_compose_project_name: str,
|
||||
docker_setup: str,
|
||||
docker_cleanup: str,
|
||||
request,
|
||||
) -> Iterator[Services]:
|
||||
"""Start all services from a docker compose file (`docker-compose up`).
|
||||
After test are finished, shutdown all services (`docker-compose down`)."""
|
||||
|
||||
if request.config.getoption("--skip-docker"):
|
||||
yield
|
||||
return
|
||||
|
||||
with get_docker_services(
|
||||
docker_compose_command,
|
||||
docker_compose_file,
|
||||
docker_compose_project_name,
|
||||
docker_setup,
|
||||
docker_cleanup,
|
||||
) as docker_service:
|
||||
yield docker_service
|
6
backend/tests/docker-compose.yml
Normal file
6
backend/tests/docker-compose.yml
Normal file
@ -0,0 +1,6 @@
|
||||
version: '2'
|
||||
services:
|
||||
scylla:
|
||||
image: scylladb/scylla:latest
|
||||
ports:
|
||||
- "9070:9042"
|
70
backend/tests/test_login.py
Normal file
70
backend/tests/test_login.py
Normal file
@ -0,0 +1,70 @@
|
||||
import os
|
||||
import socket
|
||||
|
||||
import pytest
|
||||
from bec_atlas.main import AtlasApp
|
||||
from bec_atlas.utils.setup_database import setup_database
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def scylla_container(docker_ip, docker_services):
|
||||
host = docker_ip
|
||||
if os.path.exists("/.dockerenv"):
|
||||
# if we are running in the CI, scylla was started as 'scylla' service
|
||||
host = "scylla"
|
||||
if docker_services is None:
|
||||
port = 9042
|
||||
else:
|
||||
port = docker_services.port_for("scylla", 9042)
|
||||
|
||||
setup_database(host=host, port=port)
|
||||
return host, port
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def client(scylla_container):
|
||||
host, port = scylla_container
|
||||
config = {"scylla": {"hosts": [(host, port)]}}
|
||||
|
||||
with TestClient(AtlasApp(config).app) as _client:
|
||||
yield _client
|
||||
|
||||
|
||||
@pytest.mark.timeout(60)
|
||||
def test_login(client):
|
||||
"""
|
||||
Test that the login endpoint returns a token.
|
||||
"""
|
||||
response = client.post(
|
||||
"/api/v1/user/login", json={"username": "admin@bec_atlas.ch", "password": "admin"}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
token = response.json()
|
||||
assert isinstance(token, str)
|
||||
assert len(token) > 20
|
||||
|
||||
|
||||
@pytest.mark.timeout(60)
|
||||
def test_login_wrong_password(client):
|
||||
"""
|
||||
Test that the login returns a 401 when the password is wrong.
|
||||
"""
|
||||
response = client.post(
|
||||
"/api/v1/user/login", json={"username": "admin@bec_atlas.ch", "password": "wrong_password"}
|
||||
)
|
||||
assert response.status_code == 401
|
||||
assert response.json() == {"detail": "Invalid password"}
|
||||
|
||||
|
||||
@pytest.mark.timeout(60)
|
||||
def test_login_unknown_user(client):
|
||||
"""
|
||||
Test that the login returns a 404 when the user is unknown.
|
||||
"""
|
||||
response = client.post(
|
||||
"/api/v1/user/login",
|
||||
json={"username": "no_user@bec_atlas.ch", "password": "wrong_password"},
|
||||
)
|
||||
assert response.status_code == 404
|
||||
assert response.json() == {"detail": "User not found"}
|
Reference in New Issue
Block a user