Coverage for tests/store/tests_publisher.py: 100%

76 statements  

« prev     ^ index     » next       coverage.py v7.15.0, created at 2026-07-08 12:33 +0000

1import responses 

2from urllib.parse import urlencode 

3from flask_testing import TestCase 

4from webapp.app import create_app 

5from cache.cache_utility import redis_cache 

6 

7JETBRAINS_FIND_RESPONSE = { 

8 "results": [ 

9 { 

10 "name": "pycharm-community", 

11 "snap": { 

12 "media": [ 

13 { 

14 "height": 400, 

15 "type": "icon", 

16 "url": ( 

17 "https://dashboard.snapcraft.io/site_media/" 

18 "appmedia/2024/06/Avatar-7.png" 

19 ), 

20 "width": 400, 

21 }, 

22 { 

23 "height": 2095, 

24 "type": "screenshot", 

25 "url": ( 

26 "https://dashboard.snapcraft.io/site_media/" 

27 "appmedia/2017/12/pycharm_com_general.png" 

28 ), 

29 "width": 3713, 

30 }, 

31 ], 

32 "publisher": { 

33 "display-name": "jetbrains", 

34 "id": "28zEonXNoBLvIB7xneRbltOsp0Nf7DwS", 

35 "username": "jetbrains", 

36 "validation": "verified", 

37 }, 

38 "summary": "PyCharm Community Edition", 

39 "title": "pycharm-community", 

40 }, 

41 "snap-id": "Qo9GiW9eyzgN1tXmWpQ9gdstdFsj4K7E", 

42 }, 

43 ] 

44} 

45 

46LUKEWH_FIND_RESPONSE = { 

47 "results": [ 

48 { 

49 "name": "deluge-lukewh", 

50 "snap": { 

51 "media": [ 

52 { 

53 "height": 300, 

54 "type": "icon", 

55 "url": ( 

56 "https://dashboard.snapcraft.io/site_media/" 

57 "appmedia/2020/04/the-new-deluge-icon-300x300.png" 

58 ), 

59 "width": 300, 

60 }, 

61 { 

62 "height": 720, 

63 "type": "banner", 

64 "url": ( 

65 "https://dashboard.snapcraft.io/site_media/" 

66 "appmedia/2020/05/deluge-banner1.png" 

67 ), 

68 "width": 2160, 

69 }, 

70 ], 

71 "publisher": { 

72 "display-name": "LukeWH", 

73 "id": "GMrEEEdGN4gN9BhRHjRRDCoJuUkyJJnm", 

74 "username": "lukewh", 

75 "validation": "unproven", 

76 }, 

77 "summary": ( 

78 "Deluge is a fully-featured cross-platform " 

79 "BitTorrent client" 

80 ), 

81 "title": "Deluge", 

82 }, 

83 "snap-id": "5dm2RBhIIGdr8sAnA9WJZWco2MQgbni7", 

84 }, 

85 ] 

86} 

87 

88 

89def _snap_result(name, title): 

90 return { 

91 "name": name, 

92 "snap": { 

93 "media": [ 

94 { 

95 "height": 256, 

96 "type": "icon", 

97 "url": ( 

98 "https://dashboard.snapcraft.io/site_media/" 

99 "appmedia/" + name + ".png" 

100 ), 

101 "width": 256, 

102 } 

103 ], 

104 "publisher": { 

105 "display-name": "Snapcrafters", 

106 "id": "snapcrafters-id", 

107 "username": "snapcrafters", 

108 "validation": "starred", 

109 }, 

110 "summary": title + " summary", 

111 "title": title, 

112 }, 

113 "snap-id": name + "-id", 

114 } 

115 

116 

117SNAPCRAFTERS_FIND_RESPONSE = { 

118 "results": [ 

119 _snap_result("sublime-text", "Sublime Text"), 

120 _snap_result("discord", "Discord"), 

121 _snap_result("alacritty", "Alacritty"), 

122 ] 

123} 

124 

125 

126class GetPublisherPageTest(TestCase): 

127 def setUp(self): 

128 redis_cache.fallback.clear() 

129 self.api_url_publisher_items = "".join( 

130 [ 

131 "https://api.snapcraft.io/api/v1/", 

132 "snaps/search", 

133 "?", 

134 urlencode( 

135 { 

136 "q": "publisher:28zEonXNoBLvIB7xneRbltOsp0Nf7DwS", 

137 "size": "500", 

138 "page": "1", 

139 "scope": "wide", 

140 "confinement": "strict,classic", 

141 "fields": ",".join( 

142 [ 

143 "package_name", 

144 "title", 

145 "summary", 

146 "architecture", 

147 "media", 

148 "developer_name", 

149 "developer_id", 

150 "developer_validation", 

151 "origin", 

152 "apps", 

153 "sections", 

154 ] 

155 ), 

156 "arch": "wide", 

157 } 

158 ), 

159 ] 

160 ) 

