Refactor ACME module and add cert issuance conflict handling
- Move acme.sh utilities (_run_acme, _find_acme, etc.) from lib/state to lib/acme - Rewrite _parse_list_output to support pipe, tab, and column-separated formats - Add ConflictError (409) to block issuing when cert already exists - Move _find_issuance helper to detect in-progress issuance per domain - Update issue_cert to check existing certs and return issuance status - Fix start_polling to accept event loop explicitly - Add sudoers entry for chown on vacuum-wall.conf - Extend systemd ReadWritePaths for /run/nginx.pid and /var/log/nginx - Update frontend to handle 'existing' issuance status
This commit is contained in:
+40
-7
@@ -61,17 +61,14 @@ class TestRunAcme:
|
||||
|
||||
class TestParseListOutput:
|
||||
def test_parses_single_entry(self):
|
||||
raw = "Main_Domain:example.com CA:LetsEncrypt Certificate_Date:2026-04-01 Certificate_Expires:2026-07-01 Certificate_Expired:No"
|
||||
raw = "Main_Domain\tCA\nexample.com\tLetsEncrypt\n"
|
||||
result = acme._parse_list_output(raw)
|
||||
assert len(result) == 1
|
||||
assert result[0]["main_domain"] == "example.com"
|
||||
assert result[0]["ca"] == "LetsEncrypt"
|
||||
|
||||
def test_parses_multiple_entries(self):
|
||||
raw = (
|
||||
"Main_Domain:a.com CA:LE Certificate_Expires:2026-07-01 Certificate_Expired:No\n"
|
||||
"Main_Domain:b.com CA:LE Certificate_Expires:2026-08-01 Certificate_Expired:No"
|
||||
)
|
||||
raw = "Main_Domain\tCA\na.com\tLE\nb.com\tLE\n"
|
||||
result = acme._parse_list_output(raw)
|
||||
assert len(result) == 2
|
||||
|
||||
@@ -79,10 +76,46 @@ class TestParseListOutput:
|
||||
result = acme._parse_list_output("")
|
||||
assert result == []
|
||||
|
||||
def test_skips_lines_without_colons(self):
|
||||
raw = "some random line\nMain_Domain:a.com"
|
||||
def test_skips_empty_lines(self):
|
||||
raw = "Main_Domain\tCA\nexample.com\tLE\n\n \nother.com\tLE\n"
|
||||
result = acme._parse_list_output(raw)
|
||||
assert len(result) == 2
|
||||
|
||||
def test_header_skipped(self):
|
||||
"""Header row is not included in results."""
|
||||
raw = "Main_Domain\tKeyLength\tCA\nexample.com\tec-256\tZeroSSL.com\n"
|
||||
result = acme._parse_list_output(raw)
|
||||
assert len(result) == 1
|
||||
assert result[0]["main_domain"] == "example.com"
|
||||
|
||||
def test_field_mapping(self):
|
||||
"""Tab columns are mapped to lowercased header names as dict keys."""
|
||||
raw = (
|
||||
"Main_Domain\tKeyLength\tSAN_Domains\tProfile\tCA\tCreated\tRenew\n"
|
||||
'example.com\t"ec-256"\twww.example.com\t\tZeroSSL.com\t2026-01-01\t2026-07-01\n'
|
||||
)
|
||||
result = acme._parse_list_output(raw)
|
||||
assert len(result) == 1
|
||||
entry = result[0]
|
||||
assert entry["main_domain"] == "example.com"
|
||||
assert entry["keylength"] == "ec-256"
|
||||
assert entry["san_domains"] == "www.example.com"
|
||||
assert entry["profile"] == ""
|
||||
assert entry["ca"] == "ZeroSSL.com"
|
||||
assert entry["created"] == "2026-01-01"
|
||||
assert entry["renew"] == "2026-07-01"
|
||||
|
||||
def test_quoted_values_stripped(self):
|
||||
"""Quoted values have quotes removed."""
|
||||
raw = 'Main_Domain\tKeyLength\nexample.com\t"ec-256"\n'
|
||||
result = acme._parse_list_output(raw)
|
||||
assert result[0]["keylength"] == "ec-256"
|
||||
|
||||
def test_header_only(self):
|
||||
"""Header with no data rows returns empty list."""
|
||||
raw = "Main_Domain\tKeyLength\tCA\n"
|
||||
result = acme._parse_list_output(raw)
|
||||
assert result == []
|
||||
|
||||
|
||||
class TestDaysUntil:
|
||||
|
||||
Reference in New Issue
Block a user