# Quick Test Guide: Simple Microservice Integration (Final - No User Isolation, SQLite)

Complete workflow to test FastAPI microservices working like plugins in BrainDrive using simplified architecture.

## Overview

This guide shows how to:
1. **Install** a FastAPI microservice system-wide (no user isolation)
2. **Start/Stop** the microservice through BrainDrive's universal API
3. **Test** the microservice functionality
4. **Access** microservice endpoints directly
5. **Delete** the microservice when done

## Prerequisites

- BrainDrive backend running on `http://localhost:8005`
- SQLite database
- Universal Lifecycle Manager system enabled
- **Simplified microservice database table** created (see migration below)

## Database Migration Required

Before testing, create the simplified microservice table:

```sql
-- Create the microservice table (SQLite syntax)
CREATE TABLE microservice (
    id TEXT PRIMARY KEY,
    plugin_id TEXT NOT NULL,
    name TEXT NOT NULL,
    description TEXT,
    framework TEXT NOT NULL,
    entry_point TEXT NOT NULL,
    port INTEGER NOT NULL UNIQUE,
    health_endpoint TEXT DEFAULT '/health',
    process_type TEXT DEFAULT 'uvicorn',
    
    -- Process Management
    process_id INTEGER,
    status TEXT DEFAULT 'stopped',
    last_started_at TEXT,
    last_stopped_at TEXT,
    restart_count INTEGER DEFAULT 0,
    
    -- Configuration (JSON as TEXT)
    environment_variables TEXT,
    command_args TEXT,
    working_directory TEXT,
    
    -- Monitoring
    health_check_interval INTEGER DEFAULT 30,
    last_health_check TEXT,
    health_status TEXT DEFAULT 'unknown',
    
    -- Resource Limits
    memory_limit_mb INTEGER,
    cpu_limit_percent INTEGER,
    
    -- Dependencies
    python_requirements TEXT,
    system_dependencies TEXT,
    
    -- Networking
    bind_host TEXT DEFAULT '0.0.0.0',
    external_access INTEGER DEFAULT 0,
    
    -- Timestamps
    created_at TEXT DEFAULT CURRENT_TIMESTAMP,
    updated_at TEXT DEFAULT CURRENT_TIMESTAMP,
    
    -- Foreign Key (optional)
    FOREIGN KEY (plugin_id) REFERENCES plugin(id) ON DELETE CASCADE
);

-- Create indexes
CREATE INDEX idx_microservice_plugin_id ON microservice(plugin_id);
CREATE INDEX idx_microservice_status ON microservice(status);
CREATE INDEX idx_microservice_port ON microservice(port);

-- Trigger for updated_at
CREATE TRIGGER update_microservice_timestamp 
    AFTER UPDATE ON microservice
    FOR EACH ROW
    BEGIN
        UPDATE microservice SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
    END;
```

## Step-by-Step Test

### 1. Prepare Test Microservice (Simplified)

Create the test microservice directory structure:

```bash
mkdir -p /home/hacker/BrainDriveDev/BrainDrive/backend/app/plugins/test-microservice
```

Copy these files to `/home/hacker/BrainDriveDev/BrainDrive/backend/app/plugins/test-microservice/`:

