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

173 statements  

« prev     ^ index     » next       coverage.py v7.16.0, created at 2026-09-10 22:08 +0000

1import unittest 

2 

3import datetime 

4from freezegun import freeze_time 

5import webapp.store.logic as logic 

6 

7 

8class StoreLogicTest(unittest.TestCase): 

9 # convert_channel_maps 

10 # === 

11 def test_empty_channel_map(self): 

12 channel_maps_list = [] 

13 result = logic.convert_channel_maps(channel_maps_list) 

14 

15 self.assertEqual(result, {}) 

16 

17 def test_one_track_channel_map(self): 

18 channel_maps_list = [ 

19 { 

20 "channel": { 

21 "name": "channel", 

22 "architecture": "arch", 

23 "track": "track", 

24 "risk": "risk", 

25 "released-at": "2019-01-12T16:48:41.821037+00:00", 

26 }, 

27 "created-at": "2019-01-12T16:48:41.821037+00:00", 

28 "confinement": "confinement", 

29 "download": {"size": "size"}, 

30 "version": "version", 

31 "revision": "revision", 

32 } 

33 ] 

34 

35 result = logic.convert_channel_maps(channel_maps_list) 

36 expected_result = { 

37 "arch": { 

38 "track": [ 

39 { 

40 "channel": "channel", 

41 "released-at": "12 January 2019", 

42 "confinement": "confinement", 

43 "size": "size", 

44 "risk": "risk", 

45 "version": "version", 

46 "revision": "revision", 

47 "sboms": None, 

48 } 

49 ] 

50 } 

51 } 

52 

53 self.assertEqual(result, expected_result) 

54 

55 def test_multiple_track_same_arch_channel_map(self): 

56 channel_maps_list = [ 

57 { 

58 "channel": { 

59 "name": "channel", 

60 "architecture": "arch", 

61 "track": "track", 

62 "risk": "risk", 

63 "released-at": "2019-01-12T16:48:41.821037+00:00", 

64 }, 

65 "created-at": "2019-01-12T16:48:41.821037+00:00", 

66 "confinement": "confinement", 

67 "download": {"size": "size"}, 

68 "version": "version", 

69 "revision": "revision", 

70 }, 

71 { 

72 "channel": { 

73 "name": "channel", 

74 "architecture": "arch", 

75 "track": "track1", 

76 "risk": "risk", 

77 "released-at": "2019-01-12T16:48:41.821037+00:00", 

78 }, 

79 "created-at": "2019-01-12T16:48:41.821037+00:00", 

80 "confinement": "confinement", 

81 "download": {"size": "size"}, 

82 "version": "version", 

83 "revision": "revision", 

84 }, 

85 ] 

86 result = logic.convert_channel_maps(channel_maps_list) 

87 expected_result = { 

88 "arch": { 

89 "track": [ 

90 { 

91 "channel": "channel", 

92 "released-at": "12 January 2019", 

93 "confinement": "confinement", 

94 "size": "size", 

95 "risk": "risk", 

96 "version": "version", 

97 "revision": "revision", 

98 "sboms": None, 

99 } 

100 ], 

101 "track1": [ 

102 { 

103 "channel": "channel", 

104 "released-at": "12 January 2019", 

105 "confinement": "confinement", 

106 "size": "size", 

107 "risk": "risk", 

108 "version": "version", 

109 "revision": "revision", 

110 "sboms": None, 

111 } 

112 ], 

113 } 

114 } 

115 

116 self.assertEqual(result, expected_result) 

117 

118 def test_multiple_track_different_arch_channel_map(self): 

