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

143 statements  

« prev     ^ index     » next       coverage.py v7.17.0, created at 2026-11-03 11:56 +0000

1from google.protobuf import wrappers_pb2 

2from sqlalchemy import select 

3 

4from couchers.crypto import random_hex 

5from couchers.db import session_scope 

6from couchers.helpers.upload_uses import UploadUseType, get_upload_uses, get_upload_uses_for_keys 

7from couchers.models import Base, EventOccurrence, Page, PageType, PhotoGallery, PhotoGalleryItem, Upload 

8from couchers.proto import pages_pb2 

9from tests.fixtures.api import events as events_api 

10from tests.fixtures.db import generate_user 

11from tests.fixtures.sessions import pages_session 

12from tests.test_communities import create_community 

13 

14 

15def _add_upload(user_id, key): 

16 with session_scope() as session: 

17 session.add(Upload(key=key, filename=f"{key}.jpg", creator_user_id=user_id)) 

18 

19 

20def test_get_upload_uses_unused(db): 

21 user, _ = generate_user() 

22 _add_upload(user.id, "unused_key") 

23 

24 with session_scope() as session: 

25 assert get_upload_uses(session, "unused_key") == [] 

26 

27 

28def test_get_upload_uses_profile_gallery(db): 

29 user, _ = generate_user() 

30 _add_upload(user.id, "avatar_key") 

31 _add_upload(user.id, "other_key") 

32 

33 with session_scope() as session: 

34 gallery = PhotoGallery(owner_user_id=user.id) 

35 session.add(gallery) 

36 session.flush() 

37 # lower position sorts first, so it's the avatar 

38 session.add(PhotoGalleryItem(gallery_id=gallery.id, upload_key="avatar_key", position=1.0)) 

39 session.add(PhotoGalleryItem(gallery_id=gallery.id, upload_key="other_key", position=2.0)) 

40 

41 with session_scope() as session: 

42 avatar_uses = get_upload_uses(session, "avatar_key") 

43 assert len(avatar_uses) == 1 

44 assert avatar_uses[0].use_type == UploadUseType.profile_gallery_photo_avatar 

45 assert avatar_uses[0].is_current 

46 assert avatar_uses[0].user_id == user.id 

47 assert avatar_uses[0].url is not None 

48 assert f"/user/{user.username}" in avatar_uses[0].url 

49 

50 other_uses = get_upload_uses(session, "other_key") 

51 assert len(other_uses) == 1 

52 assert other_uses[0].use_type == UploadUseType.profile_gallery_photo 

53 assert other_uses[0].is_current 

54 assert other_uses[0].user_id == user.id 

55 

56 

57def test_get_upload_uses_event(db): 

58 user, token = generate_user() 

59 

60 with session_scope() as session: 

61 create_community(session, 0, 2, "Community", [user], [], None) 

62 _add_upload(user.id, "event_key") 

63 

64 event_id = events_api.create_event(token=token, photo_key="event_key").event_id 

65 

66 with session_scope() as session: 

67 uses = get_upload_uses(session, "event_key") 

68 assert len(uses) == 1 

69 assert uses[0].use_type == UploadUseType.event 

70 assert uses[0].is_current 

71 assert uses[0].event_id == event_id 

72 assert uses[0].url is not None 

73 assert f"/event/{event_id}/" in uses[0].url 

74 

75 # a deleted occurrence still references the upload, but is no longer shown 

76 with session_scope() as session: 

77 occurrence = session.execute( 

78 select(EventOccurrence).where(EventOccurrence.photo_key == "event_key") 

79 ).scalar_one() 

80 occurrence.is_deleted = True 

81 

82 with session_scope() as session: 

83 uses = get_upload_uses(session, "event_key") 

84 assert len(uses) == 1 

85 assert uses[0].use_type == UploadUseType.event 

86 assert not uses[0].is_current 

87 

88 

89def test_get_upload_uses_page(db): 

90 user, token = generate_user() 

91 with session_scope() as session: 

92 create_community(session, 0, 2, "Root node", [user], [], None) 

93 

94 key = random_hex(32) 

95 _add_upload(user.id, key) 

96 

97 with pages_session(token) as api: 

98 res = api.CreatePlace( 

99 pages_pb2.CreatePlaceReq( 

100 title="title", 

101 content="content", 

102 photo_key=key, 

103 address="address", 

104 location=pages_pb2.Coordinate(lat=1, lng=1), 

105 ) 

106 ) 

107 page_id = res.page_id 

108 

109 with session_scope() as session: 

110 uses = get_upload_uses(session, key) 

111 assert len(uses) == 1 

