refactor(manifest): convert TypedDict to frozen dataclasses
test / run tests/run_tests.py (pull_request) Successful in 14s
test / run tests/run_tests.py (pull_request) Successful in 14s
Replace the TypedDict + 14 manifest_* free functions with frozen dataclasses (SshEntry, BottleEgress, Bottle, Agent, Manifest) carrying their own validators and constructors. Call sites import Manifest and chain attribute access; the manifest_* helpers and manifest_validate are gone. Behavior changes worth flagging: - Agent.bottle is now required (was optional with a "(none)" fallback). Manifest.from_json_obj dies if any agent lacks a 'bottle' field or references an undefined bottle, where previously start.py raised the error lazily for the specific agent being launched. - ssh.py now takes SshEntry instances; Host/IdentityFile shape checks moved upstream into Manifest construction, leaving only the IdentityFile filesystem-existence check in ssh_validate_entries. - pipelock_bottle_allowlist's per-element string check is dropped — the Manifest validator enforces it at load. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1,16 +1,18 @@
|
||||
"""Unit: bottle runtime — manifest_bottle_runtime returns the configured
|
||||
runtime (defaulting to runc); manifest_validate rejects unknown values,
|
||||
non-strings, and empty strings."""
|
||||
"""Unit: bottle runtime — Manifest.from_json_obj defaults runtime to runc,
|
||||
accepts runsc, and rejects unknown values, non-strings, and empty strings."""
|
||||
|
||||
import unittest
|
||||
|
||||
from claude_bottle.log import Die
|
||||
from claude_bottle.manifest import manifest_bottle_runtime, manifest_validate
|
||||
from claude_bottle.manifest import Manifest
|
||||
|
||||
|
||||
def _bottle(runtime_value: object | None) -> dict:
|
||||
"""Build a minimal manifest with one bottle whose runtime field is
|
||||
set (or absent if `runtime_value is _ABSENT`)."""
|
||||
_ABSENT = object()
|
||||
|
||||
|
||||
def _bottle(runtime_value: object) -> dict:
|
||||
"""Build a minimal manifest JSON shape with one bottle whose runtime
|
||||
field is set (or absent if `runtime_value is _ABSENT`)."""
|
||||
bottle: dict = {}
|
||||
if runtime_value is not _ABSENT:
|
||||
bottle["runtime"] = runtime_value
|
||||
@@ -20,30 +22,30 @@ def _bottle(runtime_value: object | None) -> dict:
|
||||
}
|
||||
|
||||
|
||||
_ABSENT = object()
|
||||
|
||||
|
||||
class TestManifestBottleRuntime(unittest.TestCase):
|
||||
def test_default_runc_when_absent(self):
|
||||
self.assertEqual("runc", manifest_bottle_runtime(_bottle(_ABSENT), "dev"))
|
||||
m = Manifest.from_json_obj(_bottle(_ABSENT))
|
||||
self.assertEqual("runc", m.bottles["dev"].runtime)
|
||||
|
||||
def test_explicit_runc(self):
|
||||
self.assertEqual("runc", manifest_bottle_runtime(_bottle("runc"), "dev"))
|
||||
m = Manifest.from_json_obj(_bottle("runc"))
|
||||
self.assertEqual("runc", m.bottles["dev"].runtime)
|
||||
|
||||
def test_explicit_runsc(self):
|
||||
self.assertEqual("runsc", manifest_bottle_runtime(_bottle("runsc"), "dev"))
|
||||
m = Manifest.from_json_obj(_bottle("runsc"))
|
||||
self.assertEqual("runsc", m.bottles["dev"].runtime)
|
||||
|
||||
def test_rejects_unknown_runtime(self):
|
||||
with self.assertRaises(Die):
|
||||
manifest_validate(_bottle("kata-runtime"))
|
||||
Manifest.from_json_obj(_bottle("kata-runtime"))
|
||||
|
||||
def test_rejects_non_string(self):
|
||||
with self.assertRaises(Die):
|
||||
manifest_validate(_bottle(42))
|
||||
Manifest.from_json_obj(_bottle(42))
|
||||
|
||||
def test_rejects_empty_string(self):
|
||||
with self.assertRaises(Die):
|
||||
manifest_validate(_bottle(""))
|
||||
Manifest.from_json_obj(_bottle(""))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user