421 lines
13 KiB
TypeScript
421 lines
13 KiB
TypeScript
/**
|
|
* 🌐 API 연동 타입 안전성 테스트
|
|
*
|
|
* 실제 백엔드 API와 연동하여 타입 안전성을 검증합니다.
|
|
*/
|
|
|
|
import { apiClient } from "@/lib/api/client";
|
|
import {
|
|
ComponentData,
|
|
WidgetComponent,
|
|
ScreenDefinition,
|
|
LayoutData,
|
|
TableInfo,
|
|
UnifiedColumnInfo,
|
|
ColumnTypeInfo,
|
|
ButtonActionType,
|
|
WebType,
|
|
isWebType,
|
|
isButtonActionType,
|
|
ynToBoolean,
|
|
booleanToYN,
|
|
} from "@/types";
|
|
|
|
export class APIIntegrationTestSuite {
|
|
/**
|
|
* 🧪 Test 1: 테이블 정보 API 타입 안전성
|
|
*/
|
|
static async testTableInfoAPI() {
|
|
console.log("🧪 API Test 1: 테이블 정보 API 타입 안전성");
|
|
|
|
try {
|
|
// 실제 API 호출
|
|
const response = await apiClient.get("/api/admin/table-management/tables", {
|
|
params: { companyCode: "COMPANY_1" },
|
|
});
|
|
|
|
if (response.data && Array.isArray(response.data)) {
|
|
const tables = response.data as TableInfo[];
|
|
|
|
tables.forEach((table, index) => {
|
|
// 필수 필드 검증
|
|
console.assert(typeof table.tableName === "string", `테이블 ${index}: tableName이 문자열이 아님`);
|
|
console.assert(typeof table.tableLabel === "string", `테이블 ${index}: tableLabel이 문자열이 아님`);
|
|
|
|
if (table.columns && Array.isArray(table.columns)) {
|
|
table.columns.forEach((column, colIndex) => {
|
|
// 컬럼 타입 검증
|
|
console.assert(
|
|
typeof column.columnName === "string",
|
|
`테이블 ${index}, 컬럼 ${colIndex}: columnName이 문자열이 아님`,
|
|
);
|
|
|
|
// WebType 안전성 검증
|
|
if (column.webType) {
|
|
const isValidWebType = isWebType(column.webType);
|
|
if (!isValidWebType) {
|
|
console.warn(
|
|
`테이블 ${table.tableName}, 컬럼 ${column.columnName}: 유효하지 않은 webType: ${column.webType}`,
|
|
);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
console.log(`✅ 테이블 정보 API: ${tables.length}개 테이블 검증 완료`);
|
|
return true;
|
|
}
|
|
} catch (error) {
|
|
console.error("❌ 테이블 정보 API 테스트 실패:", error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 🧪 Test 2: 컬럼 타입 정보 API 호환성
|
|
*/
|
|
static async testColumnTypeAPI() {
|
|
console.log("🧪 API Test 2: 컬럼 타입 정보 API 호환성");
|
|
|
|
try {
|
|
const response = await apiClient.get("/api/admin/table-management/columns", {
|
|
params: {
|
|
tableName: "user_info",
|
|
companyCode: "COMPANY_1",
|
|
},
|
|
});
|
|
|
|
if (response.data && Array.isArray(response.data)) {
|
|
const columns = response.data as ColumnTypeInfo[];
|
|
|
|
// 백엔드 타입을 프론트엔드 통합 타입으로 변환 테스트
|
|
const unifiedColumns: UnifiedColumnInfo[] = columns.map((col) => ({
|
|
columnName: col.columnName,
|
|
displayName: col.displayName,
|
|
dataType: col.dataType,
|
|
dbType: col.dbType,
|
|
webType: isWebType(col.webType) ? (col.webType as WebType) : "text",
|
|
inputType: col.inputType || "direct",
|
|
detailSettings: col.detailSettings ? JSON.parse(col.detailSettings) : {},
|
|
description: col.description || "",
|
|
isNullable: ynToBoolean(col.isNullable),
|
|
isPrimaryKey: col.isPrimaryKey,
|
|
defaultValue: col.defaultValue,
|
|
maxLength: col.maxLength,
|
|
companyCode: col.companyCode,
|
|
}));
|
|
|
|
// 변환 검증
|
|
unifiedColumns.forEach((unifiedCol, index) => {
|
|
const originalCol = columns[index];
|
|
|
|
// WebType 변환 검증
|
|
console.assert(isWebType(unifiedCol.webType), `컬럼 ${unifiedCol.columnName}: WebType 변환 실패`);
|
|
|
|
// Y/N → boolean 변환 검증
|
|
console.assert(
|
|
typeof unifiedCol.isNullable === "boolean",
|
|
`컬럼 ${unifiedCol.columnName}: isNullable boolean 변환 실패`,
|
|
);
|
|
|
|
// JSON 파싱 검증
|
|
console.assert(
|
|
typeof unifiedCol.detailSettings === "object",
|
|
`컬럼 ${unifiedCol.columnName}: detailSettings 객체 변환 실패`,
|
|
);
|
|
});
|
|
|
|
console.log(`✅ 컬럼 타입 API: ${unifiedColumns.length}개 컬럼 변환 완료`);
|
|
return true;
|
|
}
|
|
} catch (error) {
|
|
console.error("❌ 컬럼 타입 API 테스트 실패:", error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 🧪 Test 3: 화면 정의 저장/불러오기 API
|
|
*/
|
|
static async testScreenDefinitionAPI() {
|
|
console.log("🧪 API Test 3: 화면 정의 저장/불러오기 API");
|
|
|
|
try {
|
|
// 테스트용 화면 정의 생성
|
|
const testScreenDefinition: ScreenDefinition = {
|
|
screenId: 9999, // 테스트용 임시 ID
|
|
screenName: "API 테스트 화면",
|
|
screenCode: "API_TEST_SCREEN",
|
|
tableName: "test_table",
|
|
tableLabel: "테스트 테이블",
|
|
description: "API 타입 안전성 테스트용 화면",
|
|
isActive: "Y",
|
|
layoutData: {
|
|
screenId: 9999,
|
|
components: [
|
|
{
|
|
id: "testWidget",
|
|
type: "widget",
|
|
widgetType: "text",
|
|
position: { x: 10, y: 10 },
|
|
size: { width: 200, height: 40 },
|
|
label: "테스트 입력",
|
|
columnName: "test_column",
|
|
required: true,
|
|
webTypeConfig: { maxLength: 100 },
|
|
} as WidgetComponent,
|
|
],
|
|
gridSettings: {
|
|
enabled: true,
|
|
size: 10,
|
|
snapToGrid: true,
|
|
showGrid: true,
|
|
color: "#e0e0e0",
|
|
opacity: 0.5,
|
|
},
|
|
},
|
|
};
|
|
|
|
// 화면 정의 저장 시도
|
|
const saveResponse = await apiClient.post("/api/admin/screen-management/screens", testScreenDefinition);
|
|
|
|
if (saveResponse.status === 200 || saveResponse.status === 201) {
|
|
console.log("✅ 화면 정의 저장 성공");
|
|
|
|
// 저장된 화면 불러오기 시도
|
|
const loadResponse = await apiClient.get(
|
|
`/api/admin/screen-management/screens/${testScreenDefinition.screenId}`,
|
|
);
|
|
|
|
if (loadResponse.data) {
|
|
const loadedScreen = loadResponse.data as ScreenDefinition;
|
|
|
|
// 데이터 무결성 검증
|
|
console.assert(loadedScreen.screenName === testScreenDefinition.screenName, "화면명 불일치");
|
|
console.assert(loadedScreen.layoutData.components.length > 0, "컴포넌트 데이터 손실");
|
|
|
|
// 컴포넌트 타입 안전성 검증
|
|
loadedScreen.layoutData.components.forEach((component) => {
|
|
if (component.type === "widget") {
|
|
const widget = component as WidgetComponent;
|
|
console.assert(isWebType(widget.widgetType), `위젯 타입 검증 실패: ${widget.widgetType}`);
|
|
}
|
|
});
|
|
|
|
console.log("✅ 화면 정의 불러오기 및 검증 완료");
|
|
}
|
|
|
|
// 테스트 데이터 정리
|
|
try {
|
|
await apiClient.delete(`/api/admin/screen-management/screens/${testScreenDefinition.screenId}`);
|
|
console.log("✅ 테스트 데이터 정리 완료");
|
|
} catch (cleanupError) {
|
|
console.warn("⚠️ 테스트 데이터 정리 실패 (정상적일 수 있음):", cleanupError);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
} catch (error) {
|
|
console.error("❌ 화면 정의 API 테스트 실패:", error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 🧪 Test 4: 폼 데이터 저장 API 타입 안전성
|
|
*/
|
|
static async testFormDataSaveAPI() {
|
|
console.log("🧪 API Test 4: 폼 데이터 저장 API 타입 안전성");
|
|
|
|
try {
|
|
// 다양한 웹타입의 폼 데이터 준비
|
|
const formData = {
|
|
textField: "테스트 텍스트",
|
|
numberField: 123,
|
|
booleanField: true,
|
|
dateField: "2024-01-01",
|
|
selectField: "option1",
|
|
emailField: "test@example.com",
|
|
};
|
|
|
|
// 컴포넌트 정의 (폼 구조)
|
|
const formComponents: WidgetComponent[] = [
|
|
{
|
|
id: "textField",
|
|
type: "widget",
|
|
widgetType: "text",
|
|
position: { x: 0, y: 0 },
|
|
size: { width: 200, height: 40 },
|
|
label: "텍스트",
|
|
columnName: "text_column",
|
|
webTypeConfig: {},
|
|
},
|
|
{
|
|
id: "numberField",
|
|
type: "widget",
|
|
widgetType: "number",
|
|
position: { x: 0, y: 50 },
|
|
size: { width: 200, height: 40 },
|
|
label: "숫자",
|
|
columnName: "number_column",
|
|
webTypeConfig: {},
|
|
},
|
|
{
|
|
id: "booleanField",
|
|
type: "widget",
|
|
widgetType: "checkbox",
|
|
position: { x: 0, y: 100 },
|
|
size: { width: 200, height: 40 },
|
|
label: "체크박스",
|
|
columnName: "boolean_column",
|
|
webTypeConfig: {},
|
|
},
|
|
];
|
|
|
|
// 타입 안전한 데이터 변환
|
|
const processedData: Record<string, any> = {};
|
|
|
|
formComponents.forEach((component) => {
|
|
const fieldValue = formData[component.id as keyof typeof formData];
|
|
|
|
if (fieldValue !== undefined && component.columnName) {
|
|
switch (component.widgetType) {
|
|
case "text":
|
|
case "email":
|
|
processedData[component.columnName] = String(fieldValue);
|
|
break;
|
|
|
|
case "number":
|
|
processedData[component.columnName] = Number(fieldValue);
|
|
break;
|
|
|
|
case "checkbox":
|
|
case "boolean":
|
|
processedData[component.columnName] = booleanToYN(Boolean(fieldValue));
|
|
break;
|
|
|
|
case "date":
|
|
processedData[component.columnName] = fieldValue ? String(fieldValue) : null;
|
|
break;
|
|
|
|
default:
|
|
processedData[component.columnName] = fieldValue;
|
|
}
|
|
}
|
|
});
|
|
|
|
// 실제 API 호출 시뮬레이션 (일반적인 폼 저장 엔드포인트)
|
|
console.log("📤 처리된 폼 데이터:", processedData);
|
|
|
|
// 타입 검증
|
|
console.assert(typeof processedData.text_column === "string", "텍스트 필드 타입 오류");
|
|
console.assert(typeof processedData.number_column === "number", "숫자 필드 타입 오류");
|
|
console.assert(
|
|
processedData.boolean_column === "Y" || processedData.boolean_column === "N",
|
|
"불린 필드 Y/N 변환 오류",
|
|
);
|
|
|
|
console.log("✅ 폼 데이터 저장 타입 안전성 검증 완료");
|
|
return true;
|
|
} catch (error) {
|
|
console.error("❌ 폼 데이터 저장 API 테스트 실패:", error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 🧪 Test 5: 버튼 액션 실행 API 타입 안전성
|
|
*/
|
|
static async testButtonActionAPI() {
|
|
console.log("🧪 API Test 5: 버튼 액션 실행 API 타입 안전성");
|
|
|
|
try {
|
|
const buttonActions: ButtonActionType[] = [
|
|
"save",
|
|
"cancel",
|
|
"delete",
|
|
"edit",
|
|
"add",
|
|
"search",
|
|
"reset",
|
|
"submit",
|
|
"close",
|
|
"popup",
|
|
"modal",
|
|
"navigate",
|
|
"control",
|
|
];
|
|
|
|
// 각 버튼 액션 타입 검증
|
|
buttonActions.forEach((action) => {
|
|
console.assert(isButtonActionType(action), `유효하지 않은 버튼 액션: ${action}`);
|
|
});
|
|
|
|
// 잘못된 액션 타입들 검증
|
|
const invalidActions = ["insert", "update", "remove", ""];
|
|
invalidActions.forEach((action) => {
|
|
console.assert(!isButtonActionType(action), `잘못된 액션이 허용됨: ${action}`);
|
|
});
|
|
|
|
console.log("✅ 버튼 액션 타입 안전성 검증 완료");
|
|
return true;
|
|
} catch (error) {
|
|
console.error("❌ 버튼 액션 API 테스트 실패:", error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 🎯 모든 API 연동 테스트 실행
|
|
*/
|
|
static async runAllAPITests() {
|
|
console.log("🎯 API 연동 타입 안전성 테스트 시작\n");
|
|
|
|
const results = {
|
|
tableInfoAPI: false,
|
|
columnTypeAPI: false,
|
|
screenDefinitionAPI: false,
|
|
formDataSaveAPI: false,
|
|
buttonActionAPI: false,
|
|
};
|
|
|
|
try {
|
|
results.tableInfoAPI = await this.testTableInfoAPI();
|
|
results.columnTypeAPI = await this.testColumnTypeAPI();
|
|
results.screenDefinitionAPI = await this.testScreenDefinitionAPI();
|
|
results.formDataSaveAPI = await this.testFormDataSaveAPI();
|
|
results.buttonActionAPI = await this.testButtonActionAPI();
|
|
|
|
const passedTests = Object.values(results).filter(Boolean).length;
|
|
const totalTests = Object.keys(results).length;
|
|
|
|
console.log(`\n🎉 API 연동 테스트 완료: ${passedTests}/${totalTests} 통과`);
|
|
|
|
if (passedTests === totalTests) {
|
|
console.log("✅ 모든 API 연동 타입 안전성 테스트 통과!");
|
|
} else {
|
|
console.log("⚠️ 일부 API 연동 테스트 실패");
|
|
}
|
|
|
|
return {
|
|
success: passedTests === totalTests,
|
|
passedTests,
|
|
totalTests,
|
|
results,
|
|
};
|
|
} catch (error) {
|
|
console.error("❌ API 연동 테스트 실행 실패:", error);
|
|
return {
|
|
success: false,
|
|
passedTests: 0,
|
|
totalTests: Object.keys(results).length,
|
|
results,
|
|
error: String(error),
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
export default APIIntegrationTestSuite;
|