22
33import importlib .util
44import os
5+ import tempfile
6+ from pathlib import Path
57
68import requests
79from unittest import TestCase
8- from unittest .mock import mock_open , patch
10+ from unittest .mock import patch
911
1012# The script has a hyphenated filename, so we need importlib to load it
11- _SCRIPT_PATH = os .path .join (
12- os .path .dirname (os .path .abspath (__file__ )),
13- os .pardir ,
14- os .pardir ,
15- os .pardir ,
16- "metalk8s" ,
17- "addons" ,
18- "prometheus-operator" ,
19- "deployed" ,
20- "files" ,
21- "restart-on-ca-change.py" ,
13+ _SCRIPT_PATH = (
14+ Path (__file__ ).resolve ().parents [3 ]
15+ / "metalk8s"
16+ / "addons"
17+ / "prometheus-operator"
18+ / "deployed"
19+ / "files"
20+ / "restart-on-ca-change.py"
2221)
2322_spec = importlib .util .spec_from_file_location ("restart_on_ca_change" , _SCRIPT_PATH )
2423restart_on_ca_change = importlib .util .module_from_spec (_spec )
@@ -37,28 +36,36 @@ class TestHashFile(TestCase):
3736
3837 def test_returns_sha256_hex (self ):
3938 """hash_file returns a 64-char hex string."""
40- with patch ("builtins.open" , mock_open (read_data = b"cert-data" )):
41- result = restart_on_ca_change .hash_file ("/tmp/secrets/ca.crt" )
39+ with tempfile .NamedTemporaryFile () as f :
40+ f .write (b"cert-data" )
41+ f .flush ()
42+ result = restart_on_ca_change .hash_file (Path (f .name ))
4243 self .assertIsInstance (result , str )
4344 self .assertEqual (len (result ), 64 )
4445
4546 def test_different_content_different_hash (self ):
4647 """hash_file returns different hashes for different file contents."""
47- with patch ("builtins.open" , mock_open (read_data = b"cert-v1" )):
48- hash_v1 = restart_on_ca_change .hash_file ("/tmp/secrets/ca.crt" )
48+ with tempfile .NamedTemporaryFile () as f1 , tempfile .NamedTemporaryFile () as f2 :
49+ f1 .write (b"cert-v1" )
50+ f1 .flush ()
51+ hash_v1 = restart_on_ca_change .hash_file (Path (f1 .name ))
4952
50- with patch ("builtins.open" , mock_open (read_data = b"cert-v2" )):
51- hash_v2 = restart_on_ca_change .hash_file ("/tmp/secrets/ca.crt" )
53+ f2 .write (b"cert-v2" )
54+ f2 .flush ()
55+ hash_v2 = restart_on_ca_change .hash_file (Path (f2 .name ))
5256
5357 self .assertNotEqual (hash_v1 , hash_v2 )
5458
5559 def test_same_content_same_hash (self ):
5660 """hash_file returns the same hash for the same content."""
57- with patch ("builtins.open" , mock_open (read_data = b"cert-data" )):
58- hash_1 = restart_on_ca_change .hash_file ("/tmp/secrets/ca.crt" )
61+ with tempfile .NamedTemporaryFile () as f1 , tempfile .NamedTemporaryFile () as f2 :
62+ f1 .write (b"cert-data" )
63+ f1 .flush ()
64+ hash_1 = restart_on_ca_change .hash_file (Path (f1 .name ))
5965
60- with patch ("builtins.open" , mock_open (read_data = b"cert-data" )):
61- hash_2 = restart_on_ca_change .hash_file ("/tmp/secrets/ca.crt" )
66+ f2 .write (b"cert-data" )
67+ f2 .flush ()
68+ hash_2 = restart_on_ca_change .hash_file (Path (f2 .name ))
6269
6370 self .assertEqual (hash_1 , hash_2 )
6471
@@ -67,49 +74,54 @@ def test_same_content_same_hash(self):
6774class TestMain (TestCase ):
6875 """Tests for main function."""
6976
70- @patch ("os.path.exists" , return_value = False )
71- def test_ca_file_missing_skips (self , _mock_exists ):
77+ def test_ca_file_missing_skips (self ):
7278 """main skips when CA file does not exist."""
73- with patch ("builtins.print" ) as mock_print :
74- restart_on_ca_change .main ()
75- mock_print .assert_called_once_with (
76- "CA file /tmp/secrets/ca.crt does not exist, skipping"
77- )
79+ with tempfile .TemporaryDirectory () as tmpdir :
80+ env = {** ENV_VARS , "CA_DIR" : tmpdir }
81+ with patch .dict (os .environ , env ), patch ("builtins.print" ) as mock_print :
82+ restart_on_ca_change .main ()
83+ mock_print .assert_called_once_with (
84+ f"CA file { Path (tmpdir ) / 'ca.crt' } does not exist, skipping"
85+ )
7886
7987 def test_initial_load_skips_restart (self ):
8088 """main writes hash and skips restart on initial load."""
81- # ca_file exists, hash_file_path does not
82- with patch (
83- "os.path.exists" , side_effect = lambda p : p == "/tmp/secrets/ca.crt"
84- ), patch .object (
85- restart_on_ca_change , "hash_file" , return_value = "abc123"
86- ), patch (
87- "builtins.open" , mock_open ()
88- ), patch (
89- "builtins.print"
90- ) as mock_print :
91- restart_on_ca_change .main ()
92- mock_print .assert_called_once_with ("Initial CA load, skipping restart" )
89+ with tempfile .TemporaryDirectory () as tmpdir :
90+ ca_file = Path (tmpdir ) / "ca.crt"
91+ ca_file .write_bytes (b"cert-data" )
92+ env = {** ENV_VARS , "CA_DIR" : tmpdir }
93+ with patch .dict (os .environ , env ), patch ("builtins.print" ) as mock_print :
94+ restart_on_ca_change .main ()
95+ mock_print .assert_called_once_with ("Initial CA load, skipping restart" )
96+ # Hash file should have been created
97+ hash_file = Path (tmpdir ) / ".ca-hash-previous"
98+ self .assertTrue (hash_file .exists ())
9399
94100 def test_unchanged_hash_no_restart (self ):
95101 """main does nothing when hash has not changed."""
96- with patch ("os.path.exists" , return_value = True ), patch .object (
97- restart_on_ca_change , "hash_file" , return_value = "same-hash"
98- ), patch ("builtins.open" , mock_open (read_data = "same-hash" )), patch (
99- "builtins.print"
100- ) as mock_print :
101- restart_on_ca_change .main ()
102- mock_print .assert_not_called ()
102+ with tempfile .TemporaryDirectory () as tmpdir :
103+ ca_file = Path (tmpdir ) / "ca.crt"
104+ ca_file .write_bytes (b"cert-data" )
105+ # Pre-compute and write the hash
106+ current_hash = restart_on_ca_change .hash_file (ca_file )
107+ hash_file = Path (tmpdir ) / ".ca-hash-previous"
108+ hash_file .write_text (current_hash )
109+ env = {** ENV_VARS , "CA_DIR" : tmpdir }
110+ with patch .dict (os .environ , env ), patch ("builtins.print" ) as mock_print :
111+ restart_on_ca_change .main ()
112+ mock_print .assert_not_called ()
103113
104114 @patch .object (restart_on_ca_change , "trigger_restart" )
105115 def test_changed_hash_triggers_restart (self , mock_restart ):
106116 """main triggers restart when hash has changed."""
107- with patch ("os.path.exists" , return_value = True ), patch .object (
108- restart_on_ca_change , "hash_file" , return_value = "new-hash"
109- ), patch ("builtins.open" , mock_open (read_data = "old-hash" )), patch (
110- "builtins.print"
111- ) as mock_print :
112- restart_on_ca_change .main ()
117+ with tempfile .TemporaryDirectory () as tmpdir :
118+ ca_file = Path (tmpdir ) / "ca.crt"
119+ ca_file .write_bytes (b"cert-data" )
120+ hash_file = Path (tmpdir ) / ".ca-hash-previous"
121+ hash_file .write_text ("old-hash" )
122+ env = {** ENV_VARS , "CA_DIR" : tmpdir }
123+ with patch .dict (os .environ , env ), patch ("builtins.print" ) as mock_print :
124+ restart_on_ca_change .main ()
113125
114126 mock_restart .assert_called_once_with (
115127 "metalk8s-monitoring" , "oauth2-proxy-prometheus"
@@ -119,62 +131,61 @@ def test_changed_hash_triggers_restart(self, mock_restart):
119131 mock_print .call_args [0 ][0 ],
120132 )
121133
122- @patch ("builtins.print" )
123134 @patch .object (
124135 restart_on_ca_change ,
125136 "trigger_restart" ,
126137 side_effect = requests .RequestException ("refused" ),
127138 )
128- @patch .object (restart_on_ca_change , "hash_file" , return_value = "new-hash" )
129- @patch ("os.path.exists" , return_value = True )
130- def test_api_failure_exits_with_error (
131- self , _mock_exists , _mock_hash , _mock_restart , _mock_print
132- ):
139+ def test_api_failure_exits_with_error (self , _mock_restart ):
133140 """main exits with code 1 when the API call fails."""
134- with patch ("builtins.open" , mock_open (read_data = "old-hash" )):
135- with self .assertRaises (SystemExit ) as ctx :
136- restart_on_ca_change .main ()
141+ with tempfile .TemporaryDirectory () as tmpdir :
142+ ca_file = Path (tmpdir ) / "ca.crt"
143+ ca_file .write_bytes (b"cert-data" )
144+ hash_file = Path (tmpdir ) / ".ca-hash-previous"
145+ hash_file .write_text ("old-hash" )
146+ env = {** ENV_VARS , "CA_DIR" : tmpdir }
147+ with patch .dict (os .environ , env ), patch ("builtins.print" ):
148+ with self .assertRaises (SystemExit ) as ctx :
149+ restart_on_ca_change .main ()
137150
138151 self .assertEqual (ctx .exception .code , 1 )
139152
140- @patch ("builtins.print" )
141153 @patch .object (
142154 restart_on_ca_change ,
143155 "trigger_restart" ,
144156 side_effect = requests .RequestException ("refused" ),
145157 )
146- @patch .object (restart_on_ca_change , "hash_file" , return_value = "new-hash" )
147- @patch ("os.path.exists" , return_value = True )
148- def test_hash_not_persisted_on_api_failure (
149- self , _mock_exists , _mock_hash , _mock_restart , _mock_print
150- ):
158+ def test_hash_not_persisted_on_api_failure (self , _mock_restart ):
151159 """Hash file is not updated when the API call fails."""
152- mock_file = mock_open (read_data = "old-hash" )
153-
154- with patch ("builtins.open" , mock_file ):
155- try :
156- restart_on_ca_change .main ()
157- except SystemExit :
158- pass
159-
160- # Only the hash file read should have happened, no write
161- write_calls = mock_file ().write .call_args_list
162- self .assertEqual (len (write_calls ), 0 )
160+ with tempfile .TemporaryDirectory () as tmpdir :
161+ ca_file = Path (tmpdir ) / "ca.crt"
162+ ca_file .write_bytes (b"cert-data" )
163+ hash_file = Path (tmpdir ) / ".ca-hash-previous"
164+ hash_file .write_text ("old-hash" )
165+ env = {** ENV_VARS , "CA_DIR" : tmpdir }
166+ with patch .dict (os .environ , env ), patch ("builtins.print" ):
167+ try :
168+ restart_on_ca_change .main ()
169+ except SystemExit :
170+ pass
171+
172+ # Hash file should still contain the old hash
173+ self .assertEqual (hash_file .read_text (), "old-hash" )
163174
164175
165176class TestTriggerRestart (TestCase ):
166177 """Tests for trigger_restart function."""
167178
168179 @patch .object (restart_on_ca_change .requests , "patch" )
169- @patch (
170- "builtins.open" ,
171- mock_open (read_data = "fake-token" ),
172- )
173180 def test_sends_patch_request (self , mock_patch ):
174181 """trigger_restart sends a PATCH to the K8s API."""
175- restart_on_ca_change .trigger_restart (
176- "metalk8s-monitoring" , "oauth2-proxy-prometheus"
177- )
182+ with tempfile .NamedTemporaryFile (mode = "w" , suffix = ".token" ) as f :
183+ f .write ("fake-token" )
184+ f .flush ()
185+ with patch .object (restart_on_ca_change , "SA_TOKEN" , Path (f .name )):
186+ restart_on_ca_change .trigger_restart (
187+ "metalk8s-monitoring" , "oauth2-proxy-prometheus"
188+ )
178189
179190 mock_patch .assert_called_once ()
180191 args , kwargs = mock_patch .call_args
0 commit comments