**lifecycle_manager.py** (Simplified - No User Isolation)
```python
#!/usr/bin/env python3
"""
Test Microservice Lifecycle Manager - Simplified (No User Isolation, SQLite)
"""

import json
import datetime
import subprocess
import signal
import os
import asyncio
from pathlib import Path
from typing import Dict, Any
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import text
import structlog

logger = structlog.get_logger()

class TestMicroserviceLifecycleManager:
    """Simplified lifecycle manager for Test Microservice"""

    def __init__(self, plugins_base_dir: str = None):
        if plugins_base_dir:
            self.plugins_base_dir = Path(plugins_base_dir)
        else:
            self.plugins_base_dir = Path(__file__).parent.parent

        # Define plugin metadata
        self.PLUGIN_DATA = {
            "name": "Test Microservice",
            "description": "A simple FastAPI microservice for testing BrainDrive integration",
            "version": "1.0.0",
            "type": "microservice",
            "icon": "Server",
            "category": "development",
            "official": True,
            "author": "BrainDrive Team",
            "plugin_slug": "test-microservice",
            "source_type": "local",
            "source_url": "local://plugins/test-microservice"
        }

        # Define microservice configuration
        self.MICROSERVICE_CONFIG = {
            "framework": "fastapi",
            "entry_point": "service.py",
            "port": 8010,
            "health_endpoint": "/health",
            "process_type": "uvicorn",
            "environment_variables": {
                "PYTHONPATH": ".",
                "ENVIRONMENT": "development"
            },
            "command_args": ["--reload", "--log-level", "info"],
            "python_requirements": "fastapi==0.104.1\nuvicorn==0.24.0",
            "system_dependencies": [],
            "bind_host": "0.0.0.0",
            "external_access": False,
            "health_check_interval": 30,
            "memory_limit_mb": 512,
            "cpu_limit_percent": 50
        }

        # Process management (single process)
        self.process = None

    async def install_plugin(self, db: AsyncSession) -> Dict[str, Any]:
        """Install microservice (system-wide, no user isolation)"""
        try:
            logger.info("Installing test microservice")

            # Check if already installed
            existing = await self._check_existing_microservice(db)
            if existing.get('exists'):
                return {
                    'success': False,
                    'error': 'Test microservice is already installed'
                }

            plugin_id = "test-microservice"
            microservice_id = f"{plugin_id}_microservice"
            
            # Create microservice record
            microservice_query = text("""
            INSERT INTO microservice (id, plugin_id, name, description, framework,
                                    entry_point, port, health_endpoint, process_type, status,
                                    environment_variables, command_args, python_requirements,
                                    system_dependencies, bind_host, external_access,
                                    health_check_interval, memory_limit_mb, cpu_limit_percent,
                                    created_at)
            VALUES (:id, :plugin_id, :name, :description, :framework,
                    :entry_point, :port, :health_endpoint, :process_type, :status,
                    :environment_variables, :command_args, :python_requirements,
                    :system_dependencies, :bind_host, :external_access,
                    :health_check_interval, :memory_limit_mb, :cpu_limit_percent,
                    :created_at)
            """)
            
            await db.execute(microservice_query, {
                'id': microservice_id,
                'plugin_id': plugin_id,
                'name': self.PLUGIN_DATA['name'],
                'description': self.PLUGIN_DATA['description'],
                'framework': self.MICROSERVICE_CONFIG['framework'],
                'entry_point': self.MICROSERVICE_CONFIG['entry_point'],
                'port': self.MICROSERVICE_CONFIG['port'],
                'health_endpoint': self.MICROSERVICE_CONFIG['health_endpoint'],
                'process_type': self.MICROSERVICE_CONFIG['process_type'],
                'status': 'stopped',
                'environment_variables': json.dumps(self.MICROSERVICE_CONFIG['environment_variables']),
                'command_args': json.dumps(self.MICROSERVICE_CONFIG['command_args']),
                'python_requirements': self.MICROSERVICE_CONFIG['python_requirements'],
                'system_dependencies': json.dumps(self.MICROSERVICE_CONFIG['system_dependencies']),
                'bind_host': self.MICROSERVICE_CONFIG['bind_host'],
                'external_access': 1 if self.MICROSERVICE_CONFIG['external_access'] else 0,
                'health_check_interval': self.MICROSERVICE_CONFIG['health_check_interval'],
                'memory_limit_mb': self.MICROSERVICE_CONFIG['memory_limit_mb'],
                'cpu_limit_percent': self.MICROSERVICE_CONFIG['cpu_limit_percent'],
                'created_at': datetime.datetime.now().isoformat()
            })

            await db.commit()

            # Start the microservice process
            start_result = await self._start_microservice_process(microservice_id, db)
            
            logger.info("Test microservice installed successfully")
            
            return {
                'success': True,
                'plugin_id': plugin_id,
                'microservice_id': microservice_id,
                'microservice_started': start_result.get('success', False),
                'port': self.MICROSERVICE_CONFIG['port'],
                'health_endpoint': f"http://localhost:{self.MICROSERVICE_CONFIG['port']}/health"
            }

        except Exception as e:
            logger.error(f"Error installing test microservice: {e}")
            await db.rollback()
            return {'success': False, 'error': str(e)}

    async def delete_plugin(self, db: AsyncSession) -> Dict[str, Any]:
        """Delete microservice (system-wide)"""
        try:
            logger.info("Deleting test microservice")

            plugin_id = "test-microservice"
            microservice_id = f"{plugin_id}_microservice"

            # Stop the microservice process first
            await self._stop_microservice_process(microservice_id, db)

            # Delete microservice record
            microservice_delete_query = text("""
            DELETE FROM microservice WHERE id = :microservice_id
            """)
            
            result = await db.execute(microservice_delete_query, {
                'microservice_id': microservice_id
            })

            if result.rowcount == 0:
                return {'success': False, 'error': 'Test microservice not found'}

            await db.commit()

            logger.info("Test microservice deleted successfully")
            
            return {
                'success': True,
                'plugin_id': plugin_id,
                'microservice_id': microservice_id,
                'microservice_stopped': True
            }

        except Exception as e:
            logger.error(f"Error deleting test microservice: {e}")
            await db.rollback()
            return {'success': False, 'error': str(e)}

    async def get_plugin_status(self, db: AsyncSession) -> Dict[str, Any]:
        """Get microservice status (system-wide)"""
        try:
            # Check microservice installation
            microservice_data = await self._get_microservice_data(db)
            if not microservice_data:
                return {'exists': False, 'status': 'not_installed'}

            # Check process status
            process_status = await self._get_process_status()
            
            # Check if service is responding
            health_status = await self._check_health()

            return {
                'exists': True,
                'status': 'healthy' if process_status['running'] and health_status['healthy'] else 'unhealthy',
                'plugin_info': self.PLUGIN_DATA,
                'microservice_info': microservice_data,
                'process_status': process_status,
                'health_status': health_status,
                'endpoints': {
                    'health': f"http://localhost:{microservice_data['port']}/health",
                    'root': f"http://localhost:{microservice_data['port']}/",
                    'info': f"http://localhost:{microservice_data['port']}/info",
                    'docs': f"http://localhost:{microservice_data['port']}/docs"
                }
            }

        except Exception as e:
            logger.error(f"Error getting plugin status: {e}")
            return {'exists': False, 'status': 'error', 'error': str(e)}

    async def _check_existing_microservice(self, db: AsyncSession) -> Dict[str, Any]:
        """Check if microservice exists"""
        try:
            plugin_id = "test-microservice"
            
            query = text("""
            SELECT id, name FROM microservice 
            WHERE plugin_id = :plugin_id
            """)
            
            result = await db.execute(query, {'plugin_id': plugin_id})
            row = result.fetchone()
            
            return {'exists': bool(row), 'plugin_id': plugin_id}

        except Exception as e:
            logger.error(f"Error checking existing microservice: {e}")
            return {'exists': False, 'error': str(e)}

    async def _get_microservice_data(self, db: AsyncSession) -> Dict[str, Any]:
        """Get microservice data from database"""
        try:
            microservice_id = "test-microservice_microservice"
            
            query = text("""
            SELECT * FROM microservice WHERE id = :microservice_id
            """)
            
            result = await db.execute(query, {'microservice_id': microservice_id})
            row = result.fetchone()
            
            if row:
                return {
                    'id': row.id,
                    'plugin_id': row.plugin_id,
                    'name': row.name,
                    'framework': row.framework,
                    'entry_point': row.entry_point,
                    'port': row.port,
                    'health_endpoint': row.health_endpoint,
                    'process_type': row.process_type,
                    'status': row.status,
                    'process_id': row.process_id
                }
            return None

        except Exception as e:
            logger.error(f"Error getting microservice data: {e}")
            return None

    async def _start_microservice_process(self, microservice_id: str, db: AsyncSession) -> Dict[str, Any]:
        """Start the microservice process"""
        try:
            if self.process and self.process.poll() is None:
                return {'success': True, 'message': 'Process already running', 'pid': self.process.pid}

            # Get the service file path
            service_file = Path(__file__).parent / "service.py"
            
            if not service_file.exists():
                return {'success': False, 'error': f'Service file not found: {service_file}'}

            # Start the uvicorn process
            port = self.MICROSERVICE_CONFIG['port']
            
            cmd = [
                'python', '-m', 'uvicorn',
                'service:app',
                '--host', self.MICROSERVICE_CONFIG['bind_host'],
                '--port', str(port)
            ] + self.MICROSERVICE_CONFIG['command_args']
            
            # Start process
            self.process = subprocess.Popen(
                cmd,
                cwd=str(Path(__file__).parent),
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                preexec_fn=os.setsid
            )
            
            # Give it a moment to start
            await asyncio.sleep(2)
            
            # Check if process is still running
            if self.process.poll() is None:
                # Update database with process info
                await self._update_microservice_status(microservice_id, db, {
                    'status': 'running',
                    'process_id': self.process.pid,
                    'last_started_at': datetime.datetime.now().isoformat()
                })
                
                logger.info(f"Microservice started successfully on port {port}, PID: {self.process.pid}")
                return {'success': True, 'pid': self.process.pid, 'port': port}
            else:
                # Process died
                stdout, stderr = self.process.communicate()
                error_msg = f"Process failed to start. STDERR: {stderr.decode()}"
                logger.error(error_msg)
                
                await self._update_microservice_status(microservice_id, db, {'status': 'error'})
                return {'success': False, 'error': error_msg}

        except Exception as e:
            logger.error(f"Error starting microservice process: {e}")
            return {'success': False, 'error': str(e)}

    async def _stop_microservice_process(self, microservice_id: str, db: AsyncSession) -> Dict[str, Any]:
        """Stop the microservice process"""
        try:
            if not self.process:
                await self._update_microservice_status(microservice_id, db, {
                    'status': 'stopped',
                    'process_id': None,
                    'last_stopped_at': datetime.datetime.now().isoformat()
                })
                return {'success': True, 'message': 'Process not running'}

            if self.process.poll() is not None:
                # Process already terminated
                self.process = None
                await self._update_microservice_status(microservice_id, db, {
                    'status': 'stopped',
                    'process_id': None,
                    'last_stopped_at': datetime.datetime.now().isoformat()
                })
                return {'success': True, 'message': 'Process already terminated'}

            # Terminate the process
            try:
                os.killpg(os.getpgid(self.process.pid), signal.SIGTERM)
                
                try:
                    self.process.wait(timeout=5)
                except subprocess.TimeoutExpired:
                    os.killpg(os.getpgid(self.process.pid), signal.SIGKILL)
                    self.process.wait()
                
                self.process = None
                
                await self._update_microservice_status(microservice_id, db, {
                    'status': 'stopped',
                    'process_id': None,
                    'last_stopped_at': datetime.datetime.now().isoformat()
                })
                
                logger.info("Microservice stopped successfully")
                return {'success': True, 'message': 'Process terminated'}
                
            except ProcessLookupError:
                self.process = None
                await self._update_microservice_status(microservice_id, db, {
                    'status': 'stopped',
                    'process_id': None,
                    'last_stopped_at': datetime.datetime.now().isoformat()
                })
                return {'success': True, 'message': 'Process already terminated'}

        except Exception as e:
            logger.error(f"Error stopping microservice process: {e}")
            return {'success': False, 'error': str(e)}

    async def _update_microservice_status(self, microservice_id: str, db: AsyncSession, updates: Dict[str, Any]):
        """Update microservice status in database"""
        try:
            set_clauses = []
            params = {'microservice_id': microservice_id}
            
            for key, value in updates.items():
                set_clauses.append(f"{key} = :{key}")
                params[key] = value
            
            if set_clauses:
                query = text(f"""
                UPDATE microservice 
                SET {', '.join(set_clauses)}, updated_at = :updated_at
                WHERE id = :microservice_id
                """)
                
                params['updated_at'] = datetime.datetime.now().isoformat()
                await db.execute(query, params)
                await db.commit()

        except Exception as e:
            logger.error(f"Error updating microservice status: {e}")
            await db.rollback()

    async def _get_process_status(self) -> Dict[str, Any]:
        """Get process status"""
        try:
            if not self.process:
                return {'running': False, 'message': 'Process not started'}

            if self.process.poll() is None:
                return {
                    'running': True,
                    'pid': self.process.pid,
                    'port': self.MICROSERVICE_CONFIG['port']
                }
            else:
                self.process = None
                return {'running': False, 'message': 'Process terminated'}

        except Exception as e:
            logger.error(f"Error getting process status: {e}")
            return {'running': False, 'error': str(e)}

    async def _check_health(self) -> Dict[str, Any]:
        """Check if microservice is responding"""
        try:
            import aiohttp
            
            port = self.MICROSERVICE_CONFIG['port']
            health_url = f"http://localhost:{port}/health"
            
            async with aiohttp.ClientSession() as session:
                try:
                    async with session.get(health_url, timeout=aiohttp.ClientTimeout(total=5)) as response:
                        if response.status == 200:
                            health_data = await response.json()
                            return {'healthy': True, 'response': health_data}
                        else:
                            return {'healthy': False, 'error': f'Health check returned status {response.status}'}
                except aiohttp.ClientError as e:
                    return {'healthy': False, 'error': f'Health check failed: {str(e)}'}

        except ImportError:
            return {'healthy': True, 'message': 'Health check skipped (aiohttp not available)'}
        except Exception as e:
            logger.error(f"Error checking health: {e}")
            return {'healthy': False, 'error': str(e)}
```

