Skip to content

feat: integrate profile completeness check into captcha verification#9

Open
rezhajulio wants to merge 4 commits into
mainfrom
feat/captcha-profile-check
Open

feat: integrate profile completeness check into captcha verification#9
rezhajulio wants to merge 4 commits into
mainfrom
feat/captcha-profile-check

Conversation

@rezhajulio

Copy link
Copy Markdown
Owner

Summary

Combines captcha verification with a mandatory profile completeness check (photo + username) before unrestricting new members.

Changes

Captcha + Profile Check Integration

  • When a user clicks the captcha button, their profile is checked first
  • If incomplete, an alert shows what's missing (photo, username, or both) — user can retry
  • Timeout job is preserved until profile check passes, preventing users from getting stuck
  • Welcome message updated to inform users about profile requirements upfront

Handler Hardening

  • Added parse guard for malformed callback data (ValueError/IndexError)
  • Moved timeout job cancellation after successful unrestrict + DB cleanup (fixes bug where failed unrestrict would leave user stuck with no timeout)
  • Wrapped DB finalization in try/except to prevent unanswered callbacks

Test Improvements

  • Replaced fragile AsyncMock(is_complete=...) with real ProfileCheckResult dataclass in all mocks
  • Added tests: malformed callback data, unrestrict failure preserving timeout, incomplete profile (single/both missing), profile check API exception

Flow

  1. User joins → restricted → captcha message sent
  2. User clicks button → profile checked
  3. If incomplete → alert with missing items, timeout stays active
  4. If complete → unrestrict → cleanup DB → cancel timeout → success message

Tests: 555 passed, ~99.9% coverage

- Update captcha welcome message to inform users about profile requirements
- Add profile completeness check (photo + username) in captcha callback handler
- Reject verification with alert if profile is incomplete, keeping timeout active
- Fix double query.answer() bug — each code path now answers exactly once
- Add CAPTCHA_INCOMPLETE_PROFILE_MESSAGE constant for rejection alert
- Update and add tests for new captcha+profile flow (553 tests, 99% coverage)
- Add parse guard for malformed callback data (ValueError/IndexError)
- Move timeout job cancellation after successful unrestrict + DB cleanup
- Wrap DB finalization in try/except to prevent unanswered callbacks
- Replace AsyncMock with ProfileCheckResult dataclass in all test mocks
- Add tests for malformed callback data and unrestrict failure preserving timeout
Apply review feedback to feat/captcha-profile-check. The previous order
(unrestrict first, then DB cleanup) left Telegram state ahead of DB
state on a DB write failure: user unrestricted in Telegram, pending
captcha still in DB, timeout still armed. When the timeout fired,
handle_captcha_expiration re-interpreted the inconsistency as a
timeout event, sending contradictory success+timeout messages and a
false-positive "unrestricted" DM notification.

Reorder so the irreversible Telegram side effect happens LAST:
- DB finalization (remove_pending_captcha + start_new_user_probation)
  first. Reversible. If it fails, user stays restricted, DB row
  preserved, timeout armed, user can retry.
- unrestrict_user second. Irreversible. If it fails, DB row is gone
  but user is still restricted on Telegram; verify button is gone;
  user waits for admin. State is consistent.

Tests
- Replace hard-coded "Gagal memverifikasi..." literal with
  CAPTCHA_FAILED_VERIFICATION_MESSAGE constant in two tests
  (test_unknown_group_in_callback_rejects,
  test_malformed_callback_data_rejected) to remove drift risk.
- Rename test_unrestrict_failure_stops_execution ->
  test_unrestrict_failure_keeps_user_restricted and invert assertions
  for the new order (DB row is now None, edit_message_text NOT called).
- Add test_db_finalization_failure_keeps_user_restricted covering the
  DB-cleanup-failure path: unrestrict_user NOT called, DB row
  preserved, query shows failure alert.
- Delete redundant test_profile_check_exception_shows_error (strict
  subset of test_captcha_callback_profile_check_exception).
- Delete test_unrestrict_failure_preserves_timeout_job (its assertion
  was wrong for the new order; the renamed test covers the intent).
@rezhajulio rezhajulio force-pushed the feat/captcha-profile-check branch from 37a3c8c to 8c429e3 Compare June 17, 2026 14:37
Round-1 follow-ups on feat/captcha-profile-check. All items are
mechanical cleanups; no behavior change.

Constants
- Add CAPTCHA_PROFILE_CHECK_FAILED_MESSAGE. The previous behavior
  conflated "Telegram API failed" with "user failed verification"
  by showing the same generic CAPTCHA_FAILED_VERIFICATION_MESSAGE
  for both. The new message tells the user to retry in a few
  seconds, which is the right UX for a transient API error.

Handler
- captcha_callback_handler uses CAPTCHA_PROFILE_CHECK_FAILED_MESSAGE
  in the check_user_profile exception path. Other failure modes
  (parse error, missing pending captcha, unrestrict failure, DB
  finalization failure) still use CAPTCHA_FAILED_VERIFICATION_MESSAGE
  because those are deterministic verification failures, not API
  blips.

Tests
- Add _make_callback_query helper to TestCaptchaCallbackHandler.
  Refactor 10 tests to use it instead of repeating the 7-line
  MagicMock setup. Net diff: -55 lines.
- Parametrize test_captcha_callback_incomplete_profile_rejected
  across (photo-only-missing, both-missing, username-only-missing)
  using missing_items as the parameter and computing the expected
  CAPTCHA_INCOMPLETE_PROFILE_MESSAGE via MISSING_ITEMS_SEPARATOR.
  The username-only-missing case is new coverage.
- Parametrize test_malformed_callback_data_rejected across 4
  variants: non-numeric single token, missing parts (IndexError),
  int parse failure (ValueError), unparseable second part. Drop
  the empty-string case (CallbackQueryHandler's regex filters it
  before the handler sees it).
- Strengthen test_incomplete_profile_blocks_verification: replace
  "Lengkapi" + "username" substring assertions with a full-string
  match against CAPTCHA_INCOMPLETE_PROFILE_MESSAGE.format(missing_text="username").
- Add edit_message_text.assert_not_called() to the parametrized
  malformed test (Fix 5).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant