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
« prev ^ index » next coverage.py v7.16.1, created at 2026-09-19 15:47 +0000
1from datetime import timedelta
3import grpc
4import pytest
5from google.protobuf import empty_pb2
6from sqlalchemy import exists, select
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
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 )
27 with session_scope() as session:
28 queue_job(session, job=send_activeness_probes, payload=empty_pb2.Empty())
30 process_jobs()
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
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
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
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."
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 )
60 with session_scope() as session:
61 queue_job(session, job=send_activeness_probes, payload=empty_pb2.Empty())
63 process_jobs()
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
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
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
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."
86def test_activeness_probes_disabled(db, push_collector: PushCollector):
87 config.ACTIVENESS_PROBES_ENABLED = False
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 )
95 with session_scope() as session:
96 queue_job(session, job=send_activeness_probes, payload=empty_pb2.Empty())
98 process_jobs()
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
105 with session_scope() as session:
106 assert not session.execute(select(exists(ActivenessProbe))).scalar_one()
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 )
116 with session_scope() as session:
117 queue_job(session, job=send_activeness_probes, payload=empty_pb2.Empty())
119 process_jobs()
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
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
132 queue_job(session, job=send_activeness_probes, payload=empty_pb2.Empty())
134 process_jobs()
136 with session_scope() as session:
137 response = session.execute(select(ActivenessProbe.response)).scalar_one()
138 assert response == ActivenessProbeStatus.expired
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."
149 res = jail.JailInfo(empty_pb2.Empty())
150 assert not res.has_pending_activeness_probe
151 assert not res.jailed
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