|
| 1 | +from dataclasses import dataclass |
| 2 | +from datetime import datetime, timezone |
1 | 3 | from unittest.mock import patch
|
2 | 4 |
|
3 | 5 | import pytest
|
4 | 6 |
|
5 | 7 | from ops2deb.exceptions import Ops2debLockFileError
|
6 |
| -from ops2deb.fetcher import FetchResult |
7 | 8 | from ops2deb.lockfile import Lock
|
8 | 9 |
|
9 | 10 |
|
10 |
| -def test__init__should_create_empty_lock_when_lockfile_does_not_exist(lockfile_path): |
| 11 | +@dataclass |
| 12 | +class UrlAndHash: |
| 13 | + url: str |
| 14 | + sha256: str |
| 15 | + |
| 16 | + |
| 17 | +def test_init__creates_an_empty_lock_when_lockfile_does_not_exist(lockfile_path): |
| 18 | + # Given |
11 | 19 | lock = Lock(lockfile_path)
|
| 20 | + |
| 21 | + # Then |
12 | 22 | assert lock._entries == {}
|
13 | 23 |
|
14 | 24 |
|
15 |
| -def test__init__should_raise_when_lockfile_path_is_a_directory(tmp_path): |
| 25 | +def test_init__raises_when_lockfile_path_is_a_directory(tmp_path): |
| 26 | + # When |
16 | 27 | with pytest.raises(Ops2debLockFileError) as error:
|
17 | 28 | Lock(tmp_path)
|
| 29 | + |
| 30 | + # Then |
18 | 31 | assert error.match("Path points to a directory")
|
19 | 32 |
|
20 | 33 |
|
21 |
| -def test__init__should_raise_when_lockfile_path_contains_invalid_yaml(lockfile_path): |
| 34 | +def test_init__raises_when_lockfile_path_contains_invalid_yaml(lockfile_path): |
| 35 | + # Given |
22 | 36 | lockfile_path.write_text("@£¢±")
|
| 37 | + |
| 38 | + # When |
23 | 39 | with pytest.raises(Ops2debLockFileError) as error:
|
24 | 40 | Lock(lockfile_path)
|
| 41 | + |
| 42 | + # Then |
25 | 43 | assert error.match("Invalid YAML file")
|
26 | 44 |
|
27 | 45 |
|
28 |
| -def test__init__should_raise_when_lockfile_cannot_be_parsed_with_pydantic(lockfile_path): |
| 46 | +def test_init__raises_when_lockfile_cannot_be_parsed_with_pydantic(lockfile_path): |
| 47 | + # Given |
29 | 48 | lockfile_path.write_text("1")
|
| 49 | + |
| 50 | + # When |
30 | 51 | with pytest.raises(Ops2debLockFileError) as error:
|
31 | 52 | Lock(lockfile_path)
|
| 53 | + |
| 54 | + # Then |
32 | 55 | assert error.match("Invalid lockfile")
|
33 | 56 |
|
34 | 57 |
|
35 |
| -def test_sha256__should_raise_when_url_is_not_in_cache(lockfile_path): |
| 58 | +def test_sha256__raises_when_url_is_not_in_cache(lockfile_path): |
| 59 | + # Given |
36 | 60 | url = "http://tests.com/file.tar.gz"
|
| 61 | + |
| 62 | + # When |
37 | 63 | with pytest.raises(Ops2debLockFileError) as error:
|
38 | 64 | Lock(lockfile_path).sha256(url)
|
| 65 | + |
| 66 | + # Then |
39 | 67 | assert error.match(f"Unknown hash for url {url}, please run ops2deb lock")
|
40 | 68 |
|
41 | 69 |
|
42 | 70 | def test_save__should_not_create_a_file_when_lock_is_empty(lockfile_path):
|
43 |
| - Lock(lockfile_path).save() |
| 71 | + # Given |
| 72 | + lock = Lock(lockfile_path) |
| 73 | + |
| 74 | + # When |
| 75 | + lock.save() |
| 76 | + |
| 77 | + # Then |
44 | 78 | assert lockfile_path.exists() is False
|
45 | 79 |
|
46 | 80 |
|
47 |
| -def test_save__should_produce_a_lockfile_that_contains_added_entries( |
48 |
| - lockfile_path, tmp_path |
49 |
| -): |
| 81 | +def test_save__produces_a_lockfile_that_contains_added_entries(lockfile_path): |
| 82 | + # Given |
50 | 83 | lock = Lock(lockfile_path)
|
51 |
| - lock.add([FetchResult("http://tests.com/file.tar.gz", "deadbeef", tmp_path, None)]) |
| 84 | + lock.add([UrlAndHash("http://tests.com/file.tar.gz", "deadbeef")]) |
| 85 | + |
| 86 | + # When |
52 | 87 | lock.save()
|
53 |
| - lock = Lock(lockfile_path) |
54 |
| - assert lock.sha256("http://tests.com/file.tar.gz") == "deadbeef" |
| 88 | + |
| 89 | + # Then |
| 90 | + assert Lock(lockfile_path).sha256("http://tests.com/file.tar.gz") == "deadbeef" |
55 | 91 |
|
56 | 92 |
|
57 | 93 | @patch("yaml.dump")
|
58 | 94 | def test_save__should_not_write_file_when_no_entry_have_been_added_nor_removed(
|
59 | 95 | mock_dump, lockfile_path
|
60 | 96 | ):
|
| 97 | + # Given |
61 | 98 | lock = Lock(lockfile_path)
|
| 99 | + |
| 100 | + # When |
62 | 101 | lock.save()
|
| 102 | + |
| 103 | + # Then |
63 | 104 | mock_dump.assert_not_called()
|
64 | 105 |
|
65 | 106 |
|
66 |
| -def test_save__set_the_same_timestamp_to_added_entries(lockfile_path, tmp_path): |
| 107 | +def test_save__sets_the_same_timestamp_to_added_entries(lockfile_path): |
| 108 | + # Given |
67 | 109 | lock = Lock(lockfile_path)
|
68 |
| - lock.add([FetchResult("http://tests.com/file1.tar.gz", "deadbeef", tmp_path, None)]) |
69 |
| - lock.add([FetchResult("http://tests.com/file2.tar.gz", "deadbeef", tmp_path, None)]) |
| 110 | + lock.add([UrlAndHash("http://tests.com/file1.tar.gz", "deadbeef")]) |
| 111 | + lock.add([UrlAndHash("http://tests.com/file2.tar.gz", "deadbeef")]) |
| 112 | + |
| 113 | + # When |
70 | 114 | lock.save()
|
| 115 | + |
| 116 | + # Then |
71 | 117 | lock = Lock(lockfile_path)
|
72 | 118 | timestamp_1 = lock.timestamp("http://tests.com/file1.tar.gz")
|
73 | 119 | timestamp_2 = lock.timestamp("http://tests.com/file2.tar.gz")
|
74 | 120 | assert timestamp_1 == timestamp_2
|
| 121 | + |
| 122 | + |
| 123 | +@patch("ops2deb.lockfile.datetime") |
| 124 | +def test_save__should_not_include_microseconds_in_timestamps( |
| 125 | + mock_datetime, lockfile_path |
| 126 | +): |
| 127 | + # Given |
| 128 | + mock_datetime.now.return_value = datetime( |
| 129 | + 2023, 3, 4, 0, 22, 14, 1234, tzinfo=timezone.utc |
| 130 | + ) |
| 131 | + lock = Lock(lockfile_path) |
| 132 | + lock.add([UrlAndHash("http://tests.com/file1.tar.gz", "deadbeef")]) |
| 133 | + |
| 134 | + # When |
| 135 | + lock.save() |
| 136 | + |
| 137 | + # Then |
| 138 | + assert "2023-03-04 00:22:14+00:00" in lockfile_path.read_text() |
| 139 | + |
| 140 | + |
| 141 | +@patch("ops2deb.lockfile.datetime") |
| 142 | +def test_save__should_be_idempotent(mock_datetime, lockfile_path): |
| 143 | + # Given |
| 144 | + mock_datetime.now.return_value = datetime(2023, 3, 4, 0, 22, 14, tzinfo=timezone.utc) |
| 145 | + |
| 146 | + # When |
| 147 | + lock = Lock(lockfile_path) |
| 148 | + lock.add([UrlAndHash("http://tests.com/file1.tar.gz", "deadbeef")]) |
| 149 | + lock.add([UrlAndHash("http://tests.com/file2.tar.gz", "deadbeef")]) |
| 150 | + lock.save() |
| 151 | + lockfile_content_0 = lockfile_path.read_text() |
| 152 | + Lock(lockfile_path).save() |
| 153 | + lockfile_content_1 = lockfile_path.read_text() |
| 154 | + print(lockfile_content_0) |
| 155 | + |
| 156 | + # Then |
| 157 | + assert lockfile_content_0 == lockfile_content_1 |
| 158 | + |
| 159 | + |
| 160 | +@patch("ops2deb.lockfile.datetime") |
| 161 | +def test_save__should_not_use_yaml_anchors_in_timestamps(mock_datetime, lockfile_path): |
| 162 | + # happens when you reference an object multiple time in a YAML document and when the |
| 163 | + # pyyaml dumper is not configured to "ignore aliases" |
| 164 | + |
| 165 | + # Given |
| 166 | + mock_datetime.now.return_value = datetime(2023, 3, 4, 0, 22, 14, tzinfo=timezone.utc) |
| 167 | + |
| 168 | + # When |
| 169 | + lock = Lock(lockfile_path) |
| 170 | + lock.add([UrlAndHash("http://tests.com/file1.tar.gz", "deadbeef")]) |
| 171 | + lock.add([UrlAndHash("http://tests.com/file2.tar.gz", "deadbeef")]) |
| 172 | + lock.save() |
| 173 | + |
| 174 | + # Then |
| 175 | + assert "timestamp: &" not in lockfile_path.read_text() |
0 commit comments