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

252 statements  

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

1import grpc 

2import pytest 

3 

4from couchers.db import session_scope 

5from couchers.proto import groups_pb2, pages_pb2 

6from couchers.tasks import enforce_community_memberships 

7from tests.fixtures.db import generate_user, get_user_id_and_token 

8from tests.fixtures.sessions import groups_session 

9from tests.test_communities import ( # noqa 

10 create_1d_point, 

11 create_community, 

12 create_group, 

13 get_community_id, 

14 get_group_id, 

15 testing_communities, 

16) 

17 

18 

19class TestGroups: 

20 @staticmethod 

21 def test_GetGroup(testing_communities): 

22 # implicitly tests visibility and blocking, since all groups have invisible, blocked, and blocking member and admin 

23 with session_scope() as session: 

24 user2_id, token2 = get_user_id_and_token(session, "user2") 

25 w_id = get_community_id(session, "Global") 

26 c1_id = get_community_id(session, "Country 1") 

27 c2_id = get_community_id(session, "Country 2") 

28 c1r2_id = get_community_id(session, "Country 1, Region 2") 

29 c2r1_id = get_community_id(session, "Country 2, Region 1") 

30 hitchhikers_id = get_group_id(session, "Hitchhikers") 

31 c1r2foodies_id = get_group_id(session, "Country 1, Region 2, Foodies") 

32 c2r1foodies_id = get_group_id(session, "Country 2, Region 1, Foodies") 

33 

34 with groups_session(token2) as api: 

35 res = api.GetGroup( 

36 groups_pb2.GetGroupReq( 

37 group_id=hitchhikers_id, 

38 ) 

39 ) 

40 assert res.group_id == hitchhikers_id 

41 assert res.name == "Hitchhikers" 

42 assert res.slug == "hitchhikers" 

43 assert res.description == "Description for Hitchhikers" 

44 assert len(res.parents) == 2 

45 assert res.parents[0].HasField("community") 

46 assert res.parents[0].community.community_id == w_id 

47 assert res.parents[0].community.name == "Global" 

48 assert res.parents[0].community.slug == "global" 

49 assert res.parents[0].community.description == "Description for Global" 

50 assert res.parents[1].HasField("group") 

51 assert res.parents[1].group.group_id == hitchhikers_id 

52 assert res.parents[1].group.name == "Hitchhikers" 

53 assert res.parents[1].group.slug == "hitchhikers" 

54 assert res.parents[1].group.description == "Description for Hitchhikers" 

55 assert res.main_page.type == pages_pb2.PAGE_TYPE_MAIN_PAGE 

56 assert res.main_page.slug == "main-page-for-the-hitchhikers-community" 

57 assert res.main_page.last_editor_user_id == 1 

58 assert res.main_page.creator_user_id == 1 

59 assert res.main_page.owner_group_id == hitchhikers_id 

60 assert res.main_page.title == "Main page for the Hitchhikers community" 

61 assert res.main_page.content == "There is nothing here yet..." 

62 assert res.main_page.can_edit 

63 assert not res.main_page.can_moderate 

64 assert res.main_page.editor_user_ids == [1] 

65 assert res.member 

66 assert res.admin 

67 assert res.member_count == 4 

68 assert res.admin_count == 2 

69 

70 res = api.GetGroup( 

71 groups_pb2.GetGroupReq( 

72 group_id=c1r2foodies_id, 

73 ) 

74 ) 

75 assert res.group_id == c1r2foodies_id 

76 assert res.name == "Country 1, Region 2, Foodies" 

77 assert res.slug == "country-1-region-2-foodies" 

78 assert res.description == "Description for Country 1, Region 2, Foodies" 

79 assert len(res.parents) == 4 

80 assert res.parents[0].HasField("community") 

81 assert res.parents[0].community.community_id == w_id 

82 assert res.parents[0].community.name == "Global" 

83 assert res.parents[0].community.slug == "global" 

84 assert res.parents[0].community.description == "Description for Global" 

85 assert res.parents[1].HasField("community") 

86 assert res.parents[1].community.community_id == c1_id 

87 assert res.parents[1].community.name == "Country 1" 

88 assert res.parents[1].community.slug == "country-1" 

89 assert res.parents[1].community.description == "Description for Country 1" 

90 assert res.parents[2].HasField("community") 

91 assert res.parents[2].community.community_id == c1r2_id 

92 assert res.parents[2].community.name == "Country 1, Region 2" 

93 assert res.parents[2].community.slug == "country-1-region-2" 

94 assert res.parents[2].community.description == "Description for Country 1, Region 2" 

95 assert res.parents[3].HasField("group") 

96 assert res.parents[3].group.group_id == c1r2foodies_id 

97 assert res.parents[3].group.name == "Country 1, Region 2, Foodies" 