119 channel_maps_list = [ 

120 { 

121 "channel": { 

122 "name": "channel", 

123 "architecture": "arch", 

124 "track": "track", 

125 "risk": "risk", 

126 "released-at": "2019-01-12T16:48:41.821037+00:00", 

127 }, 

128 "created-at": "2019-01-12T16:48:41.821037+00:00", 

129 "confinement": "confinement", 

130 "download": {"size": "size"}, 

131 "version": "version", 

132 "revision": "revision", 

133 }, 

134 { 

135 "channel": { 

136 "name": "channel", 

137 "architecture": "arch1", 

138 "track": "track", 

139 "risk": "risk", 

140 "released-at": "2019-01-12T16:48:41.821037+00:00", 

141 }, 

142 "created-at": "2019-01-12T16:48:41.821037+00:00", 

143 "confinement": "confinement", 

144 "download": {"size": "size"}, 

145 "version": "version", 

146 "revision": "revision", 

147 }, 

148 ] 

149 

150 result = logic.convert_channel_maps(channel_maps_list) 

151 expected_result = { 

152 "arch": { 

153 "track": [ 

154 { 

155 "channel": "channel", 

156 "released-at": "12 January 2019", 

157 "confinement": "confinement", 

158 "size": "size", 

159 "risk": "risk", 

160 "version": "version", 

161 "revision": "revision", 

162 "sboms": None, 

163 } 

164 ] 

165 }, 

166 "arch1": { 

167 "track": [ 

168 { 

169 "channel": "channel", 

170 "released-at": "12 January 2019", 

171 "confinement": "confinement", 

172 "size": "size", 

173 "risk": "risk", 

174 "version": "version", 

175 "revision": "revision", 

176 "sboms": None, 

177 } 

178 ] 

179 }, 

180 } 

181 

182 self.assertEqual(result, expected_result) 

183 

184 def test_channel_map_with_sboms(self): 

185 sboms = [ 

186 { 

187 "format": "spdx2.3.json", 

188 "url": "https://api.snapcraft.io/api/v1/sboms/download/" 

189 "sbom_snap_SNAP_ID.spdx2.3.json", 

190 } 

191 ] 

192 channel_maps_list = [ 

193 { 

194 "channel": { 

195 "name": "channel", 

196 "architecture": "arch", 

197 "track": "track", 

198 "risk": "risk", 

199 "released-at": "2019-01-12T16:48:41.821037+00:00", 

200 }, 

201 "created-at": "2019-01-12T16:48:41.821037+00:00", 

202 "confinement": "confinement", 

203 "download": {"size": "size"}, 

204 "version": "version", 

205 "revision": "revision", 

206 "sboms": sboms, 

207 } 

208 ] 

209 

210 result = logic.convert_channel_maps(channel_maps_list) 

211 expected_result = { 

212 "arch": { 

213 "track": [ 

214 { 

215 "channel": "channel", 

216 "released-at": "12 January 2019", 

217 "confinement": "confinement", 

218 "size": "size", 

219 "risk": "risk", 

220 "version": "version", 

221 "revision": "revision", 

222 "sboms": sboms, 

223 } 

224 ] 

225 } 

226 } 

227 

228 self.assertEqual(result, expected_result) 

229 

230 def test_get_lowest_available_risk(self): 

231 channel_map = {"arch": {"track": [{"risk": "edge"}]}} 

232 edge_result = logic.get_lowest_available_risk(channel_map, "track") 

233 self.assertEqual(edge_result, "edge") 

234 

235 channel_map["arch"]["track"].append({"risk": "beta"}) 

236 beta_result = logic.get_lowest_available_risk(channel_map, "track") 

237 self.assertEqual(beta_result, "beta") 

238 

239 channel_map["arch"]["track"].append({"risk": "candidate"}) 

240 cand_result = logic.get_lowest_available_risk(channel_map, "track") 

241 self.assertEqual(cand_result, "candidate") 

242 

243 channel_map["arch"]["track"].append({"risk": "stable"}) 

244 stable_result = logic.get_lowest_available_risk(channel_map, "track") 

245 self.assertEqual(stable_result, "stable") 

246 

247 # assert that channel_map has been updated successfully 

248 self.assertEqual( 

249 channel_map, 

250 { 

251 "arch": { 

252 "track": [ 

253 {"risk": "edge"}, 

254 {"risk": "beta"}, 

255 {"risk": "candidate"}, 

256 {"risk": "stable"}, 

257 ] 

258 } 

259 }, 

260 ) 

261 

262 def test_get_version(self): 

