Add user delete process

This commit is contained in:
Urtzi Alfaro
2025-10-31 11:54:19 +01:00
parent 63f5c6d512
commit 269d3b5032
74 changed files with 16783 additions and 213 deletions

View File

@@ -0,0 +1,326 @@
#!/usr/bin/env bash
# ============================================================================
# Functional Test: Tenant Deletion System
# ============================================================================
# Tests the complete tenant deletion workflow with service tokens
#
# Usage:
# ./scripts/functional_test_deletion.sh <tenant_id>
#
# Example:
# ./scripts/functional_test_deletion.sh dbc2128a-7539-470c-94b9-c1e37031bd77
#
# ============================================================================
set -e # Exit on error
# Require bash 4+ for associative arrays
if [ "${BASH_VERSINFO[0]}" -lt 4 ]; then
echo "Error: This script requires bash 4.0 or higher"
echo "Current version: ${BASH_VERSION}"
exit 1
fi
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Configuration
TENANT_ID="${1:-dbc2128a-7539-470c-94b9-c1e37031bd77}"
SERVICE_TOKEN="${SERVICE_TOKEN:-eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ0ZW5hbnQtZGVsZXRpb24tb3JjaGVzdHJhdG9yIiwidXNlcl9pZCI6InRlbmFudC1kZWxldGlvbi1vcmNoZXN0cmF0b3IiLCJzZXJ2aWNlIjoidGVuYW50LWRlbGV0aW9uLW9yY2hlc3RyYXRvciIsInR5cGUiOiJzZXJ2aWNlIiwiaXNfc2VydmljZSI6dHJ1ZSwicm9sZSI6ImFkbWluIiwiZW1haWwiOiJ0ZW5hbnQtZGVsZXRpb24tb3JjaGVzdHJhdG9yQGludGVybmFsLnNlcnZpY2UiLCJleHAiOjE3OTM0NDIwMzAsImlhdCI6MTc2MTkwNjAzMCwiaXNzIjoiYmFrZXJ5LWF1dGgifQ.I6mWLpkRim2fJ1v9WH24g4YT3-ZGbuFXxCorZxhPp6c}"
# Test mode (preview or delete)
TEST_MODE="${2:-preview}" # preview or delete
# Service list with their endpoints
declare -A SERVICES=(
["orders"]="orders-service:8000"
["inventory"]="inventory-service:8000"
["recipes"]="recipes-service:8000"
["sales"]="sales-service:8000"
["production"]="production-service:8000"
["suppliers"]="suppliers-service:8000"
["pos"]="pos-service:8000"
["external"]="city-service:8000"
["forecasting"]="forecasting-service:8000"
["training"]="training-service:8000"
["alert-processor"]="alert-processor-service:8000"
["notification"]="notification-service:8000"
)
# Results tracking
TOTAL_SERVICES=12
SUCCESSFUL_TESTS=0
FAILED_TESTS=0
declare -a FAILED_SERVICES
# ============================================================================
# Helper Functions
# ============================================================================
print_header() {
echo -e "${BLUE}============================================================================${NC}"
echo -e "${BLUE}$1${NC}"
echo -e "${BLUE}============================================================================${NC}"
}
print_success() {
echo -e "${GREEN}${NC} $1"
}
print_error() {
echo -e "${RED}${NC} $1"
}
print_warning() {
echo -e "${YELLOW}${NC} $1"
}
print_info() {
echo -e "${BLUE}${NC} $1"
}
# ============================================================================
# Test Functions
# ============================================================================
test_service_preview() {
local service_name=$1
local service_host=$2
local endpoint_path=$3
echo ""
echo -e "${BLUE}Testing ${service_name}...${NC}"
# Get running pod
local pod=$(kubectl get pods -n bakery-ia -l app=${service_name}-service 2>/dev/null | grep Running | head -1 | awk '{print $1}')
if [ -z "$pod" ]; then
print_error "No running pod found for ${service_name}"
FAILED_TESTS=$((FAILED_TESTS + 1))
FAILED_SERVICES+=("${service_name}")
return 1
fi
print_info "Pod: ${pod}"
# Execute request inside pod
local response=$(kubectl exec -n bakery-ia "$pod" -- curl -s -w "\n%{http_code}" \
-H "Authorization: Bearer ${SERVICE_TOKEN}" \
"http://localhost:8000${endpoint_path}/tenant/${TENANT_ID}/deletion-preview" 2>&1)
local http_code=$(echo "$response" | tail -1)
local body=$(echo "$response" | sed '$d')
if [ "$http_code" = "200" ]; then
print_success "Preview successful (HTTP ${http_code})"
# Parse and display counts
local total_records=$(echo "$body" | grep -o '"total_records":[0-9]*' | cut -d':' -f2 || echo "0")
print_info "Records to delete: ${total_records}"
# Show breakdown if available
echo "$body" | python3 -m json.tool 2>/dev/null | grep -A50 "breakdown" | head -20 || echo ""
SUCCESSFUL_TESTS=$((SUCCESSFUL_TESTS + 1))
return 0
elif [ "$http_code" = "401" ]; then
print_error "Authentication failed (HTTP ${http_code})"
print_warning "Service token may be invalid or expired"
echo "$body"
FAILED_TESTS=$((FAILED_TESTS + 1))
FAILED_SERVICES+=("${service_name}")
return 1
elif [ "$http_code" = "403" ]; then
print_error "Authorization failed (HTTP ${http_code})"
print_warning "Service token not recognized as service"
echo "$body"
FAILED_TESTS=$((FAILED_TESTS + 1))
FAILED_SERVICES+=("${service_name}")
return 1
elif [ "$http_code" = "404" ]; then
print_error "Endpoint not found (HTTP ${http_code})"
print_warning "Deletion endpoint may not be implemented"
FAILED_TESTS=$((FAILED_TESTS + 1))
FAILED_SERVICES+=("${service_name}")
return 1
elif [ "$http_code" = "500" ]; then
print_error "Server error (HTTP ${http_code})"
echo "$body" | head -5
FAILED_TESTS=$((FAILED_TESTS + 1))
FAILED_SERVICES+=("${service_name}")
return 1
else
print_error "Unexpected response (HTTP ${http_code})"
echo "$body" | head -5
FAILED_TESTS=$((FAILED_TESTS + 1))
FAILED_SERVICES+=("${service_name}")
return 1
fi
}
test_service_deletion() {
local service_name=$1
local service_host=$2
local endpoint_path=$3
echo ""
echo -e "${BLUE}Deleting data in ${service_name}...${NC}"
# Get running pod
local pod=$(kubectl get pods -n bakery-ia -l app=${service_name}-service 2>/dev/null | grep Running | head -1 | awk '{print $1}')
if [ -z "$pod" ]; then
print_error "No running pod found for ${service_name}"
FAILED_TESTS=$((FAILED_TESTS + 1))
FAILED_SERVICES+=("${service_name}")
return 1
fi
# Execute deletion request inside pod
local response=$(kubectl exec -n bakery-ia "$pod" -- curl -s -w "\n%{http_code}" \
-X DELETE \
-H "Authorization: Bearer ${SERVICE_TOKEN}" \
"http://localhost:8000${endpoint_path}/tenant/${TENANT_ID}" 2>&1)
local http_code=$(echo "$response" | tail -1)
local body=$(echo "$response" | sed '$d')
if [ "$http_code" = "200" ]; then
print_success "Deletion successful (HTTP ${http_code})"
# Parse and display deletion summary
local total_deleted=$(echo "$body" | grep -o '"total_records_deleted":[0-9]*' | cut -d':' -f2 || echo "0")
print_info "Records deleted: ${total_deleted}"
SUCCESSFUL_TESTS=$((SUCCESSFUL_TESTS + 1))
return 0
else
print_error "Deletion failed (HTTP ${http_code})"
echo "$body" | head -5
FAILED_TESTS=$((FAILED_TESTS + 1))
FAILED_SERVICES+=("${service_name}")
return 1
fi
}
# ============================================================================
# Main Test Execution
# ============================================================================
main() {
print_header "Tenant Deletion System - Functional Test"
echo ""
print_info "Tenant ID: ${TENANT_ID}"
print_info "Test Mode: ${TEST_MODE}"
print_info "Services to test: ${TOTAL_SERVICES}"
echo ""
# Verify service token
print_info "Verifying service token..."
if python scripts/generate_service_token.py --verify "${SERVICE_TOKEN}" > /dev/null 2>&1; then
print_success "Service token is valid"
else
print_error "Service token is invalid or expired"
exit 1
fi
echo ""
print_header "Phase 1: Testing Service Previews"
# Test each service preview
test_service_preview "orders" "orders-service:8000" "/api/v1/orders"
test_service_preview "inventory" "inventory-service:8000" "/api/v1/inventory"
test_service_preview "recipes" "recipes-service:8000" "/api/v1/recipes"
test_service_preview "sales" "sales-service:8000" "/api/v1/sales"
test_service_preview "production" "production-service:8000" "/api/v1/production"
test_service_preview "suppliers" "suppliers-service:8000" "/api/v1/suppliers"
test_service_preview "pos" "pos-service:8000" "/api/v1/pos"
test_service_preview "external" "city-service:8000" "/api/v1/nominatim"
test_service_preview "forecasting" "forecasting-service:8000" "/api/v1/forecasting"
test_service_preview "training" "training-service:8000" "/api/v1/training"
test_service_preview "alert-processor" "alert-processor-service:8000" "/api/v1/analytics"
test_service_preview "notification" "notification-service:8000" "/api/v1/notifications"
# Summary
echo ""
print_header "Preview Test Results"
echo -e "Total Services: ${TOTAL_SERVICES}"
echo -e "${GREEN}Successful:${NC} ${SUCCESSFUL_TESTS}/${TOTAL_SERVICES}"
echo -e "${RED}Failed:${NC} ${FAILED_TESTS}/${TOTAL_SERVICES}"
if [ ${FAILED_TESTS} -gt 0 ]; then
echo ""
print_warning "Failed Services:"
for service in "${FAILED_SERVICES[@]}"; do
echo " - ${service}"
done
fi
# Ask for confirmation before actual deletion
if [ "$TEST_MODE" = "delete" ]; then
echo ""
print_header "Phase 2: Actual Deletion"
print_warning "This will PERMANENTLY delete data for tenant ${TENANT_ID}"
print_warning "This operation is IRREVERSIBLE"
echo ""
read -p "Are you sure you want to proceed? (yes/no): " confirm
if [ "$confirm" != "yes" ]; then
print_info "Deletion cancelled by user"
exit 0
fi
# Reset counters
SUCCESSFUL_TESTS=0
FAILED_TESTS=0
FAILED_SERVICES=()
# Execute deletions
test_service_deletion "orders" "orders-service:8000" "/api/v1/orders"
test_service_deletion "inventory" "inventory-service:8000" "/api/v1/inventory"
test_service_deletion "recipes" "recipes-service:8000" "/api/v1/recipes"
test_service_deletion "sales" "sales-service:8000" "/api/v1/sales"
test_service_deletion "production" "production-service:8000" "/api/v1/production"
test_service_deletion "suppliers" "suppliers-service:8000" "/api/v1/suppliers"
test_service_deletion "pos" "pos-service:8000" "/api/v1/pos"
test_service_deletion "external" "city-service:8000" "/api/v1/nominatim"
test_service_deletion "forecasting" "forecasting-service:8000" "/api/v1/forecasting"
test_service_deletion "training" "training-service:8000" "/api/v1/training"
test_service_deletion "alert-processor" "alert-processor-service:8000" "/api/v1/analytics"
test_service_deletion "notification" "notification-service:8000" "/api/v1/notifications"
# Deletion summary
echo ""
print_header "Deletion Test Results"
echo -e "Total Services: ${TOTAL_SERVICES}"
echo -e "${GREEN}Successful:${NC} ${SUCCESSFUL_TESTS}/${TOTAL_SERVICES}"
echo -e "${RED}Failed:${NC} ${FAILED_TESTS}/${TOTAL_SERVICES}"
if [ ${FAILED_TESTS} -gt 0 ]; then
echo ""
print_warning "Failed Services:"
for service in "${FAILED_SERVICES[@]}"; do
echo " - ${service}"
done
fi
fi
echo ""
print_header "Test Complete"
if [ ${FAILED_TESTS} -eq 0 ]; then
print_success "All tests passed successfully!"
exit 0
else
print_error "Some tests failed. See details above."
exit 1
fi
}
# Run main function
main

