generate_test_data.js (11248B)
1 #!/usr/bin/env node 2 /** 3 * Generate Test Data for Products Schema 4 * 5 * Generates realistic test data matching the UI expectations: 6 * - ProductCard interface (for listings) 7 * - Product interface (for details) 8 * 9 * Output: SQL file ready to import 10 */ 11 12 const fs = require('fs'); 13 const path = require('path'); 14 15 // Configuration 16 const NUM_PRODUCTS = 2000; 17 const NUM_RATINGS_PER_PRODUCT = { min: 5, max: 50 }; 18 19 // Sample data pools 20 const BRANDS = [ 21 'TechPro', 'SmartGear', 'EliteHome', 'ActiveLife', 'StyleHub', 22 'PureTech', 'FlexFit', 'MegaValue', 'ProMax', 'UltraGear', 23 'CoreTech', 'PrimeTech', 'NexGen', 'FutureTech', 'DigitalWave' 24 ]; 25 26 const CATEGORIES = [ 27 { name: 'Smartphones', root: 'Electronics', adjectives: ['Pro', 'Max', 'Ultra', 'Plus', 'Elite'] }, 28 { name: 'Laptops', root: 'Electronics', adjectives: ['Book', 'Pro', 'Air', 'Gaming', 'Business'] }, 29 { name: 'Accessories', root: 'Electronics', adjectives: ['Premium', 'Wireless', 'Smart', 'Pro', 'Plus'] }, 30 { name: 'Smart Home', root: 'Electronics', adjectives: ['Smart', 'Connected', 'Voice', 'AI', 'Auto'] }, 31 { name: 'Men\'s Clothing', root: 'Clothing', adjectives: ['Classic', 'Modern', 'Casual', 'Sport', 'Premium'] }, 32 { name: 'Women\'s Clothing', root: 'Clothing', adjectives: ['Elegant', 'Casual', 'Sport', 'Designer', 'Comfort'] }, 33 { name: 'Shoes', root: 'Clothing', adjectives: ['Running', 'Walking', 'Sport', 'Casual', 'Athletic'] } 34 ]; 35 36 const PRODUCT_TYPES = { 37 'Smartphones': ['Phone', 'Smartphone', 'Mobile Device', 'Handset'], 38 'Laptops': ['Laptop', 'Notebook', 'Ultrabook', 'Chromebook'], 39 'Accessories': ['Cable', 'Charger', 'Case', 'Stand', 'Mount', 'Adapter', 'Hub'], 40 'Smart Home': ['Camera', 'Doorbell', 'Thermostat', 'Lock', 'Speaker', 'Light'], 41 'Men\'s Clothing': ['Shirt', 'T-Shirt', 'Pants', 'Jeans', 'Jacket', 'Sweater'], 42 'Women\'s Clothing': ['Dress', 'Blouse', 'Skirt', 'Pants', 'Jacket', 'Sweater'], 43 'Shoes': ['Sneakers', 'Boots', 'Sandals', 'Loafers', 'Athletic Shoes'] 44 }; 45 46 const SIZES = { 47 clothing: ['XS', 'S', 'M', 'L', 'XL', 'XXL'], 48 shoes: ['6', '7', '8', '9', '10', '11', '12'], 49 tech: ['64GB', '128GB', '256GB', '512GB', '1TB'] 50 }; 51 52 const COLORS = ['Black', 'White', 'Silver', 'Gold', 'Blue', 'Red', 'Green', 'Gray', 'Rose Gold', 'Navy']; 53 54 const DESCRIPTIONS = [ 55 'High-quality product designed for everyday use.', 56 'Premium build quality with advanced features.', 57 'Perfect for both professional and personal use.', 58 'Innovative design combining style and functionality.', 59 'Top-rated product with excellent performance.', 60 'Durable construction built to last.', 61 'Feature-rich product at an affordable price.', 62 'Award-winning design with cutting-edge technology.', 63 'User-friendly interface with powerful capabilities.', 64 'Sleek and modern design for the contemporary lifestyle.' 65 ]; 66 67 // Helper functions 68 function randomInt(min, max) { 69 return Math.floor(Math.random() * (max - min + 1)) + min; 70 } 71 72 function randomChoice(array) { 73 return array[randomInt(0, array.length - 1)]; 74 } 75 76 function randomChoices(array, count) { 77 const shuffled = [...array].sort(() => 0.5 - Math.random()); 78 return shuffled.slice(0, count); 79 } 80 81 function generateUUID() { 82 return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { 83 const r = Math.random() * 16 | 0; 84 const v = c === 'x' ? r : (r & 0x3 | 0x8); 85 return v.toString(16); 86 }); 87 } 88 89 function generateASIN() { 90 const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; 91 return Array.from({ length: 10 }, () => randomChoice([...chars])).join(''); 92 } 93 94 function sqlString(str) { 95 if (str === null || str === undefined) return 'NULL'; 96 return `'${str.replace(/'/g, "''")}'`; 97 } 98 99 function sqlArray(arr) { 100 if (!arr || arr.length === 0) return 'NULL'; 101 return `ARRAY[${arr.map(sqlString).join(', ')}]`; 102 } 103 104 function calculateDiscount(initial, final) { 105 if (!initial || initial === final) return null; 106 const percent = Math.round(((initial - final) / initial) * 100); 107 return `${percent}%`; 108 } 109 110 // Generate products 111 function generateProducts() { 112 const products = []; 113 const productsByCategory = new Map(); 114 115 for (let i = 0; i < NUM_PRODUCTS; i++) { 116 const category = randomChoice(CATEGORIES); 117 const brand = randomChoice(BRANDS); 118 const productType = randomChoice(PRODUCT_TYPES[category.name] || ['Product']); 119 const adjective = randomChoice(category.adjectives); 120 const modelNumber = randomInt(1000, 9999); 121 122 // Generate pricing 123 const basePrice = randomInt(10, 2000); 124 const hasDiscount = Math.random() > 0.6; // 40% of products have discount 125 const finalPrice = hasDiscount ? basePrice * (1 - randomInt(10, 40) / 100) : basePrice; 126 const initialPrice = hasDiscount ? basePrice : null; 127 128 // Generate sizes and colors based on category 129 let sizes = null; 130 let colors = null; 131 132 if (category.root === 'Clothing' && category.name !== 'Shoes') { 133 sizes = randomChoices(SIZES.clothing, randomInt(3, 6)); 134 colors = randomChoices(COLORS, randomInt(2, 4)); 135 } else if (category.name === 'Shoes') { 136 sizes = randomChoices(SIZES.shoes, randomInt(5, 7)); 137 colors = randomChoices(COLORS, randomInt(1, 3)); 138 } else if (category.name === 'Smartphones' || category.name === 'Laptops') { 139 sizes = randomChoices(SIZES.tech, randomInt(2, 4)); 140 colors = randomChoices(COLORS.slice(0, 5), randomInt(2, 4)); 141 } else { 142 colors = randomChoices(COLORS.slice(0, 5), randomInt(1, 3)); 143 } 144 145 const product = { 146 product_id: `PROD-${String(i + 1).padStart(6, '0')}`, 147 uuid: generateUUID(), 148 asin: generateASIN(), 149 sku: `${category.name.substring(0, 3).toUpperCase()}-${brand.substring(0, 3).toUpperCase()}-${String(i + 1).padStart(6, '0')}`, 150 gtin: Math.random() > 0.5 ? `${randomInt(1000000000000, 9999999999999)}` : null, 151 product_name: `${brand} ${adjective} ${productType} ${modelNumber}`, 152 brand: brand, 153 description: randomChoice(DESCRIPTIONS), 154 category_name: category.name, 155 root_category_name: category.root, 156 price: parseFloat(finalPrice.toFixed(2)), 157 initial_price: initialPrice ? parseFloat(initialPrice.toFixed(2)) : null, 158 discount: calculateDiscount(initialPrice, finalPrice), 159 currency: 'USD', 160 stock_quantity: randomInt(0, 500), 161 sizes: sizes, 162 colors: colors, 163 url: `https://example.com/product/${i + 1}`, 164 image_url: null, 165 available_for_delivery: Math.random() > 0.1, 166 available_for_pickup: Math.random() > 0.5, 167 free_returns: Math.random() > 0.4, 168 is_active: Math.random() > 0.05, 169 deleted_at: null, 170 data_timestamp: new Date().toISOString() 171 }; 172 173 products.push(product); 174 175 if (!productsByCategory.has(category.name)) { 176 productsByCategory.set(category.name, []); 177 } 178 productsByCategory.get(category.name).push(product); 179 } 180 181 return { products, productsByCategory }; 182 } 183 184 // Generate ratings 185 function generateRatings(products) { 186 const ratings = []; 187 let ratingId = 1; 188 189 products.forEach((product, idx) => { 190 const numRatings = randomInt(NUM_RATINGS_PER_PRODUCT.min, NUM_RATINGS_PER_PRODUCT.max); 191 192 for (let i = 0; i < numRatings; i++) { 193 const rating = { 194 uuid: generateUUID(), 195 product_id: idx + 1, // Will be the serial ID 196 user_id: generateUUID(), 197 rating: randomInt(1, 5), 198 review: Math.random() > 0.6 ? randomChoice([ 199 'Great product! Highly recommended.', 200 'Good value for money.', 201 'Exceeded my expectations.', 202 'Exactly as described.', 203 'Fast shipping and great quality.', 204 'Not bad, but could be better.', 205 'Satisfied with the purchase.', 206 'Works perfectly!', 207 'Very happy with this product.', 208 'Would buy again.' 209 ]) : null 210 }; 211 212 ratings.push(rating); 213 ratingId++; 214 } 215 }); 216 217 return ratings; 218 } 219 220 // Generate SQL 221 function generateSQL(products, ratings) { 222 let sql = `-- Generated test data for products schema 223 -- Total products: ${products.length} 224 -- Total ratings: ${ratings.length} 225 -- Generated at: ${new Date().toISOString()} 226 227 SET search_path TO products, public; 228 229 -- ============================================================ 230 -- INSERT PRODUCTS 231 -- ============================================================ 232 INSERT INTO products ( 233 product_id, uuid, asin, sku, gtin, product_name, brand, description, 234 category_id, price, initial_price, discount, currency, stock_quantity, 235 sizes, colors, url, image_url, 236 available_for_delivery, available_for_pickup, free_returns, 237 is_active, deleted_at, data_timestamp 238 ) VALUES\n`; 239 240 // Insert products 241 products.forEach((p, idx) => { 242 sql += ` (`; 243 sql += `${sqlString(p.product_id)}, `; 244 sql += `${sqlString(p.uuid)}, `; 245 sql += `${sqlString(p.asin)}, `; 246 sql += `${sqlString(p.sku)}, `; 247 sql += `${sqlString(p.gtin)}, `; 248 sql += `${sqlString(p.product_name)}, `; 249 sql += `${sqlString(p.brand)}, `; 250 sql += `${sqlString(p.description)}, `; 251 sql += `(SELECT id FROM categories WHERE name = ${sqlString(p.category_name)}), `; 252 sql += `${p.price}, `; 253 sql += `${p.initial_price || 'NULL'}, `; 254 sql += `${sqlString(p.discount)}, `; 255 sql += `${sqlString(p.currency)}, `; 256 sql += `${p.stock_quantity}, `; 257 sql += `${sqlArray(p.sizes)}, `; 258 sql += `${sqlArray(p.colors)}, `; 259 sql += `${sqlString(p.url)}, `; 260 sql += `${sqlString(p.image_url)}, `; 261 sql += `${p.available_for_delivery}, `; 262 sql += `${p.available_for_pickup}, `; 263 sql += `${p.free_returns}, `; 264 sql += `${p.is_active}, `; 265 sql += `${sqlString(p.deleted_at)}, `; 266 sql += `${sqlString(p.data_timestamp)}`; 267 sql += `)${idx < products.length - 1 ? ',' : ';'}\n`; 268 }); 269 270 sql += `\n-- ============================================================\n`; 271 sql += `-- INSERT RATINGS\n`; 272 sql += `-- ============================================================\n`; 273 sql += `INSERT INTO ratings (uuid, product_id, user_id, rating, review) VALUES\n`; 274 275 // Insert ratings 276 ratings.forEach((r, idx) => { 277 sql += ` (`; 278 sql += `${sqlString(r.uuid)}, `; 279 sql += `${r.product_id}, `; 280 sql += `${sqlString(r.user_id)}, `; 281 sql += `${r.rating}, `; 282 sql += `${sqlString(r.review)}`; 283 sql += `)${idx < ratings.length - 1 ? ',' : ';'}\n`; 284 }); 285 286 return sql; 287 } 288 289 // Main execution 290 console.log('Generating test data...'); 291 console.log(`- Products: ${NUM_PRODUCTS}`); 292 console.log(`- Ratings per product: ${NUM_RATINGS_PER_PRODUCT.min}-${NUM_RATINGS_PER_PRODUCT.max}`); 293 294 const { products, productsByCategory } = generateProducts(); 295 const ratings = generateRatings(products); 296 297 console.log('\nGenerated:'); 298 console.log(` ${products.length} products`); 299 console.log(` ${ratings.length} ratings`); 300 console.log('\nProducts by category:'); 301 productsByCategory.forEach((prods, cat) => { 302 console.log(` ${cat}: ${prods.length}`); 303 }); 304 305 const sql = generateSQL(products, ratings); 306 const outputPath = path.join(__dirname, 'data', 'products_test_data.sql'); 307 308 fs.writeFileSync(outputPath, sql); 309 console.log(`\nSQL file generated: ${outputPath}`); 310 console.log('\nTo load data:'); 311 console.log(` psql -h localhost -p 5433 -U opentel_user -d opentel_db -f ${outputPath}`);