161 self.api_url_find = lambda publisher: "".join( 

162 [ 

163 "https://api.snapcraft.io/v2/", 

164 "snaps/find", 

165 "?", 

166 urlencode( 

167 { 

168 "publisher": publisher, 

169 "fields": ",".join( 

170 [ 

171 "title", 

172 "summary", 

173 "media", 

174 "publisher", 

175 ] 

176 ), 

177 } 

178 ), 

179 ] 

180 ) 

181 

182 def create_app(self): 

183 app = create_app(testing=True) 

184 app.secret_key = "secret_key" 

185 app.config["WTF_CSRF_METHODS"] = [] 

186 

187 return app 

188 

189 @responses.activate 

190 def test_community_publisher(self): 

191 responses.add( 

192 responses.GET, 

193 self.api_url_find("lukewh"), 

194 json=LUKEWH_FIND_RESPONSE, 

195 status=200, 

196 ) 

197 response = self.client.get("/publisher/lukewh") 

198 self.assertEqual(response.status_code, 200) 

199 self.assert_template_used("store/community-publisher-details.html") 

200 

201 @responses.activate 

202 def test_existant_publisher(self): 

203 responses.add( 

204 responses.GET, 

205 self.api_url_find("jetbrains"), 

206 json=JETBRAINS_FIND_RESPONSE, 

207 status=200, 

208 ) 

209 response = self.client.get("/publisher/jetbrains") 

210 self.assertEqual(response.status_code, 200) 

211 self.assert_template_used("store/publisher-details.html") 

212 

213 @responses.activate 

214 def test_non_existant_publisher(self): 

215 responses.add( 

216 responses.GET, 

217 self.api_url_find("toto"), 

218 json={"results": []}, 

219 status=200, 

220 ) 

221 response = self.client.get("/publisher/toto") 

222 self.assertEqual(response.status_code, 200) 

223 self.assert_template_used("store/community-publisher-details.html") 

224 

225 @responses.activate 

226 def test_api_error(self): 

227 responses.add( 

228 responses.GET, self.api_url_find("jetbrains"), json={}, status=504 

229 ) 

230 response = self.client.get("/publisher/jetbrains") 

231 self.assertEqual(response.status_code, 200) 

232 self.assert_template_used("store/publisher-details.html") 

233 

234 @responses.activate 

235 def test_no_snaps_from_api(self): 

236 responses.add( 

237 responses.GET, 

238 self.api_url_find("jetbrains"), 

239 json={"results": []}, 

240 status=200, 

241 ) 

242 response = self.client.get("/publisher/jetbrains") 

243 self.assertEqual(response.status_code, 200) 

244 self.assert_template_used("store/publisher-details.html") 

245 

246 @responses.activate 

247 def test_featured_snaps_hydrated_from_api(self): 

248 # jetbrains.yaml features "intellij-idea" and "pycharm". The API 

249 # returns intellij-idea (listed) but not pycharm (unlisted/gone). 

250 find_response = { 

251 "results": [ 

252 _snap_result("intellij-idea", "IntelliJ IDEA"), 

253 _snap_result("goland", "GoLand"), 

254 ] 

255 } 

256 responses.add( 

257 responses.GET, 

258 self.api_url_find("jetbrains"), 

259 json=find_response, 

260 status=200, 

261 ) 

262 response = self.client.get("/publisher/jetbrains") 

263 self.assertEqual(response.status_code, 200) 

264 

265 featured = self.get_context_variable("featured_snaps") 

266 names = [snap["package_name"] for snap in featured] 

267 # pycharm is not in the API response, so it is dropped. 

268 self.assertEqual(names, ["intellij-idea"]) 

269 

270 snap = featured[0] 

271 # title/summary/icon come from the API 

272 self.assertEqual(snap["title"], "IntelliJ IDEA") 

273 self.assertEqual(snap["summary"], "IntelliJ IDEA summary") 

274 self.assertTrue(snap["icon_url"]) 

275 # background/description come from the YAML. 

276 self.assertEqual(snap["background"], "#000000") 

277 self.assertIn("IntelliJ IDEA", snap["description"]) 

278 

279 @responses.activate 

280 def test_popular_snaps_hydrated_from_api(self): 

281 responses.add( 

282 responses.GET, 

283 self.api_url_find("snapcrafters"), 

284 json=SNAPCRAFTERS_FIND_RESPONSE, 

285 status=200, 

286 ) 

287 response = self.client.get("/publisher/snapcrafters") 

288 self.assertEqual(response.status_code, 200) 

289 self.assert_template_used("store/publisher-details.html") 

290 

291 body = response.get_data(as_text=True) 

292 # make sure we render only the snaps listed in snapcrafters-snaps.yaml 

293 self.assertIn('href="/sublime-text"', body) 

294 self.assertIn('href="/discord"', body) 

295 self.assertNotIn('href="/eclipse"', body) 

296 self.assertNotIn('href="/alacritty"', body)