View File

@@ -0,0 +1,137 @@
#!/bin/bash
# ============================================================================
# Functional Test: Tenant Deletion System (Simple Version)
# ============================================================================
set +e # Don't exit on error
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
# Configuration
TENANT_ID="${1:-dbc2128a-7539-470c-94b9-c1e37031bd77}"
SERVICE_TOKEN="${SERVICE_TOKEN}"
# Results
TOTAL_SERVICES=12
SUCCESSFUL_TESTS=0
FAILED_TESTS=0
# Helper functions
print_header() {
echo -e "${BLUE}================================================================================${NC}"
echo -e "${BLUE}$1${NC}"
echo -e "${BLUE}================================================================================${NC}"
}
print_success() {
echo -e "${GREEN}${NC} $1"
}
print_error() {
echo -e "${RED}${NC} $1"
}
print_info() {
echo -e "${BLUE}${NC} $1"
}
# Test function
test_service() {
local service_name=$1
local endpoint_path=$2
echo ""
echo -e "${BLUE}Testing ${service_name}...${NC}"
# Find running pod
local pod=$(kubectl get pods -n bakery-ia 2>/dev/null | grep "${service_name}" | grep "Running" | grep "1/1" | head -1 | awk '{print $1}')
if [ -z "$pod" ]; then
print_error "No running pod found"
FAILED_TESTS=$((FAILED_TESTS + 1))
return 1
fi
print_info "Pod: ${pod}"
# Execute request
local result=$(kubectl exec -n bakery-ia "$pod" -- curl -s -w "\nHTTP_CODE:%{http_code}" \
-H "Authorization: Bearer ${SERVICE_TOKEN}" \
"http://localhost:8000${endpoint_path}/tenant/${TENANT_ID}/deletion-preview" 2>&1)
local http_code=$(echo "$result" | grep "HTTP_CODE" | cut -d':' -f2)
local body=$(echo "$result" | sed '/HTTP_CODE/d')
if [ "$http_code" = "200" ]; then
print_success "Preview successful (HTTP ${http_code})"
local total=$(echo "$body" | grep -o '"total_records":[0-9]*' | cut -d':' -f2 | head -1)
if [ -n "$total" ]; then
print_info "Records to delete: ${total}"
fi
SUCCESSFUL_TESTS=$((SUCCESSFUL_TESTS + 1))
return 0
elif [ "$http_code" = "401" ]; then
print_error "Authentication failed (HTTP ${http_code})"
FAILED_TESTS=$((FAILED_TESTS + 1))
return 1
elif [ "$http_code" = "403" ]; then
print_error "Authorization failed (HTTP ${http_code})"
FAILED_TESTS=$((FAILED_TESTS + 1))
return 1
elif [ "$http_code" = "404" ]; then
print_error "Endpoint not found (HTTP ${http_code})"
FAILED_TESTS=$((FAILED_TESTS + 1))
return 1
elif [ "$http_code" = "500" ]; then
print_error "Server error (HTTP ${http_code})"
echo "$body" | head -3
FAILED_TESTS=$((FAILED_TESTS + 1))
return 1
else
print_error "Unexpected response (HTTP ${http_code})"
FAILED_TESTS=$((FAILED_TESTS + 1))
return 1
fi
}
# Main
print_header "Tenant Deletion System - Functional Test"
echo ""
print_info "Tenant ID: ${TENANT_ID}"
print_info "Services to test: ${TOTAL_SERVICES}"
echo ""
# Test all services
test_service "orders-service" "/api/v1/orders"
test_service "inventory-service" "/api/v1/inventory"
test_service "recipes-service" "/api/v1/recipes"
test_service "sales-service" "/api/v1/sales"
test_service "production-service" "/api/v1/production"
test_service "suppliers-service" "/api/v1/suppliers"
test_service "pos-service" "/api/v1/pos"
test_service "city-service" "/api/v1/nominatim"
test_service "forecasting-service" "/api/v1/forecasting"
test_service "training-service" "/api/v1/training"
test_service "alert-processor-service" "/api/v1/analytics"
test_service "notification-service" "/api/v1/notifications"
# Summary
echo ""
print_header "Test Results"
echo "Total Services: ${TOTAL_SERVICES}"
echo -e "${GREEN}Successful:${NC} ${SUCCESSFUL_TESTS}/${TOTAL_SERVICES}"
echo -e "${RED}Failed:${NC} ${FAILED_TESTS}/${TOTAL_SERVICES}"
echo ""
if [ ${FAILED_TESTS} -eq 0 ]; then
print_success "All tests passed!"
exit 0
else
print_error "Some tests failed"
exit 1
fi

