Coverage for app / backend / src / tests / test_calendar_events.py: 100%

69 statements  

« prev     ^ index     » next       coverage.py v7.14.0, created at 2026-05-13 02:44 +0000

1import re 

2from datetime import timedelta 

3 

4import ics 

5import pytest 

6 

7from couchers.email.calendar_events import create_host_request_ics 

8from couchers.i18n.context import LocalizationContext 

9from couchers.proto import conversations_pb2, requests_pb2 

10from couchers.proto.requests_pb2 import HostRequest 

11from couchers.utils import today 

12from tests.fixtures.db import generate_user 

13from tests.fixtures.misc import email_fields, mock_notification_email 

14from tests.fixtures.sessions import requests_session 

15from tests.test_requests import valid_request_text 

16 

17 

18@pytest.fixture(autouse=True) 

19def _(testconfig): 

20 pass 

21 

22 

23def test_ics_content(): 

24 host_request = HostRequest( 

25 host_request_id=42, from_date="2000-01-01", to_date="2000-01-02", hosting_city="New York" 

26 ) 

27 

28 ics = create_host_request_ics( 

29 host_request, other_name="Bob", hosting=True, loc_context=LocalizationContext.en_utc() 

30 ) 

31 ics = ics.replace("\r\n", "\n") 

32 

33 # Strip the domain in the UID, which depends on environment variables 

34 ics = re.sub( 

35 r"^UID:.*@(.*)$", lambda match: match[0].removesuffix(match[1]) + "<stripped>", ics, flags=re.MULTILINE 

36 ) 

37 

38 # Strip the domain in the URL and DESCRIPTION, which depends on environment variables 

39 ics = re.sub(r"^(DESCRIPTION|URL):(.*)/(\d+)", r"\1:<stripped>/\3", ics, flags=re.MULTILINE) 

40 

41 assert ( 

42 ics 

43 == """ 

44BEGIN:VCALENDAR 

45VERSION:2.0 

46PRODID:-//Couchers.org//Couchers//EN 

47BEGIN:VEVENT 

48DTSTART;VALUE=DATE:20000101 

49DTEND;VALUE=DATE:20000103 

50DESCRIPTION:<stripped>/42 

51LOCATION:New York 

52SUMMARY:Hosting Bob 

53UID:host_request.42@<stripped> 

54URL:<stripped>/42 

55END:VEVENT 

56END:VCALENDAR 

57 """.strip() 

58 ) 

59 

60 

61def test_host_request_attachments(db, moderator): 

62 host, host_token = generate_user(complete_profile=True) 

63 surfer, surfer_token = generate_user(complete_profile=True) 

64 

65 from_date = today() + timedelta(days=2) 

66 to_date = today() + timedelta(days=3) 

67 

68 # Send the host request, no calendar attachment yet 

69 with requests_session(surfer_token) as api: 

70 hr_id = api.CreateHostRequest( 

71 requests_pb2.CreateHostRequestReq( 

72 host_user_id=host.id, 

73 from_date=from_date.isoformat(), 

74 to_date=to_date.isoformat(), 

75 text=valid_request_text("can i stay plz"), 

76 ) 

77 ).host_request_id 

78 

79 with mock_notification_email() as mock: 

80 moderator.approve_host_request(hr_id) 

81 

82 mock.assert_called_once() 

83 e = email_fields(mock) 

84 assert "request" in e.subject and surfer.name in e.subject 

85 assert not e.attachments 

86 

87 # Host accepts, surfer gets a calendar attachment 

88 with requests_session(host_token) as api: 

89 with mock_notification_email() as mock: 

90 api.RespondHostRequest( 

91 requests_pb2.RespondHostRequestReq( 

92 host_request_id=hr_id, 

93 status=conversations_pb2.HOST_REQUEST_STATUS_ACCEPTED, 

94 text="Accepting host request", 

95 ) 

96 ) 

97 

98 e = email_fields(mock) 

99 assert "accept" in e.subject and host.name in e.subject 

100 ics_event = _get_email_ics_attachment_calendar_event(e) 

101 assert not ics_event.status 

102 assert ics_event.begin.date() == from_date 

103 assert ics_event.end.date() == (to_date + timedelta(days=1)) 

104 assert ics_event.name == f"Surfing with {host.name}" 

105 assert ics_event.location == host.city 

106 

107 # Surfer confirms, hosts gets a calendar attachment 

108 with requests_session(surfer_token) as api: 

109 with mock_notification_email() as mock: 

110 api.RespondHostRequest( 

111 requests_pb2.RespondHostRequestReq( 

112 host_request_id=hr_id, 

113 status=conversations_pb2.HOST_REQUEST_STATUS_CONFIRMED, 

114 text="Confirming host request", 

115 ) 

116 ) 

117 

118 e = email_fields(mock) 

119 assert "confirm" in e.subject and surfer.name in e.subject 

120 ics_event = _get_email_ics_attachment_calendar_event(e) 

121 assert not ics_event.status 

122 assert ics_event.name == f"Hosting {surfer.name}" 

123 

124 # Host cancels, surfer gets a calendar attachment 

125 with requests_session(surfer_token) as api: 

126 with mock_notification_email() as mock: 

127 api.RespondHostRequest( 

128 requests_pb2.RespondHostRequestReq( 

129 host_request_id=hr_id, 

130 status=conversations_pb2.HOST_REQUEST_STATUS_CANCELLED, 

131 text="Cancelling host request", 

132 ) 

133 ) 

134 

135 e = email_fields(mock) 

136 assert "cancel" in e.subject and surfer.name in e.subject 

137 ics_event = _get_email_ics_attachment_calendar_event(e) 

138 assert ics_event.status == "CANCELLED" 

139 

140 

141def _get_email_ics_attachment_calendar_event(e) -> ics.Event: 

142 assert len(e.attachments or []) == 1 

143 ics_attachment = e.attachments[0] 

144 assert ics_attachment.filename.endswith(".ics") 

145 ics_calendar = ics.Calendar(ics_attachment.data.decode("utf-8")) 

146 assert len(ics_calendar.events) == 1 

147 return next(iter(ics_calendar.events))