mirror of
				https://github.com/bunny-lab-io/Borealis.git
				synced 2025-10-26 15:41:58 -06:00 
			
		
		
		
	Add Engine API tests for Stage 4
This commit is contained in:
		| @@ -22,11 +22,11 @@ Lastly, everytime that you complete a stage, you will create a pull request name | ||||
|   - [x] Create domain-focused API blueprints and register_api entry point. | ||||
|   - [x] Mirror route behaviour from the legacy server via service adapters. | ||||
|   - [x] Add configuration toggles for enabling API groups incrementally. | ||||
| - [ ] **Stage 4 — Build unit and smoke tests for Engine APIs** | ||||
|   - [ ] Add pytest modules under Data/Engine/Unit_Tests exercising API blueprints. | ||||
|   - [ ] Provide fixtures that mirror the legacy SQLite schema and seed data. | ||||
|   - [ ] Assert HTTP status codes, payloads, and side effects for parity. | ||||
|   - [ ] Integrate Engine API tests into CI/local workflows. | ||||
| - [x] **Stage 4 — Build unit and smoke tests for Engine APIs** | ||||
|   - [x] Add pytest modules under Data/Engine/Unit_Tests exercising API blueprints. | ||||
|   - [x] Provide fixtures that mirror the legacy SQLite schema and seed data. | ||||
|   - [x] Assert HTTP status codes, payloads, and side effects for parity. | ||||
|   - [x] Integrate Engine API tests into CI/local workflows. | ||||
| - [ ] **Stage 5 — Bridge the legacy server to Engine APIs** | ||||
|   - [ ] Delegate API blueprint registration to the Engine factory from the legacy server. | ||||
|   - [ ] Replace legacy API routes with Engine-provided blueprints gated by a flag. | ||||
| @@ -43,5 +43,5 @@ Lastly, everytime that you complete a stage, you will create a pull request name | ||||
|   - [ ] Update legacy server to consume Engine WebSocket registration. | ||||
|  | ||||
| ## Current Status | ||||
| - **Stage:** Stage 3 — Introduce API blueprints and service adapters (completed) | ||||
| - **Stage:** Stage 4 — Build unit and smoke tests for Engine APIs (completed) | ||||
| - **Active Task:** Awaiting next stage instructions. | ||||
|   | ||||
							
								
								
									
										1
									
								
								Data/Engine/Unit_Tests/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								Data/Engine/Unit_Tests/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| """Unit and smoke tests for the Borealis Engine runtime.""" | ||||
							
								
								
									
										130
									
								
								Data/Engine/Unit_Tests/conftest.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								Data/Engine/Unit_Tests/conftest.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,130 @@ | ||||