112 assert uses[0].use_type == UploadUseType.page 

113 assert uses[0].is_current 

114 assert uses[0].page_id == page_id 

115 

116 # clearing the photo creates a new version; the old version still references the upload 

117 api.UpdatePage(pages_pb2.UpdatePageReq(page_id=page_id, photo_key=wrappers_pb2.StringValue(value=""))) 

118 

119 with session_scope() as session: 

120 uses = get_upload_uses(session, key) 

121 assert len(uses) == 1 

122 assert uses[0].use_type == UploadUseType.page 

123 assert not uses[0].is_current 

124 assert uses[0].page_id == page_id 

125 

126 

127def test_get_upload_uses_community_page(db): 

128 user, _ = generate_user() 

129 with session_scope() as session: 

130 node_id = create_community(session, 0, 2, "Community", [user], [], None).id 

131 

132 key = random_hex(32) 

133 _add_upload(user.id, key) 

134 

135 with session_scope() as session: 

136 main_page = session.execute( 

137 select(Page).where(Page.type == PageType.main_page).where(Page.parent_node_id == node_id) 

138 ).scalar_one() 

139 main_page.versions[-1].photo_key = key 

140 

141 with session_scope() as session: 

142 uses = get_upload_uses(session, key) 

143 assert len(uses) == 1 

144 assert uses[0].use_type == UploadUseType.page 

145 assert uses[0].is_current 

146 assert uses[0].url is not None 

147 assert f"/community/{node_id}/" in uses[0].url 

148 

149 

150def test_get_upload_uses_multiple(db): 

151 """An upload can be used in several places at once; all are returned.""" 

152 user, token = generate_user() 

153 with session_scope() as session: 

154 create_community(session, 0, 2, "Root node", [user], [], None) 

155 

156 key = random_hex(32) 

157 _add_upload(user.id, key) 

158 

159 with session_scope() as session: 

160 gallery = PhotoGallery(owner_user_id=user.id) 

161 session.add(gallery) 

162 session.flush() 

163 session.add(PhotoGalleryItem(gallery_id=gallery.id, upload_key=key, position=1.0)) 

164 

165 with pages_session(token) as api: 

166 api.CreatePlace( 

167 pages_pb2.CreatePlaceReq( 

168 title="title", 

169 content="content", 

170 photo_key=key, 

171 address="address", 

172 location=pages_pb2.Coordinate(lat=1, lng=1), 

173 ) 

174 ) 

175 

176 with session_scope() as session: 

177 uses = get_upload_uses(session, key) 

178 assert {use.use_type for use in uses} == { 

179 UploadUseType.profile_gallery_photo_avatar, 

180 UploadUseType.page, 

181 } 

182 

183 

184def test_get_upload_uses_for_keys_batch(db): 

185 user, _ = generate_user() 

186 _add_upload(user.id, "gallery_key") 

187 _add_upload(user.id, "unused_key") 

188 

189 with session_scope() as session: 

190 gallery = PhotoGallery(owner_user_id=user.id) 

191 session.add(gallery) 

192 session.flush() 

193 session.add(PhotoGalleryItem(gallery_id=gallery.id, upload_key="gallery_key", position=1.0)) 

194 

195 with session_scope() as session: 

196 result = get_upload_uses_for_keys(session, ["gallery_key", "unused_key", "nonexistent_key"]) 

197 

198 # only keys with uses appear in the mapping 

199 assert set(result.keys()) == {"gallery_key"} 

200 assert len(result["gallery_key"]) == 1 

201 assert result["gallery_key"][0].use_type == UploadUseType.profile_gallery_photo_avatar 

202 assert result["gallery_key"][0].user_id == user.id 

203 

204 

205def test_get_upload_uses_for_keys_empty(db): 

206 with session_scope() as session: 

207 assert get_upload_uses_for_keys(session, []) == {} 

208 

209 

210def test_upload_uses_covers_all_foreign_keys(db): 

211 """ 

212 Guards against drift: every foreign key targeting uploads.key must be handled by get_upload_uses. 

213 

214 If this fails, you added a new reference to uploads.key. Add it to get_upload_uses (and likely a new 

215 UploadUseType), then add the (table, column) here. 

216 """ 

217 referencing = set() 

218 for table in Base.metadata.tables.values(): 

219 for fk in table.foreign_keys: 

220 if fk.column.table.name == "uploads" and fk.column.name == "key": 

221 referencing.add((table.name, fk.parent.name)) 

222 

223 assert referencing == { 

224 ("photo_gallery_items", "upload_key"), 

225 ("event_occurrences", "photo_key"), 

226 ("page_versions", "photo_key"), 

227 }