263 channel_map = { 

264 "arch": { 

265 "track": [ 

266 {"risk": "edge", "version": "12"}, 

267 {"risk": "stable", "version": "10"}, 

268 ] 

269 } 

270 } 

271 edge_version = logic.extract_info_channel_map( 

272 channel_map, "track", "edge" 

273 )["version"] 

274 self.assertEqual(edge_version, "12") 

275 

276 stable_version = logic.extract_info_channel_map( 

277 channel_map, "track", "stable" 

278 )["version"] 

279 self.assertEqual(stable_version, "10") 

280 

281 def test_get_no_version(self): 

282 channel_map = { 

283 "arch": {"track": [{"risk": "stable", "version": "10"}]} 

284 } 

285 no_version = logic.extract_info_channel_map( 

286 channel_map, "track", "edge" 

287 )["version"] 

288 self.assertEqual(no_version, None) 

289 

290 def test_get_confinement(self): 

291 channel_map = { 

292 "arch": { 

293 "track": [ 

294 {"risk": "edge", "confinement": "classic"}, 

295 {"risk": "stable", "confinement": "strict"}, 

296 ] 

297 } 

298 } 

299 classic_result = logic.extract_info_channel_map( 

300 channel_map, "track", "edge" 

301 )["confinement"] 

302 self.assertEqual(classic_result, "classic") 

303 

304 strict_result = logic.extract_info_channel_map( 

305 channel_map, "track", "stable" 

306 )["confinement"] 

307 self.assertEqual(strict_result, "strict") 

308 

309 def test_get_no_confinement(self): 

310 channel_map = { 

311 "arch": {"track": [{"risk": "stable", "confinement": "strict"}]} 

312 } 

313 no_version = logic.extract_info_channel_map( 

314 channel_map, "track", "edge" 

315 )["confinement"] 

316 self.assertEqual(no_version, None) 

317 

318 def test_get_categories(self): 

319 categories = { 

320 "categories": [ 

321 {"name": "featured"}, 

322 {"name": "test"}, 

323 {"name": "development"}, 

324 ] 

325 } 

326 category_list = logic.get_categories(categories) 

327 self.assertTrue( 

328 {"name": "Development", "slug": "development"} in category_list 

329 ) 

330 self.assertTrue({"name": "Games", "slug": "games"} in category_list) 

331 self.assertTrue({"name": "Test", "slug": "test"} in category_list) 

332 

333 def test_get_video_embed_code(self): 

334 youtube_url = "https://youtube.com/watch?v=123" 

335 embed = logic.get_video_embed_code(youtube_url) 

336 self.assertEqual( 

337 embed, 

338 { 

339 "type": "youtube", 

340 "url": "https://youtube.com/embed/123", 

341 "id": "123", 

342 }, 

343 ) 

344 

345 youtu_be_url = "https://youtu.be/123" 

346 embed = logic.get_video_embed_code(youtu_be_url) 

347 self.assertEqual( 

348 embed, 

349 { 

350 "type": "youtube", 

351 "url": "https://youtube.com/embed/123", 

352 "id": "123", 

353 }, 

354 ) 

355 

356 vimeo_url = "https://vimeo.com/123123" 

357 embed = logic.get_video_embed_code(vimeo_url) 

358 self.assertEqual( 

359 embed, 

360 { 

361 "type": "vimeo", 

362 "url": "https://player.vimeo.com/video/123123", 

363 "id": "123123", 

364 }, 

365 ) 

366 

367 asciicinema_url = "https://asciinema.org/a/123" 

368 embed = logic.get_video_embed_code(asciicinema_url) 

369 self.assertEqual( 

370 embed, 

371 { 

372 "type": "asciinema", 

373 "url": "https://asciinema.org/a/123.js", 

374 "id": "123", 

375 }, 

376 ) 

377 

378 def test_convert_date_more_than_yesterday(self): 

379 date_test = "2019-01-12T16:48:41.821037+00:00" 

380 result = logic.convert_date(date_test) 

381 

382 self.assertEqual(result, "12 January 2019") 

