Coverage for src/tests/test_activeness_probes.py: 100%

96 statements  

« prev     ^ index     » next       coverage.py v7.6.10, created at 2025-03-24 14:08 +0000

1from datetime import timedelta 

2from unittest.mock import patch 

3 

4import grpc 

5import pytest 

6from google.protobuf import empty_pb2 

7 

8from couchers import errors 

9from couchers.config import config 

10from couchers.db import session_scope 

11from couchers.jobs.enqueue import queue_job 

12from couchers.models import ActivenessProbe, ActivenessProbeStatus, HostingStatus, MeetupStatus 

13from couchers.sql import couchers_select as select 

14from couchers.utils import now 

15from proto import api_pb2, jail_pb2 

16from tests.test_fixtures import ( # noqa # noqa 

17 api_session, 

18 db, 

19 email_fields, 

20 generate_user, 

21 process_jobs, 

22 push_collector, 

23 real_jail_session, 

24 testconfig, 

25) 

26 

27 

28@pytest.fixture(autouse=True) 

29def _(testconfig): 

30 pass 

31 

32 

33def test_activeness_probes_happy_path_inactive(db, push_collector): 

34 user, token = generate_user( 

35 hosting_status=HostingStatus.can_host, 

36 meetup_status=MeetupStatus.wants_to_meetup, 

37 last_active=now() - timedelta(days=335), 

38 ) 

39 

40 with session_scope() as session: 

41 queue_job(session, "send_activeness_probes", empty_pb2.Empty()) 

42 

43 process_jobs() 

44 

45 with real_jail_session(token) as jail: 

46 res = jail.JailInfo(empty_pb2.Empty()) 

47 assert res.has_pending_activeness_probe 

48 assert res.jailed 

49 

50 res = jail.RespondToActivenessProbe( 

51 jail_pb2.RespondToActivenessProbeReq(response=jail_pb2.ACTIVENESS_PROBE_RESPONSE_NO_LONGER_ACTIVE) 

52 ) 

53 assert not res.has_pending_activeness_probe 

54 assert not res.jailed 

55 

56 with api_session(token) as api: 

57 res = api.GetUser(api_pb2.GetUserReq(user=user.username)) 

58 assert res.hosting_status == api_pb2.HOSTING_STATUS_CANT_HOST 

59 assert res.meetup_status == api_pb2.MEETUP_STATUS_WANTS_TO_MEETUP 

60 

61 push_collector.assert_user_has_single_matching( 

62 user.id, 

63 title="Are you still open to hosting on Couchers.org?", 

64 body="Please log in to confirm your hosting status.", 

65 ) 

66 

67 

68def test_activeness_probes_happy_path_active(db, push_collector): 

69 user, token = generate_user( 

70 hosting_status=HostingStatus.can_host, 

71 meetup_status=MeetupStatus.wants_to_meetup, 

72 last_active=now() - timedelta(days=335), 

73 ) 

74 

75 with session_scope() as session: 

76 queue_job(session, "send_activeness_probes", empty_pb2.Empty()) 

77 

78 process_jobs() 

79 

80 with real_jail_session(token) as jail: 

81 res = jail.JailInfo(empty_pb2.Empty()) 

82 assert res.has_pending_activeness_probe 

83 assert res.jailed 

84 

85 res = jail.RespondToActivenessProbe( 

86 jail_pb2.RespondToActivenessProbeReq(response=jail_pb2.ACTIVENESS_PROBE_RESPONSE_STILL_ACTIVE) 

87 ) 

88 assert not res.has_pending_activeness_probe 

89 assert not res.jailed 

90 

91 with api_session(token) as api: 

92 res = api.GetUser(api_pb2.GetUserReq(user=user.username)) 

93 assert res.hosting_status == api_pb2.HOSTING_STATUS_CAN_HOST 

94 assert res.meetup_status == api_pb2.MEETUP_STATUS_WANTS_TO_MEETUP 

95 

96 push_collector.assert_user_has_single_matching( 

97 user.id, 

98 title="Are you still open to hosting on Couchers.org?", 

99 body="Please log in to confirm your hosting status.", 

100 ) 

101 

102 

103def test_activeness_probes_disabled(db, push_collector): 

104 new_config = config.copy() 

105 new_config["ACTIVENESS_PROBES_ENABLED"] = False 

106 

107 with patch("couchers.jobs.handlers.config", new_config): 

108 user, token = generate_user( 

109 hosting_status=HostingStatus.can_host, 

110 meetup_status=MeetupStatus.wants_to_meetup, 

111 last_active=now() - timedelta(days=335), 

112 ) 

113 

114 with session_scope() as session: 

115 queue_job(session, "send_activeness_probes", empty_pb2.Empty()) 

116 

117 process_jobs() 

118 

119 with real_jail_session(token) as jail: 

120 res = jail.JailInfo(empty_pb2.Empty()) 

121 assert not res.has_pending_activeness_probe 

122 assert not res.jailed 

123 

124 with session_scope() as session: 

125 assert not session.execute(select(ActivenessProbe)).scalar_one_or_none() 

126 

127 

128def test_activeness_probes_expiry(db, push_collector): 

129 user, token = generate_user( 

130 hosting_status=HostingStatus.can_host, 

131 meetup_status=MeetupStatus.wants_to_meetup, 

132 last_active=now() - timedelta(days=335), 

133 ) 

134 

135 with session_scope() as session: 

136 queue_job(session, "send_activeness_probes", empty_pb2.Empty()) 

137 

138 process_jobs() 

139 

140 with real_jail_session(token) as jail: 

141 res = jail.JailInfo(empty_pb2.Empty()) 

142 assert res.has_pending_activeness_probe 

143 assert res.jailed 

144 

145 with session_scope() as session: 

146 probe = session.execute(select(ActivenessProbe)).scalar_one() 

147 probe.probe_initiated = now() - timedelta(days=15) 

148 assert probe.notifications_sent == 1 

149 probe.notifications_sent = 2 

150 

151 queue_job(session, "send_activeness_probes", empty_pb2.Empty()) 

152 

153 process_jobs() 

154 

155 with session_scope() as session: 

156 probe = session.execute(select(ActivenessProbe)).scalar_one() 

157 assert probe.response == ActivenessProbeStatus.expired 

158 

159 with real_jail_session(token) as jail: 

160 # no such probe 

161 with pytest.raises(grpc.RpcError) as e: 

162 jail.RespondToActivenessProbe( 

163 jail_pb2.RespondToActivenessProbeReq(response=jail_pb2.ACTIVENESS_PROBE_RESPONSE_STILL_ACTIVE) 

164 ) 

165 assert e.value.code() == grpc.StatusCode.FAILED_PRECONDITION 

166 assert e.value.details() == errors.PROBE_NOT_FOUND 

167 

168 res = jail.JailInfo(empty_pb2.Empty()) 

169 assert not res.has_pending_activeness_probe 

170 assert not res.jailed 

171 

172 with api_session(token) as api: 

173 res = api.GetUser(api_pb2.GetUserReq(user=user.username)) 

174 assert res.hosting_status == api_pb2.HOSTING_STATUS_CANT_HOST 

175 assert res.meetup_status == api_pb2.MEETUP_STATUS_OPEN_TO_MEETUP