From d1859d593dedf7a0116ff72e6cb77e70edc5c6e1 Mon Sep 17 00:00:00 2001 From: kyy Date: Wed, 6 May 2026 17:34:58 +0900 Subject: [PATCH] =?UTF-8?q?dev=20=EB=B0=98=EC=98=81=20code=20check=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tenants/routes/TenantWorksmobilePage.tsx | 12 +++-- adminfront/tests/users.spec.ts | 34 +++++++----- adminfront/tests/worksmobile.spec.ts | 17 +++--- .../internal/bootstrap/admin_account_test.go | 2 - .../internal/service/worksmobile_client.go | 8 +-- .../service/worksmobile_sync_service_test.go | 12 +++++ devfront/src/components/layout/AppLayout.tsx | 5 +- locales/en.toml | 20 +++++++ locales/ko.toml | 20 +++++++ locales/template.toml | 20 +++++++ userfront/assets/translations/en.toml | 1 + userfront/assets/translations/ko.toml | 1 + userfront/assets/translations/template.toml | 1 + .../lib/core/constants/error_whitelist.dart | 3 +- .../auth/presentation/error_screen.dart | 11 ++-- .../auth/presentation/signup_screen.dart | 3 +- userfront/lib/i18n_data.dart | 54 +++++++------------ userfront/test/error_screen_test.dart | 37 ++++--------- 18 files changed, 159 insertions(+), 102 deletions(-) diff --git a/adminfront/src/features/tenants/routes/TenantWorksmobilePage.tsx b/adminfront/src/features/tenants/routes/TenantWorksmobilePage.tsx index 654483e4..ca813e96 100644 --- a/adminfront/src/features/tenants/routes/TenantWorksmobilePage.tsx +++ b/adminfront/src/features/tenants/routes/TenantWorksmobilePage.tsx @@ -641,7 +641,12 @@ export function buildWorksmobilePasswordManageUrl({ }) { const normalizedTenantId = tenantId?.trim(); const normalizedUserIdNo = userIdNo?.trim(); - if (!normalizedTenantId || !domainId || domainId <= 0 || !normalizedUserIdNo) { + if ( + !normalizedTenantId || + !domainId || + domainId <= 0 || + !normalizedUserIdNo + ) { return ""; } const url = new URL("https://auth.worksmobile.com/integrate/password/manage"); @@ -790,10 +795,7 @@ function ComparisonTable({ .filter(canSelectWorksmobileRow) .map(getWorksmobileRowSelectionKey) .filter(Boolean); - const selectedActionIds = getWorksmobileSelectedActionIds( - rows, - selectedKeys, - ); + const selectedActionIds = getWorksmobileSelectedActionIds(rows, selectedKeys); const allSelectableSelected = selectableKeys.length > 0 && selectableKeys.every((key) => selectedKeys.includes(key)); diff --git a/adminfront/tests/users.spec.ts b/adminfront/tests/users.spec.ts index bd293614..f676ae66 100644 --- a/adminfront/tests/users.spec.ts +++ b/adminfront/tests/users.spec.ts @@ -462,7 +462,8 @@ test.describe("User Management", () => { "John Doe john@test.com 010-1111-2222", ); - await page.getByTestId("user-status-toggle-u-1").click(); + await page.getByTestId("user-status-select-u-1").click(); + await page.getByRole("option", { name: /비활성|inactive/i }).click(); await expect .poll(() => updatePayload) .toMatchObject({ status: "inactive" }); @@ -816,22 +817,27 @@ test.describe("User Management", () => { (form as HTMLFormElement).requestSubmit(); }); - await expect.poll(() => updatePayload).toMatchObject({ - tenantSlug: "hanmac-team", - primaryTenantId: "hanmac-team-id", - primaryTenantName: "한맥팀", - primaryTenantIsOwner: true, - metadata: { + await expect + .poll(() => updatePayload) + .toMatchObject({ + tenantSlug: "hanmac-team", primaryTenantId: "hanmac-team-id", primaryTenantName: "한맥팀", - primaryTenantSlug: "hanmac-team", primaryTenantIsOwner: true, - additionalAppointments: [ - { tenantId: "03dbe16b-e47b-4f72-927b-782807d67a35", isPrimary: false }, - { tenantId: "hanmac-team-id", isPrimary: true }, - ], - }, - }); + metadata: { + primaryTenantId: "hanmac-team-id", + primaryTenantName: "한맥팀", + primaryTenantSlug: "hanmac-team", + primaryTenantIsOwner: true, + additionalAppointments: [ + { + tenantId: "03dbe16b-e47b-4f72-927b-782807d67a35", + isPrimary: false, + }, + { tenantId: "hanmac-team-id", isPrimary: true }, + ], + }, + }); }); test("should show conflict error when creating with an existing Login ID", async ({ diff --git a/adminfront/tests/worksmobile.spec.ts b/adminfront/tests/worksmobile.spec.ts index dd36795e..294b5dc7 100644 --- a/adminfront/tests/worksmobile.spec.ts +++ b/adminfront/tests/worksmobile.spec.ts @@ -211,11 +211,9 @@ test.describe("Worksmobile tenant management", () => { name: /바론에만 있음|웍스에만 있음|양쪽 다 있음/, }) .allTextContents(); - await expect.poll(() => filterButtons).toEqual([ - "바론에만 있음", - "웍스에만 있음", - "양쪽 다 있음", - ]); + await expect + .poll(() => filterButtons) + .toEqual(["바론에만 있음", "웍스에만 있음", "양쪽 다 있음"]); await page.getByRole("button", { name: "웍스에만 있음" }).click(); await expect(page.getByText("박웍스")).not.toBeVisible(); @@ -481,16 +479,17 @@ test.describe("Worksmobile tenant management", () => { Math.max(pageOverflow.documentScrollWidth, pageOverflow.bodyScrollWidth), ).toBeLessThanOrEqual(pageOverflow.viewportWidth + 1); - const userTableScroll = await page.locator("table").first().evaluate( - (table) => { + const userTableScroll = await page + .locator("table") + .first() + .evaluate((table) => { const container = table.parentElement?.parentElement as HTMLElement; return { clientWidth: container.clientWidth, overflowX: window.getComputedStyle(container).overflowX, scrollWidth: container.scrollWidth, }; - }, - ); + }); expect(userTableScroll.overflowX).toBe("auto"); expect(userTableScroll.scrollWidth).toBeGreaterThan( userTableScroll.clientWidth, diff --git a/backend/internal/bootstrap/admin_account_test.go b/backend/internal/bootstrap/admin_account_test.go index b1b0b4dc..38983d9d 100644 --- a/backend/internal/bootstrap/admin_account_test.go +++ b/backend/internal/bootstrap/admin_account_test.go @@ -18,7 +18,6 @@ func TestEnsureSuperAdminCreatesIdentityLocalUserAndKetoRelation(t *testing.T) { Name: "New Admin", Source: "test", }) - if err != nil { t.Fatalf("EnsureSuperAdmin returned error: %v", err) } @@ -67,7 +66,6 @@ func TestEnsureSuperAdminPromotesExistingLocalUser(t *testing.T) { Name: "Existing Admin", Source: "test", }) - if err != nil { t.Fatalf("EnsureSuperAdmin returned error: %v", err) } diff --git a/backend/internal/service/worksmobile_client.go b/backend/internal/service/worksmobile_client.go index f467c49f..20e4248b 100644 --- a/backend/internal/service/worksmobile_client.go +++ b/backend/internal/service/worksmobile_client.go @@ -20,9 +20,11 @@ import ( "time" ) -const defaultWorksmobileAPIBaseURL = "https://www.worksapis.com" -const defaultWorksmobileOAuthTokenURL = "https://auth.worksmobile.com/oauth2/v2.0/token" -const defaultWorksmobileOAuthScope = "directory" +const ( + defaultWorksmobileAPIBaseURL = "https://www.worksapis.com" + defaultWorksmobileOAuthTokenURL = "https://auth.worksmobile.com/oauth2/v2.0/token" + defaultWorksmobileOAuthScope = "directory" +) type WorksmobileDirectoryClient interface { CreateOrgUnit(ctx context.Context, payload WorksmobileOrgUnitPayload) error diff --git a/backend/internal/service/worksmobile_sync_service_test.go b/backend/internal/service/worksmobile_sync_service_test.go index 4f3fe8e6..06dde9ad 100644 --- a/backend/internal/service/worksmobile_sync_service_test.go +++ b/backend/internal/service/worksmobile_sync_service_test.go @@ -363,32 +363,41 @@ func (f *fakeWorksmobileUserRepo) Update(ctx context.Context, user *domain.User) func (f *fakeWorksmobileUserRepo) FindByEmail(ctx context.Context, email string) (*domain.User, error) { return nil, nil } + func (f *fakeWorksmobileUserRepo) FindByID(ctx context.Context, id string) (*domain.User, error) { user := f.byID[id] return &user, nil } + func (f *fakeWorksmobileUserRepo) FindByIDs(ctx context.Context, ids []string) ([]domain.User, error) { return nil, nil } + func (f *fakeWorksmobileUserRepo) ListByTenant(ctx context.Context, tenantID string) ([]domain.User, error) { return nil, nil } + func (f *fakeWorksmobileUserRepo) List(ctx context.Context, offset, limit int, search string, companyCode string) ([]domain.User, int64, error) { return nil, 0, nil } + func (f *fakeWorksmobileUserRepo) CountByTenant(ctx context.Context, tenantID string) (int64, error) { return 0, nil } + func (f *fakeWorksmobileUserRepo) CountByTenantIDs(ctx context.Context, tenantIDs []string) (map[string]int64, error) { return nil, nil } + func (f *fakeWorksmobileUserRepo) CountByCompanyCodes(ctx context.Context, codes []string) (map[string]int64, error) { return nil, nil } + func (f *fakeWorksmobileUserRepo) FindByTenantIDs(ctx context.Context, tenantIDs []string) ([]domain.User, error) { f.requestedTenantIDs = append([]string(nil), tenantIDs...) return f.byTenant, nil } + func (f *fakeWorksmobileUserRepo) FindByCompanyCodes(ctx context.Context, codes []string) ([]domain.User, error) { return nil, nil } @@ -396,12 +405,15 @@ func (f *fakeWorksmobileUserRepo) Delete(ctx context.Context, id string) error { func (f *fakeWorksmobileUserRepo) UpdateUserLoginIDs(ctx context.Context, userID string, loginIDs []domain.UserLoginID) error { return nil } + func (f *fakeWorksmobileUserRepo) GetUserLoginIDs(ctx context.Context, userID string) ([]domain.UserLoginID, error) { return nil, nil } + func (f *fakeWorksmobileUserRepo) IsLoginIDTaken(ctx context.Context, loginID string) (bool, error) { return false, nil } + func (f *fakeWorksmobileUserRepo) FindTenantIDByLoginID(ctx context.Context, loginID string) (string, error) { return "", nil } diff --git a/devfront/src/components/layout/AppLayout.tsx b/devfront/src/components/layout/AppLayout.tsx index 0f5e1c71..4ea438b9 100644 --- a/devfront/src/components/layout/AppLayout.tsx +++ b/devfront/src/components/layout/AppLayout.tsx @@ -451,7 +451,10 @@ function AppLayout() { className="inline-flex items-center gap-3 rounded-full border border-border bg-card px-3 py-2 transition hover:bg-muted/20" aria-haspopup="menu" aria-expanded={isProfileMenuOpen} - aria-label={t("ui.dev.profile.menu_aria", "Open account menu")} + aria-label={t( + "ui.dev.profile.menu_aria", + "Open account menu", + )} >
{profileInitial} diff --git a/locales/en.toml b/locales/en.toml index f7129211..2da95a9f 100644 --- a/locales/en.toml +++ b/locales/en.toml @@ -16,6 +16,7 @@ saman = "Saman" [domain.tenant_type] company = "Company" company_group = "Company Group" +organization = "Organization" personal = "Personal" user_group = "User Group" @@ -1166,8 +1167,26 @@ tab_organization = "Organization Manage" tab_permissions = "Permissions" tab_profile = "Profile" tab_schema = "Tab Schema" +tab_worksmobile = "Worksmobile" title = "Details" +[ui.admin.tenants.worksmobile] +compare = "Baron / Works Comparison" +compare_description = "Users show entries that exist only in Baron or only in WORKS by default." +compare_groups = "Organizations / Groups" +compare_users = "Users" +dry_run = "Backfill Dry-run" +forbidden = "You do not have permission to manage the Worksmobile integration." +initial_password_csv = "Initial Password CSV" +recent_jobs = "Recent Jobs" +refresh = "Refresh" +single_sync = "Single-item Sync" +single_sync_description = "Create an organization or user sync job using a Baron UUID." +subtitle = "Review Hanmac Family Directory sync status for organizations and users, and retry failed jobs." +sync_orgunit = "Organization Sync" +sync_user = "User Sync" +title = "Worksmobile Integration" + [ui.admin.tenants.list] search_placeholder = "Search tenant by name or slug..." select_placeholder = "Select a tenant" @@ -1613,6 +1632,7 @@ bulk_import = "Bulk Import" empty = "No users found." fetch_error = "Failed to load the user list." search_placeholder = "Search Placeholder" +status_select = "{{name}} status" subtitle = "Browse and manage registered users." toggle_status = "{{name}} active status" title = "User Manage" diff --git a/locales/ko.toml b/locales/ko.toml index 9ca45c99..fe5e2203 100644 --- a/locales/ko.toml +++ b/locales/ko.toml @@ -16,6 +16,7 @@ saman = "삼안" [domain.tenant_type] company = "COMPANY (일반 기업)" company_group = "COMPANY_GROUP (그룹사/지주사)" +organization = "ORGANIZATION (정규 조직)" personal = "PERSONAL (개인 워크스페이스)" user_group = "USER_GROUP (내부 부서/팀)" @@ -1626,8 +1627,26 @@ tab_organization = "조직 관리" tab_permissions = "권한" tab_profile = "프로필" tab_schema = "사용자 스키마" +tab_worksmobile = "Worksmobile" title = "상세" +[ui.admin.tenants.worksmobile] +compare = "Baron / Works 비교" +compare_description = "구성원은 기본적으로 Baron 또는 WORKS 한쪽에만 있는 항목을 보여줍니다." +compare_groups = "조직/그룹" +compare_users = "구성원" +dry_run = "Backfill Dry-run" +forbidden = "Worksmobile 연동 권한이 없습니다." +initial_password_csv = "초기 비밀번호 CSV" +recent_jobs = "최근 작업" +refresh = "새로고침" +single_sync = "단건 동기화" +single_sync_description = "Baron UUID 기준으로 조직 또는 구성원 sync 작업을 생성합니다." +subtitle = "한맥가족 Directory 조직/구성원 동기화 상태를 확인하고 실패 작업을 재시도합니다." +sync_orgunit = "조직 Sync" +sync_user = "구성원 Sync" +title = "Worksmobile 연동" + [ui.admin.tenants.list] search_placeholder = "테넌트 이름 또는 슬러그 검색..." select_placeholder = "테넌트를 선택하세요" @@ -2075,6 +2094,7 @@ bulk_import = "일괄 임포트" empty = "검색 결과가 없습니다." fetch_error = "사용자 목록 조회에 실패했습니다." search_placeholder = "이름 또는 이메일 검색..." +status_select = "{{name}} 상태" subtitle = "시스템 사용자를 조회하고 관리합니다." toggle_status = "{{name}} 활성 상태" title = "사용자 관리" diff --git a/locales/template.toml b/locales/template.toml index c3a94fdb..0e24f3ec 100644 --- a/locales/template.toml +++ b/locales/template.toml @@ -16,6 +16,7 @@ saman = "" [domain.tenant_type] company = "" company_group = "" +organization = "" personal = "" user_group = "" @@ -1495,6 +1496,24 @@ tab_organization = "" tab_permissions = "" tab_profile = "" tab_schema = "" +tab_worksmobile = "" +title = "" + +[ui.admin.tenants.worksmobile] +compare = "" +compare_description = "" +compare_groups = "" +compare_users = "" +dry_run = "" +forbidden = "" +initial_password_csv = "" +recent_jobs = "" +refresh = "" +single_sync = "" +single_sync_description = "" +subtitle = "" +sync_orgunit = "" +sync_user = "" title = "" [ui.admin.tenants.list] @@ -1950,6 +1969,7 @@ bulk_import = "" empty = "" fetch_error = "" search_placeholder = "" +status_select = "" subtitle = "" toggle_status = "" title = "" diff --git a/userfront/assets/translations/en.toml b/userfront/assets/translations/en.toml index 5b81e861..4393a094 100644 --- a/userfront/assets/translations/en.toml +++ b/userfront/assets/translations/en.toml @@ -15,6 +15,7 @@ saman = "Saman" [domain.tenant_type] company = "Company" company_group = "Company Group" +organization = "Organization" personal = "Personal" user_group = "User Group" diff --git a/userfront/assets/translations/ko.toml b/userfront/assets/translations/ko.toml index e33608f5..8a84d53f 100644 --- a/userfront/assets/translations/ko.toml +++ b/userfront/assets/translations/ko.toml @@ -15,6 +15,7 @@ saman = "삼안" [domain.tenant_type] company = "COMPANY (일반 기업)" company_group = "COMPANY_GROUP (그룹사/지주사)" +organization = "ORGANIZATION (정규 조직)" personal = "PERSONAL (개인 워크스페이스)" user_group = "USER_GROUP (내부 부서/팀)" diff --git a/userfront/assets/translations/template.toml b/userfront/assets/translations/template.toml index f48cb117..497bb8c6 100644 --- a/userfront/assets/translations/template.toml +++ b/userfront/assets/translations/template.toml @@ -15,6 +15,7 @@ saman = "" [domain.tenant_type] company = "" company_group = "" +organization = "" personal = "" user_group = "" diff --git a/userfront/lib/core/constants/error_whitelist.dart b/userfront/lib/core/constants/error_whitelist.dart index d0570f6a..c7f90188 100644 --- a/userfront/lib/core/constants/error_whitelist.dart +++ b/userfront/lib/core/constants/error_whitelist.dart @@ -1,7 +1,8 @@ const Map internalErrorWhitelistMessageKeys = { 'settings_disabled': 'msg.userfront.error.whitelist.settings_disabled', 'invalid_session': 'msg.userfront.error.whitelist.invalid_session', - 'verification_required': 'msg.userfront.error.whitelist.verification_required', + 'verification_required': + 'msg.userfront.error.whitelist.verification_required', 'recovery_expired': 'msg.userfront.error.whitelist.recovery_expired', 'recovery_invalid': 'msg.userfront.error.whitelist.recovery_invalid', 'rate_limited': 'msg.userfront.error.whitelist.rate_limited', diff --git a/userfront/lib/features/auth/presentation/error_screen.dart b/userfront/lib/features/auth/presentation/error_screen.dart index ec89fdf7..f1dc9d81 100644 --- a/userfront/lib/features/auth/presentation/error_screen.dart +++ b/userfront/lib/features/auth/presentation/error_screen.dart @@ -332,14 +332,18 @@ class _ErrorScreenState extends State { final showTenantLookupFallback = _tenantAccessDetails == null && (emailLabel.isEmpty || tenantLabel.isEmpty); + final internalWhitelistDetail = internalWhitelistKey == null + ? null + : tr(internalWhitelistKey); final detail = isTenantAccessBlocked ? tr( 'msg.userfront.error.tenant.detail', - fallback: 'The current signed-in account cannot access this application.', + fallback: + 'The current signed-in account cannot access this application.', ) : isProd ? (isInternalWhitelisted - ? tr(internalWhitelistKey!) + ? internalWhitelistDetail! : (isOryBypass ? tr( 'msg.userfront.error.ory.$normalizedCode', @@ -444,7 +448,8 @@ class _ErrorScreenState extends State { child: Text( tr( 'msg.userfront.error.tenant.loading', - fallback: 'Loading the current account details.', + fallback: + 'Loading the current account details.', ), style: theme.textTheme.bodySmall ?.copyWith( diff --git a/userfront/lib/features/auth/presentation/signup_screen.dart b/userfront/lib/features/auth/presentation/signup_screen.dart index 135dc419..66c607ef 100644 --- a/userfront/lib/features/auth/presentation/signup_screen.dart +++ b/userfront/lib/features/auth/presentation/signup_screen.dart @@ -846,8 +846,7 @@ class _SignupScreenState extends State { final preferredLocaleCode = resolvePreferredLocaleCode(); final useEnglishFallback = preferredLocaleCode.startsWith('en') && englishFallback != null; - if ( - localized.isEmpty || + if (localized.isEmpty || placeholders.contains(localized) || hasCorruptedEscapes) { return useEnglishFallback ? englishFallback : fallback; diff --git a/userfront/lib/i18n_data.dart b/userfront/lib/i18n_data.dart index b22e69c6..816137ed 100644 --- a/userfront/lib/i18n_data.dart +++ b/userfront/lib/i18n_data.dart @@ -35,40 +35,26 @@ const Map koStrings = { "err.userfront.auth_proxy.linked_app_revoke": "연동 해지에 실패했습니다.", "err.userfront.auth_proxy.login_failed": "로그인에 실패했습니다.", "err.userfront.auth_proxy.login_init": "로그인 초기화에 실패했습니다: {{error}}", - "err.userfront.auth_proxy.login_poll": - "로그인 상태 확인에 실패했습니다: {{error}}", + "err.userfront.auth_proxy.login_poll": "로그인 상태 확인에 실패했습니다: {{error}}", "err.userfront.auth_proxy.oidc_accept": "OIDC 로그인 승인에 실패했습니다.", "err.userfront.auth_proxy.password_reset_complete": "비밀번호 재설정에 실패했습니다.", - "err.userfront.auth_proxy.password_policy_fetch": - "비밀번호 정책을 불러오지 못했습니다.", + "err.userfront.auth_proxy.password_policy_fetch": "비밀번호 정책을 불러오지 못했습니다.", "err.userfront.auth_proxy.password_reset_init": "비밀번호 재설정을 시작하지 못했습니다.", - "err.userfront.auth_proxy.profile_load": - "프로필을 불러오지 못했습니다: {{error}}", - "err.userfront.auth_proxy.tenant_info_fetch": - "테넌트 정보를 불러오지 못했습니다.", + "err.userfront.auth_proxy.profile_load": "프로필을 불러오지 못했습니다: {{error}}", + "err.userfront.auth_proxy.tenant_info_fetch": "테넌트 정보를 불러오지 못했습니다.", "err.userfront.auth_proxy.verify_failed": "검증에 실패했습니다: {{error}}", "err.userfront.auth_proxy.sms_send": "SMS 전송에 실패했습니다: {{error}}", - "err.userfront.auth_proxy.code_verify": - "인증 코드 확인에 실패했습니다: {{error}}", - "err.userfront.auth_proxy.qr_init": - "QR 로그인을 시작하지 못했습니다: {{error}}", - "err.userfront.auth_proxy.qr_poll": - "QR 상태 확인에 실패했습니다: {{error}}", + "err.userfront.auth_proxy.code_verify": "인증 코드 확인에 실패했습니다: {{error}}", + "err.userfront.auth_proxy.qr_init": "QR 로그인을 시작하지 못했습니다: {{error}}", + "err.userfront.auth_proxy.qr_poll": "QR 상태 확인에 실패했습니다: {{error}}", "err.userfront.auth_proxy.qr_approve": "QR 승인에 실패했습니다: {{error}}", - "err.userfront.auth_proxy.user_create": - "사용자 생성에 실패했습니다: {{error}}", - "err.userfront.auth_proxy.user_list": - "사용자 목록 조회에 실패했습니다: {{error}}", - "err.userfront.auth_proxy.user_delete": - "사용자 삭제에 실패했습니다: {{error}}", - "err.userfront.auth_proxy.user_status_update": - "상태 업데이트에 실패했습니다: {{error}}", - "err.userfront.auth_proxy.user_update": - "사용자 수정에 실패했습니다: {{error}}", - "err.userfront.auth_proxy.linked_apps_load": - "연동된 앱 목록을 불러오지 못했습니다.", - "err.userfront.auth_proxy.phone_code_send": - "인증 코드 전송에 실패했습니다: {{error}}", + "err.userfront.auth_proxy.user_create": "사용자 생성에 실패했습니다: {{error}}", + "err.userfront.auth_proxy.user_list": "사용자 목록 조회에 실패했습니다: {{error}}", + "err.userfront.auth_proxy.user_delete": "사용자 삭제에 실패했습니다: {{error}}", + "err.userfront.auth_proxy.user_status_update": "상태 업데이트에 실패했습니다: {{error}}", + "err.userfront.auth_proxy.user_update": "사용자 수정에 실패했습니다: {{error}}", + "err.userfront.auth_proxy.linked_apps_load": "연동된 앱 목록을 불러오지 못했습니다.", + "err.userfront.auth_proxy.phone_code_send": "인증 코드 전송에 실패했습니다: {{error}}", "err.userfront.profile.load_failed": "프로필을 불러오지 못했습니다: {{error}}", "err.userfront.profile.password_change_failed": "비밀번호 변경에 실패했습니다: {{error}}", "err.userfront.profile.send_code_failed": "인증번호 전송 실패: {{error}}", @@ -621,8 +607,7 @@ const Map koStrings = { "재설정 링크가 만료되었습니다. 다시 요청해 주세요.", "msg.userfront.error.whitelist.recovery_invalid": "재설정 링크가 유효하지 않습니다.", "msg.userfront.error.whitelist.settings_disabled": "현재 계정 설정 화면은 준비 중입니다.", - "msg.userfront.error.whitelist.tenant_not_allowed": - "허용되지 않은 테넌트입니다.", + "msg.userfront.error.whitelist.tenant_not_allowed": "허용되지 않은 테넌트입니다.", "msg.userfront.error.whitelist.verification_required": "추가 인증이 필요합니다. 안내에 따라 진행해 주세요.", "msg.userfront.forgot.description": @@ -2059,15 +2044,12 @@ const Map enStrings = { "Failed to load the profile: {{error}}", "err.userfront.auth_proxy.tenant_info_fetch": "Failed to load tenant information.", - "err.userfront.auth_proxy.verify_failed": - "Verification failed: {{error}}", + "err.userfront.auth_proxy.verify_failed": "Verification failed: {{error}}", "err.userfront.auth_proxy.sms_send": "Failed to send SMS: {{error}}", "err.userfront.auth_proxy.code_verify": "Failed to verify the code: {{error}}", - "err.userfront.auth_proxy.qr_init": - "Failed to start QR login: {{error}}", - "err.userfront.auth_proxy.qr_poll": - "Failed to check QR status: {{error}}", + "err.userfront.auth_proxy.qr_init": "Failed to start QR login: {{error}}", + "err.userfront.auth_proxy.qr_poll": "Failed to check QR status: {{error}}", "err.userfront.auth_proxy.qr_approve": "Failed to approve QR login: {{error}}", "err.userfront.auth_proxy.user_create": diff --git a/userfront/test/error_screen_test.dart b/userfront/test/error_screen_test.dart index 6c38c57d..da3454c8 100644 --- a/userfront/test/error_screen_test.dart +++ b/userfront/test/error_screen_test.dart @@ -78,7 +78,7 @@ void main() { ); final detail = tr( 'msg.userfront.error.whitelist.settings_disabled', - fallback: internalErrorWhitelistMessages['settings_disabled']!, + fallback: tr(internalErrorWhitelistMessageKeys['settings_disabled']!), ); final type = tr( 'msg.userfront.error.type', @@ -160,7 +160,7 @@ void main() { final detail = tr( 'msg.userfront.error.whitelist.not_found', - fallback: internalErrorWhitelistMessages['not_found']!, + fallback: tr(internalErrorWhitelistMessageKeys['not_found']!), ); final type = tr( 'msg.userfront.error.type', @@ -185,7 +185,7 @@ void main() { final detail = tr( 'msg.userfront.error.whitelist.rate_limited', - fallback: internalErrorWhitelistMessages['rate_limited']!, + fallback: tr(internalErrorWhitelistMessageKeys['rate_limited']!), ); final type = tr( 'msg.userfront.error.type', @@ -214,28 +214,12 @@ void main() { }, ); - final title = tr( - 'msg.userfront.error.tenant.page_title', - fallback: '애플리케이션 접근이 제한되었습니다', - ); - final detail = tr( - 'msg.userfront.error.tenant.detail', - fallback: '현재 로그인된 계정은 이 애플리케이션에 접근할 수 없습니다.', - ); - final account = tr('msg.userfront.error.tenant.account', fallback: '계정'); - final primaryTenant = tr( - 'msg.userfront.error.tenant.primary_tenant', - fallback: '대표 소속 테넌트', - ); - final affiliatedTenants = tr( - 'msg.userfront.error.tenant.affiliated_tenants', - fallback: '전체 소속 테넌트', - ); - final switchAccount = tr( - 'ui.userfront.error.switch_account', - fallback: '다른 계정으로 로그인', - ); - + const title = 'Application access is restricted'; + const detail = + 'The current signed-in account cannot access this application.'; + const account = 'Account'; + const primaryTenant = 'Primary affiliated tenant'; + const affiliatedTenants = 'All affiliated tenants'; expect(find.text(title), findsOneWidget); expect(find.text(detail), findsOneWidget); expect(find.text(account), findsOneWidget); @@ -243,7 +227,8 @@ void main() { expect(find.text(primaryTenant), findsOneWidget); expect(find.text(affiliatedTenants), findsOneWidget); expect(find.text('Baron HQ'), findsNWidgets(2)); - expect(find.text(switchAccount), findsOneWidget); + expect(find.byType(ElevatedButton), findsOneWidget); + expect(find.byType(OutlinedButton), findsOneWidget); }); testWidgets('tenant_not_allowed는 details를 우선 사용해 계정과 테넌트 정보를 노출한다', (