Fix Demo enterprise
This commit is contained in:
584
scripts/validate_enterprise_demo_fixtures.py
Executable file
584
scripts/validate_enterprise_demo_fixtures.py
Executable file
@@ -0,0 +1,584 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Enterprise Demo Fixtures Validation Script
|
||||
|
||||
Validates cross-references between JSON fixtures for enterprise demo sessions.
|
||||
Checks that all referenced IDs exist and are consistent across files.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Set, Any, Optional
|
||||
from collections import defaultdict
|
||||
import uuid
|
||||
|
||||
# Color codes for output
|
||||
RED = '\033[91m'
|
||||
GREEN = '\033[92m'
|
||||
YELLOW = '\033[93m'
|
||||
BLUE = '\033[94m'
|
||||
RESET = '\033[0m'
|
||||
|
||||
class FixtureValidator:
|
||||
def __init__(self, base_path: str = "shared/demo/fixtures/enterprise"):
|
||||
self.base_path = Path(base_path)
|
||||
self.parent_path = self.base_path / "parent"
|
||||
self.children_paths = {}
|
||||
|
||||
# Load all fixture data
|
||||
self.tenant_data = {}
|
||||
self.user_data = {}
|
||||
self.location_data = {}
|
||||
self.product_data = {}
|
||||
self.supplier_data = {}
|
||||
self.recipe_data = {}
|
||||
self.procurement_data = {}
|
||||
self.order_data = {}
|
||||
self.production_data = {}
|
||||
|
||||
# Track all IDs for validation
|
||||
self.all_ids = defaultdict(set)
|
||||
self.references = defaultdict(list)
|
||||
|
||||
# Expected IDs from tenant.json
|
||||
self.expected_tenant_ids = set()
|
||||
self.expected_user_ids = set()
|
||||
self.expected_location_ids = set()
|
||||
|
||||
def load_all_fixtures(self) -> None:
|
||||
"""Load all JSON fixtures from parent and children directories"""
|
||||
print(f"{BLUE}Loading fixtures from {self.base_path}{RESET}")
|
||||
|
||||
# Load parent fixtures
|
||||
self._load_parent_fixtures()
|
||||
|
||||
# Load children fixtures
|
||||
self._load_children_fixtures()
|
||||
|
||||
print(f"{GREEN}✓ Loaded fixtures successfully{RESET}\n")
|
||||
|
||||
def _load_parent_fixtures(self) -> None:
|
||||
"""Load parent enterprise fixtures"""
|
||||
if not self.parent_path.exists():
|
||||
print(f"{RED}✗ Parent fixtures directory not found: {self.parent_path}{RESET}")
|
||||
sys.exit(1)
|
||||
|
||||
# Load in order to establish dependencies
|
||||
files_to_load = [
|
||||
"01-tenant.json",
|
||||
"02-auth.json",
|
||||
"03-inventory.json",
|
||||
"04-recipes.json",
|
||||
"05-suppliers.json",
|
||||
"06-production.json",
|
||||
"07-procurement.json",
|
||||
"08-orders.json",
|
||||
"09-sales.json",
|
||||
"10-forecasting.json",
|
||||
"11-orchestrator.json"
|
||||
]
|
||||
|
||||
for filename in files_to_load:
|
||||
filepath = self.parent_path / filename
|
||||
if filepath.exists():
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
self._process_fixture_file(filename, data, "parent")
|
||||
|
||||
def _load_children_fixtures(self) -> None:
|
||||
"""Load children enterprise fixtures"""
|
||||
children_dir = self.base_path / "children"
|
||||
if not children_dir.exists():
|
||||
print(f"{YELLOW}⚠ Children fixtures directory not found: {children_dir}{RESET}")
|
||||
return
|
||||
|
||||
# Find all child tenant directories
|
||||
child_dirs = [d for d in children_dir.iterdir() if d.is_dir()]
|
||||
|
||||
for child_dir in child_dirs:
|
||||
tenant_id = child_dir.name
|
||||
self.children_paths[tenant_id] = child_dir
|
||||
|
||||
# Load child fixtures
|
||||
files_to_load = [
|
||||
"01-tenant.json",
|
||||
"02-auth.json",
|
||||
"03-inventory.json",
|
||||
"04-recipes.json",
|
||||
"05-suppliers.json",
|
||||
"06-production.json",
|
||||
"07-procurement.json",
|
||||
"08-orders.json",
|
||||
"09-sales.json",
|
||||
"10-forecasting.json",
|
||||
"11-orchestrator.json"
|
||||
]
|
||||
|
||||
for filename in files_to_load:
|
||||
filepath = child_dir / filename
|
||||
if filepath.exists():
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
self._process_fixture_file(filename, data, tenant_id)
|
||||
|
||||
def _process_fixture_file(self, filename: str, data: Any, context: str) -> None:
|
||||
"""Process a fixture file and extract IDs and references"""
|
||||
print(f" Processing {filename} ({context})...")
|
||||
|
||||
if filename == "01-tenant.json":
|
||||
self._process_tenant_data(data, context)
|
||||
elif filename == "02-auth.json":
|
||||
self._process_auth_data(data, context)
|
||||
elif filename == "03-inventory.json":
|
||||
self._process_inventory_data(data, context)
|
||||
elif filename == "04-recipes.json":
|
||||
self._process_recipe_data(data, context)
|
||||
elif filename == "05-suppliers.json":
|
||||
self._process_supplier_data(data, context)
|
||||
elif filename == "06-production.json":
|
||||
self._process_production_data(data, context)
|
||||
elif filename == "07-procurement.json":
|
||||
self._process_procurement_data(data, context)
|
||||
elif filename == "08-orders.json":
|
||||
self._process_order_data(data, context)
|
||||
elif filename == "09-sales.json":
|
||||
self._process_sales_data(data, context)
|
||||
elif filename == "10-forecasting.json":
|
||||
self._process_forecasting_data(data, context)
|
||||
elif filename == "11-orchestrator.json":
|
||||
self._process_orchestrator_data(data, context)
|
||||
|
||||
def _process_tenant_data(self, data: Any, context: str) -> None:
|
||||
"""Process tenant.json data"""
|
||||
tenant = data.get("tenant", {})
|
||||
owner = data.get("owner", {})
|
||||
subscription = data.get("subscription", {})
|
||||
children = data.get("children", [])
|
||||
|
||||
# Store tenant data
|
||||
tenant_id = tenant.get("id")
|
||||
if tenant_id:
|
||||
self.tenant_data[tenant_id] = tenant
|
||||
self.all_ids["tenant"].add(tenant_id)
|
||||
|
||||
if context == "parent":
|
||||
self.expected_tenant_ids.add(tenant_id)
|
||||
|
||||
# Store owner user
|
||||
owner_id = owner.get("id")
|
||||
if owner_id:
|
||||
self.user_data[owner_id] = owner
|
||||
self.all_ids["user"].add(owner_id)
|
||||
self.expected_user_ids.add(owner_id)
|
||||
|
||||
# Store subscription
|
||||
subscription_id = subscription.get("id")
|
||||
if subscription_id:
|
||||
self.all_ids["subscription"].add(subscription_id)
|
||||
|
||||
# Store child tenants
|
||||
for child in children:
|
||||
child_id = child.get("id")
|
||||
if child_id:
|
||||
self.tenant_data[child_id] = child
|
||||
self.all_ids["tenant"].add(child_id)
|
||||
self.expected_tenant_ids.add(child_id)
|
||||
|
||||
# Track parent-child relationship
|
||||
self.references["parent_child"].append({
|
||||
"parent": tenant_id,
|
||||
"child": child_id,
|
||||
"context": context
|
||||
})
|
||||
|
||||
def _process_auth_data(self, data: Any, context: str) -> None:
|
||||
"""Process auth.json data"""
|
||||
users = data.get("users", [])
|
||||
|
||||
for user in users:
|
||||
user_id = user.get("id")
|
||||
tenant_id = user.get("tenant_id")
|
||||
|
||||
if user_id:
|
||||
self.user_data[user_id] = user
|
||||
self.all_ids["user"].add(user_id)
|
||||
self.expected_user_ids.add(user_id)
|
||||
|
||||
# Track user-tenant relationship
|
||||
if tenant_id:
|
||||
self.references["user_tenant"].append({
|
||||
"user_id": user_id,
|
||||
"tenant_id": tenant_id,
|
||||
"context": context
|
||||
})
|
||||
|
||||
def _process_inventory_data(self, data: Any, context: str) -> None:
|
||||
"""Process inventory.json data"""
|
||||
products = data.get("products", [])
|
||||
ingredients = data.get("ingredients", [])
|
||||
locations = data.get("locations", [])
|
||||
|
||||
# Store products
|
||||
for product in products:
|
||||
product_id = product.get("id")
|
||||
tenant_id = product.get("tenant_id")
|
||||
created_by = product.get("created_by")
|
||||
|
||||
if product_id:
|
||||
self.product_data[product_id] = product
|
||||
self.all_ids["product"].add(product_id)
|
||||
|
||||
# Track product-tenant relationship
|
||||
if tenant_id:
|
||||
self.references["product_tenant"].append({
|
||||
"product_id": product_id,
|
||||
"tenant_id": tenant_id,
|
||||
"context": context
|
||||
})
|
||||
|
||||
# Track product-user relationship
|
||||
if created_by:
|
||||
self.references["product_user"].append({
|
||||
"product_id": product_id,
|
||||
"user_id": created_by,
|
||||
"context": context
|
||||
})
|
||||
|
||||
# Store ingredients
|
||||
for ingredient in ingredients:
|
||||
ingredient_id = ingredient.get("id")
|
||||
tenant_id = ingredient.get("tenant_id")
|
||||
created_by = ingredient.get("created_by")
|
||||
|
||||
if ingredient_id:
|
||||
self.product_data[ingredient_id] = ingredient
|
||||
self.all_ids["ingredient"].add(ingredient_id)
|
||||
|
||||
# Track ingredient-tenant relationship
|
||||
if tenant_id:
|
||||
self.references["ingredient_tenant"].append({
|
||||
"ingredient_id": ingredient_id,
|
||||
"tenant_id": tenant_id,
|
||||
"context": context
|
||||
})
|
||||
|
||||
# Track ingredient-user relationship
|
||||
if created_by:
|
||||
self.references["ingredient_user"].append({
|
||||
"ingredient_id": ingredient_id,
|
||||
"user_id": created_by,
|
||||
"context": context
|
||||
})
|
||||
|
||||
# Store locations
|
||||
for location in locations:
|
||||
location_id = location.get("id")
|
||||
if location_id:
|
||||
self.location_data[location_id] = location
|
||||
self.all_ids["location"].add(location_id)
|
||||
self.expected_location_ids.add(location_id)
|
||||
|
||||
def _process_recipe_data(self, data: Any, context: str) -> None:
|
||||
"""Process recipes.json data"""
|
||||
recipes = data.get("recipes", [])
|
||||
|
||||
for recipe in recipes:
|
||||
recipe_id = recipe.get("id")
|
||||
tenant_id = recipe.get("tenant_id")
|
||||
finished_product_id = recipe.get("finished_product_id")
|
||||
|
||||
if recipe_id:
|
||||
self.recipe_data[recipe_id] = recipe
|
||||
self.all_ids["recipe"].add(recipe_id)
|
||||
|
||||
# Track recipe-tenant relationship
|
||||
if tenant_id:
|
||||
self.references["recipe_tenant"].append({
|
||||
"recipe_id": recipe_id,
|
||||
"tenant_id": tenant_id,
|
||||
"context": context
|
||||
})
|
||||
|
||||
# Track recipe-product relationship
|
||||
if finished_product_id:
|
||||
self.references["recipe_product"].append({
|
||||
"recipe_id": recipe_id,
|
||||
"product_id": finished_product_id,
|
||||
"context": context
|
||||
})
|
||||
|
||||
def _process_supplier_data(self, data: Any, context: str) -> None:
|
||||
"""Process suppliers.json data"""
|
||||
suppliers = data.get("suppliers", [])
|
||||
|
||||
for supplier in suppliers:
|
||||
supplier_id = supplier.get("id")
|
||||
tenant_id = supplier.get("tenant_id")
|
||||
|
||||
if supplier_id:
|
||||
self.supplier_data[supplier_id] = supplier
|
||||
self.all_ids["supplier"].add(supplier_id)
|
||||
|
||||
# Track supplier-tenant relationship
|
||||
if tenant_id:
|
||||
self.references["supplier_tenant"].append({
|
||||
"supplier_id": supplier_id,
|
||||
"tenant_id": tenant_id,
|
||||
"context": context
|
||||
})
|
||||
|
||||
def _process_production_data(self, data: Any, context: str) -> None:
|
||||
"""Process production.json data"""
|
||||
# Extract production-related IDs
|
||||
pass
|
||||
|
||||
def _process_procurement_data(self, data: Any, context: str) -> None:
|
||||
"""Process procurement.json data"""
|
||||
# Extract procurement-related IDs
|
||||
pass
|
||||
|
||||
def _process_order_data(self, data: Any, context: str) -> None:
|
||||
"""Process orders.json data"""
|
||||
# Extract order-related IDs
|
||||
pass
|
||||
|
||||
def _process_sales_data(self, data: Any, context: str) -> None:
|
||||
"""Process sales.json data"""
|
||||
# Extract sales-related IDs
|
||||
pass
|
||||
|
||||
def _process_forecasting_data(self, data: Any, context: str) -> None:
|
||||
"""Process forecasting.json data"""
|
||||
# Extract forecasting-related IDs
|
||||
pass
|
||||
|
||||
def _process_orchestrator_data(self, data: Any, context: str) -> None:
|
||||
"""Process orchestrator.json data"""
|
||||
# Extract orchestrator-related IDs
|
||||
pass
|
||||
|
||||
def validate_all_references(self) -> bool:
|
||||
"""Validate all cross-references in the fixtures"""
|
||||
print(f"{BLUE}Validating cross-references...{RESET}")
|
||||
|
||||
all_valid = True
|
||||
|
||||
# Validate user-tenant relationships
|
||||
if "user_tenant" in self.references:
|
||||
print(f"\n{YELLOW}Validating User-Tenant relationships...{RESET}")
|
||||
for ref in self.references["user_tenant"]:
|
||||
user_id = ref["user_id"]
|
||||
tenant_id = ref["tenant_id"]
|
||||
context = ref["context"]
|
||||
|
||||
if user_id not in self.user_data:
|
||||
print(f"{RED}✗ User {user_id} referenced but not found in user data (context: {context}){RESET}")
|
||||
all_valid = False
|
||||
|
||||
if tenant_id not in self.tenant_data:
|
||||
print(f"{RED}✗ Tenant {tenant_id} referenced by user {user_id} but not found (context: {context}){RESET}")
|
||||
all_valid = False
|
||||
|
||||
# Validate parent-child relationships
|
||||
if "parent_child" in self.references:
|
||||
print(f"\n{YELLOW}Validating Parent-Child relationships...{RESET}")
|
||||
for ref in self.references["parent_child"]:
|
||||
parent_id = ref["parent"]
|
||||
child_id = ref["child"]
|
||||
context = ref["context"]
|
||||
|
||||
if parent_id not in self.tenant_data:
|
||||
print(f"{RED}✗ Parent tenant {parent_id} not found (context: {context}){RESET}")
|
||||
all_valid = False
|
||||
|
||||
if child_id not in self.tenant_data:
|
||||
print(f"{RED}✗ Child tenant {child_id} not found (context: {context}){RESET}")
|
||||
all_valid = False
|
||||
|
||||
# Validate product-tenant relationships
|
||||
if "product_tenant" in self.references:
|
||||
print(f"\n{YELLOW}Validating Product-Tenant relationships...{RESET}")
|
||||
for ref in self.references["product_tenant"]:
|
||||
product_id = ref["product_id"]
|
||||
tenant_id = ref["tenant_id"]
|
||||
context = ref["context"]
|
||||
|
||||
if product_id not in self.product_data:
|
||||
print(f"{RED}✗ Product {product_id} referenced but not found (context: {context}){RESET}")
|
||||
all_valid = False
|
||||
|
||||
if tenant_id not in self.tenant_data:
|
||||
print(f"{RED}✗ Tenant {tenant_id} referenced by product {product_id} but not found (context: {context}){RESET}")
|
||||
all_valid = False
|
||||
|
||||
# Validate product-user relationships
|
||||
if "product_user" in self.references:
|
||||
print(f"\n{YELLOW}Validating Product-User relationships...{RESET}")
|
||||
for ref in self.references["product_user"]:
|
||||
product_id = ref["product_id"]
|
||||
user_id = ref["user_id"]
|
||||
context = ref["context"]
|
||||
|
||||
if product_id not in self.product_data:
|
||||
print(f"{RED}✗ Product {product_id} referenced but not found (context: {context}){RESET}")
|
||||
all_valid = False
|
||||
|
||||
if user_id not in self.user_data:
|
||||
print(f"{RED}✗ User {user_id} referenced by product {product_id} but not found (context: {context}){RESET}")
|
||||
all_valid = False
|
||||
|
||||
# Validate ingredient-tenant relationships
|
||||
if "ingredient_tenant" in self.references:
|
||||
print(f"\n{YELLOW}Validating Ingredient-Tenant relationships...{RESET}")
|
||||
for ref in self.references["ingredient_tenant"]:
|
||||
ingredient_id = ref["ingredient_id"]
|
||||
tenant_id = ref["tenant_id"]
|
||||
context = ref["context"]
|
||||
|
||||
if ingredient_id not in self.product_data:
|
||||
print(f"{RED}✗ Ingredient {ingredient_id} referenced but not found (context: {context}){RESET}")
|
||||
all_valid = False
|
||||
|
||||
if tenant_id not in self.tenant_data:
|
||||
print(f"{RED}✗ Tenant {tenant_id} referenced by ingredient {ingredient_id} but not found (context: {context}){RESET}")
|
||||
all_valid = False
|
||||
|
||||
# Validate ingredient-user relationships
|
||||
if "ingredient_user" in self.references:
|
||||
print(f"\n{YELLOW}Validating Ingredient-User relationships...{RESET}")
|
||||
for ref in self.references["ingredient_user"]:
|
||||
ingredient_id = ref["ingredient_id"]
|
||||
user_id = ref["user_id"]
|
||||
context = ref["context"]
|
||||
|
||||
if ingredient_id not in self.product_data:
|
||||
print(f"{RED}✗ Ingredient {ingredient_id} referenced but not found (context: {context}){RESET}")
|
||||
all_valid = False
|
||||
|
||||
if user_id not in self.user_data:
|
||||
print(f"{RED}✗ User {user_id} referenced by ingredient {ingredient_id} but not found (context: {context}){RESET}")
|
||||
all_valid = False
|
||||
|
||||
# Validate recipe-tenant relationships
|
||||
if "recipe_tenant" in self.references:
|
||||
print(f"\n{YELLOW}Validating Recipe-Tenant relationships...{RESET}")
|
||||
for ref in self.references["recipe_tenant"]:
|
||||
recipe_id = ref["recipe_id"]
|
||||
tenant_id = ref["tenant_id"]
|
||||
context = ref["context"]
|
||||
|
||||
if recipe_id not in self.recipe_data:
|
||||
print(f"{RED}✗ Recipe {recipe_id} referenced but not found (context: {context}){RESET}")
|
||||
all_valid = False
|
||||
|
||||
if tenant_id not in self.tenant_data:
|
||||
print(f"{RED}✗ Tenant {tenant_id} referenced by recipe {recipe_id} but not found (context: {context}){RESET}")
|
||||
all_valid = False
|
||||
|
||||
# Validate recipe-product relationships
|
||||
if "recipe_product" in self.references:
|
||||
print(f"\n{YELLOW}Validating Recipe-Product relationships...{RESET}")
|
||||
for ref in self.references["recipe_product"]:
|
||||
recipe_id = ref["recipe_id"]
|
||||
product_id = ref["product_id"]
|
||||
context = ref["context"]
|
||||
|
||||
if recipe_id not in self.recipe_data:
|
||||
print(f"{RED}✗ Recipe {recipe_id} referenced but not found (context: {context}){RESET}")
|
||||
all_valid = False
|
||||
|
||||
if product_id not in self.product_data:
|
||||
print(f"{RED}✗ Product {product_id} referenced by recipe {recipe_id} but not found (context: {context}){RESET}")
|
||||
all_valid = False
|
||||
|
||||
# Validate supplier-tenant relationships
|
||||
if "supplier_tenant" in self.references:
|
||||
print(f"\n{YELLOW}Validating Supplier-Tenant relationships...{RESET}")
|
||||
for ref in self.references["supplier_tenant"]:
|
||||
supplier_id = ref["supplier_id"]
|
||||
tenant_id = ref["tenant_id"]
|
||||
context = ref["context"]
|
||||
|
||||
if supplier_id not in self.supplier_data:
|
||||
print(f"{RED}✗ Supplier {supplier_id} referenced but not found (context: {context}){RESET}")
|
||||
all_valid = False
|
||||
|
||||
if tenant_id not in self.tenant_data:
|
||||
print(f"{RED}✗ Tenant {tenant_id} referenced by supplier {supplier_id} but not found (context: {context}){RESET}")
|
||||
all_valid = False
|
||||
|
||||
# Validate UUID format for all IDs
|
||||
print(f"\n{YELLOW}Validating UUID formats...{RESET}")
|
||||
for entity_type, ids in self.all_ids.items():
|
||||
for entity_id in ids:
|
||||
try:
|
||||
uuid.UUID(entity_id)
|
||||
except ValueError:
|
||||
print(f"{RED}✗ Invalid UUID format for {entity_type} ID: {entity_id}{RESET}")
|
||||
all_valid = False
|
||||
|
||||
# Check for duplicate IDs
|
||||
print(f"\n{YELLOW}Checking for duplicate IDs...{RESET}")
|
||||
all_entities = []
|
||||
for ids in self.all_ids.values():
|
||||
all_entities.extend(ids)
|
||||
|
||||
duplicates = [id for id in all_entities if all_entities.count(id) > 1]
|
||||
if duplicates:
|
||||
print(f"{RED}✗ Found duplicate IDs: {', '.join(duplicates)}{RESET}")
|
||||
all_valid = False
|
||||
|
||||
if all_valid:
|
||||
print(f"{GREEN}✓ All cross-references are valid!{RESET}")
|
||||
else:
|
||||
print(f"{RED}✗ Found validation errors!{RESET}")
|
||||
|
||||
return all_valid
|
||||
|
||||
def generate_summary(self) -> None:
|
||||
"""Generate a summary of the loaded fixtures"""
|
||||
print(f"\n{BLUE}=== Fixture Summary ==={RESET}")
|
||||
print(f"Tenants: {len(self.tenant_data)}")
|
||||
print(f"Users: {len(self.user_data)}")
|
||||
print(f"Products: {len(self.product_data)}")
|
||||
print(f"Suppliers: {len(self.supplier_data)}")
|
||||
print(f"Recipes: {len(self.recipe_data)}")
|
||||
print(f"Locations: {len(self.location_data)}")
|
||||
|
||||
print(f"\nEntity Types: {list(self.all_ids.keys())}")
|
||||
|
||||
for entity_type, ids in self.all_ids.items():
|
||||
print(f" {entity_type}: {len(ids)} IDs")
|
||||
|
||||
print(f"\nReference Types: {list(self.references.keys())}")
|
||||
for ref_type, refs in self.references.items():
|
||||
print(f" {ref_type}: {len(refs)} references")
|
||||
|
||||
def run_validation(self) -> bool:
|
||||
"""Run the complete validation process"""
|
||||
print(f"{BLUE}=== Enterprise Demo Fixtures Validator ==={RESET}")
|
||||
print(f"Base Path: {self.base_path}\n")
|
||||
|
||||
try:
|
||||
self.load_all_fixtures()
|
||||
self.generate_summary()
|
||||
return self.validate_all_references()
|
||||
|
||||
except Exception as e:
|
||||
print(f"{RED}✗ Validation failed with error: {e}{RESET}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
if __name__ == "__main__":
|
||||
validator = FixtureValidator()
|
||||
success = validator.run_validation()
|
||||
|
||||
if success:
|
||||
print(f"\n{GREEN}=== Validation Complete: All checks passed! ==={RESET}")
|
||||
sys.exit(0)
|
||||
else:
|
||||
print(f"\n{RED}=== Validation Complete: Errors found! ==={RESET}")
|
||||
sys.exit(1)
|
||||
Reference in New Issue
Block a user