**service.py** (FastAPI microservice - unchanged)
```python
from fastapi import FastAPI
from datetime import datetime
import os

app = FastAPI(
    title="Test Microservice",
    description="A simple test microservice for BrainDrive",
    version="1.0.0"
)

@app.get("/")
async def root():
    return {
        "message": "Test Microservice is running!",
        "service": "test-microservice",
        "timestamp": datetime.now().isoformat(),
        "pid": os.getpid()
    }

@app.get("/health")
async def health():
    return {
        "status": "healthy",
        "service": "test-microservice",
        "timestamp": datetime.now().isoformat(),
        "uptime": "running"
    }

@app.get("/info")
async def info():
    return {
        "name": "Test Microservice",
        "version": "1.0.0",
        "framework": "FastAPI",
        "endpoints": ["/", "/health", "/info", "/echo/{message}"],
        "port": 8010
    }

@app.get("/echo/{message}")
async def echo(message: str):
    return {
        "original": message,
        "echo": f"Echo: {message}",
        "timestamp": datetime.now().isoformat(),
        "service": "test-microservice"
    }

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8010)
```

### 2. Test Complete Workflow (Simplified)

#### A. Check Available Plugins
```bash
curl "http://localhost:8005/api/plugins/available" \
  -H "Authorization: Bearer YOUR_TOKEN"
```

