/** * ๐Ÿ”ฅ ๋ฒ„ํŠผ ์ œ์–ด๊ด€๋ฆฌ ์„ฑ๋Šฅ ํ…Œ์ŠคํŠธ * * ๋ชฉํ‘œ ์„ฑ๋Šฅ: * - ์ฆ‰์‹œ ์‘๋‹ต: 50-200ms * - ์บ์‹œ ํžˆํŠธ: 1-10ms * - ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—…: ์‚ฌ์šฉ์ž ์ฒด๊ฐ ์—†์Œ */ import { optimizedButtonDataflowService } from "../optimizedButtonDataflowService"; import { dataflowConfigCache } from "../dataflowCache"; import { dataflowJobQueue } from "../dataflowJobQueue"; import { ButtonActionType, ButtonTypeConfig } from "@/types/screen"; // Mock API client jest.mock("@/lib/api/client", () => ({ apiClient: { get: jest.fn(), post: jest.fn(), put: jest.fn(), }, })); describe("๐Ÿ”ฅ Button Dataflow Performance Tests", () => { beforeEach(() => { // ์บ์‹œ ์ดˆ๊ธฐํ™” dataflowConfigCache.clearAllCache(); dataflowJobQueue.clearQueue(); }); describe("๐Ÿ“Š Cache Performance", () => { it("should load config from server on first request", async () => { const startTime = performance.now(); const config = await dataflowConfigCache.getConfig("test-button-1"); const endTime = performance.now(); const responseTime = endTime - startTime; // ์ฒซ ๋ฒˆ์งธ ์š”์ฒญ์€ ์„œ๋ฒ„ ๋กœ๋”ฉ์œผ๋กœ ์ธํ•ด ๋” ์˜ค๋ž˜ ๊ฑธ๋ฆด ์ˆ˜ ์žˆ์Œ expect(responseTime).toBeLessThan(1000); // 1์ดˆ ์ด๋‚ด const metrics = dataflowConfigCache.getMetrics(); expect(metrics.totalRequests).toBe(1); expect(metrics.cacheMisses).toBe(1); }); it("should return cached config in under 10ms", async () => { // ๋จผ์ € ์บ์‹œ์— ๋กœ๋“œ await dataflowConfigCache.getConfig("test-button-2"); // ๋‘ ๋ฒˆ์งธ ์š”์ฒญ ์‹œ๊ฐ„ ์ธก์ • const startTime = performance.now(); const config = await dataflowConfigCache.getConfig("test-button-2"); const endTime = performance.now(); const responseTime = endTime - startTime; // ๐Ÿ”ฅ ์บ์‹œ ํžˆํŠธ๋Š” 10ms ์ด๋‚ด์—ฌ์•ผ ํ•จ expect(responseTime).toBeLessThan(10); const metrics = dataflowConfigCache.getMetrics(); expect(metrics.cacheHits).toBeGreaterThan(0); expect(metrics.hitRate).toBeGreaterThan(0); }); it("should maintain cache performance under load", async () => { const buttonIds = Array.from({ length: 50 }, (_, i) => `test-button-${i}`); // ์ฒซ ๋ฒˆ์งธ ๋กœ๋“œ (์บ์‹œ ์ฑ„์šฐ๊ธฐ) await Promise.all(buttonIds.map((id) => dataflowConfigCache.getConfig(id))); // ์บ์‹œ๋œ ๋ฐ์ดํ„ฐ ์„ฑ๋Šฅ ํ…Œ์ŠคํŠธ const startTime = performance.now(); await Promise.all(buttonIds.map((id) => dataflowConfigCache.getConfig(id))); const endTime = performance.now(); const totalTime = endTime - startTime; const averageTime = totalTime / buttonIds.length; // ๐Ÿ”ฅ ํ‰๊ท  ์บ์‹œ ์‘๋‹ต ์‹œ๊ฐ„ 5ms ์ด๋‚ด expect(averageTime).toBeLessThan(5); const metrics = dataflowConfigCache.getMetrics(); expect(metrics.hitRate).toBeGreaterThan(80); // 80% ์ด์ƒ ํžˆํŠธ์œจ }); }); describe("โšก Button Execution Performance", () => { const mockButtonConfig: ButtonTypeConfig = { actionType: "save" as ButtonActionType, enableDataflowControl: true, dataflowTiming: "after", dataflowConfig: { controlMode: "simple", selectedDiagramId: 1, selectedRelationshipId: "rel-123", }, }; it("should execute button action in under 200ms", async () => { const startTime = performance.now(); const result = await optimizedButtonDataflowService.executeButtonWithDataflow( "test-button-3", "save", mockButtonConfig, { testData: "value" }, "DEFAULT", ); const endTime = performance.now(); const responseTime = endTime - startTime; // ๐Ÿ”ฅ ์ฆ‰์‹œ ์‘๋‹ต ๋ชฉํ‘œ: 200ms ์ด๋‚ด expect(responseTime).toBeLessThan(200); expect(result.jobId).toBeDefined(); }); it("should handle after timing with immediate response", async () => { const config = { ...mockButtonConfig, dataflowTiming: "after" as const }; const startTime = performance.now(); const result = await optimizedButtonDataflowService.executeButtonWithDataflow( "test-button-4", "save", config, { testData: "value" }, "DEFAULT", ); const endTime = performance.now(); const responseTime = endTime - startTime; // After ํƒ€์ด๋ฐ์€ ๊ธฐ์กด ์•ก์…˜ ์ฆ‰์‹œ ์‹คํ–‰์ด๋ฏ€๋กœ ๋น ๋ฆ„ expect(responseTime).toBeLessThan(150); expect(result.immediateResult).toBeDefined(); expect(result.timing).toBe("after"); }); it("should handle simple validation quickly", async () => { const config = { ...mockButtonConfig, dataflowTiming: "before" as const, dataflowConfig: { controlMode: "advanced" as const, directControl: { sourceTable: "test_table", triggerType: "insert" as const, conditions: [ { id: "cond1", type: "condition" as const, field: "status", operator: "=" as const, value: "active", }, ], actions: [], }, }, }; const startTime = performance.now(); const result = await optimizedButtonDataflowService.executeButtonWithDataflow( "test-button-5", "save", config, { status: "active" }, "DEFAULT", ); const endTime = performance.now(); const responseTime = endTime - startTime; // ๐Ÿ”ฅ ๊ฐ„๋‹จํ•œ ๊ฒ€์ฆ์€ 50ms ์ด๋‚ด expect(responseTime).toBeLessThan(50); expect(result.immediateResult).toBeDefined(); }); }); describe("๐Ÿš€ Job Queue Performance", () => { it("should enqueue jobs instantly", () => { const startTime = performance.now(); const jobId = dataflowJobQueue.enqueue( "test-button-6", "save", mockButtonConfig, { testData: "value" }, "DEFAULT", "normal", ); const endTime = performance.now(); const responseTime = endTime - startTime; // ๐Ÿ”ฅ ํ์ž‰์€ ์ฆ‰์‹œ (5ms ์ด๋‚ด) expect(responseTime).toBeLessThan(5); expect(jobId).toBeDefined(); expect(jobId).toMatch(/^job_/); }); it("should handle multiple concurrent jobs", () => { const jobCount = 20; const startTime = performance.now(); const jobIds = Array.from({ length: jobCount }, (_, i) => dataflowJobQueue.enqueue( `test-button-${i}`, "save", mockButtonConfig, { testData: `value-${i}` }, "DEFAULT", "normal", ), ); const endTime = performance.now(); const totalTime = endTime - startTime; const averageTime = totalTime / jobCount; // ๐Ÿ”ฅ ํ‰๊ท  ํ์ž‰ ์‹œ๊ฐ„ 1ms ์ด๋‚ด expect(averageTime).toBeLessThan(1); expect(jobIds).toHaveLength(jobCount); const metrics = dataflowJobQueue.getMetrics(); expect(metrics.totalJobs).toBe(jobCount); }); it("should prioritize high priority jobs", () => { // ์ผ๋ฐ˜ ์šฐ์„ ์ˆœ์œ„ ์ž‘์—…๋“ค ์ถ”๊ฐ€ const normalJobs = Array.from({ length: 5 }, (_, i) => dataflowJobQueue.enqueue(`normal-button-${i}`, "save", mockButtonConfig, {}, "DEFAULT", "normal"), ); // ๋†’์€ ์šฐ์„ ์ˆœ์œ„ ์ž‘์—… ์ถ”๊ฐ€ const highJob = dataflowJobQueue.enqueue("high-priority-button", "save", mockButtonConfig, {}, "DEFAULT", "high"); const queueInfo = dataflowJobQueue.getQueueInfo(); // ๋†’์€ ์šฐ์„ ์ˆœ์œ„ ์ž‘์—…์ด ํ์˜ ๋งจ ์•ž์— ์žˆ์–ด์•ผ ํ•จ expect(queueInfo.pending[0].id).toBe(highJob); expect(queueInfo.pending[0].priority).toBe("high"); }); }); describe("๐Ÿ“ˆ Performance Metrics", () => { it("should track cache metrics accurately", async () => { // ์บ์‹œ ๋ฏธ์Šค ๋ฐœ์ƒ await dataflowConfigCache.getConfig("metrics-test-1"); await dataflowConfigCache.getConfig("metrics-test-2"); // ์บ์‹œ ํžˆํŠธ ๋ฐœ์ƒ await dataflowConfigCache.getConfig("metrics-test-1"); await dataflowConfigCache.getConfig("metrics-test-1"); const metrics = dataflowConfigCache.getMetrics(); expect(metrics.totalRequests).toBe(4); expect(metrics.cacheHits).toBe(2); expect(metrics.cacheMisses).toBe(2); expect(metrics.hitRate).toBe(50); expect(metrics.averageResponseTime).toBeGreaterThan(0); }); it("should track queue metrics accurately", () => { // ์ž‘์—… ์ถ”๊ฐ€ dataflowJobQueue.enqueue("metrics-button-1", "save", mockButtonConfig, {}, "DEFAULT"); dataflowJobQueue.enqueue("metrics-button-2", "delete", mockButtonConfig, {}, "DEFAULT"); const metrics = dataflowJobQueue.getMetrics(); expect(metrics.totalJobs).toBe(2); expect(metrics.pendingJobs).toBe(2); expect(metrics.processingJobs).toBe(0); }); it("should provide performance recommendations", () => { // ๋А๋ฆฐ ์‘๋‹ต ์‹œ๋ฎฌ๋ ˆ์ด์…˜ const slowCache = dataflowConfigCache as any; slowCache.metrics.averageResponseTime = 500; // 500ms const metrics = dataflowConfigCache.getMetrics(); expect(metrics.averageResponseTime).toBe(500); // ์„ฑ๋Šฅ ๊ฐœ์„  ๊ถŒ์žฅ์‚ฌํ•ญ ํ™•์ธ (์‹ค์ œ ๊ตฌํ˜„์—์„œ) // expect(recommendations).toContain('์บ์‹ฑ ์„ค์ •์„ ํ™•์ธํ•˜์„ธ์š”'); }); }); describe("๐Ÿ”ง Integration Performance", () => { it("should maintain performance under realistic load", async () => { const testScenarios = [ { timing: "after", count: 10 }, { timing: "before", count: 5 }, { timing: "replace", count: 3 }, ]; const startTime = performance.now(); for (const scenario of testScenarios) { const promises = Array.from({ length: scenario.count }, (_, i) => optimizedButtonDataflowService.executeButtonWithDataflow( `load-test-${scenario.timing}-${i}`, "save", { ...mockButtonConfig, dataflowTiming: scenario.timing as any }, { testData: `value-${i}` }, "DEFAULT", ), ); await Promise.all(promises); } const endTime = performance.now(); const totalTime = endTime - startTime; const totalRequests = testScenarios.reduce((sum, s) => sum + s.count, 0); const averageTime = totalTime / totalRequests; // ๐Ÿ”ฅ ์‹ค์ œ ํ™˜๊ฒฝ์—์„œ ํ‰๊ท  ์‘๋‹ต ์‹œ๊ฐ„ 300ms ์ด๋‚ด expect(averageTime).toBeLessThan(300); console.log(`Performance Test Results:`); console.log(` Total requests: ${totalRequests}`); console.log(` Total time: ${totalTime.toFixed(2)}ms`); console.log(` Average time: ${averageTime.toFixed(2)}ms`); }); }); }); // ๐Ÿ”ฅ ์„ฑ๋Šฅ ๋ฒค์น˜๋งˆํฌ ์œ ํ‹ธ๋ฆฌํ‹ฐ export class PerformanceBenchmark { private results: Array<{ name: string; time: number; success: boolean; }> = []; async measure(name: string, fn: () => Promise): Promise { const startTime = performance.now(); let success = true; let result: T; try { result = await fn(); } catch (error) { success = false; throw error; } finally { const endTime = performance.now(); this.results.push({ name, time: endTime - startTime, success, }); } return result!; } getResults() { return { total: this.results.length, successful: this.results.filter((r) => r.success).length, failed: this.results.filter((r) => r.success === false).length, averageTime: this.results.reduce((sum, r) => sum + r.time, 0) / this.results.length, fastest: Math.min(...this.results.map((r) => r.time)), slowest: Math.max(...this.results.map((r) => r.time)), details: this.results, }; } printReport() { const results = this.getResults(); console.log("\n๐Ÿ”ฅ Performance Benchmark Report"); console.log("================================"); console.log(`Total tests: ${results.total}`); console.log(`Successful: ${results.successful} (${((results.successful / results.total) * 100).toFixed(1)}%)`); console.log(`Failed: ${results.failed}`); console.log(`Average time: ${results.averageTime.toFixed(2)}ms`); console.log(`Fastest: ${results.fastest.toFixed(2)}ms`); console.log(`Slowest: ${results.slowest.toFixed(2)}ms`); console.log("\nDetailed Results:"); results.details.forEach((r) => { const status = r.success ? "โœ…" : "โŒ"; console.log(` ${status} ${r.name}: ${r.time.toFixed(2)}ms`); }); } }