Skip to content
Open
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
5 changes: 5 additions & 0 deletions scripts/smoke.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@echo off
REM smoke.bat - Windows wrapper for the minimal stdlib unittest smoke harness.
echo Running MUIOGO Smoke Tests...
python "%~dp0..\tests\smoke_test.py"
pause
9 changes: 9 additions & 0 deletions scripts/smoke.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/usr/bin/env bash
# smoke.sh - Wrapper for the minimal stdlib unittest smoke harness.
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"

echo "Running MUIOGO Smoke Tests..."
python3 "$PROJECT_ROOT/tests/smoke_test.py"
97 changes: 97 additions & 0 deletions scripts/verify_base.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#!/usr/bin/env bash
# ==============================================================================
# MUIOGO VERIFICATION GATEWAY
# ==============================================================================
# This script is the primary safety gate for upstream pulls from OSeMOSYS/MUIO.
# It ensures that MUIOGO's portability, security, and stability are preserved.
#
# MAINTAINER NOTES (Always Review these files during a v5.x pull):
# 1. API/app.py - Core Flask config and blueprint registration.
# 2. API/Classes/Case/OsemosysClass.py - Model execution logic.
# 3. API/Routes/Case/CaseRoute.py - Backend API for case management.
# 4. WebAPP/Classes/Osemosys.Class.js - Frontend solver coordination.
#
# REJECTED PATTERNS:
# - Hardcoded paths (e.g. C:\)
# - Insecure session/path handling
# - Breaking Python 3.10-3.12 support
# ==============================================================================

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"

BOLD="\033[1m"
GREEN="\033[92m"
YELLOW="\033[93m"
RED="\033[91m"
RESET="\033[0m"

# Disable colours when not in a real terminal
if [ ! -t 1 ]; then
BOLD="" GREEN="" YELLOW="" RED="" RESET=""
fi

_print_header() {
echo -e "\n${BOLD}=== $1 ===${RESET}"
}

_print_status() {
if [ "$1" -eq 0 ]; then
echo -e " ${GREEN}[PASS]${RESET} $2"
else
echo -e " ${RED}[FAIL]${RESET} $2"
[ -n "${3:-}" ] && echo -e "\n${3}"
exit 1
fi
}

# 1. Environment & Solvers check
_print_header "Step 1: Environment & Solvers"
# Run setup check quietly, only show output on failure
if OUTPUT=$("$SCRIPT_DIR/setup.sh" --check --no-demo-data 2>&1); then
_print_status 0 "Python venv and solvers (GLPK/CBC) are ready."
else
_print_status 1 "Environment check failed. Run ./scripts/setup.sh first." "$OUTPUT"
fi

# 2. Syntax Check (py_compile)
_print_header "Step 2: Python Syntax Check"
if python3 -m py_compile "$PROJECT_ROOT/API/app.py" 2>/dev/null; then
_print_status 0 "app.py is syntactically valid."
else
_print_status 1 "Syntax error detected in app.py."
fi

# 3. Smoke Test Harness
_print_header "Step 3: API & Route Smoke Tests"
if OUTPUT=$(python3 "$PROJECT_ROOT/tests/smoke_test.py" 2>&1); then
_print_status 0 "Smoke tests passed (API boot and core routes)."
else
_print_status 1 "Smoke tests failed." "$OUTPUT"
fi

# 4. Unresolved Merge state
_print_header "Step 4: Git Merge Integrity"
if [ -d "$PROJECT_ROOT/.git" ]; then
if [ -f "$PROJECT_ROOT/.git/MERGE_HEAD" ] || [ -f "$PROJECT_ROOT/.git/REBASE_HEAD" ] || [ -f "$PROJECT_ROOT/.git/CHERRY_PICK_HEAD" ]; then
_print_status 1 "Unresolved git merge/rebase detected (.git/MERGE_HEAD exists)."
else
_print_status 0 "No unresolved git merge/rebase state found."
fi
else
echo -e " ${YELLOW}[SKIP]${RESET} Not a git repository."
fi

# 5. Conflict Markers scan
_print_header "Step 5: Conflict Marker Scan"
# Use ^ to match markers at the beginning of the line to avoid decorative comments
if grep -rnE "^<<<<<<<|^=======|^>>>>>>>" "$PROJECT_ROOT/API" "$PROJECT_ROOT/WebAPP/App" --exclude-dir=__pycache__ > /dev/null 2>&1; then
_print_status 1 "Conflict markers found in the codebase!"
grep -rnE "^<<<<<<<|^=======|^>>>>>>>" "$PROJECT_ROOT/API" "$PROJECT_ROOT/WebAPP/App" --exclude-dir=__pycache__
else
_print_status 0 "No conflict markers found in key backend/frontend folders."
fi

echo -e "\n${BOLD}${GREEN}Verification Complete: MUIOGO is healthy and ready for upstream pull.${RESET}\n"
55 changes: 55 additions & 0 deletions tests/smoke_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import unittest
import sys
import os
from pathlib import Path

# Add project root to sys.path to allow importing API.app
PROJECT_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(PROJECT_ROOT / "API"))

class SmokeTest(unittest.TestCase):
def setUp(self):
"""Initialize the Flask test client."""
try:
from app import app
app.config['TESTING'] = True
app.config['DEBUG'] = False
self.app = app.test_client()
except ImportError as e:
self.fail(f"Failed to import API.app: {e}")
except Exception as e:
self.fail(f"Failed to initialize Flask app: {e}")

def test_app_import(self):
"""Basic check if the app module can be imported."""
import app
self.assertIsNotNone(app.app)

def test_index_route(self):
"""Check if the home route returns 200."""
response = self.app.get('/')
self.assertEqual(response.status_code, 200)

def test_get_session_route(self):
"""Check if /getSession returns 200 (even if session is empty)."""
response = self.app.get('/getSession')
self.assertEqual(response.status_code, 200)
self.assertIn('session', response.get_json())

def test_get_cases_route(self):
"""Check if /getCases returns 200 or 404 (handled error)."""
response = self.app.get('/getCases')
# /getCases returns 200 with list or 404 if directory error
self.assertIn(response.status_code, [200, 404])

def test_config_validity(self):
"""Verify that the Config class resolves paths correctly."""
from Classes.Base import Config
self.assertTrue(Config.BASE_DIR.exists())
self.assertTrue(Config.WEBAPP_PATH.exists())
# Note: We don't check for writability here as per 'read-only' constraint,
# but we check if the paths are resolved to the correct absolute locations.
self.assertIn("MUIOGO", str(Config.BASE_DIR))

if __name__ == '__main__':
unittest.main()
Loading