289 lines
12 KiB
Python
289 lines
12 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
iFixKart Migration Stress Test Generator
|
|
=========================================
|
|
Generates increasingly large Excel + media ZIP bundles to hammer the migration engine.
|
|
|
|
Levels:
|
|
--level 1 → 500 rows, 200 images
|
|
--level 2 → 2000 rows, 800 images
|
|
--level 3 → 5000 rows, 2000 images
|
|
--level 4 → 10000 rows, 5000 images
|
|
--level 5 → 25000 rows, 10000 images
|
|
|
|
Usage:
|
|
python scripts/generate_stress_test.py --level 2 --out /tmp/stress_test
|
|
python scripts/generate_stress_test.py --all
|
|
"""
|
|
import argparse
|
|
import os
|
|
import random
|
|
import string
|
|
import struct
|
|
import zipfile
|
|
import zlib
|
|
from datetime import datetime
|
|
|
|
try:
|
|
import openpyxl
|
|
except ImportError:
|
|
print("Installing openpyxl...")
|
|
os.system("pip install openpyxl -q")
|
|
import openpyxl
|
|
|
|
# ── Domain Data ──────────────────────────────────────────────
|
|
BRANDS = ["Apple", "Samsung", "Google Pixel", "Oppo", "Vivo", "Motorola",
|
|
"Nokia", "OnePlus", "Nothing", "Asus", "Realme", "Xiaomi", "iQOO"]
|
|
|
|
PARENT_CATEGORIES = ["Accessories", "Spare Parts", "Audio", "Wearables", "Charging"]
|
|
|
|
CATEGORIES = {
|
|
"Accessories": ["Screen Guards", "Back Covers", "Mobile Cases", "Camera Lens Guards"],
|
|
"Spare Parts": ["Mobile Display", "Mobile Battery", "Mobile Camera", "Mother Board", "Mobile Back Panel"],
|
|
"Audio": ["Earbuds", "Headphones", "bluetooth speaker"],
|
|
"Wearables": ["Smart Watches"],
|
|
"Charging": ["Charging Adapters", "USB-C Cables", "Mobile Charger"],
|
|
}
|
|
|
|
DEVICE_SERIES_MAP = {
|
|
"Apple": ["iPhone 14 Series", "iPhone 15 Series", "iPhone 16 Series", "iPhone 13 Series"],
|
|
"Samsung": ["Galaxy S23 Series", "Galaxy A53 Series", "Galaxy S24 Series", "Galaxy M-Series"],
|
|
"Google Pixel": ["Pixel 7 Series", "Pixel 8 Series", "Pixel 6 Series"],
|
|
"Oppo": ["Find X6 Series", "Reno 10 Series", "A-Series"],
|
|
"Vivo": ["V27 Series", "Y-Series", "X90 Series"],
|
|
"Motorola": ["Edge 40 Series", "G-Series", "Razr Series"],
|
|
"Nokia": ["G60 Series", "X30 Series", "C-Series"],
|
|
"OnePlus": ["11 Series", "12 Series", "Nord CE Series"],
|
|
"Nothing": ["Phone 2 Series", "Phone 1 Series"],
|
|
"Asus": ["ROG Phone 7 Series", "Zenfone 10 Series"],
|
|
"Realme": ["GT5 Series", "Narzo Series", "Number Series"],
|
|
"Xiaomi": ["14 Series", "Redmi Note 13 Series", "POCO X Series"],
|
|
"iQOO": ["iQOO 11 Series", "iQOO Z7 Series"],
|
|
}
|
|
|
|
DEVICE_MODELS_MAP = {
|
|
"iPhone 14 Series": ["iPhone 14", "iPhone 14 Plus", "iPhone 14 Pro", "iPhone 14 Pro Max"],
|
|
"iPhone 15 Series": ["iPhone 15", "iPhone 15 Plus", "iPhone 15 Pro", "iPhone 15 Pro Max"],
|
|
"iPhone 16 Series": ["iPhone 16", "iPhone 16 Plus", "iPhone 16 Pro", "iPhone 16 Pro Max"],
|
|
"iPhone 13 Series": ["iPhone 13", "iPhone 13 Mini", "iPhone 13 Pro", "iPhone 13 Pro Max"],
|
|
"Galaxy S23 Series": ["Samsung Galaxy S23", "Samsung Galaxy S23+", "Samsung Galaxy S23 Ultra"],
|
|
"Galaxy S24 Series": ["Samsung Galaxy S24", "Samsung Galaxy S24+", "Samsung Galaxy S24 Ultra"],
|
|
"Galaxy A53 Series": ["Samsung Galaxy A53 5G", "Samsung Galaxy A54 5G"],
|
|
"Galaxy M-Series": ["Samsung Galaxy M34 5G", "Samsung Galaxy M54 5G"],
|
|
"Pixel 7 Series": ["Google Pixel 7", "Google Pixel 7 Pro", "Google Pixel 7a"],
|
|
"Pixel 8 Series": ["Google Pixel 8", "Google Pixel 8 Pro"],
|
|
"Pixel 6 Series": ["Google Pixel 6", "Google Pixel 6 Pro", "Google Pixel 6a"],
|
|
"Find X6 Series": ["OPPO Find X6", "OPPO Find X6 Pro"],
|
|
"Reno 10 Series": ["OPPO Reno 10", "OPPO Reno 10 Pro", "OPPO Reno 10 Pro+"],
|
|
"A-Series": ["OPPO A78 5G", "OPPO A58 5G"],
|
|
"V27 Series": ["Vivo V27", "Vivo V27 Pro"],
|
|
"Y-Series": ["Vivo Y100", "Vivo Y78 5G"],
|
|
"X90 Series": ["Vivo X90 Pro", "Vivo X90"],
|
|
"Edge 40 Series": ["Motorola Edge 40", "Motorola Edge 40 Pro"],
|
|
"G-Series": ["Motorola G84 5G", "Motorola G73 5G"],
|
|
"Razr Series": ["Motorola Razr 40 Ultra", "Motorola Razr 40"],
|
|
"G60 Series": ["Nokia G60 5G"],
|
|
"X30 Series": ["Nokia X30 5G"],
|
|
"C-Series": ["Nokia C32", "Nokia C22"],
|
|
"11 Series": ["OnePlus 11", "OnePlus 11R"],
|
|
"12 Series": ["OnePlus 12", "OnePlus 12R"],
|
|
"Nord CE Series": ["OnePlus Nord CE 3 Lite", "OnePlus Nord CE 3"],
|
|
"Phone 2 Series": ["Nothing Phone 2"],
|
|
"Phone 1 Series": ["Nothing Phone 1"],
|
|
"ROG Phone 7 Series": ["ASUS ROG Phone 7", "ASUS ROG Phone 7 Ultimate"],
|
|
"Zenfone 10 Series": ["ASUS Zenfone 10"],
|
|
"GT5 Series": ["Realme GT5", "Realme GT5 Pro"],
|
|
"Narzo Series": ["Realme Narzo 60", "Realme Narzo 60 Pro"],
|
|
"Number Series": ["Realme 11 Pro", "Realme 11 Pro+"],
|
|
"14 Series": ["Xiaomi 14", "Xiaomi 14 Pro", "Xiaomi 14 Ultra"],
|
|
"Redmi Note 13 Series": ["Redmi Note 13 5G", "Redmi Note 13 Pro", "Redmi Note 13 Pro+"],
|
|
"POCO X Series": ["POCO X5 Pro 5G", "POCO X6 Pro"],
|
|
"iQOO 11 Series": ["iQOO 11", "iQOO 11 Pro"],
|
|
"iQOO Z7 Series": ["iQOO Z7 5G", "iQOO Z7 Pro"],
|
|
}
|
|
|
|
COLORS = ["Midnight Black", "Starlight White", "Sierra Blue", "Alpine Green",
|
|
"Deep Purple", "Product Red", "Storm Grey", "Graphite",
|
|
"Natural Titanium", "White Titanium", "Black Titanium", "Blue Titanium"]
|
|
|
|
COLUMNS = [
|
|
"sku", "name", "parent_name", "brand", "category", "parent_category",
|
|
"device_series", "device_model", "price", "cost_price", "stock",
|
|
"description", "barcode", "media_key", "parent_media_key",
|
|
"Color", "Storage", "RAM", "Condition", "Weight_grams",
|
|
"Warranty_months", "In_Box_Contents", "Country_of_Origin",
|
|
]
|
|
|
|
|
|
def _make_png(width=64, height=64, seed=0):
|
|
"""Generate a valid minimal PNG using pure Python (no PIL)."""
|
|
rng = random.Random(seed)
|
|
r, g, b = rng.randint(30, 220), rng.randint(30, 220), rng.randint(30, 220)
|
|
raw_rows = []
|
|
for y in range(height):
|
|
row = bytearray([0]) # filter=None
|
|
for x in range(width):
|
|
row.extend([
|
|
max(0, min(255, r + rng.randint(-40, 40))),
|
|
max(0, min(255, g + rng.randint(-40, 40))),
|
|
max(0, min(255, b + rng.randint(-40, 40))),
|
|
])
|
|
raw_rows.append(bytes(row))
|
|
compressed = zlib.compress(b"".join(raw_rows), level=1)
|
|
|
|
def chunk(tag, data):
|
|
c = struct.pack(">I", len(data)) + tag + data
|
|
return c + struct.pack(">I", zlib.crc32(tag + data) & 0xFFFFFFFF)
|
|
|
|
png = b"\x89PNG\r\n\x1a\n"
|
|
png += chunk(b"IHDR", struct.pack(">IIBBBBB", width, height, 8, 2, 0, 0, 0))
|
|
png += chunk(b"IDAT", compressed)
|
|
png += chunk(b"IEND", b"")
|
|
return png
|
|
|
|
|
|
def _slugify(text):
|
|
return text.lower().replace(" ", "-").replace("/", "-").replace("_", "-")
|
|
|
|
|
|
def generate_rows(count):
|
|
rows = []
|
|
product_idx = 0
|
|
i = 0
|
|
while i < count:
|
|
brand = random.choice(BRANDS)
|
|
parent_cat = random.choice(PARENT_CATEGORIES)
|
|
category = random.choice(CATEGORIES[parent_cat])
|
|
series = random.choice(DEVICE_SERIES_MAP.get(brand, ["Generic Series"]))
|
|
model = random.choice(DEVICE_MODELS_MAP.get(series, ["Generic Model"]))
|
|
|
|
parent_name = f"{brand} {category} for {model}"
|
|
parent_sku_base = f"IFK-{brand[:3].upper()}-{product_idx:05d}"
|
|
parent_media_key = f"{_slugify(brand)}/{_slugify(category)}"
|
|
|
|
remaining = count - i
|
|
if remaining <= 0:
|
|
break
|
|
num_variants = random.randint(1, min(8, remaining))
|
|
for v in range(num_variants):
|
|
color = random.choice(COLORS)
|
|
storage = random.choice(["64GB", "128GB", "256GB", "512GB"])
|
|
ram = random.choice(["4GB", "6GB", "8GB", "12GB"])
|
|
condition = random.choice(["New", "Refurbished"])
|
|
price = round(random.uniform(199, 4999), 2)
|
|
cost = round(price * random.uniform(0.55, 0.80), 2)
|
|
stock = random.randint(0, 500)
|
|
barcode = "".join(random.choices(string.digits, k=13))
|
|
warranty = random.choice([6, 12, 18, 24])
|
|
|
|
rows.append({
|
|
"sku": f"{parent_sku_base}-V{v:02d}",
|
|
"name": f"{parent_name} - {color} {storage}",
|
|
"parent_name": parent_name,
|
|
"brand": brand,
|
|
"category": category,
|
|
"parent_category": parent_cat,
|
|
"device_series": series,
|
|
"device_model": model,
|
|
"price": price,
|
|
"cost_price": cost,
|
|
"stock": stock,
|
|
"description": f"Premium {category} for {model}. Color: {color}. Storage: {storage}. RAM: {ram}. Warranty: {warranty}mo.",
|
|
"barcode": barcode,
|
|
"media_key": f"{parent_media_key}/{_slugify(color)}",
|
|
"parent_media_key": parent_media_key,
|
|
"Color": color,
|
|
"Storage": storage,
|
|
"RAM": ram,
|
|
"Condition": condition,
|
|
"Weight_grams": random.randint(150, 300),
|
|
"Warranty_months": warranty,
|
|
"In_Box_Contents": "Device, Cable, Manual" if condition == "New" else "Device only",
|
|
"Country_of_Origin": random.choice(["India", "China", "Taiwan", "South Korea"]),
|
|
})
|
|
i += 1
|
|
if i >= count:
|
|
break
|
|
product_idx += 1
|
|
return rows
|
|
|
|
|
|
def build_excel(rows, out_path):
|
|
print(f" 📊 Building Excel: {len(rows):,} rows...")
|
|
wb = openpyxl.Workbook(write_only=True)
|
|
ws = wb.create_sheet("Products")
|
|
ws.append(COLUMNS)
|
|
for row in rows:
|
|
ws.append([row.get(col, "") for col in COLUMNS])
|
|
wb.save(out_path)
|
|
mb = os.path.getsize(out_path) / 1024 / 1024
|
|
print(f" ✅ Excel → {out_path} ({mb:.1f} MB)")
|
|
|
|
|
|
def build_media_zip(rows, image_count, out_path):
|
|
print(f" 🖼️ Building ZIP: {image_count:,} images...")
|
|
media_keys = list({row["media_key"] for row in rows})
|
|
random.shuffle(media_keys)
|
|
images_per_key = max(1, image_count // max(len(media_keys), 1))
|
|
generated = 0
|
|
seed = 0
|
|
|
|
with zipfile.ZipFile(out_path, "w", compression=zipfile.ZIP_DEFLATED, compresslevel=3) as zf:
|
|
for mk in media_keys:
|
|
for img_i in range(images_per_key + random.randint(0, 2)):
|
|
if generated >= image_count:
|
|
break
|
|
zf.writestr(f"{mk}/img_{img_i:04d}.png", _make_png(seed=seed))
|
|
seed += 1
|
|
generated += 1
|
|
if generated >= image_count:
|
|
break
|
|
# Top off
|
|
while generated < image_count:
|
|
mk = random.choice(media_keys)
|
|
zf.writestr(f"{mk}/x_{seed:06d}.png", _make_png(seed=seed))
|
|
seed += 1
|
|
generated += 1
|
|
|
|
mb = os.path.getsize(out_path) / 1024 / 1024
|
|
print(f" ✅ ZIP → {out_path} ({mb:.1f} MB, {generated:,} images)")
|
|
|
|
|
|
LEVELS = {
|
|
1: {"rows": 500, "images": 200, "label": "WarmUp"},
|
|
2: {"rows": 2000, "images": 800, "label": "Light"},
|
|
3: {"rows": 5000, "images": 2000, "label": "Medium"},
|
|
4: {"rows": 10000, "images": 5000, "label": "Heavy"},
|
|
5: {"rows": 25000, "images": 10000, "label": "EXTREME"},
|
|
}
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="iFixKart Migration Stress Test Generator")
|
|
parser.add_argument("--level", type=int, choices=[1,2,3,4,5], default=2)
|
|
parser.add_argument("--out", type=str, default="/tmp/ifixkart_stress")
|
|
parser.add_argument("--all", dest="all_levels", action="store_true")
|
|
args = parser.parse_args()
|
|
|
|
os.makedirs(args.out, exist_ok=True)
|
|
levels_to_run = list(range(1, 6)) if args.all_levels else [args.level]
|
|
|
|
for lvl in levels_to_run:
|
|
cfg = LEVELS[lvl]
|
|
ts = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
print(f"\n{'='*60}")
|
|
print(f" 🚀 Level {lvl} — {cfg['label']} ({cfg['rows']:,} rows | {cfg['images']:,} images)")
|
|
print(f"{'='*60}")
|
|
|
|
rows = generate_rows(cfg["rows"])
|
|
xlsx = os.path.join(args.out, f"stress_L{lvl}_{cfg['label']}_{ts}.xlsx")
|
|
zipp = os.path.join(args.out, f"stress_L{lvl}_{cfg['label']}_{ts}_media.zip")
|
|
build_excel(rows, xlsx)
|
|
build_media_zip(rows, cfg["images"], zipp)
|
|
print(f"\n 📦 Upload these two files together via the migration UI.")
|
|
|
|
print(f"\n✅ Done! Files in: {args.out}/\n")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|