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

89 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-10-10 18:39 +0000

1from datetime import timedelta 

2 

3from google.protobuf import empty_pb2 

4 

5from couchers.db import session_scope 

6from couchers.proto import discussions_pb2, events_pb2, messages_pb2, requests_pb2, screens_pb2 

7from couchers.utils import datetime_to_iso8601_local, now, today 

8from tests.fixtures.db import generate_user 

9from tests.fixtures.sessions import ( 

10 account_session, 

11 discussions_session, 

12 events_session, 

13 requests_session, 

14 screens_session, 

15) 

16from tests.test_communities import create_community 

17from tests.test_requests import valid_request_text 

18 

19UPCOMING_STATUSES = [ 

20 messages_pb2.HOST_REQUEST_STATUS_ACCEPTED, 

21 messages_pb2.HOST_REQUEST_STATUS_CONFIRMED, 

22] 

23 

24 

25def _setup_accepted_host_request(token_surfer, host_user_id, moderator): 

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

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

28 with requests_session(token_surfer) as api: 

29 host_request_id = api.CreateHostRequest( 

30 requests_pb2.CreateHostRequestReq( 

31 host_user_id=host_user_id, 

32 from_date=from_date.isoformat(), 

33 to_date=to_date.isoformat(), 

34 text=valid_request_text(), 

35 ) 

36 ).host_request_id 

37 moderator.approve_host_request(host_request_id) 

38 return host_request_id 

39 

40 

41def test_GetDashboard_matches_individual_rpcs(db, moderator): 

42 user1, token1 = generate_user() 

43 user2, token2 = generate_user() 

44 

45 host_request_id = _setup_accepted_host_request(token1, user2.id, moderator) 

46 with requests_session(token2) as api: 

47 api.RespondHostRequest( 

48 requests_pb2.RespondHostRequestReq( 

49 host_request_id=host_request_id, 

50 status=messages_pb2.HOST_REQUEST_STATUS_ACCEPTED, 

51 text="Sure, come on over!", 

52 ) 

53 ) 

54 

55 # the dashboard response must be identical to fanning out to the individual RPCs with the 

56 # same parameters the web frontend uses 

57 with requests_session(token1) as api: 

58 surfing = api.ListHostRequests( 

59 requests_pb2.ListHostRequestsReq( 

60 only_sent=True, 

61 only_active=True, 

62 status_in=UPCOMING_STATUSES, 

63 sort_by=requests_pb2.HOST_REQUEST_SORT_BY_FROM_DATE, 

64 ) 

65 ) 

66 hosting = api.ListHostRequests( 

67 requests_pb2.ListHostRequestsReq( 

68 only_received=True, 

69 only_active=True, 

70 status_in=UPCOMING_STATUSES, 

71 sort_by=requests_pb2.HOST_REQUEST_SORT_BY_FROM_DATE, 

72 ) 

73 ) 

74 with events_session(token1) as api: 

75 my_events = api.ListMyEvents(events_pb2.ListMyEventsReq(page_size=3)) 

76 community_events = api.ListMyEvents( 

77 events_pb2.ListMyEventsReq( 

78 page_size=3, my_communities=True, my_communities_exclude_global=True, exclude_attending=True 

79 ) 

80 ) 

81 with discussions_session(token1) as api: 

82 discussions = api.ListMyCommunitiesDiscussions(discussions_pb2.ListMyCommunitiesDiscussionsReq(page_size=3)) 

83 with account_session(token1) as api: 

84 reminders = api.GetReminders(empty_pb2.Empty()) 

85 

86 with screens_session(token1) as api: 

87 res = api.GetDashboard(screens_pb2.GetDashboardReq()) 

88 

89 assert res.reminders == reminders 

90 assert res.surfing == surfing 

91 assert res.hosting == hosting 

92 assert res.my_events == my_events 

93 assert res.community_events == community_events 

94 assert res.discussions == discussions 

95 

96 # the surfer sees their upcoming trip under surfing, nothing under hosting 

97 assert len(res.surfing.host_requests) == 1 

98 assert res.surfing.host_requests[0].host_request_id == host_request_id 