383 

384 def test_convert_date_more_today(self): 

385 date_test = datetime.datetime.now().strftime("%Y-%m-%d") 

386 result = logic.convert_date(date_test) 

387 

388 self.assertEqual(result, "Today") 

389 

390 @freeze_time("2021-05-12 10:38:34", tz_offset=-6) 

391 def test_convert_date_timezone_yesterday(self): 

392 date_test = "2021-05-11T10:48:41.821037-06:00" 

393 result = logic.convert_date(date_test) 

394 

395 self.assertEqual(result, "Yesterday") 

396 

397 def test_get_snap_banner(self): 

398 snap_with_banner = { 

399 "media": [ 

400 {"type": "icon", "url": "icon.png"}, 

401 {"type": "banner", "url": "banner.png"}, 

402 {"type": "screenshot", "url": "screenshot.png"}, 

403 ] 

404 } 

405 

406 result = logic.get_snap_banner_url(snap_with_banner) 

407 

408 self.assertEqual(result.get("banner_url"), "banner.png") 

409 

410 def test_get_snap_banner_no_banner(self): 

411 snap_with_banner = { 

412 "media": [ 

413 {"type": "icon", "url": "icon.png"}, 

414 {"type": "screenshot", "url": "screenshot.png"}, 

415 ] 

416 } 

417 

418 result = logic.get_snap_banner_url(snap_with_banner) 

419 

420 self.assertEqual(result.get("banner_url"), None) 

421 

422 def test_get_latest_versions(self): 

423 latest_stable = { 

424 "channel": { 

425 "architecture": "amd64", 

426 "risk": "stable", 

427 "track": "latest", 

428 "released-at": "2023-02-02", 

429 } 

430 } 

431 

432 latest_edge = { 

433 "channel": { 

434 "architecture": "amd64", 

435 "risk": "edge", 

436 "track": "latest", 

437 "released-at": "2023-03-01", 

438 } 

439 } 

440 

441 latest_stable_arm = { 

442 "channel": { 

443 "architecture": "arm64", 

444 "released-at": "2023-01-04", 

445 "risk": "candidate", 

446 "track": "latest", 

447 } 

448 } 

449 

450 channel_map_single_channel = [latest_stable] 

451 

452 channel_map_stable_first = [latest_stable, latest_edge] 

453 

454 channel_map_stable_second = [latest_edge, latest_stable] 

455 

456 channel_map_multiple_stable_architectures = [ 

457 latest_edge, 

458 latest_stable, 

459 latest_stable_arm, 

460 ] 

461 

462 single_result = logic.get_latest_versions( 

463 channel_map_single_channel, "latest", "stable" 

464 ) 

465 self.assertEqual( 

466 single_result[0]["released-at-display"], "2 February 2023" 

467 ) 

468 self.assertEqual(single_result[1], None) 

469 

470 stable_first_result = logic.get_latest_versions( 

471 channel_map_stable_first, "latest", "stable" 

472 ) 

473 self.assertEqual( 

474 stable_first_result[0]["released-at-display"], "2 February 2023" 

475 ) 

476 self.assertEqual( 

477 stable_first_result[1]["released-at-display"], "1 March 2023" 

478 ) 

479 

480 stable_second_result = logic.get_latest_versions( 

481 channel_map_stable_second, "latest", "stable" 

482 ) 

483 self.assertEqual( 

484 stable_second_result[0]["released-at-display"], "2 February 2023" 

485 ) 

486 self.assertEqual( 

487 stable_second_result[1]["released-at-display"], "1 March 2023" 

488 ) 

489 

490 edge_highest_risk_result = logic.get_latest_versions( 

491 channel_map_stable_first, "latest", "edge" 

492 ) 

493 self.assertEqual( 

494 edge_highest_risk_result[0]["released-at-display"], "1 March 2023" 

495 ) 

496 self.assertEqual( 

497 edge_highest_risk_result[1]["released-at-display"], 

498 "2 February 2023", 

499 ) 

500 