98 assert res.parents[3].group.slug == "country-1-region-2-foodies" 

99 assert res.parents[3].group.description == "Description for Country 1, Region 2, Foodies" 

100 assert res.main_page.type == pages_pb2.PAGE_TYPE_MAIN_PAGE 

101 assert res.main_page.slug == "main-page-for-the-country-1-region-2-foodies-community" 

102 assert res.main_page.last_editor_user_id == 2 

103 assert res.main_page.creator_user_id == 2 

104 assert res.main_page.owner_group_id == c1r2foodies_id 

105 assert res.main_page.title == "Main page for the Country 1, Region 2, Foodies community" 

106 assert res.main_page.content == "There is nothing here yet..." 

107 assert res.main_page.can_edit 

108 assert res.main_page.can_moderate 

109 assert res.main_page.editor_user_ids == [2] 

110 assert res.member 

111 assert res.admin 

112 assert res.member_count == 3 

113 assert res.admin_count == 1 

114 

115 res = api.GetGroup( 

116 groups_pb2.GetGroupReq( 

117 group_id=c2r1foodies_id, 

118 ) 

119 ) 

120 assert res.group_id == c2r1foodies_id 

121 assert res.name == "Country 2, Region 1, Foodies" 

122 assert res.slug == "country-2-region-1-foodies" 

123 assert res.description == "Description for Country 2, Region 1, Foodies" 

124 assert len(res.parents) == 4 

125 assert res.parents[0].HasField("community") 

126 assert res.parents[0].community.community_id == w_id 

127 assert res.parents[0].community.name == "Global" 

128 assert res.parents[0].community.slug == "global" 

129 assert res.parents[0].community.description == "Description for Global" 

130 assert res.parents[1].HasField("community") 

131 assert res.parents[1].community.community_id == c2_id 

132 assert res.parents[1].community.name == "Country 2" 

133 assert res.parents[1].community.slug == "country-2" 

134 assert res.parents[1].community.description == "Description for Country 2" 

135 assert res.parents[2].HasField("community") 

136 assert res.parents[2].community.community_id == c2r1_id 

137 assert res.parents[2].community.name == "Country 2, Region 1" 

138 assert res.parents[2].community.slug == "country-2-region-1" 

139 assert res.parents[2].community.description == "Description for Country 2, Region 1" 

140 assert res.parents[3].HasField("group") 

141 assert res.parents[3].group.group_id == c2r1foodies_id 

142 assert res.parents[3].group.name == "Country 2, Region 1, Foodies" 

143 assert res.parents[3].group.slug == "country-2-region-1-foodies" 

144 assert res.parents[3].group.description == "Description for Country 2, Region 1, Foodies" 

145 assert res.main_page.type == pages_pb2.PAGE_TYPE_MAIN_PAGE 

146 assert res.main_page.slug == "main-page-for-the-country-2-region-1-foodies-community" 

147 assert res.main_page.last_editor_user_id == 6 

148 assert res.main_page.creator_user_id == 6 

149 assert res.main_page.owner_group_id == c2r1foodies_id 

150 assert res.main_page.title == "Main page for the Country 2, Region 1, Foodies community" 

151 assert res.main_page.content == "There is nothing here yet..." 

152 assert not res.main_page.can_edit 

153 assert not res.main_page.can_moderate 

154 assert res.main_page.editor_user_ids == [6] 

155 assert not res.member 

156 assert not res.admin 

157 assert res.member_count == 2 

158 assert res.admin_count == 1 

159 

160 @staticmethod 

161 def test_ListAdmins(testing_communities): 

162 # implicitly tests visibility and blocking, since all groups have invisible, blocked, and blocking admin 

163 with session_scope() as session: 

164 user1_id, token1 = get_user_id_and_token(session, "user1") 

165 user2_id, token2 = get_user_id_and_token(session, "user2") 

166 hitchhikers_id = get_group_id(session, "Hitchhikers") 

167 c1r2foodies_id = get_group_id(session, "Country 1, Region 2, Foodies") 

168 

169 with groups_session(token1) as api: 

170 res = api.ListAdmins( 

171 groups_pb2.ListAdminsReq( 

172 group_id=hitchhikers_id, 

173 ) 

174 ) 

175 assert res.admin_user_ids == [user1_id, user2_id] 

176 

177 res = api.ListAdmins( 

178 groups_pb2.ListAdminsReq( 

179 group_id=c1r2foodies_id, 

180 ) 

181 ) 

182 assert res.admin_user_ids == [user2_id] 

183 

184 @staticmethod 

185 def test_ListMembers(testing_communities): 

186 # implicitly tests visibility and blocking, since all groups have invisible, blocked, and blocking member 

187 with session_scope() as session: 

188 user1_id, token1 = get_user_id_and_token(session, "user1") 