99 assert res.surfing.host_requests[0].status == messages_pb2.HOST_REQUEST_STATUS_ACCEPTED 

100 assert len(res.hosting.host_requests) == 0 

101 

102 

103def test_GetDashboard_buckets_by_role(db, moderator): 

104 user1, token1 = generate_user() 

105 user2, token2 = generate_user() 

106 

107 host_request_id = _setup_accepted_host_request(token1, user2.id, moderator) 

108 with requests_session(token2) as api: 

109 api.RespondHostRequest( 

110 requests_pb2.RespondHostRequestReq( 

111 host_request_id=host_request_id, 

112 status=messages_pb2.HOST_REQUEST_STATUS_ACCEPTED, 

113 text="Sure, come on over!", 

114 ) 

115 ) 

116 

117 # the host sees the upcoming stay under hosting, nothing under surfing 

118 with screens_session(token2) as api: 

119 res = api.GetDashboard(screens_pb2.GetDashboardReq()) 

120 assert len(res.hosting.host_requests) == 1 

121 assert res.hosting.host_requests[0].host_request_id == host_request_id 

122 assert len(res.surfing.host_requests) == 0 

123 

124 

125def test_GetDashboard_community_events_excludes_attending(db, moderator): 

126 # Pins the exclude_attending parameter the web frontend sends: an event the user is 

127 # attending shows under my_events and must not also duplicate into community_events. 

128 user1, token1 = generate_user() 

129 user2, token2 = generate_user() 

130 

131 with session_scope() as session: 

132 # community_events excludes global-level communities, so nest down to a subregion 

133 world = create_community(session, 0, 100, "World", [user1, user2], [], None) 

134 macroregion = create_community(session, 0, 100, "Macroregion", [user1, user2], [], world) 

135 region = create_community(session, 0, 100, "Region", [user1, user2], [], macroregion) 

136 subregion = create_community(session, 0, 100, "Subregion", [user1, user2], [], region) 

137 community_id = subregion.id 

138 

139 start = now() 

140 

141 def make_event(hours: int) -> events_pb2.CreateEventReq: 

142 return events_pb2.CreateEventReq( 

143 title="Test Event", 

144 content="Test content.", 

145 location=events_pb2.EventLocation(address="Near Null Island", lat=0.1, lng=0.2), 

146 parent_community_id=community_id, 

147 start_datetime_iso8601_local=datetime_to_iso8601_local(start + timedelta(hours=hours)), 

148 end_datetime_iso8601_local=datetime_to_iso8601_local(start + timedelta(hours=hours + 1)), 

149 ) 

150 

151 with events_session(token2) as api: 

152 e_attending = api.CreateEvent(make_event(1)).event_id 

153 e_community_only = api.CreateEvent(make_event(2)).event_id 

154 

155 moderator.approve_event_occurrence(e_attending) 

156 moderator.approve_event_occurrence(e_community_only) 

157 

158 with events_session(token1) as api: 

159 api.SetEventAttendance( 

160 events_pb2.SetEventAttendanceReq(event_id=e_attending, attendance_state=events_pb2.ATTENDANCE_STATE_GOING) 

161 ) 

162 

163 with screens_session(token1) as api: 

164 res = api.GetDashboard(screens_pb2.GetDashboardReq()) 

165 

166 # the attended event shows under my_events (which with no flags includes all relationships)... 

167 assert e_attending in {e.event_id for e in res.my_events.events} 

168 # ...while community_events must exclude it (exclude_attending), showing only the rest 

169 assert [e.event_id for e in res.community_events.events] == [e_community_only] 

170 

171 

172def test_GetDashboard_empty(db): 

173 user, token = generate_user() 

174 with screens_session(token) as api: 

175 res = api.GetDashboard(screens_pb2.GetDashboardReq()) 

176 assert len(res.surfing.host_requests) == 0 

177 assert len(res.hosting.host_requests) == 0 

178 assert len(res.my_events.events) == 0 

179 assert len(res.community_events.events) == 0 

180 assert len(res.discussions.discussions) == 0 

181 assert len(res.reminders.reminders) == 0