#!/usr/bin/env python3
"""Remaining maintenance contracts with fake Perforce/SMTP, never real mail."""
import io
import os
from pathlib import Path
import smtplib
import subprocess
import tempfile
import unittest
from unittest.mock import Mock, patch
import test_maintenance_review as fixtures
class AdditionalMaintenance(unittest.TestCase):
def test_pymail_address_list_and_secure_transport(self):
with fixtures.module("pymail") as mod, tempfile.TemporaryDirectory() as tmp:
body, addresses = Path(tmp) / "body", Path(tmp) / "addresses"
body.write_text("maintenance diagnostic\n")
addresses.write_text("[email protected]\[email protected]\n")
mod.fromAddr, mod.mailsecure = "[email protected]", 1
mod.mailUser, mod.mailPass = "test-user", "fixture-only-password"
smtp = Mock()
with patch.object(mod.smtplib, "SMTP", return_value=smtp):
mod.main(["-t", str(addresses), "-s", "Diagnostic", "-i", str(body)])
self.assertEqual(smtp.starttls.call_count, 2)
self.assertEqual(smtp.login.call_count, 2)
self.assertEqual([call.args[1] for call in smtp.sendmail.call_args_list], ["[email protected]", "[email protected]"])
self.assertTrue(all("maintenance diagnostic" in call.args[2] for call in smtp.sendmail.call_args_list))
def test_pymail_transport_failure_propagates(self):
with fixtures.module("pymail") as mod, tempfile.TemporaryDirectory() as tmp:
body = Path(tmp) / "body"
body.write_text("test")
with patch.object(mod.smtplib, "SMTP", side_effect=smtplib.SMTPException("denied")):
with self.assertRaises(smtplib.SMTPException):
mod.mail(body, "subject", "[email protected]")
def test_inactive_user_notifications_filter_protected_recent_and_invalid_accounts(self):
with fixtures.module("email_pending_user_deletes") as mod:
p4 = Mock()
now = 2_000_000_000
old = str(now - 60 * 86400)
p4.run.return_value = [{"User": "alice", "Access": old, "Email": "[email protected]"},
{"User": "p4admin", "Access": old},
{"User": "recent", "Access": str(now)},
{"User": "invalid", "Access": "never"}]
smtp = Mock()
smtp.__enter__ = Mock(return_value=smtp)
smtp.__exit__ = Mock(return_value=False)
with patch.object(mod.time, "time", return_value=now), patch.object(mod.smtplib, "SMTP", return_value=smtp):
mod.warn_inactive_users(p4, 4, "smtp.example.test", "[email protected]", "", "[email protected]", "test:1666")
sent = [call.args[0] for call in smtp.send_message.call_args_list]
self.assertEqual([message["To"] for message in sent], ["[email protected]", "[email protected]"])
self.assertTrue(all("alice" in message["Subject"] for message in sent))
def test_inactive_user_query_failure_prevents_notifications(self):
with fixtures.module("email_pending_user_deletes") as mod:
p4 = Mock()
p4.run.side_effect = fixtures.P4Exception("denied")
with patch.object(mod.smtplib, "SMTP") as smtp:
with self.assertRaises(fixtures.P4Exception):
mod.warn_inactive_users(p4, 4, "smtp.example.test", "[email protected]", "", None, "test:1666")
smtp.assert_not_called()
def test_password_reset_uses_stdin_and_propagates_rejection(self):
class Input(io.StringIO):
def close(self):
self.saved = self.getvalue()
super().close()
with fixtures.module("setpass", ["1", "alice"]) as mod:
mod.defaultpass = "fixture-secret"
for status in (0, 7):
process = Mock(stdin=Input(), stdout=io.StringIO(""), stderr=io.StringIO("denied"))
process.wait.return_value = status
with patch.object(mod, "Popen", return_value=process) as popen, patch.object(mod.time, "sleep"):
if status:
with self.assertRaises(SystemExit) as error:
mod.setpassword("alice")
self.assertEqual(error.exception.code, 1)
else:
mod.setpassword("alice")
self.assertEqual(process.stdin.saved, "fixture-secret\nfixture-secret\n")
self.assertNotIn("fixture-secret", str(popen.call_args.args))
def test_mirror_groups_preserves_members_and_adds_requested_users(self):
with fixtures.module("mirroraccess", ["1", "source", "alice", "bob"]) as mod:
calls = []
def command(args, **kwargs):
calls.append((args, kwargs))
if args[1] == "groups":
return subprocess.CompletedProcess(args, 0, "team\n")
if "-o" in args:
return subprocess.CompletedProcess(args, 0, "Group: team\nUsers:\n\tsource\n")
return subprocess.CompletedProcess(args, 0, "")
with patch.object(fixtures.sdputils, "run_checked", side_effect=command):
mod.main()
self.assertEqual(calls[-1][1]["input"], "Group: team\nUsers:\n\tsource\n\talice\n\tbob\n")
def test_mirror_query_failure_prevents_group_writes(self):
with fixtures.module("mirroraccess", ["1", "source", "alice"]) as mod:
with patch.object(fixtures.sdputils, "run_checked", side_effect=subprocess.CalledProcessError(7, ["p4"])) as run:
with self.assertRaises(subprocess.CalledProcessError):
mod.main()
self.assertEqual(run.call_count, 1)
def test_update_changes_preserves_specs_and_stops_after_failed_read(self):
for fail in (False, True):
with self.subTest(fail=fail), tempfile.TemporaryDirectory() as tmp:
listing = Path(tmp) / "changes"
listing.write_text("41\n\n42\n")
writes = []
def command(args, **kwargs):
if "-o" in args:
if fail:
raise subprocess.CalledProcessError(7, args, stderr="denied")
return subprocess.CompletedProcess(args, 0, "Change: " + args[-1] + "\nDescription:\n\tkeep this\n")
writes.append(kwargs["input"])
return subprocess.CompletedProcess(args, 0, "")
with patch.dict(os.environ, {"LOGS": tmp}), patch.object(fixtures.sdputils, "run_checked", side_effect=command):
if fail:
with self.assertRaises(SystemExit) as error:
with fixtures.module("update-changes", ["blue", str(listing)]):
pass
self.assertEqual(error.exception.code, 7)
self.assertEqual(writes, [])
else:
with fixtures.module("update-changes", ["blue", str(listing)]):
pass
self.assertEqual(writes, ["Change: 41\nDescription:\n\tkeep this\n", "Change: 42\nDescription:\n\tkeep this\n"])
if __name__ == "__main__":
unittest.main()
| # | Change | User | Description | Committed | |
|---|---|---|---|---|---|
| #1 | 34004 | Russell C. Jackson (Rusty) |
Improve SDP backup/recovery, Maintenance, logging and cloud operation. Add owned checkpoint locks, recovery-aware retention, preserved database switches, bounded cleanup and coordinated AWS/Azure snapshots. Fix service-user password initialization, edge staging, standby promotion state parsing, proxy cache setup, typed depot verification and historical-server configuration/error handling. Remove unused OTP programs; keep Windows removed, server.locks cleanup and the journal-count condition unchanged. Add no global shell strict modes. Update operational documentation and add reproducible mkdirs.sh/mkdirs.cfg container assurance. Validate Ubuntu 26.04 and UBI 8.10/9.8/10.2 across both Python runtimes: 44 stages; 428 offline tests per runtime/OS, zero offline skips; 19 deployed-archive comparisons per combination. Three six-node topology runs pass all 30 recovery/failure scenarios. P4D 2023.1 through 2026.1 on UBI 8/9 pass all 14 cells and 98 scenarios. Static, documentation and provenance checks pass. Containers do not certify every fleet kernel, patch build or integration. Tested source manifest SHA-256: e1a23e17b8ced8c1148d9a250589977c6f6e98606bf2a81dc3ff5d7c909e3695 Deployed comparison archive SHA-256: 1bd23217356ee3808e040d39aea5be9519bda7c8f2b4924656a70aff8ad7293a |