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
« 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
4import grpc
5import pytest
6from google.protobuf import empty_pb2
7from sqlalchemy import exists, select
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
21@pytest.fixture(autouse=True)
22def _(testconfig):
23 pass
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 )
33 with session_scope() as session:
34 queue_job(session, job=send_activeness_probes, payload=empty_pb2.Empty())
36 process_jobs()
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
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
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
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."
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 )
66 with session_scope() as session:
67 queue_job(session, job=send_activeness_probes, payload=empty_pb2.Empty())
69 process_jobs()
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
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
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
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."
92def test_activeness_probes_disabled(db, push_collector: PushCollector):
93 new_config = config.copy()
94 new_config["ACTIVENESS_PROBES_ENABLED"] = False
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 )
103 with session_scope() as session:
104 queue_job(session, job=send_activeness_probes, payload=empty_pb2.Empty())
106 process_jobs()
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
113 with session_scope() as session:
114 assert not session.execute(select(exists(ActivenessProbe))).scalar_one()
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 )
124 with session_scope() as session:
125 queue_job(session, job=send_activeness_probes, payload=empty_pb2.Empty())
127 process_jobs()
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
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
140 queue_job(session, job=send_activeness_probes, payload=empty_pb2.Empty())
142 process_jobs()
144 with session_scope() as session:
145 response = session.execute(select(ActivenessProbe.response)).scalar_one()
146 assert response == ActivenessProbeStatus.expired
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."
157 res = jail.JailInfo(empty_pb2.Empty())
158 assert not res.has_pending_activeness_probe
159 assert not res.jailed
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