""" Integration tests for Optimum OH backend. All endpoints are sourced from frontend API calls using OPTIMUM_OH_API_URL. Run with: pytest src/testing/integration_test.py -v """ import pytest import requests BASE_URL = "http://100.125.115.116:8000/optimumoh" AUTH_BASE_URL = "http://100.125.115.116:8000/auth" OVERHAUL_SESSION_ID = "3704c82c-cfc5-47f7-813a-1661f89b0738" SCOPE_JOB_ID = "db590764-72cb-4eb1-a30f-014116df0472" CALCULATION_ID = "44f483f3-bfe4-4094-a59f-b97a10f2fea6" BUDGET_THRESHOLD = 100000 RISK_COST = 1 LOCATION_TAG = "3AL-F501A" SCOPE_NAME = "A" ASSET_NUM = "A19825" EAF_INPUT = 0.85 DURATION = 17520 SCOPE_EQUIPMENT_ACTIVITY_ID = "4527eb2b-0bc4-46b3-80a1-e1d2f67882f8" ITEMNUM = "206510" SPREADSHEET_LINK = "https://docs.google.com/spreadsheets/d/example_spreadsheet_id/edit" # --------------------------------------------------------------------------- # Users # --------------------------------------------------------------------------- USER_LIST = [ {"id": 1, "name": "devmustbeadmin", "pass": "", "email": "", "authorization_token": ""}, {"id": 2, "name": "devmustbeengineer", "pass": "", "email": "", "authorization_token": ""}, {"id": 3, "name": "devmustbeapplicationadmin", "pass": "", "email": "", "authorization_token": ""}, {"id": 4, "name": "devmustbemanagement", "pass": "", "email": "", "authorization_token": ""}, ] # --------------------------------------------------------------------------- # Role-based access constants # --------------------------------------------------------------------------- # Endpoints guarded by require_any_role(*ALLOWED_ROLES) block Management entirely. # Endpoints with only required_permission(READ) allow Management (has READ in ACL). # Endpoints with no guard at all allow every role. NON_MANAGEMENT = {"devmustbeadmin", "devmustbeengineer", "devmustbeapplicationadmin"} ALL_ALLOWED = {"devmustbeadmin", "devmustbeengineer", "devmustbeapplicationadmin", "devmustbemanagement"} def user_auth_headers(token: str) -> dict: return {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} def get_csrf_token(path: str, auth_token: str) -> str: """Fetch a one-time CSRF token from be-auth for the given endpoint path. Args: path: The endpoint path as seen by be-optimumoh (e.g. "/spareparts"). Do NOT include the "/optimumoh" Kong prefix. auth_token: The raw JWT token string (without "Bearer " prefix). Returns: The plain-text CSRF token to send in the X-CSRF-Token header. """ res = requests.post( f"{AUTH_BASE_URL}/csrf-token", headers={ "Authorization": f"Bearer {auth_token}", "Content-Type": "application/json", "X-Target-Path": path, }, json={ "_csrf_request": True, } ) print(f"Requested CSRF token for {path}, got status {res.status_code}, response: {res.text}") assert res.status_code == 200, f"Failed to get CSRF token for {path}: {res.status_code} {res.text}" data = res.json() # Response shape: {"data": {"csrf_token": "..."}, ...} return data["data"]["csrf_token"] # ───────────────────────────────────────────── # Overhaul Schedules # ───────────────────────────────────────────── def test_get_overhaul_schedules(): """GET /overhaul-schedules — require_any_role blocks Management""" for user in USER_LIST: res = requests.get(f"{BASE_URL}/overhaul-schedules", headers=user_auth_headers(user["authorization_token"])) expected = 200 if user["name"] in NON_MANAGEMENT else 403 assert res.status_code == expected, f"[{user['name']}] expected {expected}, got {res.status_code}" def test_get_overhaul_schedules_by_id(): """GET /overhaul-schedules/{overhaul_session_id} — require_any_role blocks Management""" for user in USER_LIST: res = requests.get( f"{BASE_URL}/overhaul-schedules/{OVERHAUL_SESSION_ID}", headers=user_auth_headers(user["authorization_token"]), ) expected = 200 if user["name"] in NON_MANAGEMENT else 403 assert res.status_code == expected, f"[{user['name']}] expected {expected}, got {res.status_code}: {res.text}" def test_post_overhaul_schedules_update(): """POST /overhaul-schedules/update/{scope_id} — require_any_role + EDIT blocks Management Payload: duration_oh (optional int), crew_number (optional int), status (optional str) """ payload = { "duration_oh": 720, "crew_number": 10, "status": "Upcoming", } for user in USER_LIST: csrf_token = get_csrf_token("/overhaul-schedules/update", user["authorization_token"]) headers = {**user_auth_headers(user["authorization_token"]), "X-CSRF-Token": csrf_token} res = requests.post( f"{BASE_URL}/overhaul-schedules/update/{OVERHAUL_SESSION_ID}", json=payload, headers=headers, ) print(f"[{user['name']}], got {res.status_code}, response: {res.text}") if user["name"] in NON_MANAGEMENT: assert res.status_code in (200, 204, 404), f"[{user['name']}] expected 200/204/404, got {res.status_code}" else: assert res.status_code == 403, f"[{user['name']}] expected 403, got {res.status_code}" def test_get_overhaul_schedules_history(): """GET /overhaul-schedules/history — require_any_role blocks Management""" for user in USER_LIST: res = requests.get(f"{BASE_URL}/overhaul-schedules/history", headers=user_auth_headers(user["authorization_token"])) expected = 200 if user["name"] in NON_MANAGEMENT else 403 assert res.status_code == expected, f"[{user['name']}] expected {expected}, got {res.status_code}" def test_post_overhaul_schedules_delete(): """POST /overhaul-schedules/delete/{scope_id} — require_any_role + DELETE blocks Management No body required. CSRF token required in production. """ for user in USER_LIST: csrf_token = get_csrf_token(f"/overhaul-schedules/delete/{OVERHAUL_SESSION_ID}", user["authorization_token"]) headers = {**user_auth_headers(user["authorization_token"]), "X-CSRF-Token": csrf_token} res = requests.post( f"{BASE_URL}/overhaul-schedules/delete/{OVERHAUL_SESSION_ID}", headers=headers, ) print(f"[{user['name']}], got {res.status_code}, response: {res.text}") if user["name"] in NON_MANAGEMENT: assert res.status_code in (200, 204, 404), f"[{user['name']}] expected 200/204/404, got {res.status_code}" else: assert res.status_code == 403, f"[{user['name']}] expected 403, got {res.status_code}" # ───────────────────────────────────────────── # Overhauls # ───────────────────────────────────────────── def test_get_overhauls(): """GET /overhauls — required_permission(READ) only; Management has READ in ACL""" for user in USER_LIST: res = requests.get(f"{BASE_URL}/overhauls", headers=user_auth_headers(user["authorization_token"])) assert res.status_code == 200, f"[{user['name']}] expected 200, got {res.status_code}" # ───────────────────────────────────────────── # Overhaul Gantt # ───────────────────────────────────────────── def test_get_overhaul_gantt_spreadsheet(): """GET /overhaul-gantt/spreadsheet — no role guard; all roles allowed""" for user in USER_LIST: res = requests.get( f"{BASE_URL}/overhaul-gantt/spreadsheet", headers=user_auth_headers(user["authorization_token"]), ) assert res.status_code == 200, f"[{user['name']}] expected 200, got {res.status_code}: {res.text}" # ───────────────────────────────────────────── # Spareparts # ───────────────────────────────────────────── def test_get_spareparts(): """GET /spareparts — require_any_role blocks Management""" for user in USER_LIST: res = requests.get(f"{BASE_URL}/spareparts", headers=user_auth_headers(user["authorization_token"])) expected = 200 if user["name"] in NON_MANAGEMENT else 403 assert res.status_code == expected, f"[{user['name']}] expected {expected}, got {res.status_code}" def test_get_equipment_spareparts(): """GET /equipment-spareparts/{location_tag} — require_any_role blocks Management""" for user in USER_LIST: res = requests.get( f"{BASE_URL}/equipment-spareparts/{LOCATION_TAG}", headers=user_auth_headers(user["authorization_token"]), ) expected = 200 if user["name"] in NON_MANAGEMENT else 403 assert res.status_code == expected, f"[{user['name']}] expected {expected}, got {res.status_code}" def test_post_spareparts(): """POST /spareparts — require_any_role blocks Management Payload: itemnum (str), remark (str). CSRF token required. """ payload = { "itemnum": ITEMNUM, "remark": "Test remark for integration test", } for user in USER_LIST: csrf_token = get_csrf_token("/spareparts", user["authorization_token"]) headers = {**user_auth_headers(user["authorization_token"]), "X-CSRF-Token": csrf_token} res = requests.post(f"{BASE_URL}/spareparts", json=payload, headers=headers) if user["name"] in NON_MANAGEMENT: assert res.status_code in (200, 201), f"[{user['name']}] expected 200/201, got {res.status_code}" else: assert res.status_code == 403, f"[{user['name']}] expected 403, got {res.status_code}" def test_post_overhaul_gantt_spreadsheet(): """POST /overhaul-gantt/spreadsheet — no role guard; all roles allowed Payload: spreadsheet_link (str). CSRF token required in production. """ payload = { "spreadsheet_link": SPREADSHEET_LINK, } for user in USER_LIST: csrf_token = get_csrf_token("/overhaul-gantt/spreadsheet", user["authorization_token"]) headers = {**user_auth_headers(user["authorization_token"]), "X-CSRF-Token": csrf_token} res = requests.post( f"{BASE_URL}/overhaul-gantt/spreadsheet", json=payload, headers=headers, ) assert res.status_code in (200, 201), f"[{user['name']}] expected 200/201, got {res.status_code}" # ───────────────────────────────────────────── # Scope Equipments # ───────────────────────────────────────────── def test_get_scope_equipments(): """GET /scope-equipments?page=1 — require_any_role blocks Management""" for user in USER_LIST: res = requests.get(f"{BASE_URL}/scope-equipments?page=1", headers=user_auth_headers(user["authorization_token"])) expected = 200 if user["name"] in NON_MANAGEMENT else 403 assert res.status_code == expected, f"[{user['name']}] expected {expected}, got {res.status_code}" def test_get_scope_equipments_available(): """GET /scope-equipments/available/{scope_name} — require_any_role blocks Management""" for user in USER_LIST: res = requests.get( f"{BASE_URL}/scope-equipments/available/{SCOPE_NAME}", headers=user_auth_headers(user["authorization_token"]), ) expected = 200 if user["name"] in NON_MANAGEMENT else 403 assert res.status_code == expected, f"[{user['name']}] expected {expected}, got {res.status_code}" # ───────────────────────────────────────────── # Overhaul Schedules (additional) # ───────────────────────────────────────────── def test_get_overhaul_schedules(): """GET /overhaul-schedules — require_any_role blocks Management""" for user in USER_LIST: res = requests.get(f"{BASE_URL}/overhaul-schedules", headers=user_auth_headers(user["authorization_token"])) expected = 200 if user["name"] in NON_MANAGEMENT else 403 assert res.status_code == expected, f"[{user['name']}] expected {expected}, got {res.status_code}: {res.text}" def test_get_overhaul_schedules_history(): """GET /overhaul-schedules/history — require_any_role blocks Management""" for user in USER_LIST: res = requests.get(f"{BASE_URL}/overhaul-schedules/history", headers=user_auth_headers(user["authorization_token"])) expected = 200 if user["name"] in NON_MANAGEMENT else 403 assert res.status_code == expected, f"[{user['name']}] expected {expected}, got {res.status_code}: {res.text}" # ───────────────────────────────────────────── # Overhauls sub-routes # ───────────────────────────────────────────── def test_get_overhauls_system_components(): """GET /overhauls/system-components — required_permission(READ); Management has READ""" for user in USER_LIST: res = requests.get(f"{BASE_URL}/overhauls/system-components", headers=user_auth_headers(user["authorization_token"])) assert res.status_code == 200, f"[{user['name']}] expected 200, got {res.status_code}: {res.text}" # ───────────────────────────────────────────── # Workscopes # ───────────────────────────────────────────── def test_get_workscopes(): """GET /workscopes — no role guard; all roles allowed""" for user in USER_LIST: res = requests.get(f"{BASE_URL}/workscopes", headers=user_auth_headers(user["authorization_token"])) assert res.status_code == 200, f"[{user['name']}] expected 200, got {res.status_code}: {res.text}" def test_get_workscopes_by_id(): """GET /workscopes/{scope_equipment_activity_id}?activity_id=... — no role guard; all roles allowed """ for user in USER_LIST: res = requests.get( f"{BASE_URL}/workscopes/{SCOPE_EQUIPMENT_ACTIVITY_ID}", params={"activity_id": SCOPE_EQUIPMENT_ACTIVITY_ID}, headers=user_auth_headers(user["authorization_token"]), ) assert res.status_code in (200, 404), f"[{user['name']}] expected 200/404, got {res.status_code}: {res.text}" def test_post_workscopes_update(): """POST /workscopes/update/{scope_equipment_activity_id} — no role guard; all roles allowed Payload: description (str) """ payload = { "description": "Updated workscope description", } for user in USER_LIST: csrf_token = get_csrf_token("/workscopes/update", user["authorization_token"]) headers = {**user_auth_headers(user["authorization_token"]), "X-CSRF-Token": csrf_token} res = requests.post( f"{BASE_URL}/workscopes/update/{SCOPE_EQUIPMENT_ACTIVITY_ID}", json=payload, params={"activity_id": SCOPE_EQUIPMENT_ACTIVITY_ID}, headers=headers, ) print(f"[{user['name']}], got {res.status_code}, response: {res.text}") assert res.status_code in (200, 404), f"[{user['name']}] expected 200/404, got {res.status_code}" def test_post_workscopes_delete(): """POST /workscopes/delete/{scope_equipment_activity_id} — no role guard; all roles allowed No body required. """ for user in USER_LIST: csrf_token = get_csrf_token("/workscopes/delete", user["authorization_token"]) headers = {**user_auth_headers(user["authorization_token"]), "X-CSRF-Token": csrf_token} res = requests.post( f"{BASE_URL}/workscopes/delete/{SCOPE_EQUIPMENT_ACTIVITY_ID}", params={"activity_id": SCOPE_EQUIPMENT_ACTIVITY_ID}, headers=headers, ) print(f"[{user['name']}], got {res.status_code}, response: {res.text}") assert res.status_code in (200, 204, 404), f"[{user['name']}] expected 200/204/404, got {res.status_code}" # ───────────────────────────────────────────── # Overhaul Activity # ───────────────────────────────────────────── def test_get_overhaul_activity(): """GET /overhaul-activity/{overhaul_session} — require_any_role blocks Management""" for user in USER_LIST: res = requests.get( f"{BASE_URL}/overhaul-activity/{OVERHAUL_SESSION_ID}", headers=user_auth_headers(user["authorization_token"]), ) expected = 200 if user["name"] in NON_MANAGEMENT else 403 assert res.status_code == expected, f"[{user['name']}] expected {expected}, got {res.status_code}" def test_post_overhaul_activity_current(): """POST /overhaul-activity/{session_id} — require_any_role blocks Management Payload: location_tags (List[str]). CSRF token required. """ payload = { "location_tags": [LOCATION_TAG], } for user in USER_LIST: csrf_token = get_csrf_token("/overhaul-activity", user["authorization_token"]) headers = {**user_auth_headers(user["authorization_token"]), "X-CSRF-Token": csrf_token} res = requests.post( f"{BASE_URL}/overhaul-activity/{OVERHAUL_SESSION_ID}", json=payload, headers=headers, ) if user["name"] in NON_MANAGEMENT: assert res.status_code in (200, 201), f"[{user['name']}] expected 200/201, got {res.status_code}" else: assert res.status_code == 403, f"[{user['name']}] expected 403, got {res.status_code}" def test_delete_overhaul_activity(): """POST /overhaul-activity/delete/{overhaul_session}/{location_tag} — require_any_role blocks Management. CSRF required.""" for user in USER_LIST: csrf_token = get_csrf_token(f"/overhaul-activity/delete/{OVERHAUL_SESSION_ID}/{LOCATION_TAG}", user["authorization_token"]) headers = {**user_auth_headers(user["authorization_token"]), "X-CSRF-Token": csrf_token} res = requests.post( f"{BASE_URL}/overhaul-activity/delete/{OVERHAUL_SESSION_ID}/{LOCATION_TAG}", headers=headers, ) if user["name"] in NON_MANAGEMENT: assert res.status_code in (200, 204, 404), f"[{user['name']}] expected 200/204/404, got {res.status_code}" else: assert res.status_code == 403, f"[{user['name']}] expected 403, got {res.status_code}" # ───────────────────────────────────────────── # Scope Equipments History # ───────────────────────────────────────────── def test_get_scope_equipments_history(): """GET /scope-equipments/history/{oh_session_id} — require_any_role blocks Management""" for user in USER_LIST: res = requests.get( f"{BASE_URL}/scope-equipments/history/{OVERHAUL_SESSION_ID}", headers=user_auth_headers(user["authorization_token"]), ) expected = 200 if user["name"] in NON_MANAGEMENT else 403 assert res.status_code == expected, f"[{user['name']}] expected {expected}, got {res.status_code}: {res.text}" # ───────────────────────────────────────────── # Equipment Workscopes # ───────────────────────────────────────────── def test_get_equipment_workscopes(): """GET /equipment-workscopes/{location_tag} — no role guard; all roles allowed Query params: scope (optional str) """ for user in USER_LIST: res = requests.get( f"{BASE_URL}/equipment-workscopes/{LOCATION_TAG}", headers=user_auth_headers(user["authorization_token"]), ) assert res.status_code == 200, f"[{user['name']}] expected 200, got {res.status_code}: {res.text}" def test_post_equipment_workscopes(): """POST /equipment-workscopes/{assetnum} — no role guard; all roles allowed Payload: job_ids (List[UUID]) """ payload = { "job_ids": [], } for user in USER_LIST: csrf_token = get_csrf_token("/equipment-workscopes", user["authorization_token"]) headers = {**user_auth_headers(user["authorization_token"]), "X-CSRF-Token": csrf_token} res = requests.post( f"{BASE_URL}/equipment-workscopes/{ASSET_NUM}", json=payload, headers=headers, ) print(f"[{user['name']}], got {res.status_code}, response: {res.text}") assert res.status_code in (200, 201), f"[{user['name']}] expected 200/201, got {res.status_code}" # ───────────────────────────────────────────── # Calculation — Target Reliability # ───────────────────────────────────────────── def test_get_calculation_target_reliability(): """GET /calculation/target-reliability — required_permission(READ); Management has READ Query params: oh_session_id (required), eaf_input (float), duration (int), simulation_id (optional), cut_hours (optional) """ for user in USER_LIST: res = requests.get( f"{BASE_URL}/calculation/target-reliability" f"?oh_session_id={OVERHAUL_SESSION_ID}&eaf_input={EAF_INPUT}&duration={DURATION}", headers=user_auth_headers(user["authorization_token"]), ) assert res.status_code in (200, 400, 404, 425), f"[{user['name']}] expected 200/400/404/425, got {res.status_code}: {res.text}" # ───────────────────────────────────────────── # Calculation — Time Constraint # ───────────────────────────────────────────── def test_get_calculation_time_constraint(): """GET /calculation/time-constraint — required_permission(READ); Management has READ""" for user in USER_LIST: res = requests.get( f"{BASE_URL}/calculation/time-constraint", headers=user_auth_headers(user["authorization_token"]), ) assert res.status_code == 200, f"[{user['name']}] expected 200, got {res.status_code}: {res.text}" def test_get_calculation_time_constraint_by_id(): """GET /calculation/time-constraint/{calculation_id} — required_permission(READ); Management has READ Query params: risk_cost (optional int, default 1) """ for user in USER_LIST: res = requests.get( f"{BASE_URL}/calculation/time-constraint/{CALCULATION_ID}?risk_cost={RISK_COST}", headers=user_auth_headers(user["authorization_token"]), ) assert res.status_code in (200, 404), f"[{user['name']}] expected 200/404, got {res.status_code}: {res.text}" def test_post_calculation_time_constraint_simulation(): """POST /calculation/time-constraint/{calculation_id}/simulation — required_permission(READ); Management has READ Payload: intervalDays (int) """ payload = { "intervalDays": 365, } for user in USER_LIST: csrf_token = get_csrf_token("/calculation/time-constraint/simulation", user["authorization_token"]) headers = {**user_auth_headers(user["authorization_token"]), "X-CSRF-Token": csrf_token} res = requests.post( f"{BASE_URL}/calculation/time-constraint/{CALCULATION_ID}/simulation", json=payload, headers=headers, ) print(f"[{user['name']}], got {res.status_code}, response: {res.text}") assert res.status_code in (200, 201, 404), f"[{user['name']}] expected 200/201/404, got {res.status_code}" def test_post_calculation_time_constraint_update(): """POST /calculation/time-constraint/update/{calculation_id} — required_permission(EDIT); Management has no EDIT Payload: List of { location_tag (str), name (str), is_included (bool) }. CSRF token required in production. """ payload = [ {"location_tag": LOCATION_TAG, "name": "Test Equipment", "is_included": True}, ] for user in USER_LIST: csrf_token = get_csrf_token(f"/calculation/time-constraint/update/{CALCULATION_ID}", user["authorization_token"]) headers = {**user_auth_headers(user["authorization_token"]), "X-CSRF-Token": csrf_token} res = requests.post( f"{BASE_URL}/calculation/time-constraint/update/{CALCULATION_ID}", json=payload, headers=headers, ) print(f"[{user['name']}], got {res.status_code}, response: {res.text}") if user["name"] in NON_MANAGEMENT: assert res.status_code in (200, 204, 404), f"[{user['name']}] expected 200/204/404, got {res.status_code}" else: assert res.status_code == 403, f"[{user['name']}] expected 403, got {res.status_code}" # ───────────────────────────────────────────── # Equipment Activities / Workscopes # ───────────────────────────────────────────── def test_get_equipment_workscopes(): """GET /equipment-workscopes/{location_tag} — require_any_role blocks Management""" for user in USER_LIST: res = requests.get( f"{BASE_URL}/equipment-workscopes/{LOCATION_TAG}", headers=user_auth_headers(user["authorization_token"]), ) assert res.status_code == 200, f"[{user['name']}] expected 200, got {res.status_code}"