fix(message_bizppurio): 알림톡 연결 모달에서 조회 실패와 '승인 템플릿 0건'을 구분

자격증명 오류 등으로 카카오 템플릿 조회가 실패하면 드롭다운이 비면서
'승인된 템플릿이 없습니다'로 떠서 실제 0건과 구분되지 않던 문제를 수정한다.
데이터소스 fallback 에 load_failed 마커를 실어, 드롭다운이 비었을 때
조회 실패(설정 확인 안내)와 진짜 0건(승인 템플릿 없음)을 문구로 분기한다.
에러 토스트 재도입 없이 모달 내 인라인 문구로만 구분한다.
This commit is contained in:
chym1217 committed 2026-07-28 12:40:13 +09:00
1 parent 8aa395c3a1
commit ca5c9a3438
4 files changed
+52 -3

No files matched your search

@@ -18,7 +18,7 @@
},
{
"id": "bizppurioApprovedTemplates",
"comment": "연결 모달 드롭다운 옵션(카카오 관리 API 위임). auto_fetch:false — 자격증명 미설정 시 카카오 API 422 가 전 탭에서 발화하던 문제 차단(결정 C). 연결 모달 열 때만 조회. errorHandling.default:suppress — 자격증명 미설정 시 422(카카오 API 요청 실패)를 조용히 무시한다(fallback 빈 목록 사용, 안내는 배너·'승인 템플릿 없음' 문구가 담당). 이 토스트를 막지 않으면 모달 열 때마다 에러 토스트가 뜬다.",
"comment": "연결 모달 드롭다운 옵션(카카오 관리 API 위임). auto_fetch:false — 자격증명 미설정 시 카카오 API 422 가 전 탭에서 발화하던 문제 차단(결정 C). 연결 모달 열 때만 조회. errorHandling.default:suppress — 자격증명 미설정 시 422(카카오 API 요청 실패)를 조용히 무시한다(fallback 사용, 토스트 재도입 금지 — 모달 열 때마다 에러 토스트가 뜨던 회귀). 조회 실패와 '진짜 0건'을 화면에서 구분하기 위해 fallback.data.load_failed:true 마커를 싣는다 — 정상 응답에는 이 필드가 없으므로, 드롭다운이 비었을 때 load_failed 유무로 '불러오지 못함(자격증명 확인)' vs '승인 템플릿 없음'을 분기한다.",
"type": "api",
"endpoint": "/api/plugins/sirsoft-message_bizppurio/admin/notification-bindings/approved-templates",
"method": "GET",
@@ -31,7 +31,8 @@
},
"fallback": {
"data": {
"templates": []
"templates": [],
"load_failed": true
}
}
}
@@ -249,9 +250,20 @@
]
},
{
"comment": "드롭다운이 비었고 조회 실패(fallback 마커)일 때 — 키 오류 등을 '0건'과 구분해 설정 확인을 안내.",
"type": "basic",
"name": "P",
"if": "{{(bizppurioApprovedTemplates?.data?.templates ?? []).length === 0}}",
"if": "{{(bizppurioApprovedTemplates?.data?.templates ?? []).length === 0 && bizppurioApprovedTemplates?.data?.load_failed === true}}",
"props": {
"className": "text-xs text-red-600 dark:text-red-400 mt-1"
},
"text": "$t:sirsoft-message_bizppurio.binding.templates_load_failed"
},
{
"comment": "드롭다운이 비었고 조회는 정상(load_failed 없음)일 때 — 실제 승인 템플릿 0건.",
"type": "basic",
"name": "P",
"if": "{{(bizppurioApprovedTemplates?.data?.templates ?? []).length === 0 && !(bizppurioApprovedTemplates?.data?.load_failed)}}",
"props": {
"className": "text-xs text-amber-600 dark:text-amber-400 mt-1"
},
@@ -156,6 +156,41 @@ describe('binding UI — 연결 모달(우리 소유, 코어 편집 모달과
});
});
describe('binding UI — 드롭다운 조회 실패 vs 0건 구분(결함 3)', () => {
const modal = findById(overlayRoot, 'modal_bizppurio_binding');
it('승인 템플릿 데이터소스 fallback 에 load_failed:true 마커가 있다', () => {
const ds = ((overlay as { data_sources?: Array<Record<string, unknown>> }).data_sources ?? [])
.find((d) => d.id === 'bizppurioApprovedTemplates');
const fallback = (ds?.fallback as { data?: Record<string, unknown> })?.data ?? {};
// 조회 실패 시 이 마커가 상태에 실려 '0건'과 구분된다. 정상 응답에는 이 필드가 없다.
expect(fallback.load_failed).toBe(true);
expect(Array.isArray(fallback.templates)).toBe(true);
expect((fallback.templates as unknown[]).length).toBe(0);
});
it('드롭다운이 비었을 때 조회 실패(load_failed)면 설정 확인 문구를 노출한다', () => {
const raw = JSON.stringify(modal);
// 조회 실패 분기: length===0 && load_failed===true → templates_load_failed
expect(raw).toContain('binding.templates_load_failed');
expect(raw).toContain('load_failed === true');
});
it('드롭다운이 비었을 때 조회 정상(0건)이면 승인 템플릿 없음 문구를 노출한다', () => {
const raw = JSON.stringify(modal);
// 0건 분기: length===0 && !load_failed → no_approved_templates
expect(raw).toContain('binding.no_approved_templates');
expect(raw).toContain('!(bizppurioApprovedTemplates?.data?.load_failed)');
});
it('두 문구는 상호배타 조건이라 동시에 뜨지 않는다(조회실패=빨강 / 0건=amber)', () => {
const raw = JSON.stringify(modal);
// 조회 실패 문구는 red, 0건 문구는 amber 로 시각 구분
expect(raw).toContain('text-red-600');
expect(raw).toContain('text-amber-600');
});
});
describe('binding UI — 코어 무오염 + i18n', () => {
it('overlay·footer 어디에도 코어 편집 모달 저장 body(notification-templates PUT)를 건드리지 않는다', () => {
const all = JSON.stringify(overlay) + JSON.stringify(footer);
@@ -80,6 +80,7 @@
"connected_template": "Connected Template",
"none": "Not connected",
"no_approved_templates": "No approved (sendable) alimtalk templates. Register and inspect a template first.",
"templates_load_failed": "Failed to load the template list. Check the plugin settings (credentials).",
"fallback_sms": "Fall back to SMS on failure",
"fallback_hint": "If the alimtalk message fails, this notification's body is sent as an SMS instead.",
"variables_hint": "Available variables (auto-substituted on send)",
@@ -80,6 +80,7 @@
"connected_template": "연결 템플릿",
"none": "연결 안 함",
"no_approved_templates": "발송 가능한(승인된) 알림톡 템플릿이 없습니다. 먼저 템플릿을 등록·검수하세요.",
"templates_load_failed": "템플릿 목록을 불러오지 못했습니다. 플러그인 설정(자격증명)을 확인하세요.",
"fallback_sms": "실패 시 SMS로 대체발송",
"fallback_hint": "알림톡 발송이 실패하면 이 알림의 본문 내용이 문자(SMS)로 대신 발송됩니다.",
"variables_hint": "제공 변수 (발송 시 자동 치환)",