zioinfo-mail/workspace/guardia-itsm/test_f4_pwa.py
DESKTOP-TKLFCPR\ython cfe2901a55 refactor(structure): consolidate all projects under workspace/
- itsm/    -> workspace/guardia-itsm/
- manager/ -> workspace/guardia-manager/
- app/     -> workspace/guardia-messenger/
- manual/  -> workspace/guardia-docs/

workspace/zioinfo-web/ unchanged.
git mv preserves full commit history.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 23:50:56 +09:00

206 lines
8.2 KiB
Python

"""F-4 Mobile PWA 테스트"""
import sys, os, json, re, ast
ok = True
print("=== 1. 파일 존재 확인 ===")
pwa_files = [
"static/manifest.json",
"static/sw.js",
"static/offline.html",
]
for f in pwa_files:
exists = os.path.exists(f)
status = "OK" if exists else "ERR"
if not exists: ok = False
print(f" {status} {f}")
print("\n=== 2. manifest.json 필수 필드 검증 ===")
try:
with open("static/manifest.json", encoding="utf-8") as f:
manifest = json.load(f)
required_fields = ["name", "short_name", "start_url", "display", "icons",
"background_color", "theme_color", "lang"]
for field in required_fields:
status = "OK" if field in manifest else "ERR"
if status == "ERR": ok = False
print(f" {status} {field}: {manifest.get(field, 'MISSING')!r}")
# display 값 검증
valid_displays = {"standalone", "fullscreen", "minimal-ui", "browser"}
assert manifest["display"] in valid_displays, \
f"display 값 오류: {manifest['display']}"
print(f" OK display='{manifest['display']}' (PWA 설치형)")
# 아이콘 최소 192x192, 512x512 필요
icon_sizes = {icon["sizes"] for icon in manifest.get("icons", [])}
assert "192x192" in icon_sizes, f"192x192 아이콘 없음. 있는 크기: {icon_sizes}"
assert "512x512" in icon_sizes, f"512x512 아이콘 없음. 있는 크기: {icon_sizes}"
print(f" OK 아이콘 {len(manifest['icons'])}개 (192, 512 포함)")
# shortcuts 존재 확인
shortcuts = manifest.get("shortcuts", [])
assert len(shortcuts) >= 2, f"shortcuts 최소 2개 필요: {len(shortcuts)}"
print(f" OK shortcuts {len(shortcuts)}개 정의됨")
# lang 한국어
assert manifest.get("lang") == "ko", f"lang='ko' 기대: {manifest.get('lang')}"
print(f" OK lang='ko' 한국어")
# start_url
assert manifest.get("start_url") == "/", f"start_url='/' 기대"
print(f" OK start_url='/'")
# scope
assert manifest.get("scope") == "/", f"scope='/' 기대"
print(f" OK scope='/'")
# maskable 아이콘
maskable = [i for i in manifest.get("icons", []) if "maskable" in i.get("purpose", "")]
assert len(maskable) > 0, "maskable 아이콘 없음"
print(f" OK maskable 아이콘 {len(maskable)}")
except json.JSONDecodeError as e:
print(f" ERR manifest.json JSON 파싱 오류: {e}")
ok = False
except AssertionError as e:
print(f" ERR {e}")
ok = False
print("\n=== 3. sw.js Service Worker 핵심 기능 확인 ===")
with open("static/sw.js", encoding="utf-8") as f:
sw_src = f.read()
sw_checks = [
("CACHE_VERSION", "CACHE_VERSION 버전 관리"),
("STATIC_CACHE", "STATIC_CACHE 정적 캐시"),
("API_CACHE", "API_CACHE API 캐시"),
("OFFLINE_URL", "OFFLINE_URL 오프라인 폴백"),
("PRECACHE_URLS", "PRECACHE_URLS 사전 캐시 목록"),
("install", "install 이벤트 핸들러"),
("activate", "activate 이벤트 핸들러"),
("fetch", "fetch 이벤트 핸들러"),
("skipWaiting", "skipWaiting() 즉시 활성화"),
("clients.claim", "clients.claim() 클라이언트 제어"),
("caches.delete", "이전 캐시 자동 정리"),
("cacheFirst", "cacheFirst 전략"),
("networkFirst", "networkFirst 전략"),
("NO_CACHE_PATTERNS", "NO_CACHE_PATTERNS 보안 경로"),
("/api/auth", "auth 경로 캐시 제외"),
("/api/audit", "audit 경로 캐시 제외"),
("/api/pam", "pam 경로 캐시 제외"),
("push", "push 알림 핸들러"),
("notificationclick", "알림 클릭 핸들러"),
("sync", "백그라운드 동기화"),
("showNotification", "showNotification 알림 표시"),
("requireInteraction", "CRITICAL 알림 상호작용 필요"),
("503", "오프라인 503 응답"),
("offline: true", "offline 플래그 JSON 응답"),
("request.method !== 'GET'", "POST/PUT/DELETE 캐시 제외"),
]
for sym, desc in sw_checks:
status = "OK" if sym in sw_src else "ERR"
if status == "ERR": ok = False
print(f" {status} {desc}")
print("\n=== 4. offline.html 구성 확인 ===")
with open("static/offline.html", encoding="utf-8") as f:
offline_src = f.read()
offline_checks = [
("<!DOCTYPE html>", "HTML5 DOCTYPE"),
('lang="ko"', "한국어 설정"),
("viewport", "뷰포트 메타"),
("theme-color", "theme-color 메타"),
("retryConnection", "retryConnection 재시도 함수"),
("/api/metrics/health", "헬스체크 엔드포인트 활용"),
("navigator.onLine", "온라인 상태 감지"),
("window.addEventListener('online'", "online 이벤트 리스너"),
("window.addEventListener('offline'","offline 이벤트 리스너"),
("location.reload", "자동 새로고침"),
("30000", "30초 자동 재시도"),
("history.back", "뒤로 가기"),
("animation", "오프라인 상태 애니메이션"),
]
for sym, desc in offline_checks:
status = "OK" if sym in offline_src else "ERR"
if status == "ERR": ok = False
print(f" {status} {desc}")
print("\n=== 5. index.html PWA 태그 확인 ===")
with open("static/index.html", encoding="utf-8") as f:
index_src = f.read()
index_checks = [
('rel="manifest"', "manifest 링크"),
("/static/manifest.json", "manifest.json 경로"),
('name="theme-color"', "theme-color 메타"),
('name="mobile-web-app-capable"', "모바일 앱 가능"),
('name="apple-mobile-web-app-capable"', "iOS 앱 가능"),
('name="apple-mobile-web-app-title"', "iOS 앱 제목"),
('rel="apple-touch-icon"', "iOS 터치 아이콘"),
]
for sym, desc in index_checks:
status = "OK" if sym in index_src else "ERR"
if status == "ERR": ok = False
print(f" {status} {desc}")
print("\n=== 6. app.js SW 등록 코드 확인 ===")
with open("static/app.js", encoding="utf-8") as f:
app_src = f.read()
app_checks = [
("serviceWorker", "serviceWorker 지원 감지"),
("register('/static/sw.js'", "SW 등록 경로"),
("scope: '/'", "SW 스코프 '/'"),
("updatefound", "updatefound 업데이트 감지"),
("installed", "installed 상태 감지"),
]
for sym, desc in app_checks:
status = "OK" if sym in app_src else "ERR"
if status == "ERR": ok = False
print(f" {status} {desc}")
print("\n=== 7. PWA 설치 기준 (Lighthouse) 충족 확인 ===")
try:
# 필수 조건 종합 검사
pwa_criteria = [
(manifest.get("name") and len(manifest["name"]) >= 2,
"앱 이름 2자 이상"),
(manifest.get("short_name") and len(manifest["short_name"]) <= 12,
"short_name 12자 이하"),
(manifest.get("start_url"),
"start_url 정의"),
(manifest.get("display") in {"standalone", "fullscreen", "minimal-ui"},
"display standalone/fullscreen/minimal-ui"),
(any(i["sizes"] == "192x192" for i in manifest.get("icons", [])),
"192x192 아이콘"),
(any(i["sizes"] == "512x512" for i in manifest.get("icons", [])),
"512x512 아이콘"),
("sw.js" in sw_src or "CACHE_VERSION" in sw_src,
"Service Worker 존재"),
(manifest.get("background_color"),
"background_color 정의"),
]
for check, desc in pwa_criteria:
status = "OK" if check else "ERR"
if not check: ok = False
print(f" {status} {desc}")
except Exception as e:
print(f" ERR {e}")
ok = False
print("\n=== 8. 보안 정책 확인 (캐시 제외 경로) ===")
no_cache_paths = ["/api/auth", "/api/audit", "/api/pam", "/api/otp", "/ws"]
for path in no_cache_paths:
status = "OK" if path in sw_src else "ERR"
if status == "ERR": ok = False
print(f" {status} '{path}' 캐시 제외 (보안)")
print("\n=== F-4 Mobile PWA 테스트 완료 ===")
if ok:
print("모든 검사 통과")
else:
sys.exit(1)