View File

@@ -0,0 +1,270 @@
#!/usr/bin/env python3
"""
Quick script to generate deletion service boilerplate
Usage: python generate_deletion_service.py <service_name> <model1,model2,model3>
Example: python generate_deletion_service.py pos POSConfiguration,POSTransaction,POSSession
"""
import sys
import os
from pathlib import Path
def generate_deletion_service(service_name: str, models: list[str]):
"""Generate deletion service file from template"""
service_class = f"{service_name.title().replace('_', '')}TenantDeletionService"
model_imports = ", ".join(models)
# Build preview section
preview_code = []
delete_code = []
for i, model in enumerate(models):
model_lower = model.lower().replace('_', ' ')
model_plural = f"{model_lower}s" if not model_lower.endswith('s') else model_lower
preview_code.append(f"""
# Count {model_plural}
try:
{model.lower()}_count = await self.db.scalar(
select(func.count({model}.id)).where({model}.tenant_id == tenant_id)
)
preview["{model_plural}"] = {model.lower()}_count or 0
except Exception:
preview["{model_plural}"] = 0 # Table might not exist
""")
delete_code.append(f"""
# Delete {model_plural}
try:
{model.lower()}_delete = await self.db.execute(
delete({model}).where({model}.tenant_id == tenant_id)
)
result.add_deleted_items("{model_plural}", {model.lower()}_delete.rowcount)
logger.info("Deleted {model_plural} for tenant",
tenant_id=tenant_id,
count={model.lower()}_delete.rowcount)
except Exception as e:
logger.error("Error deleting {model_plural}",
tenant_id=tenant_id,
error=str(e))
result.add_error(f"{model} deletion: {{str(e)}}")
""")
template = f'''"""
{service_name.title()} Service - Tenant Data Deletion
Handles deletion of all {service_name}-related data for a tenant
"""
from typing import Dict
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, delete, func
import structlog
from shared.services.tenant_deletion import BaseTenantDataDeletionService, TenantDataDeletionResult
logger = structlog.get_logger()
class {service_class}(BaseTenantDataDeletionService):
"""Service for deleting all {service_name}-related data for a tenant"""
def __init__(self, db_session: AsyncSession):
super().__init__("{service_name}-service")
self.db = db_session
async def get_tenant_data_preview(self, tenant_id: str) -> Dict[str, int]:
"""Get counts of what would be deleted"""
try:
preview = {{}}
# Import models here to avoid circular imports
from app.models import {model_imports}
{"".join(preview_code)}
return preview
except Exception as e:
logger.error("Error getting deletion preview",
tenant_id=tenant_id,
error=str(e))
return {{}}
async def delete_tenant_data(self, tenant_id: str) -> TenantDataDeletionResult:
"""Delete all data for a tenant"""
result = TenantDataDeletionResult(tenant_id, self.service_name)
try:
# Import models here to avoid circular imports
from app.models import {model_imports}
{"".join(delete_code)}
# Commit all deletions
await self.db.commit()
logger.info("Tenant data deletion completed",
tenant_id=tenant_id,
deleted_counts=result.deleted_counts)
except Exception as e:
logger.error("Fatal error during tenant data deletion",
tenant_id=tenant_id,
error=str(e))
await self.db.rollback()
result.add_error(f"Fatal error: {{str(e)}}")
return result
'''
return template
def generate_api_endpoints(service_name: str):
"""Generate API endpoint code"""
service_class = f"{service_name.title().replace('_', '')}TenantDeletionService"
template = f'''
# ===== Tenant Data Deletion Endpoints =====
@router.delete("/tenant/{{tenant_id}}")
async def delete_tenant_data(
tenant_id: str,
current_user: dict = Depends(get_current_user_dep),
db: AsyncSession = Depends(get_db)
):
"""
Delete all {service_name}-related data for a tenant
Only accessible by internal services (called during tenant deletion)
"""
logger.info(f"Tenant data deletion request received for tenant: {{tenant_id}}")
# Only allow internal service calls
if current_user.get("type") != "service":
raise HTTPException(
status_code=403,
detail="This endpoint is only accessible to internal services"
)
try:
from app.services.tenant_deletion_service import {service_class}
deletion_service = {service_class}(db)
result = await deletion_service.safe_delete_tenant_data(tenant_id)
return {{
"message": "Tenant data deletion completed in {service_name}-service",
"summary": result.to_dict()
}}
except Exception as e:
logger.error(f"Tenant data deletion failed for {{tenant_id}}: {{e}}")
raise HTTPException(
status_code=500,
detail=f"Failed to delete tenant data: {{str(e)}}"
)
@router.get("/tenant/{{tenant_id}}/deletion-preview")
async def preview_tenant_data_deletion(
tenant_id: str,
current_user: dict = Depends(get_current_user_dep),
db: AsyncSession = Depends(get_db)
):
"""
Preview what data would be deleted for a tenant (dry-run)
Accessible by internal services and tenant admins
"""
# Allow internal services and admins
is_service = current_user.get("type") == "service"
is_admin = current_user.get("role") in ["owner", "admin"]
if not (is_service or is_admin):
raise HTTPException(
status_code=403,
detail="Insufficient permissions"
)
try:
from app.services.tenant_deletion_service import {service_class}
deletion_service = {service_class}(db)
preview = await deletion_service.get_tenant_data_preview(tenant_id)
return {{
"tenant_id": tenant_id,
"service": "{service_name}-service",
"data_counts": preview,
"total_items": sum(preview.values())
}}
except Exception as e:
logger.error(f"Deletion preview failed for {{tenant_id}}: {{e}}")
raise HTTPException(
status_code=500,
detail=f"Failed to get deletion preview: {{str(e)}}"
)
'''
return template
def main():
if len(sys.argv) < 3:
print("Usage: python generate_deletion_service.py <service_name> <model1,model2,model3>")
print("Example: python generate_deletion_service.py pos POSConfiguration,POSTransaction,POSSession")
sys.exit(1)
service_name = sys.argv[1]
models = [m.strip() for m in sys.argv[2].split(',')]
# Generate service file
service_code = generate_deletion_service(service_name, models)
# Generate API endpoints
api_code = generate_api_endpoints(service_name)
# Output files
service_dir = Path(f"services/{service_name}/app/services")
print(f"\n{'='*80}")
print(f"Generated code for {service_name} service with models: {', '.join(models)}")
print(f"{'='*80}\n")
print("1. DELETION SERVICE FILE:")
print(f" Location: {service_dir}/tenant_deletion_service.py")
print("-" * 80)
print(service_code)
print()
print("\n2. API ENDPOINTS TO ADD:")
print(f" Add to: services/{service_name}/app/api/<router>.py")
print("-" * 80)
print(api_code)
print()
# Optionally write files
write = input("\nWrite files to disk? (y/n): ").lower().strip()
if write == 'y':
# Create service file
service_dir.mkdir(parents=True, exist_ok=True)
service_file = service_dir / "tenant_deletion_service.py"
with open(service_file, 'w') as f:
f.write(service_code)
print(f"\n✅ Created: {service_file}")
print(f"\n⚠️ Next steps:")
print(f" 1. Review and customize {service_file}")
print(f" 2. Add the API endpoints to services/{service_name}/app/api/<router>.py")
print(f" 3. Test with: curl -X GET 'http://localhost:8000/api/v1/{service_name}/tenant/{{id}}/deletion-preview'")
else:
print("\n✅ Files not written. Copy the code above manually.")
if __name__ == "__main__":
main()

