/** * ๐Ÿ”ฅ ์ŠคํŠธ๋ ˆ์Šค ํ…Œ์ŠคํŠธ ์‹œ๋‚˜๋ฆฌ์˜ค * * ํƒ€์ž… ์‹œ์Šคํ…œ์˜ ๊ฒฌ๊ณ ํ•จ์„ ๊ฒ€์ฆํ•˜๊ธฐ ์œ„ํ•œ ๊ทนํ•œ ์ƒํ™ฉ ํ…Œ์ŠคํŠธ */ import { ComponentData, WidgetComponent, WebType, ScreenDefinition, LayoutData, // ํƒ€์ž… ๊ฐ€๋“œ ๋ฐ ์œ ํ‹ธ๋ฆฌํ‹ฐ isWebType, isButtonActionType, isWidgetComponent, asWidgetComponent, ynToBoolean, booleanToYN, } from "@/types"; // ์ŠคํŠธ๋ ˆ์Šค ํ…Œ์ŠคํŠธ์šฉ ํ™•์žฅ๋œ ํ™”๋ฉด ์ •์˜ interface TestScreenDefinition extends ScreenDefinition { layoutData?: LayoutData; } export class StressTestSuite { private static results: Array<{ testName: string; status: "passed" | "failed" | "warning"; duration: number; details: string; metrics?: Record; }> = []; /** * ๐Ÿ”ฅ Test 1: ๋Œ€๋Ÿ‰ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ์ŠคํŠธ๋ ˆ์Šค ํ…Œ์ŠคํŠธ */ static async testMassiveDataProcessing() { console.log("๐Ÿ”ฅ ์ŠคํŠธ๋ ˆ์Šค ํ…Œ์ŠคํŠธ 1: ๋Œ€๋Ÿ‰ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ"); const startTime = performance.now(); try { // 10,000๊ฐœ์˜ ์ปดํฌ๋„ŒํŠธ ์ƒ์„ฑ ๋ฐ ํƒ€์ž… ๊ฒ€์ฆ const componentCount = 10000; const components: ComponentData[] = []; const webTypes: WebType[] = ["text", "number", "date", "select", "checkbox", "textarea", "email", "decimal"]; console.log(`๐Ÿ“Š ${componentCount}๊ฐœ์˜ ์ปดํฌ๋„ŒํŠธ ์ƒ์„ฑ ์ค‘...`); for (let i = 0; i < componentCount; i++) { const randomWebType = webTypes[i % webTypes.length]; const component: WidgetComponent = { id: `stress-widget-${i}`, type: "widget", widgetType: randomWebType, position: { x: Math.random() * 1000, y: Math.random() * 1000 }, size: { width: 100 + Math.random() * 200, height: 30 + Math.random() * 50 }, label: `์ŠคํŠธ๋ ˆ์Šค ํ…Œ์ŠคํŠธ ์ปดํฌ๋„ŒํŠธ ${i}`, columnName: `stress_column_${i}`, required: Math.random() > 0.5, readonly: Math.random() > 0.7, webTypeConfig: { maxLength: Math.floor(Math.random() * 500), placeholder: `ํ…Œ์ŠคํŠธ ํ”Œ๋ ˆ์ด์Šคํ™€๋” ${i}`, }, }; components.push(component); } console.log("๐Ÿ” ํƒ€์ž… ๊ฒ€์ฆ ์‹œ์ž‘..."); let validComponents = 0; let invalidComponents = 0; // ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ ํƒ€์ž… ๊ฒ€์ฆ for (const component of components) { if (isWidgetComponent(component)) { const widget = asWidgetComponent(component); if (widget && isWebType(widget.widgetType)) { validComponents++; } else { invalidComponents++; } } else { invalidComponents++; } } const endTime = performance.now(); const duration = endTime - startTime; const metrics = { totalComponents: componentCount, validComponents, invalidComponents, processingTimeMs: duration, componentsPerSecond: Math.round(componentCount / (duration / 1000)), }; console.log("๐Ÿ“ˆ ๋Œ€๋Ÿ‰ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ๊ฒฐ๊ณผ:", metrics); this.results.push({ testName: "๋Œ€๋Ÿ‰ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ", status: invalidComponents === 0 ? "passed" : "failed", duration, details: `${validComponents}/${componentCount} ์ปดํฌ๋„ŒํŠธ ๊ฒ€์ฆ ์„ฑ๊ณต`, metrics, }); return metrics; } catch (error) { const endTime = performance.now(); this.results.push({ testName: "๋Œ€๋Ÿ‰ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ", status: "failed", duration: endTime - startTime, details: `์˜ค๋ฅ˜ ๋ฐœ์ƒ: ${error}`, }); throw error; } } /** * ๐Ÿ”ฅ Test 2: ํƒ€์ž… ์˜ค์—ผ ๋ฐ ์†์ƒ ์‹œ๋‚˜๋ฆฌ์˜ค */ static async testTypeCorruption() { console.log("๐Ÿ”ฅ ์ŠคํŠธ๋ ˆ์Šค ํ…Œ์ŠคํŠธ 2: ํƒ€์ž… ์˜ค์—ผ ๋ฐ ์†์ƒ"); const startTime = performance.now(); try { // ๋‹ค์–‘ํ•œ ์ž˜๋ชป๋œ ๋ฐ์ดํ„ฐ๋“ค๋กœ ํƒ€์ž… ์‹œ์Šคํ…œ ๊ณต๊ฒฉ const corruptedInputs = [ // ์ž˜๋ชป๋œ WebType๋“ค { webType: null, expected: false }, { webType: undefined, expected: false }, { webType: "", expected: false }, { webType: "invalid_type", expected: false }, { webType: "VARCHAR(255)", expected: false }, { webType: "submit", expected: false }, // ButtonActionType๊ณผ ํ˜ผ๋™ { webType: "widget", expected: false }, // ComponentType๊ณผ ํ˜ผ๋™ { webType: 123, expected: false }, { webType: {}, expected: false }, { webType: [], expected: false }, { webType: "text", expected: true }, // ์œ ์ผํ•œ ์˜ฌ๋ฐ”๋ฅธ ๊ฐ’ // ์ž˜๋ชป๋œ ButtonActionType๋“ค { buttonAction: "insert", expected: false }, { buttonAction: "update", expected: false }, { buttonAction: "remove", expected: false }, { buttonAction: "text", expected: false }, // WebType๊ณผ ํ˜ผ๋™ { buttonAction: null, expected: false }, { buttonAction: 456, expected: false }, { buttonAction: "save", expected: true }, // ์˜ฌ๋ฐ”๋ฅธ ๊ฐ’ ]; let passedChecks = 0; let failedChecks = 0; console.log("๐Ÿฆ  ํƒ€์ž… ์˜ค์—ผ ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ ์ค‘..."); corruptedInputs.forEach((input, index) => { if ("webType" in input) { const isValid = isWebType(input.webType as unknown); if (isValid === input.expected) { passedChecks++; } else { failedChecks++; console.warn( `โŒ WebType ๊ฒ€์ฆ ์‹คํŒจ #${index}:`, input.webType, "expected:", input.expected, "got:", isValid, ); } } if ("buttonAction" in input) { const isValid = isButtonActionType(input.buttonAction as unknown); if (isValid === input.expected) { passedChecks++; } else { failedChecks++; console.warn( `โŒ ButtonActionType ๊ฒ€์ฆ ์‹คํŒจ #${index}:`, input.buttonAction, "expected:", input.expected, "got:", isValid, ); } } }); // ๊ทนํ•œ ๋ฉ”๋ชจ๋ฆฌ ์˜ค์—ผ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ const memoryCorruptionTest = () => { const largeString = "x".repeat(1000000); // 1MB ๋ฌธ์ž์—ด const corruptedComponent = { id: largeString, type: "widget", // ์˜ฌ๋ฐ”๋ฅธ ํƒ€์ž…์ด์ง€๋งŒ widgetType: largeString, // ์ž˜๋ชป๋œ ์›นํƒ€์ž… (๋งค์šฐ ๊ธด ๋ฌธ์ž์—ด) position: { x: Infinity, y: -Infinity }, // ์ž˜๋ชป๋œ ์œ„์น˜๊ฐ’ size: { width: NaN, height: -1 }, // ์ž˜๋ชป๋œ ํฌ๊ธฐ๊ฐ’ label: null, // null ๋ผ๋ฒจ // ํ•„์ˆ˜ ํ•„๋“œ๋“ค์ด ๋ˆ„๋ฝ๋จ }; // ๋” ์—„๊ฒฉํ•œ ๊ฒ€์ฆ์„ ์œ„ํ•ด ์‹ค์ œ WidgetComponent ์ธํ„ฐํŽ˜์ด์Šค์™€ ๋น„๊ต const isValidWidget = isWidgetComponent(corruptedComponent as unknown); // ์ถ”๊ฐ€ ๊ฒ€์ฆ: widgetType์ด ์œ ํšจํ•œ WebType์ธ์ง€ ํ™•์ธ const hasValidWebType = corruptedComponent.widgetType && isWebType(corruptedComponent.widgetType); // ์ถ”๊ฐ€ ๊ฒ€์ฆ: ํ•„์ˆ˜ ํ•„๋“œ๋“ค์ด ์กด์žฌํ•˜๊ณ  ์œ ํšจํ•œ์ง€ ํ™•์ธ const hasValidStructure = corruptedComponent.position && typeof corruptedComponent.position.x === "number" && typeof corruptedComponent.position.y === "number" && !isNaN(corruptedComponent.position.x) && !isNaN(corruptedComponent.position.y) && corruptedComponent.size && typeof corruptedComponent.size.width === "number" && typeof corruptedComponent.size.height === "number" && !isNaN(corruptedComponent.size.width) && !isNaN(corruptedComponent.size.height) && corruptedComponent.size.width > 0 && corruptedComponent.size.height > 0; // ๋ชจ๋“  ๊ฒ€์ฆ์ด ํ†ต๊ณผํ•ด์•ผ true ๋ฐ˜ํ™˜ (์‹ค์ œ๋กœ๋Š” ๋ชจ๋‘ ์‹คํŒจํ•ด์•ผ ํ•จ) return isValidWidget && hasValidWebType && hasValidStructure; }; const memoryTestResult = memoryCorruptionTest(); const endTime = performance.now(); const duration = endTime - startTime; const metrics = { totalChecks: corruptedInputs.length, passedChecks, failedChecks, memoryCorruptionHandled: !memoryTestResult, // ์˜ค์—ผ๋œ ์ปดํฌ๋„ŒํŠธ๋Š” ๊ฑฐ๋ถ€๋˜์–ด์•ผ ํ•จ duration, }; console.log("๐Ÿฆ  ํƒ€์ž… ์˜ค์—ผ ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ:", metrics); this.results.push({ testName: "ํƒ€์ž… ์˜ค์—ผ ๋ฐ ์†์ƒ", status: failedChecks === 0 && metrics.memoryCorruptionHandled ? "passed" : "failed", duration, details: `${passedChecks}/${corruptedInputs.length} ์˜ค์—ผ ๋ฐ์ดํ„ฐ ์ฐจ๋‹จ ์„ฑ๊ณต`, metrics, }); return metrics; } catch (error) { const endTime = performance.now(); this.results.push({ testName: "ํƒ€์ž… ์˜ค์—ผ ๋ฐ ์†์ƒ", status: "failed", duration: endTime - startTime, details: `์˜ค๋ฅ˜ ๋ฐœ์ƒ: ${error}`, }); throw error; } } /** * ๐Ÿ”ฅ Test 3: ๋™์‹œ ์ž‘์—… ๋ฐ ๊ฒฝํ•ฉ ์ƒํƒœ ํ…Œ์ŠคํŠธ */ static async testConcurrentOperations() { console.log("๐Ÿ”ฅ ์ŠคํŠธ๋ ˆ์Šค ํ…Œ์ŠคํŠธ 3: ๋™์‹œ ์ž‘์—… ๋ฐ ๊ฒฝํ•ฉ ์ƒํƒœ"); const startTime = performance.now(); try { const concurrentTasks = 100; const operationsPerTask = 100; console.log(`โšก ${concurrentTasks}๊ฐœ์˜ ๋™์‹œ ์ž‘์—… ์‹œ์ž‘ (๊ฐ๊ฐ ${operationsPerTask}๊ฐœ ์—ฐ์‚ฐ)...`); // ๋™์‹œ์— ์‹คํ–‰๋  ์ž‘์—…๋“ค const concurrentPromises = Array.from({ length: concurrentTasks }, async (_, taskIndex) => { const taskResults = { taskIndex, successfulOperations: 0, failedOperations: 0, typeGuardCalls: 0, conversionCalls: 0, }; for (let i = 0; i < operationsPerTask; i++) { try { // ํƒ€์ž… ๊ฐ€๋“œ ํ…Œ์ŠคํŠธ const randomWebType = ["text", "number", "invalid"][Math.floor(Math.random() * 3)]; isWebType(randomWebType as unknown); taskResults.typeGuardCalls++; // Y/N ๋ณ€ํ™˜ ํ…Œ์ŠคํŠธ const randomBoolean = Math.random() > 0.5; const ynValue = booleanToYN(randomBoolean); const backToBoolean = ynToBoolean(ynValue); taskResults.conversionCalls++; if (backToBoolean === randomBoolean) { taskResults.successfulOperations++; } else { taskResults.failedOperations++; } // ์ปดํฌ๋„ŒํŠธ ์ƒ์„ฑ ๋ฐ ํƒ€์ž… ๊ฐ€๋“œ ํ…Œ์ŠคํŠธ const component: WidgetComponent = { id: `concurrent-${taskIndex}-${i}`, type: "widget", widgetType: "text", position: { x: 0, y: 0 }, size: { width: 100, height: 30 }, label: `Concurrent ${taskIndex}-${i}`, webTypeConfig: {}, }; if (isWidgetComponent(component)) { taskResults.successfulOperations++; } else { taskResults.failedOperations++; } } catch { taskResults.failedOperations++; } } return taskResults; }); // ๋ชจ๋“  ๋™์‹œ ์ž‘์—… ์™„๋ฃŒ ๋Œ€๊ธฐ const allResults = await Promise.all(concurrentPromises); const aggregatedResults = allResults.reduce( (acc, result) => ({ totalTasks: acc.totalTasks + 1, totalSuccessfulOperations: acc.totalSuccessfulOperations + result.successfulOperations, totalFailedOperations: acc.totalFailedOperations + result.failedOperations, totalTypeGuardCalls: acc.totalTypeGuardCalls + result.typeGuardCalls, totalConversionCalls: acc.totalConversionCalls + result.conversionCalls, }), { totalTasks: 0, totalSuccessfulOperations: 0, totalFailedOperations: 0, totalTypeGuardCalls: 0, totalConversionCalls: 0, }, ); const endTime = performance.now(); const duration = endTime - startTime; const metrics = { ...aggregatedResults, concurrentTasks, operationsPerTask, totalOperations: concurrentTasks * operationsPerTask * 3, // ๊ฐ ๋ฃจํ”„์—์„œ 3๊ฐœ ์—ฐ์‚ฐ duration, operationsPerSecond: Math.round( (aggregatedResults.totalSuccessfulOperations + aggregatedResults.totalFailedOperations) / (duration / 1000), ), successRate: (aggregatedResults.totalSuccessfulOperations / (aggregatedResults.totalSuccessfulOperations + aggregatedResults.totalFailedOperations)) * 100, }; console.log("โšก ๋™์‹œ ์ž‘์—… ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ:", metrics); this.results.push({ testName: "๋™์‹œ ์ž‘์—… ๋ฐ ๊ฒฝํ•ฉ ์ƒํƒœ", status: metrics.successRate > 95 ? "passed" : "failed", duration, details: `${metrics.successRate.toFixed(2)}% ์„ฑ๊ณต๋ฅ `, metrics, }); return metrics; } catch (error) { const endTime = performance.now(); this.results.push({ testName: "๋™์‹œ ์ž‘์—… ๋ฐ ๊ฒฝํ•ฉ ์ƒํƒœ", status: "failed", duration: endTime - startTime, details: `์˜ค๋ฅ˜ ๋ฐœ์ƒ: ${error}`, }); throw error; } } /** * ๐Ÿ”ฅ Test 4: ๋ฉ”๋ชจ๋ฆฌ ๋ถ€ํ•˜ ๋ฐ ๊ฐ€๋น„์ง€ ์ปฌ๋ ‰์…˜ ์ŠคํŠธ๋ ˆ์Šค */ static async testMemoryStress() { console.log("๐Ÿ”ฅ ์ŠคํŠธ๋ ˆ์Šค ํ…Œ์ŠคํŠธ 4: ๋ฉ”๋ชจ๋ฆฌ ๋ถ€ํ•˜ ๋ฐ ๊ฐ€๋น„์ง€ ์ปฌ๋ ‰์…˜"); const startTime = performance.now(); try { const iterations = 1000; const objectsPerIteration = 1000; console.log(`๐Ÿง  ๋ฉ”๋ชจ๋ฆฌ ์ŠคํŠธ๋ ˆ์Šค ํ…Œ์ŠคํŠธ: ${iterations}ํšŒ ๋ฐ˜๋ณต, ๋งคํšŒ ${objectsPerIteration}๊ฐœ ๊ฐ์ฒด ์ƒ์„ฑ`); let totalObjectsCreated = 0; let gcTriggered = 0; // ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰ ๋ชจ๋‹ˆํ„ฐ๋ง (๊ฐ€๋Šฅํ•œ ๊ฒฝ์šฐ) const initialMemory = (performance as unknown as { memory?: { usedJSHeapSize: number } }).memory?.usedJSHeapSize || 0; for (let iteration = 0; iteration < iterations; iteration++) { // ๋Œ€๋Ÿ‰์˜ ๊ฐ์ฒด ์ƒ์„ฑ const tempObjects: ComponentData[] = []; for (let i = 0; i < objectsPerIteration; i++) { const largeComponent: WidgetComponent = { id: `memory-stress-${iteration}-${i}`, type: "widget", widgetType: "textarea", position: { x: Math.random() * 10000, y: Math.random() * 10000 }, size: { width: Math.random() * 1000, height: Math.random() * 1000 }, label: "๋ฉ”๋ชจ๋ฆฌ ์ŠคํŠธ๋ ˆ์Šค ํ…Œ์ŠคํŠธ ์ปดํฌ๋„ŒํŠธ ".repeat(10), // ๊ธด ๋ฌธ์ž์—ด columnName: `stress_test_column_with_very_long_name_${iteration}_${i}`, placeholder: "๋งค์šฐ ๊ธด ํ”Œ๋ ˆ์ด์Šคํ™€๋” ํ…์ŠคํŠธ ".repeat(20), webTypeConfig: { maxLength: 10000, rows: 50, placeholder: "๋Œ€์šฉ๋Ÿ‰ ํ…์ŠคํŠธ ์˜์—ญ ".repeat(50), validation: { pattern: "๋งค์šฐ ๋ณต์žกํ•œ ์ •๊ทœ์‹ ํŒจํ„ด ".repeat(10), errorMessage: "๋ณต์žกํ•œ ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€ ".repeat(10), }, }, }; // ํƒ€์ž… ๊ฒ€์ฆ if (isWidgetComponent(largeComponent)) { tempObjects.push(largeComponent); totalObjectsCreated++; } } // ๋” ์ ๊ทน์ ์ธ ๋ฉ”๋ชจ๋ฆฌ ํ•ด์ œ (๊ฐ€๋น„์ง€ ์ปฌ๋ ‰์…˜ ์œ ๋„) if (iteration % 50 === 0) { // ๋” ์ž์ฃผ ์ •๋ฆฌ (100 โ†’ 50) tempObjects.length = 0; // ๋ฐฐ์—ด ์ดˆ๊ธฐํ™” // ๊ฐ•์ œ์ ์ธ ๊ฐ€๋น„์ง€ ์ปฌ๋ ‰์…˜ ํžŒํŠธ ์ œ๊ณต if (typeof global !== "undefined" && (global as unknown as { gc?: () => void }).gc) { (global as unknown as { gc: () => void }).gc(); gcTriggered++; } // ์ถ”๊ฐ€์ ์ธ ๋ฉ”๋ชจ๋ฆฌ ์ •๋ฆฌ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ // ํฐ ๊ฐ์ฒด๋“ค์„ null๋กœ ์„ค์ •ํ•˜์—ฌ ์ฐธ์กฐ ํ•ด์ œ for (let cleanupIndex = 0; cleanupIndex < 10; cleanupIndex++) { const dummyCleanup = new Array(1000).fill(null); dummyCleanup.length = 0; } console.log(`๐Ÿ—‘๏ธ ๊ฐ€๋น„์ง€ ์ปฌ๋ ‰์…˜ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ (๋ฐ˜๋ณต ${iteration}/${iterations})`); } } const finalMemory = (performance as unknown as { memory?: { usedJSHeapSize: number } }).memory?.usedJSHeapSize || 0; const memoryDelta = finalMemory - initialMemory; const endTime = performance.now(); const duration = endTime - startTime; const metrics = { iterations, objectsPerIteration, totalObjectsCreated, gcTriggered, initialMemoryBytes: initialMemory, finalMemoryBytes: finalMemory, memoryDeltaBytes: memoryDelta, memoryDeltaMB: Math.round((memoryDelta / (1024 * 1024)) * 100) / 100, duration, objectsPerSecond: Math.round(totalObjectsCreated / (duration / 1000)), }; console.log("๐Ÿง  ๋ฉ”๋ชจ๋ฆฌ ์ŠคํŠธ๋ ˆ์Šค ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ:", metrics); // ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ์ฒดํฌ (๋งค์šฐ ๋‹จ์ˆœํ•œ ํœด๋ฆฌ์Šคํ‹ฑ) const suspectedMemoryLeak = metrics.memoryDeltaMB > 100; // 100MB ์ด์ƒ ์ฆ๊ฐ€ ์‹œ ์˜์‹ฌ this.results.push({ testName: "๋ฉ”๋ชจ๋ฆฌ ๋ถ€ํ•˜ ๋ฐ ๊ฐ€๋น„์ง€ ์ปฌ๋ ‰์…˜", status: suspectedMemoryLeak ? "warning" : "passed", duration, details: `${metrics.totalObjectsCreated}๊ฐœ ๊ฐ์ฒด ์ƒ์„ฑ, ๋ฉ”๋ชจ๋ฆฌ ๋ณ€ํ™”: ${metrics.memoryDeltaMB}MB`, metrics, }); return metrics; } catch (error) { const endTime = performance.now(); this.results.push({ testName: "๋ฉ”๋ชจ๋ฆฌ ๋ถ€ํ•˜ ๋ฐ ๊ฐ€๋น„์ง€ ์ปฌ๋ ‰์…˜", status: "failed", duration: endTime - startTime, details: `์˜ค๋ฅ˜ ๋ฐœ์ƒ: ${error}`, }); throw error; } } /** * ๐Ÿ”ฅ Test 5: API ์ŠคํŠธ๋ ˆ์Šค ๋ฐ ๋„คํŠธ์›Œํฌ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ */ static async testAPIStress() { console.log("๐Ÿ”ฅ ์ŠคํŠธ๋ ˆ์Šค ํ…Œ์ŠคํŠธ 5: API ์ŠคํŠธ๋ ˆ์Šค ๋ฐ ๋„คํŠธ์›Œํฌ ์‹œ๋ฎฌ๋ ˆ์ด์…˜"); const startTime = performance.now(); try { // ๋Œ€๋Ÿ‰์˜ API ์š”์ฒญ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ const apiCalls = 100; const batchSize = 10; console.log(`๐ŸŒ ${apiCalls}๊ฐœ์˜ API ํ˜ธ์ถœ์„ ${batchSize}๊ฐœ์”ฉ ๋ฐฐ์น˜๋กœ ์ฒ˜๋ฆฌ...`); let successfulCalls = 0; let failedCalls = 0; const responseTimes: number[] = []; // ๋ฐฐ์น˜๋ณ„๋กœ API ํ˜ธ์ถœ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ for (let batch = 0; batch < Math.ceil(apiCalls / batchSize); batch++) { const batchPromises = []; for (let i = 0; i < batchSize && batch * batchSize + i < apiCalls; i++) { const callIndex = batch * batchSize + i; // API ํ˜ธ์ถœ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ (์‹ค์ œ๋กœ๋Š” ํƒ€์ž… ์ฒ˜๋ฆฌ ๋กœ์ง) const apiCallSimulation = async () => { const callStart = performance.now(); try { // ๋ณต์žกํ•œ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ ์ƒ์„ฑ ๋ฐ ๊ฒ€์ฆ const components: ComponentData[] = Array.from( { length: 50 }, (_, idx) => ({ id: `stress-component-${callIndex}-${idx}`, type: "widget" as const, widgetType: "text" as WebType, position: { x: idx * 10, y: idx * 5 }, size: { width: 200, height: 40 }, label: `์ปดํฌ๋„ŒํŠธ ${idx}`, webTypeConfig: {}, }) as WidgetComponent, ); const complexScreenData: TestScreenDefinition = { screenId: callIndex, screenName: `์ŠคํŠธ๋ ˆ์Šค ํ…Œ์ŠคํŠธ ํ™”๋ฉด ${callIndex}`, screenCode: `STRESS_SCREEN_${callIndex}`, tableName: `stress_table_${callIndex}`, tableLabel: `์ŠคํŠธ๋ ˆ์Šค ํ…Œ์ด๋ธ” ${callIndex}`, companyCode: "COMPANY_1", description: `API ์ŠคํŠธ๋ ˆ์Šค ํ…Œ์ŠคํŠธ์šฉ ํ™”๋ฉด ${callIndex}`, isActive: Math.random() > 0.5 ? "Y" : "N", createdDate: new Date(), updatedDate: new Date(), layoutData: { screenId: callIndex, components, gridSettings: { enabled: true, size: 10, color: "#e0e0e0", opacity: 0.5, snapToGrid: true, }, }, }; // ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ ํƒ€์ž… ๊ฒ€์ฆ let validComponents = 0; if (complexScreenData.layoutData?.components) { for (const component of complexScreenData.layoutData.components) { if (isWidgetComponent(component)) { validComponents++; } } } const callEnd = performance.now(); const responseTime = callEnd - callStart; responseTimes.push(responseTime); const totalComponents = complexScreenData.layoutData?.components?.length || 0; if (validComponents === totalComponents) { successfulCalls++; return { success: true, responseTime, validComponents }; } else { failedCalls++; return { success: false, responseTime, validComponents }; } } catch (error) { const callEnd = performance.now(); const responseTime = callEnd - callStart; responseTimes.push(responseTime); failedCalls++; return { success: false, responseTime, error }; } }; batchPromises.push(apiCallSimulation()); } // ๋ฐฐ์น˜ ์™„๋ฃŒ ๋Œ€๊ธฐ await Promise.all(batchPromises); // ๋ฐฐ์น˜ ๊ฐ„ ์งง์€ ๋Œ€๊ธฐ (์‹ค์ œ ๋„คํŠธ์›Œํฌ ์ง€์—ฐ ์‹œ๋ฎฌ๋ ˆ์ด์…˜) await new Promise((resolve) => setTimeout(resolve, 10)); } const endTime = performance.now(); const duration = endTime - startTime; // ์‘๋‹ต ์‹œ๊ฐ„ ํ†ต๊ณ„ const avgResponseTime = responseTimes.reduce((a, b) => a + b, 0) / responseTimes.length; const maxResponseTime = Math.max(...responseTimes); const minResponseTime = Math.min(...responseTimes); const metrics = { totalAPICalls: apiCalls, successfulCalls, failedCalls, successRate: (successfulCalls / apiCalls) * 100, avgResponseTimeMs: Math.round(avgResponseTime * 100) / 100, maxResponseTimeMs: Math.round(maxResponseTime * 100) / 100, minResponseTimeMs: Math.round(minResponseTime * 100) / 100, totalDuration: duration, callsPerSecond: Math.round(apiCalls / (duration / 1000)), }; console.log("๐ŸŒ API ์ŠคํŠธ๋ ˆ์Šค ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ:", metrics); this.results.push({ testName: "API ์ŠคํŠธ๋ ˆ์Šค ๋ฐ ๋„คํŠธ์›Œํฌ ์‹œ๋ฎฌ๋ ˆ์ด์…˜", status: metrics.successRate > 95 ? "passed" : "failed", duration, details: `${metrics.successRate.toFixed(2)}% ์„ฑ๊ณต๋ฅ , ํ‰๊ท  ์‘๋‹ต์‹œ๊ฐ„: ${metrics.avgResponseTimeMs}ms`, metrics, }); return metrics; } catch (error) { const endTime = performance.now(); this.results.push({ testName: "API ์ŠคํŠธ๋ ˆ์Šค ๋ฐ ๋„คํŠธ์›Œํฌ ์‹œ๋ฎฌ๋ ˆ์ด์…˜", status: "failed", duration: endTime - startTime, details: `์˜ค๋ฅ˜ ๋ฐœ์ƒ: ${error}`, }); throw error; } } /** * ๐ŸŽฏ ๋ชจ๋“  ์ŠคํŠธ๋ ˆ์Šค ํ…Œ์ŠคํŠธ ์‹คํ–‰ */ static async runAllStressTests() { console.log("๐ŸŽฏ ์ŠคํŠธ๋ ˆ์Šค ํ…Œ์ŠคํŠธ ์Šค์œ„ํŠธ ์‹œ์ž‘"); console.log("โš ๏ธ ์‹œ์Šคํ…œ์— ๋†’์€ ๋ถ€ํ•˜๋ฅผ ๊ฐ€ํ•  ์˜ˆ์ •์ž…๋‹ˆ๋‹ค...\n"); const overallStart = performance.now(); this.results = []; // ๊ฒฐ๊ณผ ์ดˆ๊ธฐํ™” try { // 1. ๋Œ€๋Ÿ‰ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ console.log("=".repeat(60)); await this.testMassiveDataProcessing(); // 2. ํƒ€์ž… ์˜ค์—ผ ๋ฐ ์†์ƒ console.log("=".repeat(60)); await this.testTypeCorruption(); // 3. ๋™์‹œ ์ž‘์—… ๋ฐ ๊ฒฝํ•ฉ ์ƒํƒœ console.log("=".repeat(60)); await this.testConcurrentOperations(); // 4. ๋ฉ”๋ชจ๋ฆฌ ๋ถ€ํ•˜ console.log("=".repeat(60)); await this.testMemoryStress(); // 5. API ์ŠคํŠธ๋ ˆ์Šค console.log("=".repeat(60)); await this.testAPIStress(); const overallEnd = performance.now(); const totalDuration = overallEnd - overallStart; // ๊ฒฐ๊ณผ ๋ถ„์„ const passedTests = this.results.filter((r) => r.status === "passed").length; const failedTests = this.results.filter((r) => r.status === "failed").length; const warningTests = this.results.filter((r) => r.status === "warning").length; console.log("\n" + "=".repeat(60)); console.log("๐ŸŽ‰ ์ŠคํŠธ๋ ˆ์Šค ํ…Œ์ŠคํŠธ ์™„๋ฃŒ!"); console.log("=".repeat(60)); console.log(`๐Ÿ“Š ์ด ํ…Œ์ŠคํŠธ: ${this.results.length}`); console.log(`โœ… ํ†ต๊ณผ: ${passedTests}`); console.log(`โŒ ์‹คํŒจ: ${failedTests}`); console.log(`โš ๏ธ ๊ฒฝ๊ณ : ${warningTests}`); console.log(`โฑ๏ธ ์ด ์†Œ์š”์‹œ๊ฐ„: ${Math.round(totalDuration)}ms`); console.log(""); // ๊ฐœ๋ณ„ ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ ์ถœ๋ ฅ this.results.forEach((result, index) => { const statusIcon = result.status === "passed" ? "โœ…" : result.status === "failed" ? "โŒ" : "โš ๏ธ"; console.log(`${statusIcon} ${index + 1}. ${result.testName}`); console.log(` โ””โ”€ ${result.details} (${Math.round(result.duration)}ms)`); }); return { success: failedTests === 0, totalTests: this.results.length, passedTests, failedTests, warningTests, totalDuration, results: this.results, recommendation: this.generateRecommendations(), }; } catch (error) { console.error("โŒ ์ŠคํŠธ๋ ˆ์Šค ํ…Œ์ŠคํŠธ ์‹คํ–‰ ์ค‘ ์น˜๋ช…์  ์˜ค๋ฅ˜:", error); return { success: false, error: String(error), results: this.results, }; } } /** * ๐Ÿ“‹ ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ ๊ธฐ๋ฐ˜ ๊ถŒ์žฅ์‚ฌํ•ญ ์ƒ์„ฑ */ private static generateRecommendations(): string[] { const recommendations: string[] = []; this.results.forEach((result) => { if (result.status === "failed") { recommendations.push(`๐Ÿ”ง ${result.testName}: ์‹คํŒจ ์›์ธ์„ ๋ถ„์„ํ•˜๊ณ  ํƒ€์ž… ์‹œ์Šคํ…œ์„ ๊ฐ•ํ™”ํ•˜์„ธ์š”.`); } if (result.status === "warning") { recommendations.push(`โš ๏ธ ${result.testName}: ์ž ์žฌ์  ๋ฌธ์ œ๊ฐ€ ๊ฐ์ง€๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋ชจ๋‹ˆํ„ฐ๋ง์„ ๊ฐ•ํ™”ํ•˜์„ธ์š”.`); } if (result.metrics) { // ์„ฑ๋Šฅ ๊ธฐ๋ฐ˜ ๊ถŒ์žฅ์‚ฌํ•ญ if (result.metrics.operationsPerSecond && result.metrics.operationsPerSecond < 1000) { recommendations.push( `โšก ${result.testName}: ์„ฑ๋Šฅ ์ตœ์ ํ™”๋ฅผ ๊ณ ๋ คํ•˜์„ธ์š” (${result.metrics.operationsPerSecond} ops/sec).`, ); } if (result.metrics.memoryDeltaMB && result.metrics.memoryDeltaMB > 50) { recommendations.push( `๐Ÿง  ${result.testName}: ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰ ์ตœ์ ํ™”๋ฅผ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค (${result.metrics.memoryDeltaMB}MB ์ฆ๊ฐ€).`, ); } if (result.metrics.successRate && result.metrics.successRate < 99) { recommendations.push( `๐ŸŽฏ ${result.testName}: ์„ฑ๊ณต๋ฅ  ๊ฐœ์„ ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค (${result.metrics.successRate.toFixed(2)}%).`, ); } } }); if (recommendations.length === 0) { recommendations.push("๐ŸŽ‰ ๋ชจ๋“  ์ŠคํŠธ๋ ˆ์Šค ํ…Œ์ŠคํŠธ๋ฅผ ์„ฑ๊ณต์ ์œผ๋กœ ํ†ต๊ณผํ–ˆ์Šต๋‹ˆ๋‹ค! ํƒ€์ž… ์‹œ์Šคํ…œ์ด ๋งค์šฐ ๊ฒฌ๊ณ ํ•ฉ๋‹ˆ๋‹ค."); } return recommendations; } /** * ๐Ÿ“Š ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ ๋ฐ˜ํ™˜ (์™ธ๋ถ€์—์„œ ์ ‘๊ทผ ๊ฐ€๋Šฅ) */ static getResults() { return this.results; } } export default StressTestSuite;