189 user2_id, token2 = get_user_id_and_token(session, "user2") 

190 user4_id, token4 = get_user_id_and_token(session, "user4") 

191 user5_id, token5 = get_user_id_and_token(session, "user5") 

192 user8_id, token8 = get_user_id_and_token(session, "user8") 

193 hitchhikers_id = get_group_id(session, "Hitchhikers") 

194 c1r2foodies_id = get_group_id(session, "Country 1, Region 2, Foodies") 

195 

196 with groups_session(token1) as api: 

197 res = api.ListMembers( 

198 groups_pb2.ListMembersReq( 

199 group_id=hitchhikers_id, 

200 ) 

201 ) 

202 assert res.member_user_ids == [user1_id, user2_id, user5_id, user8_id] 

203 

204 res = api.ListMembers( 

205 groups_pb2.ListMembersReq( 

206 group_id=c1r2foodies_id, 

207 ) 

208 ) 

209 assert res.member_user_ids == [user2_id, user4_id, user5_id] 

210 

211 @staticmethod 

212 def test_ListDiscussions(testing_communities): 

213 with session_scope() as session: 

214 user1_id, token1 = get_user_id_and_token(session, "user1") 

215 hitchhikers_id = get_group_id(session, "Hitchhikers") 

216 

217 with groups_session(token1) as api: 

218 res = api.ListDiscussions( 

219 groups_pb2.ListDiscussionsReq( 

220 group_id=hitchhikers_id, 

221 page_size=5, 

222 ) 

223 ) 

224 assert [d.title for d in res.discussions] == [ 

225 "Discussion title 8", 

226 "Discussion title 9", 

227 "Discussion title 10", 

228 "Discussion title 11", 

229 "Discussion title 12", 

230 ] 

231 for d in res.discussions: 

232 assert d.thread.thread_id > 0 

233 assert d.thread.num_responses == 0 

234 

235 res = api.ListDiscussions( 

236 groups_pb2.ListDiscussionsReq( 

237 group_id=hitchhikers_id, 

238 page_token=res.next_page_token, 

239 page_size=5, 

240 ) 

241 ) 

242 assert [d.title for d in res.discussions] == [ 

243 "Discussion title 13", 

244 "Discussion title 14", 

245 ] 

246 for d in res.discussions: 

247 assert d.thread.thread_id > 0 

248 assert d.thread.num_responses == 0 

249 

250 @staticmethod 

251 def test_ListUserGroups(testing_communities): 

252 with session_scope() as session: 

253 user1_id, token1 = get_user_id_and_token(session, "user1") 

254 hitchhikers_id = get_group_id(session, "Hitchhikers") 

255 foodies_id = get_group_id(session, "Country 1, Region 1, Foodies") 

256 skaters_id = get_group_id(session, "Country 1, Region 1, Skaters") 

257 

258 # List user1's groups from user1's account 

259 with groups_session(token1) as api: 

260 res = api.ListUserGroups(groups_pb2.ListUserGroupsReq()) 

261 assert [g.group_id for g in res.groups] == [hitchhikers_id, foodies_id, skaters_id] 

262 

263 @staticmethod 

264 def test_ListOtherUserGroups(testing_communities): 

265 with session_scope() as session: 

266 user1_id, token1 = get_user_id_and_token(session, "user1") 

267 user2_id, token2 = get_user_id_and_token(session, "user2") 

268 hitchhikers_id = get_group_id(session, "Hitchhikers") 

269 foodies_id = get_group_id(session, "Country 1, Region 1, Foodies") 

270 skaters_id = get_group_id(session, "Country 1, Region 1, Skaters") 

271 

272 # List user1's groups from user2's account 

273 with groups_session(token2) as api: 

274 res = api.ListUserGroups(groups_pb2.ListUserGroupsReq(user_id=user1_id)) 

275 assert [g.group_id for g in res.groups] == [hitchhikers_id, foodies_id, skaters_id] 

276 

277 @staticmethod 

278 def test_ListEvents(testing_communities): 

279 with session_scope() as session: 

280 user7_id, token7 = get_user_id_and_token(session, "user7") 

281 hitchhikers_id = get_group_id(session, "Hitchhikers") 

282 

283 with groups_session(token7) as api: 

284 res = api.ListEvents( 

285 groups_pb2.ListEventsReq( 

286 group_id=hitchhikers_id, 

287 page_size=3, 

288 ) 

289 ) 

290 assert [d.title for d in res.events] == [ 

291 "Event title 7", 

292 "Event title 8", 

293 "Event title 9", 

294 ] 

295 

296 res = api.ListEvents( 

297 groups_pb2.ListEventsReq( 

298 group_id=hitchhikers_id, 

299 page_token=res.next_page_token, 

300 page_size=2, 

301 ) 

302 ) 

