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
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-10 22:08 +0000
1import unittest
3import datetime
4from freezegun import freeze_time
5import webapp.store.logic as logic
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)
15 self.assertEqual(result, {})
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 ]
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 }
53 self.assertEqual(result, expected_result)
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 }
116 self.assertEqual(result, expected_result)
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 ]
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 }
182 self.assertEqual(result, expected_result)
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 ]
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 }
228 self.assertEqual(result, expected_result)
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")
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")
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")
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")
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 )
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")
276 stable_version = logic.extract_info_channel_map(
277 channel_map, "track", "stable"
278 )["version"]
279 self.assertEqual(stable_version, "10")
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)
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")
304 strict_result = logic.extract_info_channel_map(
305 channel_map, "track", "stable"
306 )["confinement"]
307 self.assertEqual(strict_result, "strict")
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)
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)
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 )
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 )
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 )
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 )
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)
382 self.assertEqual(result, "12 January 2019")
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)
388 self.assertEqual(result, "Today")
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)
395 self.assertEqual(result, "Yesterday")
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 }
406 result = logic.get_snap_banner_url(snap_with_banner)
408 self.assertEqual(result.get("banner_url"), "banner.png")
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 }
418 result = logic.get_snap_banner_url(snap_with_banner)
420 self.assertEqual(result.get("banner_url"), None)
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 }
432 latest_edge = {
433 "channel": {
434 "architecture": "amd64",
435 "risk": "edge",
436 "track": "latest",
437 "released-at": "2023-03-01",
438 }
439 }
441 latest_stable_arm = {
442 "channel": {
443 "architecture": "arm64",
444 "released-at": "2023-01-04",
445 "risk": "candidate",
446 "track": "latest",
447 }
448 }
450 channel_map_single_channel = [latest_stable]
452 channel_map_stable_first = [latest_stable, latest_edge]
454 channel_map_stable_second = [latest_edge, latest_stable]
456 channel_map_multiple_stable_architectures = [
457 latest_edge,
458 latest_stable,
459 latest_stable_arm,
460 ]
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)
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 )
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 )
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 )
501 multiple_stable_channels_results = logic.get_latest_versions(
502 channel_map_multiple_stable_architectures, "latest", "stable"
503 )
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 )
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 }
523 latest_edge = {
524 "channel": {
525 "risk": "edge",
526 "track": "latest",
527 "released-at": "2023-03-01",
528 }
529 }
531 result = logic.get_last_updated_versions([latest_stable, latest_edge])
533 self.assertEqual(
534 result, [latest_edge["channel"], latest_stable["channel"]]
535 )
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)
542 result = logic.is_snap_old("")
543 self.assertFalse(result)
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)
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)
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)
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)
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)
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
583 # With default threshold (2 years), should not be old
584 result = logic.is_snap_old(date_one_year_ago)
585 self.assertFalse(result)
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)
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 ]
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}")