Files
bot-bottle/tests
didericis 1f36d53f7b
test / run tests/run_tests.py (pull_request) Successful in 14s
refactor(manifest): convert TypedDict to frozen dataclasses
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>
2026-05-10 21:20:15 -04:00
..

Tests

Plain-Python test suite using stdlib unittest. No external dependencies. Unit tests run anywhere Python 3 is present; integration tests need Docker and skip cleanly otherwise.

Layout

tests/
  run_tests.py                    # entry point
  fixtures.py                     # JSON manifest builders
  _docker.py                      # docker-availability skip helper
  test_pipelock_naming.py         # unit
  test_pipelock_classify.py       # unit
  test_pipelock_allowlist.py      # unit
  test_pipelock_yaml.py           # unit
  test_pipelock_image.py          # integration
  test_pipelock_sidecar_smoke.py  # integration
  test_dry_run_plan.py            # integration
  test_orphan_cleanup.py          # integration

Running

tests/run_tests.py                                  # everything
tests/run_tests.py unit                             # unit only
tests/run_tests.py integration                      # integration only
tests/run_tests.py tests/test_pipelock_yaml.py      # one file

You can also run via python -m unittest:

python -m unittest discover -s tests
python -m unittest tests.test_pipelock_yaml

What the integration tests cover

  • test_pipelock_image.py — the pinned digest is reachable, ENTRYPOINT is /pipelock, and CMD includes run.
  • test_pipelock_sidecar_smoke.pydocker create + docker cp the generated YAML to /etc/pipelock.yaml + docker start, then probe /health.
  • test_dry_run_plan.pycli.py start --dry-run shows the resolved egress allowlist and creates zero docker resources.
  • test_orphan_cleanup.py — network_remove and pipelock_stop are idempotent against missing resources, so the EXIT trap can call them unconditionally.

What's NOT covered

  • claude_bottle/ssh.py end-to-end (would need a fake SSH host inside the container).
  • A live SSH-through-pipelock tunnel against a real Tailscale-style IP.
  • DLP false-positive measurements.
  • TLS handling / cert pinning behavior.

Adding a test

  1. Pick a filename: test_<topic>.py. Add it to INTEGRATION_NAMES in run_tests.py if it needs Docker.
  2. Boilerplate:
    import unittest
    
    from claude_bottle.<module> import <symbol>
    
    class TestThing(unittest.TestCase):
        def test_x(self):
            ...
    
    if __name__ == "__main__":
        unittest.main()
    
  3. For Docker-dependent tests, decorate the class with @skip_unless_docker() from tests._docker.