244
scripts/generate_service_token.py Executable file
View File

@@ -0,0 +1,244 @@
#!/usr/bin/env python3
"""
Generate Service-to-Service Authentication Token
This script generates JWT tokens for service-to-service communication
in the Bakery-IA tenant deletion system.
Usage:
python scripts/generate_service_token.py <service_name> [--days DAYS]
Examples:
# Generate token for orchestrator (1 year expiration)
python scripts/generate_service_token.py tenant-deletion-orchestrator
# Generate token for specific service with custom expiration
python scripts/generate_service_token.py auth-service --days 90
# Generate tokens for all services
python scripts/generate_service_token.py --all
"""
import sys
import os
import argparse
from datetime import timedelta
from pathlib import Path
# Add project root to path
project_root = Path(__file__).parent.parent
sys.path.insert(0, str(project_root))
from shared.auth.jwt_handler import JWTHandler
# Get JWT secret from environment (same as services use)
JWT_SECRET_KEY = os.getenv("JWT_SECRET_KEY", "your-secret-key-change-in-production-min-32-chars")
# Service names used in the system
SERVICES = [
"tenant-deletion-orchestrator",
"auth-service",
"tenant-service",
"orders-service",
"inventory-service",
"recipes-service",
"sales-service",
"production-service",
"suppliers-service",
"pos-service",
"external-service",
"forecasting-service",
"training-service",
"alert-processor-service",
"notification-service"
]
def generate_token(service_name: str, days: int = 365) -> str:
"""
Generate a service token
Args:
service_name: Name of the service
days: Token expiration in days (default: 365)
Returns:
JWT service token
"""
jwt_handler = JWTHandler(
secret_key=JWT_SECRET_KEY,
algorithm="HS256"
)
token = jwt_handler.create_service_token(
service_name=service_name,
expires_delta=timedelta(days=days)
)
return token
def verify_token(token: str) -> dict:
"""
Verify a service token and return its payload
Args:
token: JWT token to verify
Returns:
Token payload dictionary
"""
jwt_handler = JWTHandler(
secret_key=JWT_SECRET_KEY,
algorithm="HS256"
)
payload = jwt_handler.verify_token(token)
if not payload:
raise ValueError("Invalid or expired token")
return payload
def main():
parser = argparse.ArgumentParser(
description="Generate service-to-service authentication tokens",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
# Generate token for orchestrator
%(prog)s tenant-deletion-orchestrator
# Generate token with custom expiration
%(prog)s auth-service --days 90
# Generate tokens for all services
%(prog)s --all
# Verify a token
%(prog)s --verify <token>
"""
)
parser.add_argument(
"service_name",
nargs="?",
help="Name of the service (e.g., 'tenant-deletion-orchestrator')"
)
parser.add_argument(
"--days",
type=int,
default=365,
help="Token expiration in days (default: 365)"
)
parser.add_argument(
"--all",
action="store_true",
help="Generate tokens for all services"
)
parser.add_argument(
"--verify",
metavar="TOKEN",
help="Verify a token and show its payload"
)
parser.add_argument(
"--list-services",
action="store_true",
help="List all available service names"
)
args = parser.parse_args()
# List services
if args.list_services:
print("\nAvailable Services:")
print("=" * 50)
for service in SERVICES:
print(f" - {service}")
print()
return 0
# Verify token
if args.verify:
try:
payload = verify_token(args.verify)
print("\n✓ Token is valid!")
print("=" * 50)
print(f"Service Name: {payload.get('service')}")
print(f"Type: {payload.get('type')}")
print(f"Is Service: {payload.get('is_service')}")
print(f"Role: {payload.get('role')}")
print(f"Issued At: {payload.get('iat')}")
print(f"Expires At: {payload.get('exp')}")
print("=" * 50)
print()
return 0
except Exception as e:
print(f"\n✗ Token verification failed: {e}\n")
return 1
# Generate for all services
if args.all:
print(f"\nGenerating service tokens (expires in {args.days} days)...")
print("=" * 80)
for service in SERVICES:
try:
token = generate_token(service, args.days)
print(f"\n{service}:")
print(f" export {service.upper().replace('-', '_')}_TOKEN='{token}'")
except Exception as e:
print(f"\n✗ Failed to generate token for {service}: {e}")
print("\n" + "=" * 80)
print("\n Copy the export statements above to set environment variables")
print(" Or save them to a .env file for your services\n")
return 0
# Generate for single service
if not args.service_name:
parser.print_help()
return 1
try:
print(f"\nGenerating service token for: {args.service_name}")
print(f"Expiration: {args.days} days")
print("=" * 80)
token = generate_token(args.service_name, args.days)
print("\n✓ Token generated successfully!\n")
print("Token:")
print(f" {token}")
print()
print("Environment Variable:")
env_var = args.service_name.upper().replace('-', '_') + '_TOKEN'
print(f" export {env_var}='{token}'")
print()
print("Usage in Code:")
print(f" headers = {{'Authorization': f'Bearer {{os.getenv(\"{env_var}\")}}'}}")
print()
print("Test with curl:")
print(f" curl -H 'Authorization: Bearer {token}' https://localhost/api/v1/...")
print()
print("=" * 80)
print()
# Verify the token we just created
print("Verifying token...")
payload = verify_token(token)
print("✓ Token is valid and verified!\n")
return 0
except Exception as e:
print(f"\n✗ Error: {e}\n")
return 1
if __name__ == "__main__":
sys.exit(main())

78
scripts/quick_test_deletion.sh Executable file
View File

@@ -0,0 +1,78 @@
#!/bin/bash
# Quick test script for deletion endpoints via localhost (port-forwarded or ingress)
# This tests with the real Bakery-IA demo tenant
set -e
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
# Demo tenant from the system
TENANT_ID="dbc2128a-7539-470c-94b9-c1e37031bd77"
DEMO_SESSION_ID="demo_8rkT9JjXWFuVmdqT798Nyg"
# Base URL (through ingress or port-forward)
BASE_URL="${BASE_URL:-https://localhost}"
echo -e "${BLUE}Testing Deletion System with Real Services${NC}"
echo -e "${BLUE}===========================================${NC}"
echo ""
echo -e "Tenant ID: ${YELLOW}$TENANT_ID${NC}"
echo -e "Base URL: ${YELLOW}$BASE_URL${NC}"
echo ""
# Test function
test_service() {
local service_name=$1
local endpoint=$2
echo -n "Testing $service_name... "
# Try to access the deletion preview endpoint
response=$(curl -k -s -w "\n%{http_code}" \
-H "X-Demo-Session-Id: $DEMO_SESSION_ID" \
-H "X-Tenant-ID: $TENANT_ID" \
"$BASE_URL$endpoint/tenant/$TENANT_ID/deletion-preview" 2>&1)
http_code=$(echo "$response" | tail -1)
body=$(echo "$response" | sed '$d')
if [ "$http_code" = "200" ]; then
# Try to parse total records
total=$(echo "$body" | grep -o '"total_records":[0-9]*' | cut -d':' -f2 || echo "?")
echo -e "${GREEN}${NC} (HTTP $http_code, Records: $total)"
elif [ "$http_code" = "401" ] || [ "$http_code" = "403" ]; then
echo -e "${YELLOW}${NC} (HTTP $http_code - Auth required)"
elif [ "$http_code" = "404" ]; then
echo -e "${RED}${NC} (HTTP $http_code - Endpoint not found)"
else
echo -e "${RED}${NC} (HTTP $http_code)"
fi
}
# Test all services
echo "Testing deletion preview endpoints:"
echo ""
test_service "Orders" "/api/v1/orders"
test_service "Inventory" "/api/v1/inventory"
test_service "Recipes" "/api/v1/recipes"
test_service "Sales" "/api/v1/sales"
test_service "Production" "/api/v1/production"
test_service "Suppliers" "/api/v1/suppliers"
test_service "POS" "/api/v1/pos"
test_service "External" "/api/v1/external"
test_service "Forecasting" "/api/v1/forecasting"
test_service "Training" "/api/v1/training"
test_service "Alert Processor" "/api/v1/alerts"
test_service "Notification" "/api/v1/notifications"
echo ""
echo -e "${BLUE}Test completed!${NC}"
echo ""
echo -e "${YELLOW}Note:${NC} 401/403 responses are expected - deletion endpoints require service tokens"
echo -e "${YELLOW}Note:${NC} To test with proper auth, set up service-to-service authentication"

View File

@@ -0,0 +1,140 @@
#!/bin/bash
# Quick script to test all deletion endpoints
# Usage: ./test_deletion_endpoints.sh <tenant_id>
set -e
TENANT_ID=${1:-"test-tenant-123"}
BASE_URL=${BASE_URL:-"http://localhost:8000"}
TOKEN=${AUTH_TOKEN:-"test-token"}
echo "================================"
echo "Testing Deletion Endpoints"
echo "Tenant ID: $TENANT_ID"
echo "Base URL: $BASE_URL"
echo "================================"
echo ""
# Colors
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Function to test endpoint
test_endpoint() {
local service=$1
local method=$2
local path=$3
local expected_status=${4:-200}
echo -n "Testing $service ($method $path)... "
response=$(curl -s -w "\n%{http_code}" \
-X $method \
-H "Authorization: Bearer $TOKEN" \
-H "X-Internal-Service: test-script" \
"$BASE_URL/api/v1/$path" 2>&1)
status_code=$(echo "$response" | tail -n1)
body=$(echo "$response" | head -n-1)
if [ "$status_code" == "$expected_status" ] || [ "$status_code" == "404" ]; then
if [ "$status_code" == "404" ]; then
echo -e "${YELLOW}NOT IMPLEMENTED${NC} (404)"
else
echo -e "${GREEN}✓ PASSED${NC} ($status_code)"
if [ "$method" == "GET" ]; then
# Show preview counts
total=$(echo "$body" | jq -r '.total_items // 0' 2>/dev/null || echo "N/A")
if [ "$total" != "N/A" ]; then
echo " → Preview: $total items would be deleted"
fi
elif [ "$method" == "DELETE" ]; then
# Show deletion summary
deleted=$(echo "$body" | jq -r '.summary.total_deleted // 0' 2>/dev/null || echo "N/A")
if [ "$deleted" != "N/A" ]; then
echo " → Deleted: $deleted items"
fi
fi
fi
else
echo -e "${RED}✗ FAILED${NC} ($status_code)"
echo " Response: $body"
fi
}
echo "=== COMPLETED SERVICES ==="
echo ""
echo "1. Tenant Service:"
test_endpoint "tenant" "GET" "tenants/$TENANT_ID"
test_endpoint "tenant" "DELETE" "tenants/$TENANT_ID"
echo ""
echo "2. Orders Service:"
test_endpoint "orders" "GET" "orders/tenant/$TENANT_ID/deletion-preview"
test_endpoint "orders" "DELETE" "orders/tenant/$TENANT_ID"
echo ""
echo "3. Inventory Service:"
test_endpoint "inventory" "GET" "inventory/tenant/$TENANT_ID/deletion-preview"
test_endpoint "inventory" "DELETE" "inventory/tenant/$TENANT_ID"
echo ""
echo "4. Recipes Service:"
test_endpoint "recipes" "GET" "recipes/tenant/$TENANT_ID/deletion-preview"
test_endpoint "recipes" "DELETE" "recipes/tenant/$TENANT_ID"
echo ""
echo "5. Sales Service:"
test_endpoint "sales" "GET" "sales/tenant/$TENANT_ID/deletion-preview"
test_endpoint "sales" "DELETE" "sales/tenant/$TENANT_ID"
echo ""
echo "6. Production Service:"
test_endpoint "production" "GET" "production/tenant/$TENANT_ID/deletion-preview"
test_endpoint "production" "DELETE" "production/tenant/$TENANT_ID"
echo ""
echo "7. Suppliers Service:"
test_endpoint "suppliers" "GET" "suppliers/tenant/$TENANT_ID/deletion-preview"
test_endpoint "suppliers" "DELETE" "suppliers/tenant/$TENANT_ID"
echo ""
echo "=== PENDING SERVICES ==="
echo ""
echo "8. POS Service:"
test_endpoint "pos" "GET" "pos/tenant/$TENANT_ID/deletion-preview"
test_endpoint "pos" "DELETE" "pos/tenant/$TENANT_ID"
echo ""
echo "9. External Service:"
test_endpoint "external" "GET" "external/tenant/$TENANT_ID/deletion-preview"
test_endpoint "external" "DELETE" "external/tenant/$TENANT_ID"
echo ""
echo "10. Alert Processor Service:"
test_endpoint "alert_processor" "GET" "alerts/tenant/$TENANT_ID/deletion-preview"
test_endpoint "alert_processor" "DELETE" "alerts/tenant/$TENANT_ID"
echo ""
echo "11. Forecasting Service:"
test_endpoint "forecasting" "GET" "forecasts/tenant/$TENANT_ID/deletion-preview"
test_endpoint "forecasting" "DELETE" "forecasts/tenant/$TENANT_ID"
echo ""
echo "12. Training Service:"
test_endpoint "training" "GET" "models/tenant/$TENANT_ID/deletion-preview"
test_endpoint "training" "DELETE" "models/tenant/$TENANT_ID"
echo ""
echo "13. Notification Service:"
test_endpoint "notification" "GET" "notifications/tenant/$TENANT_ID/deletion-preview"
test_endpoint "notification" "DELETE" "notifications/tenant/$TENANT_ID"
echo ""
echo "================================"
echo "Testing Complete!"
echo "================================"

225
scripts/test_deletion_system.sh Executable file
View File

@@ -0,0 +1,225 @@
#!/bin/bash
# ================================================================
# Tenant Deletion System - Integration Test Script
# ================================================================
# Tests all 12 services' deletion endpoints
# Usage: ./scripts/test_deletion_system.sh [tenant_id]
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Configuration
TENANT_ID="${1:-dbc2128a-7539-470c-94b9-c1e37031bd77}" # Default demo tenant
SERVICE_TOKEN="${SERVICE_TOKEN:-demo_service_token}"
# Service URLs (update these based on your environment)
ORDERS_URL="${ORDERS_URL:-http://localhost:8000/api/v1/orders}"
INVENTORY_URL="${INVENTORY_URL:-http://localhost:8001/api/v1/inventory}"
RECIPES_URL="${RECIPES_URL:-http://localhost:8002/api/v1/recipes}"
SALES_URL="${SALES_URL:-http://localhost:8003/api/v1/sales}"
PRODUCTION_URL="${PRODUCTION_URL:-http://localhost:8004/api/v1/production}"
SUPPLIERS_URL="${SUPPLIERS_URL:-http://localhost:8005/api/v1/suppliers}"
POS_URL="${POS_URL:-http://localhost:8006/api/v1/pos}"
EXTERNAL_URL="${EXTERNAL_URL:-http://localhost:8007/api/v1/external}"
FORECASTING_URL="${FORECASTING_URL:-http://localhost:8008/api/v1/forecasting}"
TRAINING_URL="${TRAINING_URL:-http://localhost:8009/api/v1/training}"
ALERT_PROCESSOR_URL="${ALERT_PROCESSOR_URL:-http://localhost:8010/api/v1/alerts}"
NOTIFICATION_URL="${NOTIFICATION_URL:-http://localhost:8011/api/v1/notifications}"
# Test results
TOTAL_TESTS=0
PASSED_TESTS=0
FAILED_TESTS=0
declare -a FAILED_SERVICES
# Helper functions
print_header() {
echo -e "${BLUE}================================================${NC}"
echo -e "${BLUE}$1${NC}"
echo -e "${BLUE}================================================${NC}"
}
print_success() {
echo -e "${GREEN}${NC} $1"
}
print_error() {
echo -e "${RED}${NC} $1"
}
print_warning() {
echo -e "${YELLOW}${NC} $1"
}
print_info() {
echo -e "${BLUE}${NC} $1"
}
# Test individual service deletion preview
test_service_preview() {
local service_name=$1
local service_url=$2
local endpoint_path=$3
TOTAL_TESTS=$((TOTAL_TESTS + 1))
echo ""
print_info "Testing $service_name service..."
local full_url="${service_url}${endpoint_path}/tenant/${TENANT_ID}/deletion-preview"
# Make request
response=$(curl -k -s -w "\nHTTP_STATUS:%{http_code}" \
-H "Authorization: Bearer ${SERVICE_TOKEN}" \
-H "X-Service-Token: ${SERVICE_TOKEN}" \
"${full_url}" 2>&1)
# Extract HTTP status
http_status=$(echo "$response" | grep "HTTP_STATUS" | cut -d':' -f2)
body=$(echo "$response" | sed '/HTTP_STATUS/d')
if [ "$http_status" = "200" ]; then
# Parse total records if available
total_records=$(echo "$body" | grep -o '"total_records":[0-9]*' | cut -d':' -f2 || echo "N/A")
print_success "$service_name: HTTP $http_status (Records: $total_records)"
PASSED_TESTS=$((PASSED_TESTS + 1))
# Show preview details if verbose
if [ "${VERBOSE:-0}" = "1" ]; then
echo "$body" | jq '.' 2>/dev/null || echo "$body"
fi
else
print_error "$service_name: HTTP $http_status"
FAILED_TESTS=$((FAILED_TESTS + 1))
FAILED_SERVICES+=("$service_name")
# Show error details
echo " URL: $full_url"
echo " Response: $body" | head -n 5
fi
}
# Main test execution
main() {
print_header "Tenant Deletion System - Integration Tests"
print_info "Testing tenant: $TENANT_ID"
print_info "Using service token: ${SERVICE_TOKEN:0:20}..."
echo ""
# Test all services
print_header "Testing Individual Services (12 total)"
test_service_preview "Orders" "$ORDERS_URL" "/orders"
test_service_preview "Inventory" "$INVENTORY_URL" "/inventory"
test_service_preview "Recipes" "$RECIPES_URL" "/recipes"
test_service_preview "Sales" "$SALES_URL" "/sales"
test_service_preview "Production" "$PRODUCTION_URL" "/production"
test_service_preview "Suppliers" "$SUPPLIERS_URL" "/suppliers"
test_service_preview "POS" "$POS_URL" "/pos"
test_service_preview "External" "$EXTERNAL_URL" "/external"
test_service_preview "Forecasting" "$FORECASTING_URL" "/forecasting"
test_service_preview "Training" "$TRAINING_URL" "/training"
test_service_preview "Alert Processor" "$ALERT_PROCESSOR_URL" "/alerts"
test_service_preview "Notification" "$NOTIFICATION_URL" "/notifications"
# Print summary
echo ""
print_header "Test Summary"
echo -e "Total Tests: $TOTAL_TESTS"
echo -e "${GREEN}Passed: $PASSED_TESTS${NC}"
if [ $FAILED_TESTS -gt 0 ]; then
echo -e "${RED}Failed: $FAILED_TESTS${NC}"
echo ""
print_error "Failed services:"
for service in "${FAILED_SERVICES[@]}"; do
echo " - $service"
done
echo ""
print_warning "Some services are not accessible or not implemented."
print_info "Make sure all services are running and URLs are correct."
exit 1
else
echo -e "${GREEN}Failed: $FAILED_TESTS${NC}"
echo ""
print_success "All services passed! ✨"
exit 0
fi
}
# Check dependencies
check_dependencies() {
if ! command -v curl &> /dev/null; then
print_error "curl is required but not installed."
exit 1
fi
if ! command -v jq &> /dev/null; then
print_warning "jq not found. Install for better output formatting."
fi
}
# Show usage
show_usage() {
cat << EOF
Usage: $0 [OPTIONS] [tenant_id]
Test the tenant deletion system across all 12 microservices.
Options:
-h, --help Show this help message
-v, --verbose Show detailed response bodies
-t, --tenant ID Specify tenant ID to test (default: demo tenant)
Environment Variables:
SERVICE_TOKEN Service authentication token
*_URL Individual service URLs (e.g., ORDERS_URL)
Examples:
# Test with default demo tenant
$0
# Test specific tenant
$0 abc-123-def-456
# Test with verbose output
VERBOSE=1 $0
# Test with custom service URLs
ORDERS_URL=http://orders:8000/api/v1/orders $0
EOF
}
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
show_usage
exit 0
;;
-v|--verbose)
VERBOSE=1
shift
;;
-t|--tenant)
TENANT_ID="$2"
shift 2
;;
*)
TENANT_ID="$1"
shift
;;
esac
done
# Run tests
check_dependencies
main