Coverage for src/lexigram/admin/validation/rules.py: 100%

34 statements  

« prev     ^ index     » next       coverage.py v7.15.4, created at 2026-08-21 14:56 +0800

1"""Admin-specific validation rules. 

2 

3Extends the core ``Rule`` ABC with admin domain constraints for email 

4addresses, passwords, and usernames. 

5""" 

6 

7from __future__ import annotations 

8 

9import re 

10from typing import Any 

11 

12from lexigram.contracts.exceptions.domain import FieldError 

13from lexigram.result import Err, Ok, Result 

14from lexigram.validation.rules import AbstractRule 

15 

16_ADMIN_EMAIL_RE = re.compile(r"^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$") 

17 

18_USERNAME_RE = re.compile(r"^[a-zA-Z0-9_]{3,64}$") 

19 

20 

21class IsValidAdminEmail(AbstractRule): 

22 """Validate that a value is a properly formatted admin email address. 

23 

24 Requires an ``@`` separator, a local part, and a domain with at least 

25 one dot and a two-or-more character TLD. 

26 """ 

27 

28 def __call__(self, value: Any, field_name: str) -> Result[Any, FieldError]: 

29 if value is not None and not _ADMIN_EMAIL_RE.match(str(value)): 

30 return Err( 

31 FieldError( 

32 field=field_name, 

33 message=f"{field_name} is not a valid email address", 

34 code="invalid_admin_email", 

35 ) 

36 ) 

37 return Ok(value) 

38 

39 

40class StrongPassword(AbstractRule): 

41 """Validate that a password meets admin strength requirements. 

42 

43 Rules: 

44 - Minimum 8 characters 

45 - At least one uppercase letter 

46 - At least one lowercase letter 

47 - At least one digit 

48 - At least one special character (non-alphanumeric) 

49 """ 

50 

51 def __call__(self, value: Any, field_name: str) -> Result[Any, FieldError]: 

52 if value is None: 

53 return Ok(value) 

54 pwd = str(value) 

55 if len(pwd) < 8: 

56 return Err( 

57 FieldError( 

58 field=field_name, 

59 message=f"{field_name} must be at least 8 characters", 

60 code="password_too_short", 

61 ) 

62 ) 

63 if not re.search(r"[A-Z]", pwd): 

64 return Err( 

65 FieldError( 

66 field=field_name, 

67 message=f"{field_name} must contain at least one uppercase letter", 

68 code="password_no_uppercase", 

69 ) 

70 ) 

71 if not re.search(r"[a-z]", pwd): 

72 return Err( 

73 FieldError( 

74 field=field_name, 

75 message=f"{field_name} must contain at least one lowercase letter", 

76 code="password_no_lowercase", 

77 ) 

78 ) 

79 if not re.search(r"\d", pwd): 

80 return Err( 

81 FieldError( 

82 field=field_name, 

83 message=f"{field_name} must contain at least one digit", 

84 code="password_no_digit", 

85 ) 

86 ) 

87 if not re.search(r"[^a-zA-Z0-9]", pwd): 

88 return Err( 

89 FieldError( 

90 field=field_name, 

91 message=f"{field_name} must contain at least one special character", 

92 code="password_no_special", 

93 ) 

94 ) 

95 return Ok(value) 

96 

97 

98class IsValidUsername(AbstractRule): 

99 """Validate that a value is an acceptable admin username. 

100 

101 Accepts only alphanumeric characters and underscores, with a length 

102 between 3 and 64 characters (inclusive). 

103 """ 

104 

105 def __call__(self, value: Any, field_name: str) -> Result[Any, FieldError]: 

106 if value is not None and not _USERNAME_RE.match(str(value)): 

107 return Err( 

108 FieldError( 

109 field=field_name, 

110 message=( 

111 f"{field_name} must be 3-64 alphanumeric characters " 

112 "or underscores" 

113 ), 

114 code="invalid_username", 

115 ) 

116 ) 

117 return Ok(value)