# 멀티 테넌시 검증 및 테스트 가이드

**작성일**: 2025-11-19
**목적**: Phase 1-4 구현 후 테넌트 격리 기능 검증

---

## 📋 목차

1. [테스트 환경 준비](#테스트-환경-준비)
2. [테스트 시나리오](#테스트-시나리오)
3. [체크리스트](#체크리스트)
4. [문제 해결](#문제-해결)

---

## 테스트 환경 준비

### 1. 개발 서버 실행

```bash
npm run dev
```

### 2. 브라우저 개발자 도구 열기

- Chrome: `F12` 또는 `Cmd+Option+I` (Mac)
- Console 탭과 Application 탭을 주로 사용

### 3. 테스트 사용자 확인

현재 등록된 테스트 사용자 (모두 tenant.id: 282):

| userId | name | tenant.id | 역할 |
|--------|------|-----------|------|
| TestUser1 | 이재욱 | 282 | 일반 사용자 |
| TestUser2 | 박관리 | 282 | 생산관리자 |
| TestUser3 | 드미트리 | 282 | 시스템 관리자 |

**⚠️ 테넌트 전환 테스트를 위해 다른 tenant.id를 가진 사용자가 필요합니다.**

---

## 테스트 시나리오

### 시나리오 1: 기본 캐시 동작 확인 ✅

**목적**: TenantAwareCache가 제대로 동작하는지 확인

**단계**:
1. 로그인: TestUser3 (tenant.id: 282)
2. `/master-data/item-master-data-management` 페이지 이동
3. 데이터 입력:
   - 규격 마스터 1개 추가
   - 품목 분류 1개 추가
4. **개발자 도구 → Application → Session Storage** 확인

**기대 결과**:
```
✅ sessionStorage에 다음 키가 생성되어야 함:
- mes-282-itemMasters
- mes-282-specificationMasters
- mes-282-itemCategories
- (기타 입력한 데이터)

✅ 각 키의 값에 tenantId: 282 포함
✅ timestamp 포함
```

**확인 방법**:
```javascript
// Console에서 실행
Object.keys(sessionStorage).filter(k => k.startsWith('mes-'))
// 결과: ["mes-282-itemMasters", "mes-282-specificationMasters", ...]
```

---

### 시나리오 2: 페이지 새로고침 시 캐시 로드 ✅

**목적**: 캐시에서 데이터를 제대로 불러오는지 확인

**단계**:
1. 시나리오 1 완료 후
2. `F5` 또는 `Cmd+R`로 새로고침
3. Console에서 로그 확인

**기대 결과**:
```
✅ Console 로그:
[Cache] Loaded from cache: itemMasters
[Cache] Loaded from cache: specificationMasters
...

✅ 입력했던 데이터가 그대로 표시됨
✅ 서버 API 호출 없이 캐시에서 로드
```

---

### 시나리오 3: TTL (1시간) 만료 확인 ⏱️

**목적**: 캐시가 1시간 후 자동 삭제되는지 확인

**⚠️ 주의**: 실제 1시간을 기다릴 수 없으므로 **수동 테스트**

**단계**:
1. sessionStorage에서 캐시 데이터 조회:
   ```javascript
   const cached = sessionStorage.getItem('mes-282-itemMasters');
   const parsed = JSON.parse(cached);
   console.log('Timestamp:', new Date(parsed.timestamp));
   console.log('Age (minutes):', (Date.now() - parsed.timestamp) / 60000);
   ```

2. **수동으로 timestamp 수정** (과거 시간으로):
   ```javascript
   const cached = sessionStorage.getItem('mes-282-itemMasters');
   const parsed = JSON.parse(cached);

   // 2시간 전으로 설정 (TTL 1시간 초과)
   parsed.timestamp = Date.now() - (7200 * 1000);

   sessionStorage.setItem('mes-282-itemMasters', JSON.stringify(parsed));
   ```

3. 페이지 새로고침

**기대 결과**:
```
✅ Console 로그:
[Cache] Expired cache for key: itemMasters

✅ 만료된 캐시 자동 삭제
✅ 초기 데이터로 리셋
```

---

### 시나리오 4: 다중 탭 격리 확인 🔗

**목적**: 탭마다 독립적인 sessionStorage 사용 확인

**단계**:
1. **탭 1**: TestUser3 로그인 → 데이터 입력 (규격 마스터 A)
2. **탭 2**: 동일 URL을 새 탭으로 열기 (`Cmd+T` → URL 복사)
3. 탭 2에서 sessionStorage 확인

**기대 결과**:
```
✅ 탭 2의 sessionStorage는 비어있음
✅ 탭 1의 데이터가 탭 2에 공유되지 않음
✅ 각 탭이 독립적으로 동작

sessionStorage는 탭마다 격리됨!
```

**확인 방법**:
```javascript
// 탭 1
sessionStorage.setItem('test', 'tab1');

// 탭 2 (새로 열린 탭)
sessionStorage.getItem('test'); // null (공유 안 됨)
```

---

### 시나리오 5: 탭 닫기 시 자동 삭제 🗑️

**목적**: 탭을 닫으면 sessionStorage가 자동으로 삭제되는지 확인

**단계**:
1. 탭에서 데이터 입력
2. Application → Session Storage에서 데이터 확인
3. **탭 닫기**
4. **동일 URL을 새 탭으로 다시 열기**
5. Session Storage 확인

**기대 결과**:
```
✅ sessionStorage가 완전히 비어있음
✅ 이전 탭의 데이터가 남아있지 않음
✅ 새로운 세션으로 시작
```

---

### 시나리오 6: 로그아웃 시 캐시 삭제 🚪

**목적**: 로그아웃하면 테넌트 캐시가 완전히 삭제되는지 확인

**단계**:
1. TestUser3 로그인 → 데이터 입력
2. sessionStorage 확인 (캐시 있음)
3. **로그아웃 버튼 클릭**
4. Console 로그 확인
5. sessionStorage 다시 확인

**기대 결과**:
```
✅ Console 로그:
[Cache] Cleared sessionStorage: mes-282-itemMasters
[Cache] Cleared sessionStorage: mes-282-specificationMasters
...
[Auth] Logged out and cleared tenant cache

✅ sessionStorage에서 mes-282-* 키가 모두 삭제됨
✅ localStorage에서 mes-currentUser도 삭제됨
```

**확인 방법**:
```javascript
// 로그아웃 후
Object.keys(sessionStorage).filter(k => k.startsWith('mes-282-'))
// 결과: [] (빈 배열)
```

---

### 시나리오 7: 테넌트 전환 시 캐시 삭제 🔄

**⚠️ 현재 제약**: 모든 테스트 사용자가 tenant.id: 282를 사용 중

**필요 작업**: 다른 tenant.id를 가진 사용자 추가

#### 7-1. 테스트 사용자 추가 (tenant.id: 283)

`src/contexts/AuthContext.tsx` 수정:

```typescript
const initialUsers: User[] = [
  // ... 기존 사용자 ...
  {
    userId: "TestUser4",
    name: "김테넌트",
    position: "다른 회사 관리자",
    roles: [
      {
        id: 1,
        name: "admin",
        description: "관리자"
      }
    ],
    tenant: {
      id: 283,  // ✅ 다른 테넌트!
      company_name: "(주)다른회사",
      business_num: "987-65-43210",
      tenant_st_code: "active",
      other_tenants: []
    },
    menu: [
      {
        id: "13664",
        label: "시스템 대시보드",
        iconName: "layout-dashboard",
        path: "/dashboard"
      }
    ]
  }
];
```

#### 7-2. 테넌트 전환 테스트

**단계**:
1. **TestUser3 로그인** (tenant.id: 282)
   - 데이터 입력 (규격 마스터 A, B)
   - sessionStorage 확인: `mes-282-specificationMasters`

2. **로그아웃**

3. **TestUser4 로그인** (tenant.id: 283)
   - Console 로그 확인

**기대 결과**:
```
✅ Console 로그:
[Auth] Tenant changed: 282 → 283
[Cache] Cleared sessionStorage: mes-282-itemMasters
[Cache] Cleared sessionStorage: mes-282-specificationMasters
...

✅ 이전 테넌트(282)의 캐시가 모두 삭제됨
✅ TestUser4의 데이터는 mes-283-* 키로 저장됨
✅ 테넌트 간 데이터 격리 확인
```

**확인 방법**:
```javascript
// 테넌트 전환 후
Object.keys(sessionStorage).forEach(key => {
  console.log(key);
});

// 결과:
// mes-283-itemMasters  (새 테넌트)
// mes-283-specificationMasters
// (mes-282-* 키는 없어야 함!)
```

---

### 시나리오 8: PHP 백엔드 tenant.id 검증 🛡️

**⚠️ 주의**: PHP 백엔드가 실행 중이어야 함

**목적**: 다른 테넌트의 데이터 접근 시 403 반환 확인

**단계**:
1. **TestUser3 로그인** (tenant.id: 282)
2. 브라우저 Console에서 다른 테넌트 API 직접 호출:

```javascript
// 자신의 테넌트 (282) - 성공해야 함
fetch('/api/tenants/282/item-master-config')
  .then(r => r.json())
  .then(d => console.log('Own tenant:', d));

// 다른 테넌트 (283) - 403 에러여야 함
fetch('/api/tenants/283/item-master-config')
  .then(r => r.json())
  .then(d => console.log('Other tenant:', d));
```

**기대 결과**:
```
✅ 자신의 테넌트 (282):
{
  success: true,
  data: { ... }
}

✅ 다른 테넌트 (283):
{
  success: false,
  error: {
    code: "FORBIDDEN",
    message: "접근 권한이 없습니다."
  }
}
Status: 403 Forbidden

✅ Next.js는 단순히 PHP 응답을 전달만 함
✅ PHP가 tenant.id 불일치를 감지하고 403 반환
```

---

## 체크리스트

### 캐시 동작 ✅
- [ ] sessionStorage에 `mes-{tenantId}-{key}` 형식으로 저장
- [ ] 캐시 데이터에 `tenantId`, `timestamp`, `version` 포함
- [ ] 페이지 새로고침 시 캐시에서 로드
- [ ] TTL (1시간) 만료 시 자동 삭제

### 탭 격리 🔗
- [ ] 탭마다 독립적인 sessionStorage
- [ ] 다른 탭과 데이터 공유 안 됨
- [ ] 탭 닫으면 sessionStorage 자동 삭제

### 로그아웃 🚪
- [ ] 로그아웃 시 `mes-{tenantId}-*` 캐시 모두 삭제
- [ ] Console에 삭제 로그 출력
- [ ] localStorage의 `mes-currentUser` 삭제

### 테넌트 전환 🔄
- [ ] 테넌트 변경 감지 (useEffect)
- [ ] 이전 테넌트 캐시 자동 삭제
- [ ] 새 테넌트 데이터는 새 키로 저장
- [ ] Console에 전환 로그 출력

### API 보안 🛡️
- [ ] 자신의 테넌트 API 호출 성공
- [ ] 다른 테넌트 API 호출 시 403 Forbidden
- [ ] PHP 백엔드가 tenant.id 검증 수행
- [ ] Next.js는 PHP 응답 그대로 전달

---

## 문제 해결

### 문제 1: 캐시가 저장되지 않음

**증상**: sessionStorage가 비어있음

**원인**:
- ItemMasterContext가 제대로 마운트되지 않음
- tenantId가 null

**해결**:
1. Console에서 확인:
   ```javascript
   // AuthContext의 currentUser 확인
   console.log(JSON.parse(localStorage.getItem('mes-currentUser')));

   // tenant.id 확인
   console.log(user?.tenant?.id);
   ```

2. ItemMasterContext가 AuthContext 하위에 있는지 확인

### 문제 2: 테넌트 전환 시 캐시가 삭제되지 않음

**증상**: 이전 테넌트 캐시가 남아있음

**원인**:
- `useEffect` 의존성 배열 문제
- `previousTenantIdRef` 초기화 안 됨

**해결**:
```typescript
// AuthContext.tsx 확인
useEffect(() => {
  const prevTenantId = previousTenantIdRef.current;
  const currentTenantId = currentUser?.tenant?.id;

  if (prevTenantId && currentTenantId && prevTenantId !== currentTenantId) {
    console.log(`[Auth] Tenant changed: ${prevTenantId} → ${currentTenantId}`);
    clearTenantCache(prevTenantId);
  }

  previousTenantIdRef.current = currentTenantId || null;
}, [currentUser?.tenant?.id]);
```

### 문제 3: TTL 만료 후에도 캐시가 남아있음

**증상**: 1시간 이상 지난 캐시가 그대로 사용됨

**원인**:
- `TenantAwareCache.get()` 메서드에서 TTL 체크 안 함

**해결**:
```typescript
// TenantAwareCache.ts 확인
get<T>(key: string): T | null {
  // ...

  // TTL 검증
  if (Date.now() - parsed.timestamp > this.ttl) {
    console.warn(`[Cache] Expired cache for key: ${key}`);
    this.remove(key);
    return null;
  }

  return parsed.data;
}
```

### 문제 4: PHP 403 에러가 반환되지 않음

**증상**: 다른 테넌트 API 호출이 성공함

**원인**:
- PHP 백엔드에 tenant.id 검증 로직이 없음
- JWT에 tenant.id가 포함되지 않음

**해결**:
1. PHP 백엔드 확인 (프론트엔드 작업 범위 밖)
2. JWT payload에 `tenant_id` 포함 여부 확인
3. PHP middleware에서 tenant.id 검증 로직 확인

---

## 테스트 완료 기준

### ✅ 모든 시나리오 통과
- 시나리오 1-8 모두 기대 결과와 일치

### ✅ 모든 체크리스트 완료
- 캐시, 탭, 로그아웃, 테넌트 전환, API 보안

### ✅ Console 에러 없음
- 개발자 도구 Console에 빨간색 에러 없음

### ✅ 성능 확인
- 페이지 로드 시간 < 1초
- 캐시 히트 시 API 호출 없음

---

## 다음 단계

Phase 5 완료 후:
- **Phase 6**: 품목기준관리 페이지 작업 진행
- API 연동 및 실제 CRUD 구현
- UI/UX 개선

---

**작성자**: Claude
**버전**: 1.0
**최종 업데이트**: 2025-11-19