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

102 statements  

« prev     ^ index     » next       coverage.py v7.13.2, created at 2026-02-03 06:18 +0000

1from datetime import timedelta 

2from unittest.mock import patch 

3 

4import grpc 

5import pytest 

6from google.protobuf import empty_pb2 

7from sqlalchemy import exists, select 

8 

9from couchers.config import config 

10from couchers.db import session_scope 

11from couchers.jobs.enqueue import queue_job 

12from couchers.jobs.handlers import send_activeness_probes 

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

14from couchers.proto import api_pb2, jail_pb2 

15from couchers.utils import now 

16from tests.fixtures.db import generate_user 

17from tests.fixtures.misc import PushCollector, process_jobs 

18from tests.fixtures.sessions import api_session, real_jail_session 

19 

20 

21@pytest.fixture(autouse=True) 

22def _(testconfig): 

23 pass 

24 

25 

26def test_activeness_probes_happy_path_inactive(db, push_collector: PushCollector): 

27 user, token = generate_user( 

28 hosting_status=HostingStatus.can_host, 

29 meetup_status=MeetupStatus.wants_to_meetup, 

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

31 ) 

32 

33 with session_scope() as session: 

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

35 

36 process_jobs() 

37 

38 with real_jail_session(token) as jail: 

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

40 assert res.has_pending_activeness_probe 

41 assert res.jailed 

42 

43 res = jail.RespondToActivenessProbe( 

44 jail_pb2.RespondToActivenessProbeReq(response=jail_pb2.ACTIVENESS_PROBE_RESPONSE_NO_LONGER_ACTIVE) 

45 ) 

46 assert not res.has_pending_activeness_probe 

47 assert not res.jailed 

48 

49 with api_session(token) as api: 

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

51 assert res.hosting_status == api_pb2.HOSTING_STATUS_CANT_HOST 

52 assert res.meetup_status == api_pb2.MEETUP_STATUS_WANTS_TO_MEETUP 

53 

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

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

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

57 

58 

59def test_activeness_probes_happy_path_active(db, push_collector: PushCollector): 

60 user, token = generate_user( 

61 hosting_status=HostingStatus.can_host, 

62 meetup_status=MeetupStatus.wants_to_meetup, 

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

64 ) 

65 

66 with session_scope() as session: 

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

68 

69 process_jobs() 

70 

71 with real_jail_session(token) as jail: 

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

73 assert res.has_pending_activeness_probe 

74 assert res.jailed 

75 

76 res = jail.RespondToActivenessProbe( 

77 jail_pb2.RespondToActivenessProbeReq(response=jail_pb2.ACTIVENESS_PROBE_RESPONSE_STILL_ACTIVE) 

78 ) 

79 assert not res.has_pending_activeness_probe 

80 assert not res.jailed 

81 

82 with api_session(token) as api: 

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

84 assert res.hosting_status == api_pb2.HOSTING_STATUS_CAN_HOST 

85 assert res.meetup_status == api_pb2.MEETUP_STATUS_WANTS_TO_MEETUP 

86 

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

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

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

90 

91 

92def test_activeness_probes_disabled(db, push_collector: PushCollector): 

93 new_config = config.copy() 

94 new_config["ACTIVENESS_PROBES_ENABLED"] = False 

95 

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

97 user, token = generate_user( 

98 hosting_status=HostingStatus.can_host, 

99 meetup_status=MeetupStatus.wants_to_meetup, 

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

101 ) 

102 

103 with session_scope() as session: 

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

105 

106 process_jobs() 

107 

108 with real_jail_session(token) as jail: 

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

110 assert not res.has_pending_activeness_probe 

111 assert not res.jailed 

112 

113 with session_scope() as session: 

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

115 

116 

117def test_activeness_probes_expiry(db, push_collector: PushCollector): 

118 user, token = generate_user( 

119 hosting_status=HostingStatus.can_host, 

120 meetup_status=MeetupStatus.wants_to_meetup, 

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

122 ) 

123 

124 with session_scope() as session: 

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

126 

127 process_jobs() 

128 

129 with real_jail_session(token) as jail: 

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

131 assert res.has_pending_activeness_probe 

132 assert res.jailed 

133 

134 with session_scope() as session: 

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

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

137 assert probe.notifications_sent == 1 

138 probe.notifications_sent = 2 

139 

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

141 

142 process_jobs() 

143 

144 with session_scope() as session: 

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

146 assert response == ActivenessProbeStatus.expired 

147 

148 with real_jail_session(token) as jail: 

149 # no such probe 

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

151 jail.RespondToActivenessProbe( 

152 jail_pb2.RespondToActivenessProbeReq(response=jail_pb2.ACTIVENESS_PROBE_RESPONSE_STILL_ACTIVE) 

153 ) 

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

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

156 

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

158 assert not res.has_pending_activeness_probe 

159 assert not res.jailed 

160 

161 with api_session(token) as api: 

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

163 assert res.hosting_status == api_pb2.HOSTING_STATUS_MAYBE 

164 assert res.meetup_status == api_pb2.MEETUP_STATUS_OPEN_TO_MEETUP