303 assert [d.title for d in res.events] == [ 

304 "Event title 10", 

305 "Event title 11", 

306 ] 

307 

308 res = api.ListEvents( 

309 groups_pb2.ListEventsReq( 

310 group_id=hitchhikers_id, 

311 page_token=res.next_page_token, 

312 page_size=2, 

313 ) 

314 ) 

315 assert [d.title for d in res.events] == [ 

316 "Event title 12", 

317 ] 

318 assert not res.next_page_token 

319 

320 

321def test_JoinGroup_and_LeaveGroup(testing_communities): 

322 # these tests are separate from above as they mutate the database 

323 with session_scope() as session: 

324 user_id, token = get_user_id_and_token(session, "user3") 

325 h_id = get_group_id(session, "Hitchhikers") 

326 

327 with groups_session(token) as api: 

328 # not in group at start 

329 assert not api.GetGroup(groups_pb2.GetGroupReq(group_id=h_id)).member 

330 

331 # can't leave 

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

333 res = api.LeaveGroup( 

334 groups_pb2.LeaveGroupReq( 

335 group_id=h_id, 

336 ) 

337 ) 

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

339 assert e.value.details() == "You're not in that group." 

340 

341 # didn't magically join 

342 assert not api.GetGroup(groups_pb2.GetGroupReq(group_id=h_id)).member 

343 

344 # but can join 

345 res = api.JoinGroup( 

346 groups_pb2.JoinGroupReq( 

347 group_id=h_id, 

348 ) 

349 ) 

350 

351 # should be there now 

352 assert api.GetGroup(groups_pb2.GetGroupReq(group_id=h_id)).member 

353 

354 # can't join again 

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

356 res = api.JoinGroup( 

357 groups_pb2.JoinGroupReq( 

358 group_id=h_id, 

359 ) 

360 ) 

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

362 assert e.value.details() == "You're already in that group." 

363 

364 # didn't magically leave 

365 assert api.GetGroup(groups_pb2.GetGroupReq(group_id=h_id)).member 

366 

367 # now we can leave though 

368 res = api.LeaveGroup( 

369 groups_pb2.LeaveGroupReq( 

370 group_id=h_id, 

371 ) 

372 ) 

373 

374 # managed to leave 

375 assert not api.GetGroup(groups_pb2.GetGroupReq(group_id=h_id)).member 

376 

377 

378def test_LeaveGroup_regression(db): 

379 # see test_LeaveCommunity_regression 

380 

381 # admin 

382 user1, token1 = generate_user(username="user1", geom=create_1d_point(200), geom_radius=0.1) 

383 # joiner/leaver 

384 user2, token2 = generate_user(username="user2", geom=create_1d_point(201), geom_radius=0.1) 

385 

386 with session_scope() as session: 

387 c0 = create_community(session, 0, 100, "Community 0", [user1], [], None) 

388 g1 = create_group(session, "Group 1", [user1], [], c0) 

389 g2 = create_group(session, "Group 2", [user1], [], c0) 

390 g1_id = g1.id 

391 g2_id = g2.id 

392 

393 enforce_community_memberships() 

394 

395 with groups_session(token1) as api: 

396 assert api.GetGroup(groups_pb2.GetGroupReq(group_id=g1_id)).member 

397 assert api.GetGroup(groups_pb2.GetGroupReq(group_id=g2_id)).member 

398 

399 with groups_session(token2) as api: 

400 # first check we're not in any groups 

401 assert not api.GetGroup(groups_pb2.GetGroupReq(group_id=g1_id)).member 

402 assert not api.GetGroup(groups_pb2.GetGroupReq(group_id=g2_id)).member 

403 

404 # join some groups 

405 api.JoinGroup(groups_pb2.JoinGroupReq(group_id=g1_id)) 

406 api.JoinGroup(groups_pb2.JoinGroupReq(group_id=g2_id)) 

407 

408 # check memberships 

409 assert api.GetGroup(groups_pb2.GetGroupReq(group_id=g1_id)).member 

410 assert api.GetGroup(groups_pb2.GetGroupReq(group_id=g2_id)).member 

411 

412 # leave just g2 

413 api.LeaveGroup(groups_pb2.LeaveGroupReq(group_id=g2_id)) 

414 

415 # check memberships 

416 assert api.GetGroup(groups_pb2.GetGroupReq(group_id=g1_id)).member 

417 assert not api.GetGroup(groups_pb2.GetGroupReq(group_id=g2_id)).member 

418 

419 

420# TODO: also requires implementing content transfer functionality 

421# Note: allegedly groups cannot contain content other than discussions! 

422 

423# def test_ListPlaces(db, testing_communities): 

424# pass 

425 

426# def test_ListGuides(db, testing_communities): 

427# pass 

428 

429# def test_ListEvents(db, testing_communities): 

430# pass