#### B. Install Microservice (System-wide)
```bash
curl -X POST "http://localhost:8005/api/plugins/test-microservice/install" \
  -H "Authorization: Bearer YOUR_TOKEN"
```

**Expected Response:**
```json
{
  "status": "success",
  "message": "Plugin 'test-microservice' installed successfully",
  "data": {
    "plugin_slug": "test-microservice",
    "plugin_id": "test-microservice",
    "microservice_id": "test-microservice_microservice",
    "microservice_started": true,
    "port": 8010,
    "health_endpoint": "http://localhost:8010/health"
  }
}
```

#### C. Check Installation Status
```bash
curl "http://localhost:8005/api/plugins/test-microservice/status" \
  -H "Authorization: Bearer YOUR_TOKEN"
```

**Expected Response:**
```json
{
  "status": "success",
  "data": {
    "exists": true,
    "status": "healthy",
    "plugin_info": {
      "name": "Test Microservice",
      "version": "1.0.0",
      "type": "microservice"
    },
    "microservice_info": {
      "id": "test-microservice_microservice",
      "plugin_id": "test-microservice",
      "name": "Test Microservice",
      "framework": "fastapi",
      "port": 8010,
      "status": "running",
      "process_id": 12345
    },
    "process_status": {
      "running": true,
      "pid": 12345,
      "port": 8010
    },
    "health_status": {
      "healthy": true,
      "response": {
        "status": "healthy",
        "service": "test-microservice"
      }
    },
    "endpoints": {
      "health": "http://localhost:8010/health",
      "root": "http://localhost:8010/",
      "info": "http://localhost:8010/info",
      "docs": "http://localhost:8010/docs"
    }
  }
}
```

