From 6f02d7bc492e9623cb8806f3553b2832a800a28a Mon Sep 17 00:00:00 2001 From: Fabio Boi Date: Sun, 4 Oct 2026 11:40:37 +0200 Subject: [PATCH] Correct source transfer encoding for test_enroll.py --- test_enroll.py | 140 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 139 insertions(+), 1 deletion(-) diff --git a/test_enroll.py b/test_enroll.py index 3bcdd2a..c7567f8 100644 --- a/test_enroll.py +++ b/test_enroll.py @@ -1 +1,139 @@ -IiIiT2ZmbGluZSByZWdyZXNzaW9uIHRlc3RzOiBubyByZWFsIGNvbnRhaW5lcnMsIHNlcnZpY2VzLCBBUFQgb3IgY3JlZGVudGlhbHMuIiIiCmltcG9ydCBvcwpmcm9tIHBhdGhsaWIgaW1wb3J0IFBhdGgKaW1wb3J0IHN1YnByb2Nlc3MKaW1wb3J0IHRlbXBmaWxlCmltcG9ydCB1bml0dGVzdAoKU09VUkNFID0gUGF0aChfX2ZpbGVfXykud2l0aF9uYW1lKCdseGMtZW5yb2xsLnNoJykucmVhZF90ZXh0KCkKV09SS0VSID0gU09VUkNFLnNwbGl0KCI8PCdXT1JLRVInXG4iLCAxKVsxXS5zcGxpdCgnXG5XT1JLRVJcbicsIDEpWzBdCgoKZGVmIGJhc2goY29kZSwgKmFyZ3MpOgogICAgcmV0dXJuIHN1YnByb2Nlc3MucnVuKFsnYmFzaCcsICctYycsIGNvZGUsICctLScsICphcmdzXSwgY2FwdHVyZV9vdXRwdXQ9VHJ1ZSwgdGV4dD1UcnVlKQoKCmNsYXNzIEVucm9sbFRlc3RzKHVuaXR0ZXN0LlRlc3RDYXNlKToKICAgIGRlZiB0ZXN0X3N5bnRheChzZWxmKToKICAgICAgICBmb3IgY29kZSBpbiAoU09VUkNFLCBXT1JLRVIpOgogICAgICAgICAgICByID0gc3VicHJvY2Vzcy5ydW4oWydiYXNoJywgJy1uJ10sIGlucHV0PWNvZGUsIHRleHQ9VHJ1ZSwgY2FwdHVyZV9vdXRwdXQ9VHJ1ZSkKICAgICAgICAgICAgc2VsZi5hc3NlcnRFcXVhbChyLnJldHVybmNvZGUsIDAsIHIuc3RkZXJyKQoKICAgIGRlZiB0ZXN0X2ludmVudG9yeV9leGNsdWRlc19jb21wbGV0ZV9zdG9wcGVkX2xvY2tlZF9hbmRfdW5zdXBwb3J0ZWQoc2VsZik6CiAgICAgICAgIyBGdW5jdGlvbi1vbmx5IGhvc3QgaGFybmVzcy4gTWFjIEJhc2ggMyBsYWNrcyBtb2Rlcm4gbm91bnNldC1hcnJheSBzZW1hbnRpY3MuCiAgICAgICAgbGliID0gU09VUkNFLnJzcGxpdCgnXG5tYWluXG4nLCAxKVswXQogICAgICAgIGxpYiA9IGxpYi5yZXBsYWNlKCdzZXQgLUVldW8gcGlwZWZhaWwnLCAnc2V0IC1FZW8gcGlwZWZhaWwnLCAxKQogICAgICAgIGxpYiA9IGxpYi5yZXBsYWNlKCJbWyAkRVVJRCA9PSAwIF1dIHx8IGZhaWwgJ0JpdHRlIGF1ZiBkZW0gUHJveG1veC1Ib3N0IGFscyByb290IHN0YXJ0ZW4uJyIsICc6JykKICAgICAgICBsaWIgPSBsaWIucmVwbGFjZSgnZm9yIHRvb2wgaW4gcGN0IHRpbWVvdXQgcHl0aG9uMzsgZG8gY29tbWFuZCAtdiAiJHRvb2wiID4vZGV2L251bGwgfHwgZmFpbCAiJHRvb2wgZmVobHQgYXVmIGRlbSBIb3N0LiI7IGRvbmUnLCAnOicpCiAgICAgICAgbW9ja3MgPSByJycnCnRpbWVvdXQoKSB7IHNoaWZ0OyAiJEAiOyB9CnBjdCgpIHsKICBjYXNlICIkMSIgaW4KICAgIGxpc3QpIHByaW50ZiAnVk1JRCBTdGF0dXMgTG9jayBOYW1lXG4xMDIgcnVubmluZyBkb2NrZXJcbjEwNiBydW5uaW5nIGFkZ3VhcmRcbjEwNyBydW5uaW5nIGNvbXBsZXRlXG4xMDggc3RvcHBlZCBzdG9wcGVkXG4xMDkgcnVubmluZyBhbHBpbmVcbjExMiBydW5uaW5nIGxvY2tlZFxuMTMzIHJ1bm5pbmcgbWFuYWdlclxuJzs7CiAgICBjb25maWcpIGlmIFtbICQyID09IDExMiBdXTsgdGhlbiBlY2hvICdsb2NrOiBiYWNrdXAnOyBlbHNlIGVjaG8gJ2hvc3RuYW1lOiBmaXh0dXJlJzsgZmk7OwogICAgZXhlYykKICAgICAgW1sgJDQgPT0gc2ggJiYgJDUgPT0gLXMgXV0gfHwgcmV0dXJuIDk5CiAgICAgIGNhdCA+L2Rldi9udWxsCiAgICAgIGNhc2UgIiQyIiBpbgogICAgICAgIDEwMikgZWNobyAnZG9ja2VyfGRlYmlhbnxib29rd29ybXwxfDB8MHwxfDF8ZG9uZSc7OwogICAgICAgIDEwNikgZWNobyAnYWRndWFyZHxkZWJpYW58Ym9va3dvcm18MHwxfDB8MHwxfGRvbmUnOzsKICAgICAgICAxMDcpIGVjaG8gJ2NvbXBsZXRlfHVidW50dXxub2JsZXwxfDF8MHwxfDF8ZG9uZSc7OwogICAgICAgIDEwOSkgZWNobyAnYWxwaW5lfGFscGluZXx1bmtub3dufDB8MHwwfDB8MHxkb25lJzs7CiAgICAgICAgMTMzKSBlY2hvICdtYW5hZ2VyfGRlYmlhbnxib29rd29ybXwwfDF8MXwwfDF8ZG9uZSc7OwogICAgICBlc2FjOzsKICAgICopIGVjaG8gIlVORVhQRUNURUQgTVVUQVRJT046ICQqIiA+JjI7IHJldHVybiA5OTs7CiAgZXNhYwp9CnNjYW4KcHJpbnRmICdDQU5ESURBVEVTPSVzXG4nICIke0lEU1sqXX0iCnByaW50ZiAnTUFOQUdFUlM9JXNcbicgIiR7TUFOQUdFUlNbKl19IgonJycKICAgICAgICByID0gYmFzaChsaWIgKyBtb2NrcykKICAgICAgICBzZWxmLmFzc2VydEVxdWFsKHIucmV0dXJuY29kZSwgMCwgci5zdGRlcnIpCiAgICAgICAgc2VsZi5hc3NlcnRJbignQ0FORElEQVRFUz0xMDIgMTA2IDEzMycsIHIuc3Rkb3V0KQogICAgICAgIHNlbGYuYXNzZXJ0SW4oJ01BTkFHRVJTPTEzMycsIHIuc3Rkb3V0KQogICAgICAgIHNlbGYuYXNzZXJ0Tm90SW4oJ1VORVhQRUNURUQgTVVUQVRJT04nLCByLnN0ZGVycikKCiAgICBkZWYgcnVuX3dvcmtlcihzZWxmLCB3YW50X3RzLCB3YW50X3d6LCBzc2g9MCwgaW5zdGFsbGVkPUZhbHNlLCBmYWlsdXJlPUZhbHNlKToKICAgICAgICB3aXRoIHRlbXBmaWxlLlRlbXBvcmFyeURpcmVjdG9yeSgpIGFzIHRkOgogICAgICAgICAgICBwID0gUGF0aCh0ZCkKICAgICAgICAgICAgKHAvJ29zc2VjL3Zhci9ydW4nKS5ta2RpcihwYXJlbnRzPVRydWUpCiAgICAgICAgICAgIChwLydvc3NlYy92YXIvcnVuL3dhenVoLWFnZW50ZC5zdGF0ZScpLndyaXRlX3RleHQoInN0YXR1cz0nY29ubmVjdGVkJ1xuIikKICAgICAgICAgICAgKHAvJ29zLXJlbGVhc2UnKS53cml0ZV90ZXh0KCdJRD1kZWJpYW5cblZFUlNJT05fQ09ERU5BTUU9Ym9va3dvcm1cblZFUlNJT049IjEyIChib29rd29ybSkiXG4nKQogICAgICAgICAgICAocC8naGVhZHNjYWxlLmtleScpLndyaXRlX3RleHQoJ2ZpeHR1cmUtc2VjcmV0LW5vdC1mb3ItbG9nZ2luZycpCiAgICAgICAgICAgIHdvcmtlciA9IFdPUktFUgogICAgICAgICAgICBmb3Igb2xkLCBuZXcgaW4gKAogICAgICAgICAgICAgICAgKCcvdmFyL29zc2VjJywgc3RyKHAvJ29zc2VjJykpLAogICAgICAgICAgICAgICAgKCcvZXRjL29zLXJlbGVhc2UnLCBzdHIocC8nb3MtcmVsZWFzZScpKSwKICAgICAgICAgICAgICAgICgnL2V0Yy9hcHQnLCBzdHIocC8nYXB0JykpLAogICAgICAgICAgICAgICAgKCcvdXNyL3NoYXJlL2tleXJpbmdzJywgc3RyKHAvJ2tleXJpbmdzJykpLAogICAgICAgICAgICAgICAgKCcvZGV2L25ldC90dW4nLCAnL2Rldi9udWxsJyksCiAgICAgICAgICAgICk6CiAgICAgICAgICAgICAgICB3b3JrZXIgPSB3b3JrZXIucmVwbGFjZShvbGQsIG5ldykKICAgICAgICAgICAgbW9ja3MgPSByJycnCmNvbW1hbmQoKSB7CiAgaWYgW1sgJDEgPT0gLXYgJiYgJDIgPT0gdGFpbHNjYWxlIF1dOyB0aGVuIFtbICRNT0NLX0lOU1RBTExFRCA9PSAxIF1dOyBlbHNlIGJ1aWx0aW4gY29tbWFuZCAiJEAiOyBmaQp9CmRwa2ctcXVlcnkoKSB7IFtbICRNT0NLX0lOU1RBTExFRCAhPSAxIF1dIHx8IHByaW50ZiAnaW5zdGFsbCBvayBpbnN0YWxsZWQnOyB9CnRpbWVvdXQoKSB7CiAgc2hpZnQKICBpZiBbWyAkMSA9PSBiYXNoICYmICQyID09IC1jIF1dOyB0aGVuIHJldHVybiAwOyBmaQogICIkQCIKfQphcHQtZ2V0KCkgewogIGVjaG8gIkFQVCAkKiIKICBpZiBbWyAkTU9DS19GQUlMVVJFID09IDEgJiYgIiAkKiAiID09IConIGluc3RhbGwgJyogXV07IHRoZW4gcmV0dXJuIDQyOyBmaQp9CmFwdC1jYWNoZSgpIHsgZWNobyAnd2F6dWgtYWdlbnQgfCA0LjE0LjgtMSB8IGZpeHR1cmUnOyB9CmFwdC1tYXJrKCkgeyBlY2hvICJNQVJLICQqIjsgfQpjdXJsKCkgeyBsb2NhbCBsYXN0PScnOyBmb3IgYSBpbiAiJEAiOyBkbyBsYXN0PSRhOyBkb25lOyBwcmludGYgJ2ZpeHR1cmUnID4iJGxhc3QiOyB9CmdwZygpIHsgbG9jYWwgcHJldj0nJyBvdXQ9Jyc7IGZvciBhIGluICIkQCI7IGRvIFtbICRwcmV2ICE9IC1vIF1dIHx8IG91dD0kYTsgcHJldj0kYTsgZG9uZTsgcHJpbnRmICdmaXh0dXJlJyA+IiRvdXQiOyB9CnN5c3RlbWN0bCgpIHsgZWNobyAiU0VSVklDRSAkKiI7IH0KdGFpbHNjYWxlKCkgeyBlY2hvICJUQUlMU0NBTEUgJCoiOyB9CmlkKCkgeyBpZiBbWyAkMSA9PSAtdSBdXTsgdGhlbiBlY2hvIDEwMDA7IGVsc2UgcmV0dXJuIDA7IGZpOyB9CicnJwogICAgICAgICAgICBtb2NrcyA9ICdNT0NLX0lOU1RBTExFRD0lZDsgTU9DS19GQUlMVVJFPSVkXG4nICUgKGluc3RhbGxlZCwgZmFpbHVyZSkgKyBtb2NrcwogICAgICAgICAgICByID0gYmFzaChtb2NrcyArIHdvcmtlciwgdGQsICdtYW5hZ2VyLmV4YW1wbGUuY29tJywgJzQuMTQuOC0xJywKICAgICAgICAgICAgICAgICAgICAgJ2h0dHBzOi8vaGVhZHNjYWxlLmV4YW1wbGUuY29tJywgJ2ZpeHR1cmUnLCBzdHIod2FudF90cyksIHN0cih3YW50X3d6KSwKICAgICAgICAgICAgICAgICAgICAgc3RyKHNzaCksICdmaXh0dXJldXNlcicsICcwJywgJzAnKQogICAgICAgICAgICBzZWxmLmFzc2VydE5vdEluKCdmaXh0dXJlLXNlY3JldC1ub3QtZm9yLWxvZ2dpbmcnLCByLnN0ZG91dCtyLnN0ZGVycikKICAgICAgICAgICAgc2VsZi5hc3NlcnRGYWxzZSgocC8naGVhZHNjYWxlLmtleScpLmV4aXN0cygpLCAnc2VjcmV0IGNsZWFudXAgZGlkIG5vdCBydW4nKQogICAgICAgICAgICByZXR1cm4gcgoKICAgIGRlZiB0ZXN0X2luc3RhbGxfYm90aF9wcmVzZXJ2ZXNfbWFuYWdlcl92ZXJzaW9uX2FmdGVyX29zX3JlbGVhc2Uoc2VsZik6CiAgICAgICAgciA9IHNlbGYucnVuX3dvcmtlcigxLCAxKQogICAgICAgIHNlbGYuYXNzZXJ0RXF1YWwoci5yZXR1cm5jb2RlLCAwLCByLnN0ZGVycikKICAgICAgICBzZWxmLmFzc2VydEluKCd3YXp1aC1hZ2VudD00LjE0LjgtMScsIHIuc3Rkb3V0KQogICAgICAgIHNlbGYuYXNzZXJ0SW4oJy0tYXV0aC1rZXk9ZmlsZTonLCByLnN0ZG91dCkKICAgICAgICBzZWxmLmFzc2VydEluKCctLWFjY2VwdC1kbnM9ZmFsc2UnLCByLnN0ZG91dCkKICAgICAgICBzZWxmLmFzc2VydEluKCdtaXQgZGVtIE1hbmFnZXIgdmVyYnVuZGVuJywgci5zdGRvdXQpCgogICAgZGVmIHRlc3RfZXhpc3RpbmdfY29tcG9uZW50c19hcmVfbm90X3JlaW5zdGFsbGVkKHNlbGYpOgogICAgICAgIHIgPSBzZWxmLnJ1bl93b3JrZXIoMSwgMSwgaW5zdGFsbGVkPVRydWUpCiAgICAgICAgc2VsZi5hc3NlcnRFcXVhbChyLnJldHVybmNvZGUsIDAsIHIuc3RkZXJyKQogICAgICAgIHNlbGYuYXNzZXJ0Tm90SW4oJ0FQVCAnLCByLnN0ZG91dCkKICAgICAgICBzZWxmLmFzc2VydE5vdEluKCdUQUlMU0NBTEUgdXAnLCByLnN0ZG91dCkKCiAgICBkZWYgdGVzdF90YWlsc2NhbGVfc3NoX3VzZXNfbm9fb3BlbnNzaF9zZXJ2ZXJfb3JfcHVibGljX2tleShzZWxmKToKICAgICAgICByID0gc2VsZi5ydW5fd29ya2VyKDAsIDAsIHNzaD0xLCBpbnN0YWxsZWQ9VHJ1ZSkKICAgICAgICBzZWxmLmFzc2VydEVxdWFsKHIucmV0dXJuY29kZSwgMCwgci5zdGRlcnIpCiAgICAgICAgc2VsZi5hc3NlcnRJbignVEFJTFNDQUxFIHNldCAtLXNzaD10cnVlJywgci5zdGRvdXQpCiAgICAgICAgc2VsZi5hc3NlcnROb3RJbignb3BlbnNzaC1zZXJ2ZXInLCByLnN0ZG91dCkKICAgICAgICBzZWxmLmFzc2VydE5vdEluKCdTRVJWSUNFIGVuYWJsZSAtLW5vdyBzc2gnLCByLnN0ZG91dCkKICAgICAgICBzZWxmLmFzc2VydE5vdEluKCdhdXRob3JpemVkX2tleXMnLCBXT1JLRVIpCgogICAgZGVmIHRlc3RfaW5zdGFsbF9mYWlsdXJlX3N0b3BzX2FuZF9jbGVhbnNfc2VjcmV0cyhzZWxmKToKICAgICAgICByID0gc2VsZi5ydW5fd29ya2VyKDEsIDEsIGZhaWx1cmU9VHJ1ZSkKICAgICAgICBzZWxmLmFzc2VydE5vdEVxdWFsKHIucmV0dXJuY29kZSwgMCkKICAgICAgICBzZWxmLmFzc2VydE5vdEluKCdUQUlMU0NBTEUgdXAnLCByLnN0ZG91dCkKICAgICAgICBzZWxmLmFzc2VydE5vdEluKCdNQVJLIGhvbGQnLCByLnN0ZG91dCkKCiAgICBkZWYgdGVzdF9wdWJsaWNfc291cmNlX2hhc19ub19zaXRlX3NwZWNpZmljX3ZhbHVlcyhzZWxmKToKICAgICAgICBmb3IgZm9yYmlkZGVuIGluICgnZmFib2kuZGUnLCAnMTkyLjE2OC4nLCAnMTAuMTAuJywgJ3Rza2V5LWF1dGgtJywgJ2hza2V5LWF1dGgtJyk6CiAgICAgICAgICAgIHNlbGYuYXNzZXJ0Tm90SW4oZm9yYmlkZGVuLCBTT1VSQ0UpCiAgICAgICAgc2VsZi5hc3NlcnROb3RJbignc291cmNlICIkVkFSU19GSUxFIicsIFNPVVJDRSkKICAgICAgICBzZWxmLmFzc2VydE5vdEluKCdldmFsICcsIFNPVVJDRSkKCgppZiBfX25hbWVfXyA9PSAnX19tYWluX18nOgogICAgdW5pdHRlc3QubWFpbih2ZXJib3NpdHk9MikK \ No newline at end of file +"""Offline regression tests: no real containers, services, APT or credentials.""" +import os +from pathlib import Path +import subprocess +import tempfile +import unittest + +SOURCE = Path(__file__).with_name('lxc-enroll.sh').read_text() +WORKER = SOURCE.split("<<'WORKER'\n", 1)[1].split('\nWORKER\n', 1)[0] + + +def bash(code, *args): + return subprocess.run(['bash', '-c', code, '--', *args], capture_output=True, text=True) + + +class EnrollTests(unittest.TestCase): + def test_syntax(self): + for code in (SOURCE, WORKER): + r = subprocess.run(['bash', '-n'], input=code, text=True, capture_output=True) + self.assertEqual(r.returncode, 0, r.stderr) + + def test_inventory_excludes_complete_stopped_locked_and_unsupported(self): + # Function-only host harness. Mac Bash 3 lacks modern nounset-array semantics. + lib = SOURCE.rsplit('\nmain\n', 1)[0] + lib = lib.replace('set -Eeuo pipefail', 'set -Eeo pipefail', 1) + lib = lib.replace("[[ $EUID == 0 ]] || fail 'Bitte auf dem Proxmox-Host als root starten.'", ':') + lib = lib.replace('for tool in pct timeout python3; do command -v "$tool" >/dev/null || fail "$tool fehlt auf dem Host."; done', ':') + mocks = r''' +timeout() { shift; "$@"; } +pct() { + case "$1" in + list) printf 'VMID Status Lock Name\n102 running docker\n106 running adguard\n107 running complete\n108 stopped stopped\n109 running alpine\n112 running locked\n133 running manager\n';; + config) if [[ $2 == 112 ]]; then echo 'lock: backup'; else echo 'hostname: fixture'; fi;; + exec) + [[ $4 == sh && $5 == -s ]] || return 99 + cat >/dev/null + case "$2" in + 102) echo 'docker|debian|bookworm|1|0|0|1|1|done';; + 106) echo 'adguard|debian|bookworm|0|1|0|0|1|done';; + 107) echo 'complete|ubuntu|noble|1|1|0|1|1|done';; + 109) echo 'alpine|alpine|unknown|0|0|0|0|0|done';; + 133) echo 'manager|debian|bookworm|0|1|1|0|1|done';; + esac;; + *) echo "UNEXPECTED MUTATION: $*" >&2; return 99;; + esac +} +scan +printf 'CANDIDATES=%s\n' "${IDS[*]}" +printf 'MANAGERS=%s\n' "${MANAGERS[*]}" +''' + r = bash(lib + mocks) + self.assertEqual(r.returncode, 0, r.stderr) + self.assertIn('CANDIDATES=102 106 133', r.stdout) + self.assertIn('MANAGERS=133', r.stdout) + self.assertNotIn('UNEXPECTED MUTATION', r.stderr) + + def run_worker(self, want_ts, want_wz, ssh=0, installed=False, failure=False): + with tempfile.TemporaryDirectory() as td: + p = Path(td) + (p/'ossec/var/run').mkdir(parents=True) + (p/'ossec/var/run/wazuh-agentd.state').write_text("status='connected'\n") + (p/'os-release').write_text('ID=debian\nVERSION_CODENAME=bookworm\nVERSION="12 (bookworm)"\n') + (p/'headscale.key').write_text('fixture-secret-not-for-logging') + worker = WORKER + for old, new in ( + ('/var/ossec', str(p/'ossec')), + ('/etc/os-release', str(p/'os-release')), + ('/etc/apt', str(p/'apt')), + ('/usr/share/keyrings', str(p/'keyrings')), + ('/dev/net/tun', '/dev/null'), + ): + worker = worker.replace(old, new) + mocks = r''' +command() { + if [[ $1 == -v && $2 == tailscale ]]; then [[ $MOCK_INSTALLED == 1 ]]; else builtin command "$@"; fi +} +dpkg-query() { [[ $MOCK_INSTALLED != 1 ]] || printf 'install ok installed'; } +timeout() { + shift + if [[ $1 == bash && $2 == -c ]]; then return 0; fi + "$@" +} +apt-get() { + echo "APT $*" + if [[ $MOCK_FAILURE == 1 && " $* " == *' install '* ]]; then return 42; fi +} +apt-cache() { echo 'wazuh-agent | 4.14.8-1 | fixture'; } +apt-mark() { echo "MARK $*"; } +curl() { local last=''; for a in "$@"; do last=$a; done; printf 'fixture' >"$last"; } +gpg() { local prev='' out=''; for a in "$@"; do [[ $prev != -o ]] || out=$a; prev=$a; done; printf 'fixture' >"$out"; } +systemctl() { echo "SERVICE $*"; } +tailscale() { echo "TAILSCALE $*"; } +id() { if [[ $1 == -u ]]; then echo 1000; else return 0; fi; } +''' + mocks = 'MOCK_INSTALLED=%d; MOCK_FAILURE=%d\n' % (installed, failure) + mocks + r = bash(mocks + worker, td, 'manager.example.com', '4.14.8-1', + 'https://headscale.example.com', 'fixture', str(want_ts), str(want_wz), + str(ssh), 'fixtureuser', '0', '0') + self.assertNotIn('fixture-secret-not-for-logging', r.stdout+r.stderr) + self.assertFalse((p/'headscale.key').exists(), 'secret cleanup did not run') + return r + + def test_install_both_preserves_manager_version_after_os_release(self): + r = self.run_worker(1, 1) + self.assertEqual(r.returncode, 0, r.stderr) + self.assertIn('wazuh-agent=4.14.8-1', r.stdout) + self.assertIn('--auth-key=file:', r.stdout) + self.assertIn('--accept-dns=false', r.stdout) + self.assertIn('mit dem Manager verbunden', r.stdout) + + def test_existing_components_are_not_reinstalled(self): + r = self.run_worker(1, 1, installed=True) + self.assertEqual(r.returncode, 0, r.stderr) + self.assertNotIn('APT ', r.stdout) + self.assertNotIn('TAILSCALE up', r.stdout) + + def test_tailscale_ssh_uses_no_openssh_server_or_public_key(self): + r = self.run_worker(0, 0, ssh=1, installed=True) + self.assertEqual(r.returncode, 0, r.stderr) + self.assertIn('TAILSCALE set --ssh=true', r.stdout) + self.assertNotIn('openssh-server', r.stdout) + self.assertNotIn('SERVICE enable --now ssh', r.stdout) + self.assertNotIn('authorized_keys', WORKER) + + def test_install_failure_stops_and_cleans_secrets(self): + r = self.run_worker(1, 1, failure=True) + self.assertNotEqual(r.returncode, 0) + self.assertNotIn('TAILSCALE up', r.stdout) + self.assertNotIn('MARK hold', r.stdout) + + def test_public_source_has_no_site_specific_values(self): + for forbidden in ('faboi.de', '192.168.', '10.10.', 'tskey-auth-', 'hskey-auth-'): + self.assertNotIn(forbidden, SOURCE) + self.assertNotIn('source "$VARS_FILE"', SOURCE) + self.assertNotIn('eval ', SOURCE) + + +if __name__ == '__main__': + unittest.main(verbosity=2)