Coverage for app / backend / src / couchers / helpers / postal_verification.py: 100%

12 statements  

« prev     ^ index     » next       coverage.py v7.13.2, created at 2026-02-03 06:18 +0000

1import secrets 

2from typing import cast 

3 

4from sqlalchemy import exists, select 

5from sqlalchemy.orm import Session 

6 

7from couchers.constants import ( 

8 POSTAL_VERIFICATION_CODE_ALPHABET, 

9 POSTAL_VERIFICATION_CODE_LENGTH, 

10) 

11from couchers.models import User 

12from couchers.models.postal_verification import PostalVerificationAttempt 

13 

14 

15def generate_postal_verification_code() -> str: 

16 """ 

17 Generates a random 6-character uppercase alphanumeric code. 

18 Uses a reduced alphabet to avoid confusion (no I, O, 0, 1). 

19 """ 

20 return "".join(secrets.choice(POSTAL_VERIFICATION_CODE_ALPHABET) for _ in range(POSTAL_VERIFICATION_CODE_LENGTH)) 

21 

22 

23def has_postal_verification(session: Session, user: User) -> bool: 

24 """ 

25 Check if user has valid postal verification. 

26 

27 Similar to strong verification, we query the database rather than 

28 storing a denormalized flag on the user. 

29 """ 

30 result = session.execute( 

31 select( 

32 exists( 

33 select(PostalVerificationAttempt) 

34 .where(PostalVerificationAttempt.user_id == user.id) 

35 .where(PostalVerificationAttempt.is_valid) 

36 ) 

37 ) 

38 ).scalar() 

39 

40 return cast(bool, result)