Coverage for functions \ flipdare \ mailer \ user \ voting_email.py: 85%

68 statements  

« prev     ^ index     » next       coverage.py v7.13.0, created at 2026-05-08 12:22 +1000

1#!/usr/bin/env python 

2# Copyright (c) 2026 Flipdare Pty Ltd. All rights reserved. 

3# 

4# This file is part of Flipdare's proprietary software and contains 

5# confidential and copyrighted material. Unauthorised copying, 

6# modification, distribution, or use of this file is strictly 

7# prohibited without prior written permission from Flipdare Pty Ltd. 

8# 

9# This software includes third-party components licensed under MIT, 

10# BSD, and Apache 2.0 licences. See THIRD_PARTY_NOTICES for details. 

11# 

12 

13from __future__ import annotations 

14 

15from typing import Literal, Self, override 

16from flipdare.app_globals import truncate_string 

17from flipdare.mailer._jinja_email_template import JinjaEmailTemplate 

18from flipdare.mailer.app_email_params import AppEmailParams 

19from flipdare.mailer.app_email_type import AppEmailType 

20from flipdare.generated.schema.email.subject.user.vote_subject_schema import VoteSubjectSchema 

21from flipdare.generated.schema.email.body.user.voting_email_schema import VotingEmailSchema 

22from flipdare.generated.shared.app_deep_link import AppDeepLink 

23from flipdare.generated.shared.model.dare.ballot_result import BallotResult 

24from flipdare.message.vote_message import VoteMessage 

25from flipdare.wrapper import DareWrapper, UserWrapper 

26 

27__all__ = ["VotingEmail"] 

28 

29type VotingEmailType = Literal[ 

30 AppEmailType.USR_VOTE_STARTED, 

31 AppEmailType.USR_VOTE_REMINDER, 

32 AppEmailType.USR_VOTE_DECISION, 

33] 

34 

35 

36class VotingEmail(JinjaEmailTemplate[VotingEmailSchema]): 

37 

38 SCHEMA_CLASS = VotingEmailSchema 

39 

40 def __init__( 

41 self, 

42 to_user: UserWrapper, 

43 voting_email_type: VotingEmailType, 

44 dare_id: str, 

45 dare_name: str, 

46 title: str, 

47 status_str: str, 

48 description: str, 

49 ) -> None: 

50 data = self._build_data( 

51 to_user=to_user, 

52 dare_id=dare_id, 

53 title=title, 

54 description=description, 

55 status_str=status_str, 

56 ) 

57 

58 super().__init__( 

59 data=data, 

60 params=AppEmailParams( 

61 email_type=voting_email_type, 

62 schema=VoteSubjectSchema( 

63 name=dare_name, 

64 ), 

65 ), 

66 ) 

67 

68 @classmethod 

69 def from_dare( 

70 cls, 

71 dare: DareWrapper, 

72 to_user: UserWrapper, 

73 ) -> Self: 

74 dare_id = dare.doc_id 

75 dare_name = dare.short_description 

76 status_str = VoteMessage.STARTED 

77 

78 return cls( 

79 to_user=to_user, 

80 dare_id=dare_id, 

81 dare_name=dare_name, 

82 title=dare.title, 

83 description=dare.message, 

84 status_str=status_str, 

85 voting_email_type=AppEmailType.USR_VOTE_STARTED, 

86 ) 

87 

88 @classmethod 

89 def from_dare_result( 

90 cls, 

91 dare: DareWrapper, 

92 to_user: UserWrapper, 

93 result: BallotResult | None = None, 

94 ) -> Self: 

95 dare_id = dare.doc_id 

96 dare_name = dare.short_description 

97 status_str = VoteMessage.from_result(result) 

98 

99 return cls( 

100 to_user=to_user, 

101 dare_id=dare_id, 

102 dare_name=dare_name, 

103 title=dare.title, 

104 description=dare.message, 

105 status_str=status_str, 

106 voting_email_type=VotingEmail.type_from_result(result), 

107 ) 

108 

109 @staticmethod 

110 def type_from_result(result: BallotResult | None) -> VotingEmailType: 

111 if result is None: 

112 return AppEmailType.USR_VOTE_STARTED 

113 

114 match result: 

115 case ( 

116 BallotResult.TIE 

117 | BallotResult.EXPIRED 

118 | BallotResult.ACCEPTED 

119 | BallotResult.AUTO_ACCEPTED 

120 | BallotResult.REJECTED 

121 | BallotResult.AUTO_REJECTED 

122 ): 

123 return AppEmailType.USR_VOTE_DECISION 

124 case BallotResult.NOT_ENOUGH_VOTES: 

125 return AppEmailType.USR_VOTE_REMINDER 

126 

127 @override 

128 def newline_fields(self) -> list[str]: 

129 return ["description"] 

130 

131 @property 

132 @override 

133 def data(self) -> VotingEmailSchema: 

134 assert isinstance(self._data, dict) # narrowing, we known we have a dict.. 

135 return self._data 

136 

137 @property 

138 def status(self) -> str: 

139 return self.data["status"] 

140 

141 @property 

142 def dare_link(self) -> str: 

143 return self.data["dare_link"] 

144 

145 @property 

146 def dare_link_text(self) -> str: 

147 return self.data["dare_link_text"] 

148 

149 @property 

150 def to_name(self) -> str: 

151 return self.data["to_name"] 

152 

153 @property 

154 def description(self) -> str: 

155 return self.data["description"] 

156 

157 def _build_data( 

158 self, 

159 to_user: UserWrapper, 

160 dare_id: str, 

161 title: str, 

162 description: str, 

163 status_str: str, 

164 ) -> VotingEmailSchema: 

165 

166 to_name = to_user.model.contact_name 

167 dare_link = AppDeepLink.DARE.app_link(dare_id) 

168 dare_link_text = truncate_string(title) 

169 

170 return VotingEmailSchema( 

171 to_name=to_name, 

172 status=status_str, 

173 dare_link=dare_link, 

174 dare_link_text=dare_link_text, 

175 description=description, 

176 )