396 lines
12 KiB
TypeScript
396 lines
12 KiB
TypeScript
/**
|
|
* 🔥 버튼 제어관리 성능 테스트
|
|
*
|
|
* 목표 성능:
|
|
* - 즉시 응답: 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<T>(name: string, fn: () => Promise<T>): Promise<T> {
|
|
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`);
|
|
});
|
|
}
|
|
}
|