test: update test suite for git-gate manifest redesign (PRD 0047)
- fixtures.py: fixture_with_git_dict uses git-gate.repos + url/identity/host_key - test_manifest_git: rewrite to use git-gate.repos; replace duplicate-name test (names = dict keys, always unique) with two-repos-different-hosts test - test_manifest_git_user: _manifest → git-gate.user; update error message assertions - test_manifest_agent_git_user: git → git-gate throughout; repos rejection test - test_manifest_extends: git.remotes/git.user → git-gate.repos/git-gate.user - test_provision_git: IP test updated — no host alias, single insteadOf - test_compose: git.remotes → git-gate.repos + new field names - test_docker_provision_git_user: git.user → git-gate.user - test_git_gate: inline manifest dict updated to git-gate.repos - test_smolmachines_provision: git_json → git_gate_json; remove _remote_host
This commit is contained in:
+11
-13
@@ -38,23 +38,21 @@ def fixture_with_egress_dict() -> dict[str, Any]:
|
|||||||
|
|
||||||
|
|
||||||
def fixture_with_git_dict() -> dict[str, Any]:
|
def fixture_with_git_dict() -> dict[str, Any]:
|
||||||
"""Bottle declares a git-gate upstream. JSON shape."""
|
"""Bottle declares git-gate upstreams. JSON shape."""
|
||||||
return {
|
return {
|
||||||
"bottles": {
|
"bottles": {
|
||||||
"dev": {
|
"dev": {
|
||||||
"git": {
|
"git-gate": {
|
||||||
"remotes": {
|
"repos": {
|
||||||
"gitea.dideric.is": {
|
"bot-bottle": {
|
||||||
"Name": "bot-bottle",
|
"url": "ssh://git@gitea.dideric.is:30009/didericis/bot-bottle.git",
|
||||||
"Upstream": "ssh://git@gitea.dideric.is:30009/didericis/bot-bottle.git",
|
"identity": "/dev/null",
|
||||||
"IdentityFile": "/dev/null",
|
"host_key": "ssh-ed25519 AAAA...",
|
||||||
"KnownHostKey": "ssh-ed25519 AAAA...",
|
|
||||||
},
|
},
|
||||||
"github.com": {
|
"foo": {
|
||||||
"Name": "foo",
|
"url": "ssh://git@github.com/didericis/foo.git",
|
||||||
"Upstream": "ssh://git@github.com/didericis/foo.git",
|
"identity": "/dev/null",
|
||||||
"IdentityFile": "/dev/null",
|
"host_key": "ssh-ed25519 BBBB...",
|
||||||
"KnownHostKey": "ssh-ed25519 BBBB...",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,11 +49,10 @@ def _manifest(*, supervise: bool, with_git: bool, with_egress: bool) -> Manifest
|
|||||||
if supervise:
|
if supervise:
|
||||||
bottle["supervise"] = True
|
bottle["supervise"] = True
|
||||||
if with_git:
|
if with_git:
|
||||||
bottle["git"] = {"remotes": {
|
bottle["git-gate"] = {"repos": {
|
||||||
"example.com": {
|
"upstream": {
|
||||||
"Name": "upstream",
|
"url": "ssh://git@example.com:22/x/y.git",
|
||||||
"Upstream": "ssh://git@example.com:22/x/y.git",
|
"identity": "/etc/hostname", # any existing file
|
||||||
"IdentityFile": "/etc/hostname", # any existing file
|
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
if with_egress:
|
if with_egress:
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ def _plan(*, git_user: dict | None = None,
|
|||||||
stage_dir: Path | None = None) -> DockerBottlePlan:
|
stage_dir: Path | None = None) -> DockerBottlePlan:
|
||||||
bottle_json: dict = {}
|
bottle_json: dict = {}
|
||||||
if git_user is not None:
|
if git_user is not None:
|
||||||
bottle_json["git"] = {"user": git_user}
|
bottle_json["git-gate"] = {"user": git_user}
|
||||||
manifest = Manifest.from_json_obj({
|
manifest = Manifest.from_json_obj({
|
||||||
"bottles": {"dev": bottle_json},
|
"bottles": {"dev": bottle_json},
|
||||||
"agents": {"demo": {"skills": [], "prompt": "", "bottle": "dev"}},
|
"agents": {"demo": {"skills": [], "prompt": "", "bottle": "dev"}},
|
||||||
|
|||||||
@@ -220,11 +220,10 @@ class TestPrepare(unittest.TestCase):
|
|||||||
|
|
||||||
def test_prepare_skips_known_hosts_file_when_key_missing(self):
|
def test_prepare_skips_known_hosts_file_when_key_missing(self):
|
||||||
manifest = Manifest.from_json_obj({
|
manifest = Manifest.from_json_obj({
|
||||||
"bottles": {"dev": {"git": {"remotes": {
|
"bottles": {"dev": {"git-gate": {"repos": {
|
||||||
"github.com": {
|
"foo": {
|
||||||
"Name": "foo",
|
"url": "ssh://git@github.com/didericis/foo.git",
|
||||||
"Upstream": "ssh://git@github.com/didericis/foo.git",
|
"identity": "/dev/null",
|
||||||
"IdentityFile": "/dev/null",
|
|
||||||
},
|
},
|
||||||
}}}},
|
}}}},
|
||||||
"agents": {"demo": {"skills": [], "prompt": "", "bottle": "dev"}},
|
"agents": {"demo": {"skills": [], "prompt": "", "bottle": "dev"}},
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
"""Unit: agent-level git.user overlay + provenance (PRD 0027, issue #94).
|
"""Unit: agent-level git-gate.user overlay + provenance (PRD 0027, PRD 0047).
|
||||||
|
|
||||||
An agent file may declare `git.user` (name/email). At
|
An agent file may declare `git-gate.user` (name/email). At
|
||||||
`Manifest.bottle_for()` it overlays the referenced bottle's
|
`Manifest.bottle_for()` it overlays the referenced bottle's
|
||||||
`git.user` per-field, agent-wins-on-non-empty. `git.remotes` is
|
`git-gate.user` per-field, agent-wins-on-non-empty. `git-gate.repos` is
|
||||||
rejected on agents. `Manifest.git_identity_summary()` reports the
|
rejected on agents. `Manifest.git_identity_summary()` reports the
|
||||||
effective identity with per-field `(agent)`/`(bottle)` provenance.
|
effective identity with per-field `(agent)`/`(bottle)` provenance.
|
||||||
|
|
||||||
The `from_json_obj` path drives `Agent.from_dict` + `bottle_for`;
|
The `from_json_obj` path drives `Agent.from_dict` + `bottle_for`;
|
||||||
a temp-dir case locks the md loader (the `_AGENT_KEYS` allow + the
|
a temp-dir case locks the md loader (the `_AGENT_KEYS` allow + the
|
||||||
`git` threading into `agent_dict`)."""
|
`git-gate` threading into `agent_dict`)."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
@@ -34,10 +34,10 @@ def _error_message(callable_, *args, **kwargs) -> str:
|
|||||||
def _manifest(*, bottle_user=None, agent_git=None) -> Manifest:
|
def _manifest(*, bottle_user=None, agent_git=None) -> Manifest:
|
||||||
bottle: dict = {}
|
bottle: dict = {}
|
||||||
if bottle_user is not None:
|
if bottle_user is not None:
|
||||||
bottle = {"git": {"user": bottle_user}}
|
bottle = {"git-gate": {"user": bottle_user}}
|
||||||
agent: dict = {"skills": [], "prompt": "", "bottle": "dev"}
|
agent: dict = {"skills": [], "prompt": "", "bottle": "dev"}
|
||||||
if agent_git is not None:
|
if agent_git is not None:
|
||||||
agent["git"] = agent_git
|
agent["git-gate"] = agent_git
|
||||||
return Manifest.from_json_obj({
|
return Manifest.from_json_obj({
|
||||||
"bottles": {"dev": bottle},
|
"bottles": {"dev": bottle},
|
||||||
"agents": {"impl": agent},
|
"agents": {"impl": agent},
|
||||||
@@ -71,7 +71,6 @@ class TestAgentGitUserOverlay(unittest.TestCase):
|
|||||||
|
|
||||||
def test_agent_identity_with_bottle_declaring_none(self):
|
def test_agent_identity_with_bottle_declaring_none(self):
|
||||||
m = _manifest(agent_git={"user": {"name": "a", "email": "a@b"}})
|
m = _manifest(agent_git={"user": {"name": "a", "email": "a@b"}})
|
||||||
# The underlying bottle declares no identity; the merged one does.
|
|
||||||
self.assertTrue(m.bottles["dev"].git_user.is_empty())
|
self.assertTrue(m.bottles["dev"].git_user.is_empty())
|
||||||
self.assertFalse(m.bottle_for("impl").git_user.is_empty())
|
self.assertFalse(m.bottle_for("impl").git_user.is_empty())
|
||||||
|
|
||||||
@@ -82,14 +81,10 @@ class TestAgentGitUserOverlay(unittest.TestCase):
|
|||||||
self.assertEqual("b@c", u.email)
|
self.assertEqual("b@c", u.email)
|
||||||
|
|
||||||
def test_bottle_for_returns_same_instance_when_no_overlay(self):
|
def test_bottle_for_returns_same_instance_when_no_overlay(self):
|
||||||
# No agent git.user → no replace(); the cached Bottle is
|
|
||||||
# returned as-is (identity check guards against churn).
|
|
||||||
m = _manifest(bottle_user={"name": "B"})
|
m = _manifest(bottle_user={"name": "B"})
|
||||||
self.assertIs(m.bottles["dev"], m.bottle_for("impl"))
|
self.assertIs(m.bottles["dev"], m.bottle_for("impl"))
|
||||||
|
|
||||||
def test_bottle_for_returns_same_instance_when_overlay_is_noop(self):
|
def test_bottle_for_returns_same_instance_when_overlay_is_noop(self):
|
||||||
# Agent restates exactly what the bottle already has → merged
|
|
||||||
# == bottle.git_user → same instance, no replace().
|
|
||||||
m = _manifest(
|
m = _manifest(
|
||||||
bottle_user={"name": "B", "email": "b@c"},
|
bottle_user={"name": "B", "email": "b@c"},
|
||||||
agent_git={"user": {"name": "B", "email": "b@c"}},
|
agent_git={"user": {"name": "B", "email": "b@c"}},
|
||||||
@@ -101,11 +96,11 @@ class TestAgentGitUserOverlay(unittest.TestCase):
|
|||||||
"bottles": {"dev": {
|
"bottles": {"dev": {
|
||||||
"env": {"FOO": "bar"},
|
"env": {"FOO": "bar"},
|
||||||
"supervise": True,
|
"supervise": True,
|
||||||
"git": {"user": {"name": "B"}},
|
"git-gate": {"user": {"name": "B"}},
|
||||||
}},
|
}},
|
||||||
"agents": {"impl": {
|
"agents": {"impl": {
|
||||||
"bottle": "dev", "skills": [], "prompt": "",
|
"bottle": "dev", "skills": [], "prompt": "",
|
||||||
"git": {"user": {"name": "a"}},
|
"git-gate": {"user": {"name": "a"}},
|
||||||
}},
|
}},
|
||||||
})
|
})
|
||||||
b = m.bottle_for("impl")
|
b = m.bottle_for("impl")
|
||||||
@@ -115,11 +110,11 @@ class TestAgentGitUserOverlay(unittest.TestCase):
|
|||||||
|
|
||||||
|
|
||||||
class TestAgentGitUserRejections(unittest.TestCase):
|
class TestAgentGitUserRejections(unittest.TestCase):
|
||||||
def test_agent_remotes_dies_bottle_only(self):
|
def test_agent_repos_dies_bottle_only(self):
|
||||||
msg = _error_message(_manifest, agent_git={
|
msg = _error_message(_manifest, agent_git={
|
||||||
"remotes": {"h": {"Name": "r", "Upstream": "ssh://x/y.git"}},
|
"repos": {"r": {"url": "ssh://git@x/y.git", "identity": "/dev/null"}},
|
||||||
})
|
})
|
||||||
self.assertIn("git.remotes", msg)
|
self.assertIn("git-gate.repos", msg)
|
||||||
self.assertIn("bottle-only", msg)
|
self.assertIn("bottle-only", msg)
|
||||||
|
|
||||||
def test_agent_unknown_git_subkey_dies(self):
|
def test_agent_unknown_git_subkey_dies(self):
|
||||||
@@ -127,7 +122,6 @@ class TestAgentGitUserRejections(unittest.TestCase):
|
|||||||
self.assertIn("not allowed at the agent level", msg)
|
self.assertIn("not allowed at the agent level", msg)
|
||||||
|
|
||||||
def test_agent_git_user_both_empty_dies(self):
|
def test_agent_git_user_both_empty_dies(self):
|
||||||
# Reuses GitUser.from_dict validation.
|
|
||||||
msg = _error_message(_manifest, agent_git={"user": {"name": "", "email": ""}})
|
msg = _error_message(_manifest, agent_git={"user": {"name": "", "email": ""}})
|
||||||
self.assertIn("neither name nor email", msg)
|
self.assertIn("neither name nor email", msg)
|
||||||
|
|
||||||
@@ -164,7 +158,7 @@ class TestGitIdentitySummary(unittest.TestCase):
|
|||||||
|
|
||||||
_BOTTLE_DEV = """
|
_BOTTLE_DEV = """
|
||||||
---
|
---
|
||||||
git:
|
git-gate:
|
||||||
user:
|
user:
|
||||||
name: bottle-name
|
name: bottle-name
|
||||||
email: bottle@example.com
|
email: bottle@example.com
|
||||||
@@ -176,7 +170,7 @@ _BOTTLE_DEV = """
|
|||||||
_AGENT_WITH_GIT = """
|
_AGENT_WITH_GIT = """
|
||||||
---
|
---
|
||||||
bottle: dev
|
bottle: dev
|
||||||
git:
|
git-gate:
|
||||||
user:
|
user:
|
||||||
name: agent-name
|
name: agent-name
|
||||||
---
|
---
|
||||||
@@ -184,14 +178,14 @@ _AGENT_WITH_GIT = """
|
|||||||
impl agent.
|
impl agent.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_AGENT_WITH_REMOTES = """
|
_AGENT_WITH_REPOS = """
|
||||||
---
|
---
|
||||||
bottle: dev
|
bottle: dev
|
||||||
git:
|
git-gate:
|
||||||
remotes:
|
repos:
|
||||||
h:
|
r:
|
||||||
Name: r
|
url: ssh://git@x/y.git
|
||||||
Upstream: ssh://x/y.git
|
identity: /dev/null
|
||||||
---
|
---
|
||||||
|
|
||||||
bad agent.
|
bad agent.
|
||||||
@@ -199,9 +193,9 @@ _AGENT_WITH_REMOTES = """
|
|||||||
|
|
||||||
|
|
||||||
class TestAgentGitUserMdLoader(unittest.TestCase):
|
class TestAgentGitUserMdLoader(unittest.TestCase):
|
||||||
"""Locks the md path: `git` is an accepted agent key and threads
|
"""Locks the md path: `git-gate` is an accepted agent key and threads
|
||||||
into the parsed Agent (not rejected as an unknown frontmatter
|
into the parsed Agent (not rejected as an unknown frontmatter key),
|
||||||
key), and agent `git.remotes` dies through the same loader."""
|
and agent `git-gate.repos` dies through the same loader."""
|
||||||
|
|
||||||
def setUp(self) -> None:
|
def setUp(self) -> None:
|
||||||
self.home = Path(tempfile.mkdtemp(prefix="cb-home-"))
|
self.home = Path(tempfile.mkdtemp(prefix="cb-home-"))
|
||||||
@@ -225,18 +219,18 @@ class TestAgentGitUserMdLoader(unittest.TestCase):
|
|||||||
self._write("agents/impl.md", _AGENT_WITH_GIT)
|
self._write("agents/impl.md", _AGENT_WITH_GIT)
|
||||||
m = Manifest.resolve(str(self.home))
|
m = Manifest.resolve(str(self.home))
|
||||||
u = m.bottle_for("impl").git_user
|
u = m.bottle_for("impl").git_user
|
||||||
self.assertEqual("agent-name", u.name) # agent wins
|
self.assertEqual("agent-name", u.name)
|
||||||
self.assertEqual("bottle@example.com", u.email) # bottle falls through
|
self.assertEqual("bottle@example.com", u.email)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
"name=agent-name (agent), email=bottle@example.com (bottle)",
|
"name=agent-name (agent), email=bottle@example.com (bottle)",
|
||||||
m.git_identity_summary("impl"),
|
m.git_identity_summary("impl"),
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_md_agent_remotes_dies(self):
|
def test_md_agent_repos_dies(self):
|
||||||
self._write("bottles/dev.md", _BOTTLE_DEV)
|
self._write("bottles/dev.md", _BOTTLE_DEV)
|
||||||
self._write("agents/impl.md", _AGENT_WITH_REMOTES)
|
self._write("agents/impl.md", _AGENT_WITH_REPOS)
|
||||||
msg = _error_message(Manifest.resolve, str(self.home))
|
msg = _error_message(Manifest.resolve, str(self.home))
|
||||||
self.assertIn("git.remotes", msg)
|
self.assertIn("git-gate.repos", msg)
|
||||||
self.assertIn("bottle-only", msg)
|
self.assertIn("bottle-only", msg)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -113,42 +113,30 @@ class TestExtendsEnvMerge(unittest.TestCase):
|
|||||||
|
|
||||||
|
|
||||||
class TestExtendsGitMerge(unittest.TestCase):
|
class TestExtendsGitMerge(unittest.TestCase):
|
||||||
"""git.user overlays by field; git.remotes merges by upstream
|
"""git-gate.user overlays by field; git-gate.repos merges by upstream
|
||||||
host, with child entries replacing duplicate hosts."""
|
host, with child entries replacing duplicate hosts."""
|
||||||
|
|
||||||
_GIT_ENTRY_A = {
|
_GIT_ENTRY_A = {"url": "ssh://git@host-a/a.git", "identity": "/dev/null"}
|
||||||
"Name": "a",
|
_GIT_ENTRY_B = {"url": "ssh://git@host-b/b.git", "identity": "/dev/null"}
|
||||||
"Upstream": "ssh://git@host-a/a.git",
|
|
||||||
"IdentityFile": "/dev/null",
|
|
||||||
}
|
|
||||||
_GIT_ENTRY_B = {
|
|
||||||
"Name": "b",
|
|
||||||
"Upstream": "ssh://git@host-b/b.git",
|
|
||||||
"IdentityFile": "/dev/null",
|
|
||||||
}
|
|
||||||
|
|
||||||
def test_child_git_remotes_merge_with_parent(self):
|
def test_child_git_repos_merge_with_parent(self):
|
||||||
m = _build(
|
m = _build(
|
||||||
base={"git": {"remotes": {"host-a": self._GIT_ENTRY_A}}},
|
base={"git-gate": {"repos": {"a": self._GIT_ENTRY_A}}},
|
||||||
child={
|
child={
|
||||||
"extends": "base",
|
"extends": "base",
|
||||||
"git": {"remotes": {"host-b": self._GIT_ENTRY_B}},
|
"git-gate": {"repos": {"b": self._GIT_ENTRY_B}},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
names = [e.Name for e in m.bottles["child"].git]
|
names = [e.Name for e in m.bottles["child"].git]
|
||||||
self.assertEqual(["a", "b"], names)
|
self.assertEqual(["a", "b"], names)
|
||||||
|
|
||||||
def test_child_git_remote_replaces_same_host(self):
|
def test_child_git_repo_replaces_same_host(self):
|
||||||
replacement = {
|
replacement = {"url": "ssh://git@host-a/replacement.git", "identity": "/dev/null"}
|
||||||
"Name": "a2",
|
|
||||||
"Upstream": "ssh://git@host-a/replacement.git",
|
|
||||||
"IdentityFile": "/dev/null",
|
|
||||||
}
|
|
||||||
m = _build(
|
m = _build(
|
||||||
base={"git": {"remotes": {"host-a": self._GIT_ENTRY_A}}},
|
base={"git-gate": {"repos": {"a": self._GIT_ENTRY_A}}},
|
||||||
child={
|
child={
|
||||||
"extends": "base",
|
"extends": "base",
|
||||||
"git": {"remotes": {"host-a": replacement}},
|
"git-gate": {"repos": {"a2": replacement}},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
entries = m.bottles["child"].git
|
entries = m.bottles["child"].git
|
||||||
@@ -156,30 +144,30 @@ class TestExtendsGitMerge(unittest.TestCase):
|
|||||||
self.assertEqual("a2", entries[0].Name)
|
self.assertEqual("a2", entries[0].Name)
|
||||||
self.assertEqual("replacement.git", entries[0].UpstreamPath)
|
self.assertEqual("replacement.git", entries[0].UpstreamPath)
|
||||||
|
|
||||||
def test_child_omits_git_inherits_full_list(self):
|
def test_child_omits_git_gate_inherits_full_list(self):
|
||||||
m = _build(
|
m = _build(
|
||||||
base={"git": {"remotes": {
|
base={"git-gate": {"repos": {
|
||||||
"host-a": self._GIT_ENTRY_A,
|
"a": self._GIT_ENTRY_A,
|
||||||
"host-b": self._GIT_ENTRY_B,
|
"b": self._GIT_ENTRY_B,
|
||||||
}}},
|
}}},
|
||||||
child={"extends": "base"},
|
child={"extends": "base"},
|
||||||
)
|
)
|
||||||
names = [e.Name for e in m.bottles["child"].git]
|
names = [e.Name for e in m.bottles["child"].git]
|
||||||
self.assertEqual(["a", "b"], names)
|
self.assertEqual(["a", "b"], names)
|
||||||
|
|
||||||
def test_child_explicit_empty_git_clears_parent(self):
|
def test_child_explicit_empty_repos_clears_parent(self):
|
||||||
# `git.remotes: {}` is the documented way to say "drop
|
# `git-gate.repos: {}` is the documented way to say "drop
|
||||||
# the parent's remotes" rather than "inherit them".
|
# the parent's repos" rather than "inherit them".
|
||||||
m = _build(
|
m = _build(
|
||||||
base={"git": {"remotes": {"host-a": self._GIT_ENTRY_A}}},
|
base={"git-gate": {"repos": {"a": self._GIT_ENTRY_A}}},
|
||||||
child={"extends": "base", "git": {"remotes": {}}},
|
child={"extends": "base", "git-gate": {"repos": {}}},
|
||||||
)
|
)
|
||||||
self.assertEqual((), m.bottles["child"].git)
|
self.assertEqual((), m.bottles["child"].git)
|
||||||
|
|
||||||
def test_child_git_user_inherits_parent_remotes(self):
|
def test_child_git_user_inherits_parent_repos(self):
|
||||||
m = _build(
|
m = _build(
|
||||||
base={"git": {"remotes": {"host-a": self._GIT_ENTRY_A}}},
|
base={"git-gate": {"repos": {"a": self._GIT_ENTRY_A}}},
|
||||||
child={"extends": "base", "git": {"user": {"name": "Child"}}},
|
child={"extends": "base", "git-gate": {"user": {"name": "Child"}}},
|
||||||
)
|
)
|
||||||
self.assertEqual(["a"], [e.Name for e in m.bottles["child"].git])
|
self.assertEqual(["a"], [e.Name for e in m.bottles["child"].git])
|
||||||
self.assertEqual("Child", m.bottles["child"].git_user.name)
|
self.assertEqual("Child", m.bottles["child"].git_user.name)
|
||||||
@@ -209,12 +197,12 @@ class TestExtendsListsFullReplace(unittest.TestCase):
|
|||||||
|
|
||||||
|
|
||||||
class TestExtendsGitUserOverlay(unittest.TestCase):
|
class TestExtendsGitUserOverlay(unittest.TestCase):
|
||||||
"""git.user: per-field overlay. Each non-empty field on child
|
"""git-gate.user: per-field overlay. Each non-empty field on child
|
||||||
wins; empties fall through to parent."""
|
wins; empties fall through to parent."""
|
||||||
|
|
||||||
def test_parent_full_child_omits(self):
|
def test_parent_full_child_omits(self):
|
||||||
m = _build(
|
m = _build(
|
||||||
base={"git": {"user": {"name": "Parent", "email": "p@x"}}},
|
base={"git-gate": {"user": {"name": "Parent", "email": "p@x"}}},
|
||||||
child={"extends": "base"},
|
child={"extends": "base"},
|
||||||
)
|
)
|
||||||
u = m.bottles["child"].git_user
|
u = m.bottles["child"].git_user
|
||||||
@@ -223,10 +211,10 @@ class TestExtendsGitUserOverlay(unittest.TestCase):
|
|||||||
|
|
||||||
def test_child_overrides_both(self):
|
def test_child_overrides_both(self):
|
||||||
m = _build(
|
m = _build(
|
||||||
base={"git": {"user": {"name": "Parent", "email": "p@x"}}},
|
base={"git-gate": {"user": {"name": "Parent", "email": "p@x"}}},
|
||||||
child={
|
child={
|
||||||
"extends": "base",
|
"extends": "base",
|
||||||
"git": {"user": {"name": "Child", "email": "c@x"}},
|
"git-gate": {"user": {"name": "Child", "email": "c@x"}},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
u = m.bottles["child"].git_user
|
u = m.bottles["child"].git_user
|
||||||
@@ -234,11 +222,9 @@ class TestExtendsGitUserOverlay(unittest.TestCase):
|
|||||||
self.assertEqual("c@x", u.email)
|
self.assertEqual("c@x", u.email)
|
||||||
|
|
||||||
def test_child_adds_email_inherits_name(self):
|
def test_child_adds_email_inherits_name(self):
|
||||||
# Parent sets only name; child sets only email. Both end
|
|
||||||
# up populated on the child.
|
|
||||||
m = _build(
|
m = _build(
|
||||||
base={"git": {"user": {"name": "Parent"}}},
|
base={"git-gate": {"user": {"name": "Parent"}}},
|
||||||
child={"extends": "base", "git": {"user": {"email": "c@x"}}},
|
child={"extends": "base", "git-gate": {"user": {"email": "c@x"}}},
|
||||||
)
|
)
|
||||||
u = m.bottles["child"].git_user
|
u = m.bottles["child"].git_user
|
||||||
self.assertEqual("Parent", u.name)
|
self.assertEqual("Parent", u.name)
|
||||||
@@ -246,11 +232,10 @@ class TestExtendsGitUserOverlay(unittest.TestCase):
|
|||||||
|
|
||||||
def test_child_overrides_only_email(self):
|
def test_child_overrides_only_email(self):
|
||||||
m = _build(
|
m = _build(
|
||||||
base={"git": {"user": {"name": "Parent", "email": "p@x"}}},
|
base={"git-gate": {"user": {"name": "Parent", "email": "p@x"}}},
|
||||||
child={"extends": "base", "git": {"user": {"email": "c@x"}}},
|
child={"extends": "base", "git-gate": {"user": {"email": "c@x"}}},
|
||||||
)
|
)
|
||||||
u = m.bottles["child"].git_user
|
u = m.bottles["child"].git_user
|
||||||
# Child overrides email; name inherited from parent.
|
|
||||||
self.assertEqual("Parent", u.name)
|
self.assertEqual("Parent", u.name)
|
||||||
self.assertEqual("c@x", u.email)
|
self.assertEqual("c@x", u.email)
|
||||||
|
|
||||||
|
|||||||
+136
-131
@@ -1,39 +1,25 @@
|
|||||||
"""Unit: Bottle.git manifest parsing + validation (PRD 0008)."""
|
"""Unit: git-gate.repos manifest parsing + validation (PRD 0047)."""
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from bot_bottle.manifest import ManifestError, Manifest
|
from bot_bottle.manifest import ManifestError, Manifest
|
||||||
|
|
||||||
|
|
||||||
def _manifest(git_entries):
|
def _manifest(repos: dict) -> dict:
|
||||||
return {
|
return {
|
||||||
"bottles": {"dev": {"git": {"remotes": {
|
"bottles": {"dev": {"git-gate": {"repos": repos}}},
|
||||||
_host_for(entry): entry for entry in git_entries
|
|
||||||
}}}},
|
|
||||||
"agents": {"demo": {"skills": [], "prompt": "", "bottle": "dev"}},
|
"agents": {"demo": {"skills": [], "prompt": "", "bottle": "dev"}},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def _host_for(entry):
|
|
||||||
upstream = entry.get("Upstream", "")
|
|
||||||
if "@a.example" in upstream:
|
|
||||||
return "a.example"
|
|
||||||
if "@b.example" in upstream:
|
|
||||||
return "b.example"
|
|
||||||
if "@github.com" in upstream:
|
|
||||||
return "github.com"
|
|
||||||
if "@gitea.dideric.is" in upstream:
|
|
||||||
return "gitea.dideric.is"
|
|
||||||
return "example.com"
|
|
||||||
|
|
||||||
|
|
||||||
class TestGitEntryParsing(unittest.TestCase):
|
class TestGitEntryParsing(unittest.TestCase):
|
||||||
def test_parses_minimal_entry(self):
|
def test_parses_minimal_entry(self):
|
||||||
m = Manifest.from_json_obj(_manifest([{
|
m = Manifest.from_json_obj(_manifest({
|
||||||
"Name": "bot-bottle",
|
"bot-bottle": {
|
||||||
"Upstream": "ssh://git@gitea.dideric.is:30009/didericis/bot-bottle.git",
|
"url": "ssh://git@gitea.dideric.is:30009/didericis/bot-bottle.git",
|
||||||
"IdentityFile": "/dev/null",
|
"identity": "/dev/null",
|
||||||
}]))
|
},
|
||||||
|
}))
|
||||||
entries = m.bottles["dev"].git
|
entries = m.bottles["dev"].git
|
||||||
self.assertEqual(1, len(entries))
|
self.assertEqual(1, len(entries))
|
||||||
e = entries[0]
|
e = entries[0]
|
||||||
@@ -44,138 +30,145 @@ class TestGitEntryParsing(unittest.TestCase):
|
|||||||
self.assertEqual("didericis/bot-bottle.git", e.UpstreamPath)
|
self.assertEqual("didericis/bot-bottle.git", e.UpstreamPath)
|
||||||
|
|
||||||
def test_default_port_is_22(self):
|
def test_default_port_is_22(self):
|
||||||
m = Manifest.from_json_obj(_manifest([{
|
m = Manifest.from_json_obj(_manifest({
|
||||||
"Name": "foo",
|
"foo": {
|
||||||
"Upstream": "ssh://git@github.com/didericis/foo.git",
|
"url": "ssh://git@github.com/didericis/foo.git",
|
||||||
"IdentityFile": "/dev/null",
|
"identity": "/dev/null",
|
||||||
}]))
|
},
|
||||||
|
}))
|
||||||
e = m.bottles["dev"].git[0]
|
e = m.bottles["dev"].git[0]
|
||||||
self.assertEqual("22", e.UpstreamPort)
|
self.assertEqual("22", e.UpstreamPort)
|
||||||
self.assertEqual("github.com", e.UpstreamHost)
|
self.assertEqual("github.com", e.UpstreamHost)
|
||||||
|
|
||||||
def test_known_host_key_optional(self):
|
def test_host_key_optional(self):
|
||||||
m = Manifest.from_json_obj(_manifest([{
|
m = Manifest.from_json_obj(_manifest({
|
||||||
"Name": "foo",
|
"foo": {
|
||||||
"Upstream": "ssh://git@github.com/foo.git",
|
"url": "ssh://git@github.com/foo.git",
|
||||||
"IdentityFile": "/dev/null",
|
"identity": "/dev/null",
|
||||||
}]))
|
},
|
||||||
|
}))
|
||||||
self.assertEqual("", m.bottles["dev"].git[0].KnownHostKey)
|
self.assertEqual("", m.bottles["dev"].git[0].KnownHostKey)
|
||||||
|
|
||||||
def test_missing_name_dies(self):
|
def test_host_key_stored(self):
|
||||||
with self.assertRaises(ManifestError):
|
m = Manifest.from_json_obj(_manifest({
|
||||||
Manifest.from_json_obj(_manifest([{
|
"foo": {
|
||||||
"Upstream": "ssh://git@github.com/foo.git",
|
"url": "ssh://git@github.com/foo.git",
|
||||||
"IdentityFile": "/dev/null",
|
"identity": "/dev/null",
|
||||||
}]))
|
"host_key": "ssh-ed25519 AAAA",
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
self.assertEqual("ssh-ed25519 AAAA", m.bottles["dev"].git[0].KnownHostKey)
|
||||||
|
|
||||||
def test_missing_upstream_dies(self):
|
def test_repo_name_becomes_Name(self):
|
||||||
with self.assertRaises(ManifestError):
|
m = Manifest.from_json_obj(_manifest({
|
||||||
Manifest.from_json_obj(_manifest([{
|
"my-repo": {
|
||||||
"Name": "foo",
|
"url": "ssh://git@github.com/foo.git",
|
||||||
"IdentityFile": "/dev/null",
|
"identity": "/dev/null",
|
||||||
}]))
|
},
|
||||||
|
}))
|
||||||
|
self.assertEqual("my-repo", m.bottles["dev"].git[0].Name)
|
||||||
|
|
||||||
def test_missing_identity_file_dies(self):
|
def test_missing_url_dies(self):
|
||||||
with self.assertRaises(ManifestError):
|
with self.assertRaises(ManifestError):
|
||||||
Manifest.from_json_obj(_manifest([{
|
Manifest.from_json_obj(_manifest({
|
||||||
"Name": "foo",
|
"foo": {"identity": "/dev/null"},
|
||||||
"Upstream": "ssh://git@github.com/foo.git",
|
}))
|
||||||
}]))
|
|
||||||
|
|
||||||
def test_non_ssh_upstream_dies(self):
|
def test_missing_identity_dies(self):
|
||||||
with self.assertRaises(ManifestError):
|
with self.assertRaises(ManifestError):
|
||||||
Manifest.from_json_obj(_manifest([{
|
Manifest.from_json_obj(_manifest({
|
||||||
"Name": "foo",
|
"foo": {"url": "ssh://git@github.com/foo.git"},
|
||||||
"Upstream": "https://github.com/didericis/foo.git",
|
}))
|
||||||
"IdentityFile": "/dev/null",
|
|
||||||
}]))
|
|
||||||
|
|
||||||
def test_scp_style_upstream_dies(self):
|
def test_unknown_key_in_entry_dies(self):
|
||||||
# SCP-style "git@host:path" is intentionally not supported in
|
|
||||||
# v1 — ssh:// only.
|
|
||||||
with self.assertRaises(ManifestError):
|
with self.assertRaises(ManifestError):
|
||||||
Manifest.from_json_obj(_manifest([{
|
Manifest.from_json_obj(_manifest({
|
||||||
"Name": "foo",
|
"foo": {
|
||||||
"Upstream": "git@github.com:didericis/foo.git",
|
"url": "ssh://git@github.com/foo.git",
|
||||||
"IdentityFile": "/dev/null",
|
"identity": "/dev/null",
|
||||||
}]))
|
"IdentityFile": "/dev/null", # old PascalCase key
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
def test_upstream_without_user_dies(self):
|
def test_non_ssh_url_dies(self):
|
||||||
with self.assertRaises(ManifestError):
|
with self.assertRaises(ManifestError):
|
||||||
Manifest.from_json_obj(_manifest([{
|
Manifest.from_json_obj(_manifest({
|
||||||
"Name": "foo",
|
"foo": {
|
||||||
"Upstream": "ssh://github.com/foo.git",
|
"url": "https://github.com/didericis/foo.git",
|
||||||
"IdentityFile": "/dev/null",
|
"identity": "/dev/null",
|
||||||
}]))
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
def test_upstream_without_path_dies(self):
|
def test_scp_style_url_dies(self):
|
||||||
with self.assertRaises(ManifestError):
|
with self.assertRaises(ManifestError):
|
||||||
Manifest.from_json_obj(_manifest([{
|
Manifest.from_json_obj(_manifest({
|
||||||
"Name": "foo",
|
"foo": {
|
||||||
"Upstream": "ssh://git@github.com",
|
"url": "git@github.com:didericis/foo.git",
|
||||||
"IdentityFile": "/dev/null",
|
"identity": "/dev/null",
|
||||||
}]))
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
def test_url_without_user_dies(self):
|
||||||
|
with self.assertRaises(ManifestError):
|
||||||
|
Manifest.from_json_obj(_manifest({
|
||||||
|
"foo": {
|
||||||
|
"url": "ssh://github.com/foo.git",
|
||||||
|
"identity": "/dev/null",
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
def test_url_without_path_dies(self):
|
||||||
|
with self.assertRaises(ManifestError):
|
||||||
|
Manifest.from_json_obj(_manifest({
|
||||||
|
"foo": {
|
||||||
|
"url": "ssh://git@github.com",
|
||||||
|
"identity": "/dev/null",
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
def test_non_numeric_port_dies(self):
|
def test_non_numeric_port_dies(self):
|
||||||
with self.assertRaises(ManifestError):
|
with self.assertRaises(ManifestError):
|
||||||
Manifest.from_json_obj(_manifest([{
|
Manifest.from_json_obj(_manifest({
|
||||||
"Name": "foo",
|
"foo": {
|
||||||
"Upstream": "ssh://git@github.com:notaport/foo.git",
|
"url": "ssh://git@github.com:notaport/foo.git",
|
||||||
"IdentityFile": "/dev/null",
|
"identity": "/dev/null",
|
||||||
}]))
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
def test_ip_literal_upstream(self):
|
||||||
|
m = Manifest.from_json_obj(_manifest({
|
||||||
|
"bot-bottle": {
|
||||||
|
"url": "ssh://git@100.78.141.42:30009/didericis/bot-bottle.git",
|
||||||
|
"identity": "/dev/null",
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
e = m.bottles["dev"].git[0]
|
||||||
|
self.assertEqual("100.78.141.42", e.UpstreamHost)
|
||||||
|
self.assertEqual("30009", e.UpstreamPort)
|
||||||
|
self.assertEqual("bot-bottle", e.Name)
|
||||||
|
|
||||||
|
|
||||||
class TestGitEntryCrossValidation(unittest.TestCase):
|
class TestGitEntryCrossValidation(unittest.TestCase):
|
||||||
def test_duplicate_name_dies(self):
|
def test_two_repos_different_hosts_both_parsed(self):
|
||||||
with self.assertRaises(ManifestError):
|
# Repo names come from dict keys; two distinct keys always produce
|
||||||
Manifest.from_json_obj({
|
# two distinct entries (uniqueness is guaranteed at the YAML/dict level).
|
||||||
"bottles": {"dev": {"git": {"remotes": {
|
|
||||||
"a.example": {
|
|
||||||
"Name": "foo",
|
|
||||||
"Upstream": "ssh://git@a.example/x.git",
|
|
||||||
"IdentityFile": "/dev/null",
|
|
||||||
},
|
|
||||||
"b.example": {
|
|
||||||
"Name": "foo",
|
|
||||||
"Upstream": "ssh://git@b.example/y.git",
|
|
||||||
"IdentityFile": "/dev/null",
|
|
||||||
},
|
|
||||||
}}}},
|
|
||||||
"agents": {"demo": {"skills": [], "prompt": "", "bottle": "dev"}},
|
|
||||||
})
|
|
||||||
|
|
||||||
def test_remote_key_must_match_upstream_host(self):
|
|
||||||
with self.assertRaises(ManifestError):
|
|
||||||
Manifest.from_json_obj({
|
|
||||||
"bottles": {"dev": {"git": {"remotes": {
|
|
||||||
"wrong.example": {
|
|
||||||
"Name": "foo",
|
|
||||||
"Upstream": "ssh://git@github.com/foo.git",
|
|
||||||
"IdentityFile": "/dev/null",
|
|
||||||
},
|
|
||||||
}}}},
|
|
||||||
"agents": {"demo": {"skills": [], "prompt": "", "bottle": "dev"}},
|
|
||||||
})
|
|
||||||
|
|
||||||
def test_remote_key_can_name_logical_host_for_ip_upstream(self):
|
|
||||||
m = Manifest.from_json_obj({
|
m = Manifest.from_json_obj({
|
||||||
"bottles": {"dev": {"git": {"remotes": {
|
"bottles": {"dev": {"git-gate": {"repos": {
|
||||||
"gitea.dideric.is": {
|
"foo": {
|
||||||
"Name": "bot-bottle",
|
"url": "ssh://git@a.example/x.git",
|
||||||
"Upstream": "ssh://git@100.78.141.42:30009/didericis/bot-bottle.git",
|
"identity": "/dev/null",
|
||||||
"IdentityFile": "/dev/null",
|
},
|
||||||
|
"bar": {
|
||||||
|
"url": "ssh://git@b.example/y.git",
|
||||||
|
"identity": "/dev/null",
|
||||||
},
|
},
|
||||||
}}}},
|
}}}},
|
||||||
"agents": {"demo": {"skills": [], "prompt": "", "bottle": "dev"}},
|
"agents": {"demo": {"skills": [], "prompt": "", "bottle": "dev"}},
|
||||||
})
|
})
|
||||||
e = m.bottles["dev"].git[0]
|
names = {e.Name for e in m.bottles["dev"].git}
|
||||||
self.assertEqual("gitea.dideric.is", e.RemoteKey)
|
self.assertEqual({"foo", "bar"}, names)
|
||||||
self.assertEqual("100.78.141.42", e.UpstreamHost)
|
|
||||||
self.assertEqual("30009", e.UpstreamPort)
|
|
||||||
|
|
||||||
def test_legacy_ssh_field_dies_with_hint(self):
|
def test_legacy_ssh_field_dies_with_hint(self):
|
||||||
# PRD 0009: bottle.ssh is removed; manifests carrying it must
|
|
||||||
# fail loudly with a hint pointing at bottle.git.
|
|
||||||
with self.assertRaises(ManifestError):
|
with self.assertRaises(ManifestError):
|
||||||
Manifest.from_json_obj({
|
Manifest.from_json_obj({
|
||||||
"bottles": {
|
"bottles": {
|
||||||
@@ -192,25 +185,37 @@ class TestGitEntryCrossValidation(unittest.TestCase):
|
|||||||
"agents": {"demo": {"skills": [], "prompt": "", "bottle": "dev"}},
|
"agents": {"demo": {"skills": [], "prompt": "", "bottle": "dev"}},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
def test_legacy_git_key_dies_with_hint(self):
|
||||||
|
msg = ""
|
||||||
|
try:
|
||||||
|
Manifest.from_json_obj({
|
||||||
|
"bottles": {"dev": {"git": {"remotes": {}}}},
|
||||||
|
"agents": {"demo": {"skills": [], "prompt": "", "bottle": "dev"}},
|
||||||
|
})
|
||||||
|
except ManifestError as e:
|
||||||
|
msg = str(e)
|
||||||
|
self.assertIn("git-gate", msg)
|
||||||
|
self.assertIn("PRD 0047", msg)
|
||||||
|
|
||||||
class TestEmptyGitField(unittest.TestCase):
|
|
||||||
def test_no_git_field_yields_empty_tuple(self):
|
class TestEmptyGitGateField(unittest.TestCase):
|
||||||
|
def test_no_git_gate_field_yields_empty_tuple(self):
|
||||||
m = Manifest.from_json_obj({
|
m = Manifest.from_json_obj({
|
||||||
"bottles": {"dev": {}},
|
"bottles": {"dev": {}},
|
||||||
"agents": {"demo": {"skills": [], "prompt": "", "bottle": "dev"}},
|
"agents": {"demo": {"skills": [], "prompt": "", "bottle": "dev"}},
|
||||||
})
|
})
|
||||||
self.assertEqual((), m.bottles["dev"].git)
|
self.assertEqual((), m.bottles["dev"].git)
|
||||||
|
|
||||||
def test_git_object_type_required(self):
|
def test_git_gate_object_type_required(self):
|
||||||
with self.assertRaises(ManifestError):
|
with self.assertRaises(ManifestError):
|
||||||
Manifest.from_json_obj({
|
Manifest.from_json_obj({
|
||||||
"bottles": {"dev": {"git": "not-a-list"}},
|
"bottles": {"dev": {"git-gate": "not-a-dict"}},
|
||||||
"agents": {"demo": {"skills": [], "prompt": "", "bottle": "dev"}},
|
"agents": {"demo": {"skills": [], "prompt": "", "bottle": "dev"}},
|
||||||
})
|
})
|
||||||
|
|
||||||
def test_empty_remotes_yields_empty_tuple(self):
|
def test_empty_repos_yields_empty_tuple(self):
|
||||||
m = Manifest.from_json_obj({
|
m = Manifest.from_json_obj({
|
||||||
"bottles": {"dev": {"git": {"remotes": {}}}},
|
"bottles": {"dev": {"git-gate": {"repos": {}}}},
|
||||||
"agents": {"demo": {"skills": [], "prompt": "", "bottle": "dev"}},
|
"agents": {"demo": {"skills": [], "prompt": "", "bottle": "dev"}},
|
||||||
})
|
})
|
||||||
self.assertEqual((), m.bottles["dev"].git)
|
self.assertEqual((), m.bottles["dev"].git)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
"""Unit: Bottle git.user manifest parsing + validation (issue #86)."""
|
"""Unit: Bottle git-gate.user manifest parsing + validation (issue #86, PRD 0047)."""
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ def _error_message(callable_, *args, **kwargs) -> str:
|
|||||||
|
|
||||||
def _manifest(git_user):
|
def _manifest(git_user):
|
||||||
return {
|
return {
|
||||||
"bottles": {"dev": {"git": {"user": git_user}}},
|
"bottles": {"dev": {"git-gate": {"user": git_user}}},
|
||||||
"agents": {"demo": {"skills": [], "prompt": "", "bottle": "dev"}},
|
"agents": {"demo": {"skills": [], "prompt": "", "bottle": "dev"}},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,13 +75,13 @@ class TestGitUserParsing(unittest.TestCase):
|
|||||||
msg = _error_message(
|
msg = _error_message(
|
||||||
Manifest.from_json_obj, _manifest({"name": 42}),
|
Manifest.from_json_obj, _manifest({"name": 42}),
|
||||||
)
|
)
|
||||||
self.assertIn("git.user.name must be a string", msg)
|
self.assertIn("git-gate.user.name must be a string", msg)
|
||||||
|
|
||||||
def test_non_string_email_dies(self):
|
def test_non_string_email_dies(self):
|
||||||
msg = _error_message(
|
msg = _error_message(
|
||||||
Manifest.from_json_obj, _manifest({"email": ["x@y.z"]}),
|
Manifest.from_json_obj, _manifest({"email": ["x@y.z"]}),
|
||||||
)
|
)
|
||||||
self.assertIn("git.user.email must be a string", msg)
|
self.assertIn("git-gate.user.email must be a string", msg)
|
||||||
|
|
||||||
def test_legacy_top_level_git_user_dies(self):
|
def test_legacy_top_level_git_user_dies(self):
|
||||||
msg = _error_message(
|
msg = _error_message(
|
||||||
@@ -92,7 +92,7 @@ class TestGitUserParsing(unittest.TestCase):
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
self.assertIn("git_user", msg)
|
self.assertIn("git_user", msg)
|
||||||
self.assertIn("git.user", msg)
|
self.assertIn("git-gate.user", msg)
|
||||||
|
|
||||||
|
|
||||||
class TestGitUserDirect(unittest.TestCase):
|
class TestGitUserDirect(unittest.TestCase):
|
||||||
|
|||||||
@@ -69,13 +69,14 @@ class TestGitGateGitconfigRender(unittest.TestCase):
|
|||||||
'[url "http://127.0.0.16:57001/bot-bottle.git"]', out,
|
'[url "http://127.0.0.16:57001/bot-bottle.git"]', out,
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_ip_upstream_also_rewrites_logical_remote_key(self):
|
def test_ip_upstream_emits_single_insteadof(self):
|
||||||
|
# In the new format the dict key is the repo name, not a host
|
||||||
|
# alias, so there is only one insteadOf rule — for the IP URL.
|
||||||
m = Manifest.from_json_obj({
|
m = Manifest.from_json_obj({
|
||||||
"bottles": {"dev": {"git": {"remotes": {
|
"bottles": {"dev": {"git-gate": {"repos": {
|
||||||
"gitea.dideric.is": {
|
"bot-bottle": {
|
||||||
"Name": "bot-bottle",
|
"url": "ssh://git@100.78.141.42:30009/didericis/bot-bottle.git",
|
||||||
"Upstream": "ssh://git@100.78.141.42:30009/didericis/bot-bottle.git",
|
"identity": "/dev/null",
|
||||||
"IdentityFile": "/dev/null",
|
|
||||||
},
|
},
|
||||||
}}}},
|
}}}},
|
||||||
"agents": {"demo": {"skills": [], "prompt": "", "bottle": "dev"}},
|
"agents": {"demo": {"skills": [], "prompt": "", "bottle": "dev"}},
|
||||||
@@ -86,11 +87,7 @@ class TestGitGateGitconfigRender(unittest.TestCase):
|
|||||||
"ssh://git@100.78.141.42:30009/didericis/bot-bottle.git",
|
"ssh://git@100.78.141.42:30009/didericis/bot-bottle.git",
|
||||||
out,
|
out,
|
||||||
)
|
)
|
||||||
self.assertIn(
|
self.assertNotIn("gitea.dideric.is", out)
|
||||||
"\tinsteadOf = "
|
|
||||||
"ssh://git@gitea.dideric.is:30009/didericis/bot-bottle.git",
|
|
||||||
out,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -42,11 +42,6 @@ from bot_bottle.supervise import SupervisePlan
|
|||||||
from bot_bottle.workspace import workspace_plan
|
from bot_bottle.workspace import workspace_plan
|
||||||
|
|
||||||
|
|
||||||
def _remote_host(g: GitEntry) -> str:
|
|
||||||
if g.UpstreamHost:
|
|
||||||
return g.UpstreamHost
|
|
||||||
return g.Upstream.split("@", 1)[1].split("/", 1)[0].split(":", 1)[0]
|
|
||||||
|
|
||||||
|
|
||||||
def _plan(
|
def _plan(
|
||||||
*,
|
*,
|
||||||
@@ -69,20 +64,19 @@ def _plan(
|
|||||||
guest_env: dict[str, str] | None = None,
|
guest_env: dict[str, str] | None = None,
|
||||||
) -> SmolmachinesBottlePlan:
|
) -> SmolmachinesBottlePlan:
|
||||||
bottle_json: dict = {}
|
bottle_json: dict = {}
|
||||||
git_json: dict = {}
|
git_gate_json: dict = {}
|
||||||
if git:
|
if git:
|
||||||
git_json["remotes"] = {
|
git_gate_json["repos"] = {
|
||||||
_remote_host(g): {
|
g.Name: {
|
||||||
"Name": g.Name,
|
"url": g.Upstream,
|
||||||
"Upstream": g.Upstream,
|
"identity": g.IdentityFile,
|
||||||
"IdentityFile": g.IdentityFile,
|
|
||||||
}
|
}
|
||||||
for g in git
|
for g in git
|
||||||
}
|
}
|
||||||
if git_user is not None:
|
if git_user is not None:
|
||||||
git_json["user"] = git_user
|
git_gate_json["user"] = git_user
|
||||||
if git_json:
|
if git_gate_json:
|
||||||
bottle_json["git"] = git_json
|
bottle_json["git-gate"] = git_gate_json
|
||||||
if supervise:
|
if supervise:
|
||||||
bottle_json["supervise"] = True
|
bottle_json["supervise"] = True
|
||||||
manifest = Manifest.from_json_obj({
|
manifest = Manifest.from_json_obj({
|
||||||
|
|||||||
Reference in New Issue
Block a user