diff --git a/API_calls/StudentDatafromLDRESTAPI.py b/API_calls/StudentDatafromLDRESTAPI.py index 48b7655..00c2046 100644 --- a/API_calls/StudentDatafromLDRESTAPI.py +++ b/API_calls/StudentDatafromLDRESTAPI.py @@ -1,10 +1,12 @@ import requests import time import logging -from config.settings import BASE_GESSI_URL +from config.settings import BASE_GESSI_URL, LD_API_KEY, LD_API_KEY_HEADER logger = logging.getLogger(__name__) +LD_HEADERS = {LD_API_KEY_HEADER: LD_API_KEY} + def fetch_projects() -> list: """ Retrieve the list of projects from the LD REST API. @@ -16,7 +18,7 @@ def fetch_projects() -> list: for attempt in range(max_retries): try: - response = requests.get(url, timeout=60) + response = requests.get(url, headers=LD_HEADERS, timeout=60) response.raise_for_status() # Raise an exception if status != 200 projects = response.json() return projects @@ -36,7 +38,7 @@ def fetch_project_details(project_id: int) -> dict: """ url = f"{BASE_GESSI_URL}/projects/{project_id}" # Use increased timeout here as well - response = requests.get(url, timeout=60) + response = requests.get(url, headers=LD_HEADERS, timeout=60) response.raise_for_status() return response.json() diff --git a/README.md b/README.md index 5210ac5..6fc8542 100644 --- a/README.md +++ b/README.md @@ -98,10 +98,11 @@ docker compose up -d --build ld_eval | `MONGO_USER` / `MONGO_PASS` | Credentials (leave blank for local dev) | | `MONGO_AUTHSRC` | Auth DB (usually `admin`) | | `BASE_GESSI_URL` | REST endpoint of the public dashboard | +| `LD_API_KEY` | Shared Learning Dashboard API key sent as `X-LD-API-Key` | | `QUALITY_MODELS_DIR` | Path to `QUALITY_MODELS` folder | | Scheduler: `_Start_scheduler_date`, `_End_scheduler_date`, `_Hour_scheduler` … | Daily refresh window (see `config_files/config_variables.py`) | -All vars can be placed in `.env` and are loaded automatically. +All vars can be placed in `.env` and are loaded automatically. Generate `LD_API_KEY` once from the repository root with `python3 -c 'import secrets; print(secrets.token_urlsafe(48))'` and copy the same value into local consumer `.env` files when running outside Docker. --- diff --git a/config/settings.py b/config/settings.py index 302986a..6fbe1ef 100644 --- a/config/settings.py +++ b/config/settings.py @@ -4,14 +4,29 @@ # Determine the base directory (adjust if needed) BASE_DIR = Path(__file__).resolve().parent.parent +ROOT_DIR = BASE_DIR.parent -# Load environment variables from the .env file +# Load centralized root configuration first, then module-local values for +# standalone runs. Existing environment variables keep priority. +load_dotenv(ROOT_DIR / ".env") load_dotenv(BASE_DIR / ".env") +def _require_env(name: str) -> str: + value = os.getenv(name) + if not value: + raise RuntimeError( + f"Missing required environment variable: {name}. " + f"Please set it in the root .env or this module's .env file." + ) + return value + + QUALITY_MODELS_DIR = os.getenv("QUALITY_MODELS_DIR", "QUALITY_MODELS") BASE_GESSI_URL = os.getenv("BASE_GESSI_URL", "") +LD_API_KEY = _require_env("LD_API_KEY") +LD_API_KEY_HEADER = "X-LD-API-Key" # Mongo database settings diff --git a/template.env b/template.env new file mode 100644 index 0000000..2c282d7 --- /dev/null +++ b/template.env @@ -0,0 +1,16 @@ +# LD Eval local configuration. +# Docker receives LD_API_KEY from the root .env; use this file only for standalone runs. + +BASE_GESSI_URL=http://localhost:8888/api + +# Generate once in the repository root: +# python3 -c 'import secrets; print(secrets.token_urlsafe(48))' +# Copy the same value from the root .env. +LD_API_KEY= + +MONGO_HOST=mongodb +MONGO_PORT=27017 +MONGO_DB=mongo +MONGO_USER= +MONGO_PASS= +MONGO_AUTHSRC=mongo diff --git a/tests/conftest.py b/tests/conftest.py index e855bd4..3d8e8c5 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,8 +1,11 @@ import sys +import os from pathlib import Path ROOT_DIR = Path(__file__).resolve().parents[1] +os.environ.setdefault("LD_API_KEY", "test-ld-api-key") + if str(ROOT_DIR) not in sys.path: sys.path.insert(0, str(ROOT_DIR)) diff --git a/tests/test_student_data_api_key.py b/tests/test_student_data_api_key.py new file mode 100644 index 0000000..02e1169 --- /dev/null +++ b/tests/test_student_data_api_key.py @@ -0,0 +1,39 @@ +from unittest.mock import Mock + +import API_calls.StudentDatafromLDRESTAPI as student_api + + +def test_fetch_projects_sends_ld_api_key(monkeypatch): + response = Mock() + response.json.return_value = [] + response.raise_for_status.return_value = None + mock_get = Mock(return_value=response) + + monkeypatch.setattr(student_api, "BASE_GESSI_URL", "http://tomcat:8080/api") + monkeypatch.setattr(student_api, "LD_HEADERS", {"X-LD-API-Key": "test-ld-api-key"}) + monkeypatch.setattr(student_api.requests, "get", mock_get) + + assert student_api.fetch_projects() == [] + mock_get.assert_called_once_with( + "http://tomcat:8080/api/projects", + headers={"X-LD-API-Key": "test-ld-api-key"}, + timeout=60, + ) + + +def test_fetch_project_details_sends_ld_api_key(monkeypatch): + response = Mock() + response.json.return_value = {"id": 1} + response.raise_for_status.return_value = None + mock_get = Mock(return_value=response) + + monkeypatch.setattr(student_api, "BASE_GESSI_URL", "http://tomcat:8080/api") + monkeypatch.setattr(student_api, "LD_HEADERS", {"X-LD-API-Key": "test-ld-api-key"}) + monkeypatch.setattr(student_api.requests, "get", mock_get) + + assert student_api.fetch_project_details(1) == {"id": 1} + mock_get.assert_called_once_with( + "http://tomcat:8080/api/projects/1", + headers={"X-LD-API-Key": "test-ld-api-key"}, + timeout=60, + )