#### D. Test Direct Microservice Access
```bash
# Test all endpoints directly
curl "http://localhost:8010/"
curl "http://localhost:8010/health"
curl "http://localhost:8010/info"
curl "http://localhost:8010/echo/hello-world"
```

#### E. Uninstall Microservice
```bash
curl -X DELETE "http://localhost:8005/api/plugins/test-microservice/uninstall" \
  -H "Authorization: Bearer YOUR_TOKEN"
```

**Expected Response:**
```json
{
  "status": "success",
  "message": "Plugin 'test-microservice' uninstalled successfully",
  "data": {
    "plugin_id": "test-microservice",
    "microservice_id": "test-microservice_microservice",
    "microservice_stopped": true
  }
}
```

## Success Criteria Checklist

✅ **System-wide Installation** - Microservice installs without user isolation  
✅ **SQLite Compatibility** - Uses proper SQLite data types and syntax  
✅ **Process Management** - Microservice starts/stops with database tracking  
✅ **Direct Access** - Can access microservice endpoints directly  
✅ **Plugin API Compatibility** - Uses same install/uninstall/status API  
✅ **Database Integration** - Microservice records stored in dedicated table  
✅ **Port Management** - Unique port assignment system-wide  
✅ **Simplified Lifecycle** - Install → run → uninstall workflow works  

## Key Benefits of Simplified Approach

### ✅ **No User Complexity**
- **System-wide microservices** - No per-user isolation needed
- **Simple plugin IDs** - No user prefixes required
- **Unified management** - One microservice instance per type

### ✅ **SQLite Optimized**
- **Proper data types** - TEXT for strings, INTEGER for numbers
- **Boolean handling** - Uses 0/1 for boolean values
- **Timestamp format** - ISO strings for SQLite compatibility
- **JSON storage** - Configuration stored as TEXT

### ✅ **Simplified Architecture**
- **Single process** - One microservice process per type
- **Direct access** - No proxy complexity
- **Easy testing** - Straightforward install/test/uninstall workflow

This simplified approach is much more appropriate for a system-wide microservice architecture using SQLite without user isolation complexity.