ERP-node/frontend/test-scenarios/type-safety-tests.ts

542 lines
17 KiB
TypeScript

/**
* 🧪 타입 안전성 종합 테스트 시나리오
*
* 화면관리, 제어관리, 테이블 타입관리 시스템의 타입 안전성을 검증합니다.
* 실제 사용자 시나리오에서 발생할 수 있는 모든 타입 오류 상황을 테스트합니다.
*/
import {
ComponentData,
WebType,
ButtonActionType,
WidgetComponent,
ContainerComponent,
// 타입 가드 함수들
isWidgetComponent,
isContainerComponent,
isWebType,
isButtonActionType,
// 안전한 캐스팅 함수들
asWidgetComponent,
asContainerComponent,
// 변환 함수들
ynToBoolean,
booleanToYN,
// 테이블 관련
UnifiedColumnInfo,
ColumnTypeInfo,
// 제어 관련
ExtendedButtonTypeConfig,
// 화면 관련
ScreenDefinition,
GroupState,
} from "@/types";
// ===== 1단계: 기본 타입 검증 테스트 =====
export class TypeSafetyTestSuite {
/**
* 🧪 Test 1: WebType 타입 안전성 검증
*/
static testWebTypeValidation() {
console.log("🧪 Test 1: WebType 타입 안전성 검증");
// 유효한 WebType들
const validWebTypes = [
"text",
"number",
"decimal",
"date",
"datetime",
"select",
"dropdown",
"radio",
"checkbox",
"boolean",
"textarea",
"code",
"entity",
"file",
"email",
"tel",
"url",
"button",
];
// 무효한 타입들 (기존 시스템에서 문제가 되었던 것들)
const invalidWebTypes = [
"text_area", // 기존에 사용되던 잘못된 타입
"VARCHAR", // DB 타입과 혼동
"submit", // ButtonActionType과 혼동
"container", // ComponentType과 혼동
"",
null,
undefined,
];
validWebTypes.forEach((type) => {
const isValid = isWebType(type);
console.assert(isValid, `유효한 WebType이 거부됨: ${type}`);
if (isValid) {
console.log(`✅ Valid WebType: ${type}`);
}
});
invalidWebTypes.forEach((type) => {
const isValid = isWebType(type as any);
console.assert(!isValid, `무효한 WebType이 허용됨: ${type}`);
if (!isValid) {
console.log(`❌ Invalid WebType rejected: ${type}`);
}
});
}
/**
* 🧪 Test 2: ComponentData 타입 가드 안전성
*/
static testComponentTypeGuards() {
console.log("\n🧪 Test 2: ComponentData 타입 가드 안전성");
// 올바른 컴포넌트 생성
const widgetComponent: WidgetComponent = {
id: "widget-1",
type: "widget",
position: { x: 0, y: 0 },
size: { width: 200, height: 40 },
widgetType: "text",
label: "테스트 텍스트",
placeholder: "입력하세요",
required: false,
readonly: false,
webTypeConfig: {},
};
const containerComponent: ContainerComponent = {
id: "container-1",
type: "container",
position: { x: 0, y: 0 },
size: { width: 400, height: 300 },
label: "컨테이너",
children: [],
layoutDirection: "vertical",
};
// 타입 가드 테스트
console.assert(isWidgetComponent(widgetComponent), "WidgetComponent 타입 가드 실패");
console.assert(isContainerComponent(containerComponent), "ContainerComponent 타입 가드 실패");
console.assert(!isWidgetComponent(containerComponent), "잘못된 타입이 통과됨");
console.assert(!isContainerComponent(widgetComponent), "잘못된 타입이 통과됨");
// 안전한 캐스팅 테스트
const safeWidget = asWidgetComponent(widgetComponent);
const safeContainer = asContainerComponent(containerComponent);
console.assert(safeWidget !== null, "안전한 위젯 캐스팅 실패");
console.assert(safeContainer !== null, "안전한 컨테이너 캐스팅 실패");
console.assert(asWidgetComponent(containerComponent) === null, "잘못된 캐스팅이 허용됨");
console.log("✅ Component 타입 가드 모든 테스트 통과");
}
/**
* 🧪 Test 3: DB 호환성 (Y/N ↔ boolean) 변환 테스트
*/
static testYNBooleanConversion() {
console.log("\n🧪 Test 3: DB 호환성 Y/N ↔ boolean 변환 테스트");
// Y/N → boolean 변환
console.assert(ynToBoolean("Y") === true, "Y → true 변환 실패");
console.assert(ynToBoolean("N") === false, "N → false 변환 실패");
console.assert(ynToBoolean("") === false, "빈 문자열 → false 변환 실패");
console.assert(ynToBoolean(undefined) === false, "undefined → false 변환 실패");
// boolean → Y/N 변환
console.assert(booleanToYN(true) === "Y", "true → Y 변환 실패");
console.assert(booleanToYN(false) === "N", "false → N 변환 실패");
// 실제 DB 시나리오 시뮬레이션
const dbColumnData = {
isActive: "Y",
isVisible: "N",
isPrimaryKey: "Y",
isNullable: "N",
};
const convertedData = {
isActive: ynToBoolean(dbColumnData.isActive),
isVisible: ynToBoolean(dbColumnData.isVisible),
isPrimaryKey: ynToBoolean(dbColumnData.isPrimaryKey),
isNullable: ynToBoolean(dbColumnData.isNullable),
};
console.assert(convertedData.isActive === true, "DB isActive 변환 실패");
console.assert(convertedData.isVisible === false, "DB isVisible 변환 실패");
console.log("✅ Y/N ↔ boolean 변환 모든 테스트 통과");
}
/**
* 🧪 Test 4: 실제 폼 저장 시나리오 시뮬레이션
*/
static async testFormSaveScenarios() {
console.log("\n🧪 Test 4: 실제 폼 저장 시나리오 시뮬레이션");
// 시나리오 1: 혼합 웹타입 폼 데이터
const formData = {
userName: "홍길동",
userAge: 25,
userEmail: "hong@example.com",
isActive: true,
birthDate: "1999-01-01",
userRole: "admin",
description: "테스트 사용자입니다.",
};
// 시나리오 2: 컴포넌트별 웹타입 매핑
const formComponents: ComponentData[] = [
{
id: "userName",
type: "widget",
widgetType: "text",
position: { x: 0, y: 0 },
size: { width: 200, height: 40 },
label: "사용자명",
columnName: "user_name",
webTypeConfig: {},
} as WidgetComponent,
{
id: "userAge",
type: "widget",
widgetType: "number",
position: { x: 0, y: 50 },
size: { width: 200, height: 40 },
label: "나이",
columnName: "user_age",
webTypeConfig: { min: 0, max: 120 },
} as WidgetComponent,
{
id: "isActive",
type: "widget",
widgetType: "checkbox",
position: { x: 0, y: 100 },
size: { width: 200, height: 40 },
label: "활성화",
columnName: "is_active",
webTypeConfig: {},
} as WidgetComponent,
];
// 타입 안전한 데이터 처리
const processedData: Record<string, any> = {};
for (const component of formComponents) {
if (isWidgetComponent(component)) {
const { columnName, widgetType } = component;
if (columnName && widgetType && formData.hasOwnProperty(component.id)) {
const rawValue = formData[component.id as keyof typeof formData];
// 웹타입별 안전한 변환
switch (widgetType) {
case "text":
case "email":
case "textarea":
processedData[columnName] = String(rawValue);
break;
case "number":
case "decimal":
processedData[columnName] = Number(rawValue);
break;
case "checkbox":
case "boolean":
processedData[columnName] = booleanToYN(Boolean(rawValue));
break;
case "date":
case "datetime":
processedData[columnName] = rawValue ? String(rawValue) : null;
break;
default:
console.warn(`처리되지 않은 웹타입: ${widgetType}`);
processedData[columnName] = rawValue;
}
}
}
}
console.log("✅ 폼 데이터 타입 안전 변환:", processedData);
// 검증: 모든 값이 올바른 타입으로 변환되었는지 확인
console.assert(typeof processedData.user_name === "string", "사용자명 타입 변환 실패");
console.assert(typeof processedData.user_age === "number", "나이 타입 변환 실패");
console.assert(processedData.is_active === "Y" || processedData.is_active === "N", "활성화 상태 변환 실패");
}
/**
* 🧪 Test 5: 버튼 제어관리 타입 안전성 테스트
*/
static testButtonControlTypesSafety() {
console.log("\n🧪 Test 5: 버튼 제어관리 타입 안전성 테스트");
// ButtonActionType 안전성 검증
const validActions: ButtonActionType[] = [
"save",
"cancel",
"delete",
"edit",
"add",
"search",
"reset",
"submit",
"close",
"popup",
"modal",
"navigate",
"control",
];
validActions.forEach((action) => {
console.assert(isButtonActionType(action), `유효한 ButtonActionType 거부: ${action}`);
});
// 무효한 액션 타입들
const invalidActions = ["insert", "update", "remove", ""];
invalidActions.forEach((action) => {
console.assert(!isButtonActionType(action), `무효한 ButtonActionType 허용: ${action}`);
});
console.log("✅ 버튼 제어관리 타입 안전성 테스트 통과");
}
/**
* 🧪 Test 6: 테이블 컬럼 정보 타입 호환성 테스트
*/
static testTableColumnTypeCompatibility() {
console.log("\n🧪 Test 6: 테이블 컬럼 정보 타입 호환성 테스트");
// 백엔드에서 받은 원시 컬럼 정보 (ColumnTypeInfo)
const backendColumnInfo: ColumnTypeInfo = {
columnName: "user_name",
displayName: "사용자명",
dataType: "varchar",
dbType: "character varying(100)",
webType: "text", // string 타입 (백엔드)
inputType: "direct",
detailSettings: JSON.stringify({ maxLength: 100 }),
description: "사용자의 이름을 저장하는 컬럼",
isNullable: "N", // Y/N 문자열
isPrimaryKey: false,
defaultValue: "",
maxLength: 100,
};
// 프론트엔드 통합 컬럼 정보로 변환 (UnifiedColumnInfo)
const unifiedColumnInfo: UnifiedColumnInfo = {
columnName: backendColumnInfo.columnName,
displayName: backendColumnInfo.displayName,
dataType: backendColumnInfo.dataType,
dbType: backendColumnInfo.dbType,
webType: isWebType(backendColumnInfo.webType) ? (backendColumnInfo.webType as WebType) : "text", // 안전한 타입 변환
inputType: backendColumnInfo.inputType,
detailSettings: JSON.parse(backendColumnInfo.detailSettings || "{}"),
description: backendColumnInfo.description,
isNullable: ynToBoolean(backendColumnInfo.isNullable), // Y/N → boolean
isPrimaryKey: backendColumnInfo.isPrimaryKey,
defaultValue: backendColumnInfo.defaultValue,
maxLength: backendColumnInfo.maxLength,
companyCode: backendColumnInfo.companyCode,
};
// 검증
console.assert(isWebType(unifiedColumnInfo.webType), "WebType 변환 실패");
console.assert(typeof unifiedColumnInfo.isNullable === "boolean", "isNullable 타입 변환 실패");
console.assert(typeof unifiedColumnInfo.detailSettings === "object", "detailSettings JSON 파싱 실패");
console.log("✅ 테이블 컬럼 타입 호환성 테스트 통과");
console.log("변환된 컬럼 정보:", unifiedColumnInfo);
}
/**
* 🧪 Test 7: 복합 시나리오 - 화면 설계 + 데이터 저장 + 제어 실행
*/
static async testComplexScenario() {
console.log("\n🧪 Test 7: 복합 시나리오 - 화면 설계 + 데이터 저장 + 제어 실행");
try {
// Step 1: 화면 정의 생성 (단순화된 버전)
const screenDefinition: ScreenDefinition = {
screenId: 1001,
screenName: "사용자 관리",
screenCode: "USER_MANAGEMENT",
tableName: "user_info",
tableLabel: "사용자 정보",
description: "사용자 정보를 관리하는 화면",
isActive: "Y",
};
// 개별 컴포넌트 생성
const components: ComponentData[] = [
{
id: "userName",
type: "widget",
widgetType: "text",
position: { x: 10, y: 10 },
size: { width: 200, height: 40 },
label: "사용자명",
columnName: "user_name",
required: true,
webTypeConfig: { maxLength: 50 },
} as WidgetComponent,
];
// Step 2: 컴포넌트 타입 안전성 검증
components.forEach((component) => {
if (isWidgetComponent(component)) {
console.assert(isWebType(component.widgetType), `잘못된 위젯타입: ${component.widgetType}`);
}
});
// Step 3: 그룹 상태 시뮬레이션
const groupState: GroupState = {
isGrouping: true,
selectedComponents: ["userName", "saveButton"],
groupTarget: "userForm",
groupMode: "create",
groupTitle: "사용자 입력 폼",
};
// Step 4: 실제 저장 시뮬레이션
const formData = {
userName: "테스트 사용자",
userEmail: "test@example.com",
isActive: true,
};
console.log("✅ 복합 시나리오 모든 단계 성공");
console.log("- 화면 정의 생성: ✓");
console.log("- 컴포넌트 타입 검증: ✓");
console.log("- 그룹 상태 관리: ✓");
console.log("- 데이터 저장 시뮬레이션: ✓");
} catch (error) {
console.error("❌ 복합 시나리오 실패:", error);
throw error;
}
}
/**
* 🎯 모든 테스트 실행
*/
static async runAllTests() {
console.log("🎯 타입 안전성 종합 테스트 시작\n");
try {
this.testWebTypeValidation();
this.testComponentTypeGuards();
this.testYNBooleanConversion();
await this.testFormSaveScenarios();
this.testButtonControlTypesSafety();
this.testTableColumnTypeCompatibility();
await this.testComplexScenario();
console.log("\n🎉 모든 타입 안전성 테스트 통과!");
console.log("✅ 화면관리, 제어관리, 테이블타입관리 시스템의 타입 안전성이 보장됩니다.");
return {
success: true,
passedTests: 7,
failedTests: 0,
message: "모든 타입 안전성 테스트 통과",
};
} catch (error) {
console.error("❌ 타입 안전성 테스트 실패:", error);
return {
success: false,
passedTests: 0,
failedTests: 1,
message: `테스트 실패: ${error}`,
};
}
}
}
// 🔥 스트레스 테스트 시나리오
export class StressTestScenarios {
/**
* 🔥 극한 상황 테스트: 잘못된 타입들의 혼재
*/
static testMixedInvalidTypes() {
console.log("🔥 극한 상황 테스트: 잘못된 타입들의 혼재");
// API로부터 받을 수 있는 다양한 잘못된 데이터들
const corruptedData = [
{ webType: "text_area", expected: false }, // 기존 잘못된 타입
{ webType: "VARCHAR(255)", expected: false }, // DB 타입 혼입
{ webType: "submit", expected: false }, // ButtonActionType 혼입
{ webType: "", expected: false }, // 빈 문자열
{ webType: null, expected: false }, // null
{ webType: undefined, expected: false }, // undefined
{ webType: 123, expected: false }, // 숫자
{ webType: {}, expected: false }, // 객체
{ webType: "text", expected: true }, // 올바른 타입
];
corruptedData.forEach(({ webType, expected }) => {
const result = isWebType(webType as any);
console.assert(
result === expected,
`타입 검증 실패: ${JSON.stringify(webType)} → expected: ${expected}, got: ${result}`,
);
});
console.log("✅ 극한 상황 타입 검증 테스트 통과");
}
/**
* 🔥 대량 데이터 처리 시나리오
*/
static testBulkDataProcessing() {
console.log("🔥 대량 데이터 처리 시나리오");
// 1000개의 컴포넌트 생성 및 타입 검증
const components: ComponentData[] = [];
const webTypes: WebType[] = ["text", "number", "date", "select", "checkbox"];
for (let i = 0; i < 1000; i++) {
const randomWebType = webTypes[i % webTypes.length];
const component: WidgetComponent = {
id: `widget-${i}`,
type: "widget",
widgetType: randomWebType,
position: { x: i % 100, y: Math.floor(i / 100) * 50 },
size: { width: 200, height: 40 },
label: `Component ${i}`,
webTypeConfig: {},
};
components.push(component);
}
// 모든 컴포넌트 타입 검증
let validCount = 0;
components.forEach((component) => {
if (isWidgetComponent(component) && isWebType(component.widgetType)) {
validCount++;
}
});
console.assert(validCount === 1000, `대량 데이터 검증 실패: ${validCount}/1000`);
console.log(`✅ 대량 데이터 처리 성공: ${validCount}/1000 컴포넌트 검증 완료`);
}
}
// Export for use in tests
export default TypeSafetyTestSuite;