| from __future__ import annotations | ||||
|  | ||||
| import sqlite3 | ||||
| from dataclasses import dataclass | ||||
| from pathlib import Path | ||||
| from typing import Iterator | ||||
|  | ||||
| import pytest | ||||
| from flask import Flask | ||||
|  | ||||
| from Data.Engine.server import create_app | ||||
|  | ||||
|  | ||||
| _SCHEMA_DEFINITION = """ | ||||
| CREATE TABLE IF NOT EXISTS devices ( | ||||
|     guid TEXT PRIMARY KEY, | ||||
|     hostname TEXT, | ||||
|     created_at INTEGER, | ||||
|     last_seen INTEGER, | ||||
|     ssl_key_fingerprint TEXT, | ||||
|     token_version INTEGER, | ||||
|     status TEXT, | ||||
|     key_added_at TEXT | ||||
| ); | ||||
| CREATE TABLE IF NOT EXISTS refresh_tokens ( | ||||
|     id TEXT PRIMARY KEY, | ||||
|     guid TEXT, | ||||
|     token_hash TEXT, | ||||
|     dpop_jkt TEXT, | ||||
|     created_at TEXT, | ||||
|     expires_at TEXT, | ||||
|     revoked_at TEXT, | ||||
|     last_used_at TEXT | ||||
| ); | ||||
| CREATE TABLE IF NOT EXISTS enrollment_install_codes ( | ||||
|     id TEXT PRIMARY KEY, | ||||
|     code TEXT UNIQUE, | ||||
|     expires_at TEXT, | ||||
|     used_at TEXT, | ||||
|     used_by_guid TEXT, | ||||
|     max_uses INTEGER, | ||||
|     use_count INTEGER, | ||||
|     last_used_at TEXT | ||||
| ); | ||||
| CREATE TABLE IF NOT EXISTS device_approvals ( | ||||
|     id TEXT PRIMARY KEY, | ||||
|     approval_reference TEXT UNIQUE, | ||||
|     guid TEXT, | ||||
|     hostname_claimed TEXT, | ||||
|     ssl_key_fingerprint_claimed TEXT, | ||||
|     enrollment_code_id TEXT, | ||||
|     status TEXT, | ||||
|     client_nonce TEXT, | ||||
|     server_nonce TEXT, | ||||
|     agent_pubkey_der BLOB, | ||||
|     created_at TEXT, | ||||
|     updated_at TEXT, | ||||
|     approved_by_user_id TEXT | ||||
| ); | ||||
| CREATE TABLE IF NOT EXISTS device_keys ( | ||||
|     id TEXT PRIMARY KEY, | ||||
|     guid TEXT, | ||||
|     ssl_key_fingerprint TEXT, | ||||
|     added_at TEXT, | ||||
|     retired_at TEXT | ||||
| ); | ||||
| """ | ||||
|  | ||||
|  | ||||
| @dataclass | ||||
| class EngineTestHarness: | ||||
|     app: Flask | ||||
|     db_path: Path | ||||
|     bundle_contents: str | ||||
|  | ||||
|  | ||||
| def _initialise_legacy_schema(db_path: Path) -> None: | ||||
|     db_path.parent.mkdir(parents=True, exist_ok=True) | ||||
|     conn = sqlite3.connect(str(db_path)) | ||||
|     try: | ||||
|         conn.executescript(_SCHEMA_DEFINITION) | ||||
|         conn.commit() | ||||
|     finally: | ||||
|         conn.close() | ||||
|  | ||||
|  | ||||
| @pytest.fixture() | ||||
| def engine_harness(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Iterator[EngineTestHarness]: | ||||
|     project_root = Path(__file__).resolve().parents[3] | ||||
|     monkeypatch.setenv("BOREALIS_PROJECT_ROOT", str(project_root)) | ||||
|  | ||||
|     runtime_dir = tmp_path / "runtime" | ||||
|     runtime_dir.mkdir() | ||||
|     cert_root = tmp_path / "certificates" | ||||
|     cert_root.mkdir() | ||||
|  | ||||
|     monkeypatch.setenv("BOREALIS_SERVER_ROOT", str(runtime_dir)) | ||||
|     monkeypatch.setenv("BOREALIS_CERT_ROOT", str(cert_root)) | ||||
|     monkeypatch.setenv("BOREALIS_SERVER_CERT_ROOT", str(cert_root / "Server")) | ||||
|     monkeypatch.setenv("BOREALIS_AGENT_CERT_ROOT", str(cert_root / "Agent")) | ||||
|  | ||||
|     db_path = tmp_path / "database" / "engine.sqlite3" | ||||
|     _initialise_legacy_schema(db_path) | ||||
|  | ||||
|     tls_dir = tmp_path / "tls" | ||||
|     tls_dir.mkdir() | ||||
|     bundle_contents = "-----BEGIN CERTIFICATE-----\nengine-test\n-----END CERTIFICATE-----\n" | ||||
|     cert_path = tls_dir / "server-cert.pem" | ||||
|     key_path = tls_dir / "server-key.pem" | ||||
|     bundle_path = tls_dir / "server-bundle.pem" | ||||
|     cert_path.write_text(bundle_contents, encoding="utf-8") | ||||
|     key_path.write_text("test-key", encoding="utf-8") | ||||
|     bundle_path.write_text(bundle_contents, encoding="utf-8") | ||||
|  | ||||
|     log_path = tmp_path / "logs" / "server.log" | ||||
|     log_path.parent.mkdir(parents=True, exist_ok=True) | ||||
|  | ||||
|     config = { | ||||
|         "DATABASE_PATH": str(db_path), | ||||
|         "TLS_CERT_PATH": str(cert_path), | ||||
|         "TLS_KEY_PATH": str(key_path), | ||||
|         "TLS_BUNDLE_PATH": str(bundle_path), | ||||
|         "LOG_FILE": str(log_path), | ||||
|         "API_GROUPS": ("tokens", "enrollment"), | ||||
|     } | ||||
|  | ||||
|     app, _socketio, _context = create_app(config) | ||||
|     app.config.update(TESTING=True) | ||||
|  | ||||
|     yield EngineTestHarness(app=app, db_path=db_path, bundle_contents=bundle_contents) | ||||
							
								
								
									
										206
									
								
								Data/Engine/Unit_Tests/test_enrollment_api.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										206
									
								
								Data/Engine/Unit_Tests/test_enrollment_api.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,206 @@ | ||||
| from __future__ import annotations | ||||
|  | ||||
| import base64 | ||||
| import os | ||||
| import sqlite3 | ||||
| import uuid | ||||
| from datetime import datetime, timedelta, timezone | ||||
|  | ||||
| from cryptography.hazmat.primitives import serialization | ||||
| from cryptography.hazmat.primitives.asymmetric import ed25519 | ||||
| from flask.testing import FlaskClient | ||||
|  | ||||
| from Modules.crypto import keys as crypto_keys | ||||
|  | ||||
| from .conftest import EngineTestHarness | ||||
|  | ||||
|  | ||||
| def _now() -> datetime: | ||||
|     return datetime.now(tz=timezone.utc) | ||||
|  | ||||
|  | ||||
| def _iso(dt: datetime) -> str: | ||||
|     return dt.astimezone(timezone.utc).isoformat() | ||||
|  | ||||
|  | ||||
| def _seed_install_code(db_path: os.PathLike[str], code: str) -> str: | ||||
|     record_id = str(uuid.uuid4()) | ||||
|     expires_at = _iso(_now() + timedelta(days=1)) | ||||
|     with sqlite3.connect(str(db_path)) as conn: | ||||
|         conn.execute( | ||||
|             """ | ||||
|             INSERT INTO enrollment_install_codes ( | ||||
|                 id, code, expires_at, used_at, used_by_guid, max_uses, use_count, last_used_at | ||||
|             ) VALUES (?, ?, ?, ?, ?, ?, ?, ?) | ||||
|             """, | ||||
|             (record_id, code, expires_at, None, None, 1, 0, None), | ||||
|         ) | ||||
|         conn.commit() | ||||
|     return record_id | ||||
|  | ||||
|  | ||||
| def _generate_agent_material() -> tuple[ed25519.Ed25519PrivateKey, bytes, str]: | ||||
|     private_key = ed25519.Ed25519PrivateKey.generate() | ||||
|     public_der = private_key.public_key().public_bytes( | ||||
|         encoding=serialization.Encoding.DER, | ||||
|         format=serialization.PublicFormat.SubjectPublicKeyInfo, | ||||
|     ) | ||||
|     public_b64 = base64.b64encode(public_der).decode("ascii") | ||||
|     return private_key, public_der, public_b64 | ||||
|  | ||||
|  | ||||
| def test_enrollment_request_creates_pending_approval(engine_harness: EngineTestHarness) -> None: | ||||
|     harness = engine_harness | ||||
|     client: FlaskClient = harness.app.test_client() | ||||
|  | ||||
|     install_code = "INSTALL-CODE-001" | ||||
|     install_code_id = _seed_install_code(harness.db_path, install_code) | ||||
|     private_key, public_der, public_b64 = _generate_agent_material() | ||||
|     client_nonce_bytes = os.urandom(32) | ||||
|     client_nonce_b64 = base64.b64encode(client_nonce_bytes).decode("ascii") | ||||
|  | ||||
|     response = client.post( | ||||
|         "/api/agent/enroll/request", | ||||
|         json={ | ||||
|             "hostname": "agent-node-01", | ||||
|             "enrollment_code": install_code, | ||||
|             "agent_pubkey": public_b64, | ||||
|             "client_nonce": client_nonce_b64, | ||||
|         }, | ||||
|         headers={"X-Borealis-Agent-Context": "interactive"}, | ||||
|     ) | ||||
|  | ||||
|     assert response.status_code == 200 | ||||
|     payload = response.get_json() | ||||
|     assert payload["status"] == "pending" | ||||
|     assert payload["server_certificate"] == harness.bundle_contents | ||||
|     approval_reference = payload["approval_reference"] | ||||
|  | ||||
|     with sqlite3.connect(str(harness.db_path)) as conn: | ||||
|         cur = conn.cursor() | ||||
|         cur.execute( | ||||
|             """ | ||||
|             SELECT hostname_claimed, ssl_key_fingerprint_claimed, client_nonce, status, enrollment_code_id | ||||
|               FROM device_approvals | ||||
|              WHERE approval_reference = ? | ||||
|             """, | ||||
|             (approval_reference,), | ||||
|         ) | ||||
|         row = cur.fetchone() | ||||
|  | ||||
|     assert row is not None | ||||
|     hostname_claimed, fingerprint, stored_client_nonce, status, stored_code_id = row | ||||
|     assert hostname_claimed == "agent-node-01" | ||||
|     assert stored_client_nonce == client_nonce_b64 | ||||
|     assert status == "pending" | ||||
|     assert stored_code_id == install_code_id | ||||
|     expected_fingerprint = crypto_keys.fingerprint_from_spki_der(public_der) | ||||
|     assert fingerprint == expected_fingerprint | ||||
|  | ||||
|  | ||||
| def test_enrollment_poll_finalizes_when_approved(engine_harness: EngineTestHarness) -> None: | ||||
|     harness = engine_harness | ||||
|     client: FlaskClient = harness.app.test_client() | ||||
|  | ||||
|     install_code = "INSTALL-CODE-002" | ||||
|     install_code_id = _seed_install_code(harness.db_path, install_code) | ||||
|     private_key, public_der, public_b64 = _generate_agent_material() | ||||
|     client_nonce_bytes = os.urandom(32) | ||||
|     client_nonce_b64 = base64.b64encode(client_nonce_bytes).decode("ascii") | ||||
|  | ||||
|     request_response = client.post( | ||||
|         "/api/agent/enroll/request", | ||||
|         json={ | ||||
|             "hostname": "agent-node-02", | ||||
|             "enrollment_code": install_code, | ||||
|             "agent_pubkey": public_b64, | ||||
|             "client_nonce": client_nonce_b64, | ||||
|         }, | ||||
|         headers={"X-Borealis-Agent-Context": "system"}, | ||||
|     ) | ||||
|     assert request_response.status_code == 200 | ||||
|     request_payload = request_response.get_json() | ||||
|     approval_reference = request_payload["approval_reference"] | ||||
|     server_nonce_b64 = request_payload["server_nonce"] | ||||
|  | ||||
|     approved_at = _iso(_now()) | ||||
|     with sqlite3.connect(str(harness.db_path)) as conn: | ||||
|         conn.execute( | ||||
|             """ | ||||
|             UPDATE device_approvals | ||||
|                SET status = 'approved', | ||||
|                    updated_at = ?, | ||||
|                    approved_by_user_id = 'operator' | ||||
|              WHERE approval_reference = ? | ||||
|             """, | ||||
|             (approved_at, approval_reference), | ||||
|         ) | ||||
|         conn.commit() | ||||
|  | ||||
|     message = base64.b64decode(server_nonce_b64, validate=True) + approval_reference.encode("utf-8") + client_nonce_bytes | ||||
|     proof_sig = private_key.sign(message) | ||||
|     proof_sig_b64 = base64.b64encode(proof_sig).decode("ascii") | ||||
|  | ||||
|     poll_response = client.post( | ||||
|         "/api/agent/enroll/poll", | ||||
|         json={ | ||||
|             "approval_reference": approval_reference, | ||||
|             "client_nonce": client_nonce_b64, | ||||
|             "proof_sig": proof_sig_b64, | ||||
|         }, | ||||
|     ) | ||||
|  | ||||
|     assert poll_response.status_code == 200 | ||||
|     poll_payload = poll_response.get_json() | ||||
|     assert poll_payload["status"] == "approved" | ||||
|     assert poll_payload["token_type"] == "Bearer" | ||||
|     assert poll_payload["server_certificate"] == harness.bundle_contents | ||||
|  | ||||
|     final_guid = poll_payload["guid"] | ||||
|     assert isinstance(final_guid, str) and len(final_guid) == 36 | ||||
|  | ||||
|     with sqlite3.connect(str(harness.db_path)) as conn: | ||||
|         cur = conn.cursor() | ||||
|         cur.execute( | ||||
|             "SELECT guid, status FROM device_approvals WHERE approval_reference = ?", | ||||
|             (approval_reference,), | ||||
|         ) | ||||
|         approval_row = cur.fetchone() | ||||
|         cur.execute( | ||||
|             "SELECT hostname, ssl_key_fingerprint, token_version FROM devices WHERE guid = ?", | ||||
|             (final_guid,), | ||||
|         ) | ||||
|         device_row = cur.fetchone() | ||||
|         cur.execute( | ||||
|             "SELECT COUNT(*) FROM refresh_tokens WHERE guid = ?", | ||||
|             (final_guid,), | ||||
|         ) | ||||
|         refresh_count = cur.fetchone()[0] | ||||
|         cur.execute( | ||||
|             "SELECT use_count, used_by_guid FROM enrollment_install_codes WHERE id = ?", | ||||
|             (install_code_id,), | ||||
|         ) | ||||
|         install_row = cur.fetchone() | ||||
|         cur.execute( | ||||
|             "SELECT COUNT(*) FROM device_keys WHERE guid = ?", | ||||
|             (final_guid,), | ||||
|         ) | ||||
|         key_count = cur.fetchone()[0] | ||||
|  | ||||
|     assert approval_row is not None | ||||
|     approval_guid, approval_status = approval_row | ||||
|     assert approval_status == "completed" | ||||
|     assert approval_guid == final_guid | ||||
|  | ||||
|     assert device_row is not None | ||||
|     hostname, fingerprint, token_version = device_row | ||||
|     assert hostname == "agent-node-02" | ||||
|     assert fingerprint == crypto_keys.fingerprint_from_spki_der(public_der) | ||||
|     assert token_version >= 1 | ||||
|  | ||||
|     assert refresh_count == 1 | ||||
|     assert install_row is not None | ||||
|     use_count, used_by_guid = install_row | ||||
|     assert use_count == 1 | ||||
|     assert used_by_guid == final_guid | ||||
|     assert key_count == 1 | ||||
							
								
								
									
										92
									
								
								Data/Engine/Unit_Tests/test_tokens_api.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								Data/Engine/Unit_Tests/test_tokens_api.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,92 @@ | ||||
| from __future__ import annotations | ||||
|  | ||||
| import hashlib | ||||
| import sqlite3 | ||||
| from datetime import datetime, timedelta, timezone | ||||
|  | ||||
| from flask.testing import FlaskClient | ||||
|  | ||||
| from .conftest import EngineTestHarness | ||||
|  | ||||
|  | ||||
| def _iso(dt: datetime) -> str: | ||||
|     return dt.astimezone(timezone.utc).isoformat() | ||||
|  | ||||
|  | ||||
| def test_refresh_token_success(engine_harness: EngineTestHarness) -> None: | ||||
|     harness = engine_harness | ||||
|     client: FlaskClient = harness.app.test_client() | ||||
|  | ||||
|     guid = "54E8C9E2-6B3D-4B51-A456-4ACB94C45F00" | ||||
|     refresh_token = "refresh-token-value" | ||||
|     token_hash = hashlib.sha256(refresh_token.encode("utf-8")).hexdigest() | ||||
|     now = datetime.now(tz=timezone.utc) | ||||
|     expires_at = now + timedelta(days=1) | ||||
|  | ||||
|     with sqlite3.connect(str(harness.db_path)) as conn: | ||||
|         cur = conn.cursor() | ||||
|         cur.execute( | ||||
|             """ | ||||
|             INSERT INTO devices (guid, hostname, created_at, last_seen, ssl_key_fingerprint, | ||||
|                                  token_version, status, key_added_at) | ||||
|             VALUES (?, ?, ?, ?, ?, ?, ?, ?) | ||||
|             """, | ||||
|             ( | ||||
|                 guid, | ||||
|                 "device-one", | ||||
|                 int(now.timestamp()), | ||||
|                 int(now.timestamp()), | ||||
|                 "fingerprint", | ||||
|                 1, | ||||
|                 "active", | ||||
|                 _iso(now), | ||||
|             ), | ||||
|         ) | ||||
|         cur.execute( | ||||
|             """ | ||||
|             INSERT INTO refresh_tokens (id, guid, token_hash, created_at, expires_at, revoked_at, last_used_at) | ||||
|             VALUES (?, ?, ?, ?, ?, NULL, NULL) | ||||
|             """, | ||||
|             ( | ||||
|                 "token-row", | ||||
|                 guid, | ||||
|                 token_hash, | ||||
|                 _iso(now), | ||||
|                 _iso(expires_at), | ||||
|             ), | ||||
|         ) | ||||
|         conn.commit() | ||||
|  | ||||
|     response = client.post( | ||||
|         "/api/agent/token/refresh", | ||||
|         json={"guid": guid, "refresh_token": refresh_token}, | ||||
|     ) | ||||
|     assert response.status_code == 200 | ||||
|     payload = response.get_json() | ||||
|     assert payload["token_type"] == "Bearer" | ||||
|     assert payload["expires_in"] == 900 | ||||
|     assert isinstance(payload["access_token"], str) and payload["access_token"] | ||||
|  | ||||
|     with sqlite3.connect(str(harness.db_path)) as conn: | ||||
|         cur = conn.cursor() | ||||
|         cur.execute( | ||||
|             "SELECT last_used_at, revoked_at FROM refresh_tokens WHERE guid = ?", | ||||
|             (guid,), | ||||
|         ) | ||||
|         row = cur.fetchone() | ||||
|     assert row is not None | ||||
|     last_used_at, revoked_at = row | ||||
|     assert last_used_at is not None | ||||
|     assert revoked_at is None | ||||
|  | ||||
|  | ||||
| def test_refresh_token_requires_payload(engine_harness: EngineTestHarness) -> None: | ||||
|     client: FlaskClient = engine_harness.app.test_client() | ||||
|  | ||||
|     response = client.post( | ||||
|         "/api/agent/token/refresh", | ||||
|         json={"guid": "", "refresh_token": ""}, | ||||
|     ) | ||||
|     assert response.status_code == 400 | ||||
|     payload = response.get_json() | ||||
|     assert payload["error"] == "invalid_request" | ||||
		Reference in New Issue
	
	Block a user