501 multiple_stable_channels_results = logic.get_latest_versions( 

502 channel_map_multiple_stable_architectures, "latest", "stable" 

503 ) 

504 

505 self.assertEqual( 

506 multiple_stable_channels_results[0]["released-at-display"], 

507 "2 February 2023", 

508 ) 

509 self.assertEqual( 

510 multiple_stable_channels_results[1]["released-at-display"], 

511 "1 March 2023", 

512 ) 

513 

514 def test_get_last_updated_versions(self): 

515 latest_stable = { 

516 "channel": { 

517 "risk": "stable", 

518 "track": "latest", 

519 "released-at": "2023-02-02", 

520 } 

521 } 

522 

523 latest_edge = { 

524 "channel": { 

525 "risk": "edge", 

526 "track": "latest", 

527 "released-at": "2023-03-01", 

528 } 

529 } 

530 

531 result = logic.get_last_updated_versions([latest_stable, latest_edge]) 

532 

533 self.assertEqual( 

534 result, [latest_edge["channel"], latest_stable["channel"]] 

535 ) 

536 

537 def test_is_snap_old_empty_date(self): 

538 """Test that empty or None date returns not old""" 

539 result = logic.is_snap_old(None) 

540 self.assertFalse(result) 

541 

542 result = logic.is_snap_old("") 

543 self.assertFalse(result) 

544 

545 def test_is_snap_old_invalid_date(self): 

546 """Test that invalid date returns not old""" 

547 result = logic.is_snap_old("invalid-date") 

548 self.assertFalse(result) 

549 

550 @freeze_time("2024-01-01") 

551 def test_is_snap_old_recent_snap(self): 

552 """Test that a recently updated snap is not considered old""" 

553 recent_date = "2023-06-01T10:00:00Z" 

554 result = logic.is_snap_old(recent_date) 

555 self.assertFalse(result) 

556 

557 @freeze_time("2024-01-02T10:00:00Z") 

558 def test_is_snap_old_exactly_two_years(self): 

559 """Test that a snap updated exactly 2 years ago is considered old""" 

560 old_date = "2022-01-02T10:00:00Z" # Exactly 2 years ago 

561 result = logic.is_snap_old(old_date) 

562 self.assertTrue(result) 

563 

564 @freeze_time("2024-01-02") 

565 def test_is_snap_old_very_old_snap(self): 

566 """Test that a very old snap is considered old""" 

567 very_old_date = "2020-01-01T10:00:00Z" 

568 result = logic.is_snap_old(very_old_date) 

569 self.assertTrue(result) 

570 

571 @freeze_time("2024-01-01") 

572 def test_is_snap_old_just_under_two_years(self): 

573 """Test that a snap updated just under 2 years ago is not old""" 

574 almost_old_date = "2022-02-01T10:00:00Z" 

575 result = logic.is_snap_old(almost_old_date) 

576 self.assertFalse(result) 

577 

578 @freeze_time("2024-01-02") 

579 def test_is_snap_old_custom_threshold(self): 

580 """Test that custom threshold works correctly""" 

581 date_one_year_ago = "2022-12-01T10:00:00Z" # Over 1 year ago 

582 

583 # With default threshold (2 years), should not be old 

584 result = logic.is_snap_old(date_one_year_ago) 

585 self.assertFalse(result) 

586 

587 # With custom threshold (1 year), should be old 

588 result = logic.is_snap_old(date_one_year_ago, old_threshold_years=1) 

589 self.assertTrue(result) 

590 

591 def test_is_snap_old_different_date_formats(self): 

592 """Test that different ISO date formats are handled correctly""" 

593 # Test with different timezone formats 

594 dates_to_test = [ 

595 "2020-01-01T10:00:00Z", 

596 "2020-01-01T10:00:00+00:00", 

597 "2020-01-01T10:00:00.000Z", 

598 "2020-01-01T10:00:00.123456+00:00", 

599 ] 

600 

601 for date_str in dates_to_test: 

602 with freeze_time("2024-01-02"): 

603 result = logic.is_snap_old(date_str) 

604 self.assertTrue(result, f"Failed for date format: {date_str}")