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

96 statements  

« prev     ^ index     » next       coverage.py v7.16.1, created at 2026-09-19 15:47 +0000

1from datetime import timedelta 

2 

3import grpc 

4import pytest 

5from google.protobuf import empty_pb2 

6from sqlalchemy import exists, select 

7 

8from couchers.config import config 

9from couchers.db import session_scope 

10from couchers.jobs.enqueue import queue_job 

11from couchers.jobs.handlers import send_activeness_probes 

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

13from couchers.proto import api_pb2, jail_pb2 

14from couchers.utils import now 

15from tests.fixtures.db import generate_user 

16from tests.fixtures.misc import PushCollector, process_jobs 

17from tests.fixtures.sessions import api_session, real_jail_session 

18 

19 

20def test_activeness_probes_happy_path_inactive(db, push_collector: PushCollector): 

21 user, token = generate_user( 

22 hosting_status=HostingStatus.can_host, 

23 meetup_status=MeetupStatus.wants_to_meetup, 

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

25 ) 

26 

27 with session_scope() as session: 

28 queue_job(session, job=send_activeness_probes, payload=empty_pb2.Empty()) 

29 

30 process_jobs() 

31 

32 with real_jail_session(token) as jail: 

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

34 assert res.has_pending_activeness_probe 

35 assert res.jailed 

36 

37 res = jail.RespondToActivenessProbe( 

38 jail_pb2.RespondToActivenessProbeReq(response=jail_pb2.ACTIVENESS_PROBE_RESPONSE_NO_LONGER_ACTIVE) 

39 ) 

40 assert not res.has_pending_activeness_probe 

41 assert not res.jailed 

42 

43 with api_session(token) as api: 

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

45 assert res.hosting_status == api_pb2.HOSTING_STATUS_CANT_HOST 

46 assert res.meetup_status == api_pb2.MEETUP_STATUS_WANTS_TO_MEETUP 

47 

48 push = push_collector.pop_for_user(user.id, last=True) 

49 assert push.content.title == "Still open to hosting?" 

50 assert push.content.body == "Log in to confirm your hosting status." 

51 

52 

53def test_activeness_probes_happy_path_active(db, push_collector: PushCollector): 

54 user, token = generate_user( 

55 hosting_status=HostingStatus.can_host, 

56 meetup_status=MeetupStatus.wants_to_meetup, 

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

58 ) 

59 

60 with session_scope() as session: 

61 queue_job(session, job=send_activeness_probes, payload=empty_pb2.Empty()) 

62 

63 process_jobs() 

64 

65 with real_jail_session(token) as jail: 

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

67 assert res.has_pending_activeness_probe 

68 assert res.jailed 

69 

70 res = jail.RespondToActivenessProbe( 

71 jail_pb2.RespondToActivenessProbeReq(response=jail_pb2.ACTIVENESS_PROBE_RESPONSE_STILL_ACTIVE) 

72 ) 

73 assert not res.has_pending_activeness_probe 

74 assert not res.jailed 

75 

76 with api_session(token) as api: 

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

78 assert res.hosting_status == api_pb2.HOSTING_STATUS_CAN_HOST 

79 assert res.meetup_status == api_pb2.MEETUP_STATUS_WANTS_TO_MEETUP 

80 

81 push = push_collector.pop_for_user(user.id, last=True) 

82 assert push.content.title == "Still open to hosting?" 

83 assert push.content.body == "Log in to confirm your hosting status." 

84 

85 

86def test_activeness_probes_disabled(db, push_collector: PushCollector): 

87 config.ACTIVENESS_PROBES_ENABLED = False 

88 

89 user, token = generate_user( 

90 hosting_status=HostingStatus.can_host, 

91 meetup_status=MeetupStatus.wants_to_meetup, 

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

93 ) 

94 

95 with session_scope() as session: 

96 queue_job(session, job=send_activeness_probes, payload=empty_pb2.Empty()) 

97 

98 process_jobs() 

99 

100 with real_jail_session(token) as jail: 

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

102 assert not res.has_pending_activeness_probe 

103 assert not res.jailed 

104 

105 with session_scope() as session: 

106 assert not session.execute(select(exists(ActivenessProbe))).scalar_one() 

107 

108 

109def test_activeness_probes_expiry(db, push_collector: PushCollector): 

110 user, token = generate_user( 

111 hosting_status=HostingStatus.can_host, 

112 meetup_status=MeetupStatus.wants_to_meetup, 

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

114 ) 

115 

116 with session_scope() as session: 

117 queue_job(session, job=send_activeness_probes, payload=empty_pb2.Empty()) 

118 

119 process_jobs() 

120 

121 with real_jail_session(token) as jail: 

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

123 assert res.has_pending_activeness_probe 

124 assert res.jailed 

125 

126 with session_scope() as session: 

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

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

129 assert probe.notifications_sent == 1 

130 probe.notifications_sent = 2 

131 

132 queue_job(session, job=send_activeness_probes, payload=empty_pb2.Empty()) 

133 

134 process_jobs() 

135 

136 with session_scope() as session: 

137 response = session.execute(select(ActivenessProbe.response)).scalar_one() 

138 assert response == ActivenessProbeStatus.expired 

139 

140 with real_jail_session(token) as jail: 

141 # no such probe 

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

143 jail.RespondToActivenessProbe( 

144 jail_pb2.RespondToActivenessProbeReq(response=jail_pb2.ACTIVENESS_PROBE_RESPONSE_STILL_ACTIVE) 

145 ) 

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

147 assert e.value.details() == "You don't currently have an activeness probe." 

148 

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

150 assert not res.has_pending_activeness_probe 

151 assert not res.jailed 

152 

153 with api_session(token) as api: 

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

155 assert res.hosting_status == api_pb2.HOSTING_STATUS_MAYBE 

156 assert res.meetup_status == api_pb2.MEETUP_STATUS_OPEN_TO_MEETUP