generated from coulomb/repo-seed
REUSE-WP-0019-T01: forge host abstraction + URL migration inventory
Some checks failed
ci / validate-registry (push) Has been cancelled
Some checks failed
ci / validate-registry (push) Has been cancelled
reuse_surface/forge_host.py: parse/derive/rewrite raw index URLs across Gitea and Forgejo (handles both the legacy /raw/<branch>/... form and the canonical /raw/branch/<branch>/... form both forges serve without a 303 redirect). migrate_source_host() verifies the new URL resolves via HTTP HEAD before writing -- refuses to point a repo at a host it hasn't actually migrated to. New CLI: reuse-surface federation migrate-host --repo <slug> --to <base-url> [--from <check>] [--dry-run] [--no-verify] [--update-hub]. Inventory: cross-referenced sources.yaml against each repo's actual git origin. 11/61 repos already on Forgejo; found 2 with stale sources.yaml entries (activity-core, state-hub) despite having migrated. Fixed for real: local sources.yaml + production hub registration (hub update), verified against GET /v1/federated post-migration. config-atlas's WP-0017-T06 303 confirmed NOT a host-transition symptom (already diagnosed there as something else). Also fixed two host-agnostic gaps found while inventorying: registry_update.py and maintain_llm.py only recognized .gitea/workflows/, missing repos already on .forgejo/workflows/. Fixed a stale copy-paste example (state-hub's now-wrong old URL) in docs/RegistryFederation.md, and a pre-existing unrelated port typo (8088 vs the real llm-connect default 8080) in tools/README.md and registry/README.md. 17 new pytest cases (tests/test_forge_host.py), 106 total pass. Recomposed federated.yaml post-migration: still 61 capabilities. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
188
tests/test_forge_host.py
Normal file
188
tests/test_forge_host.py
Normal file
@@ -0,0 +1,188 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from reuse_surface.forge_host import (
|
||||
derive_raw_url,
|
||||
forge_base_url,
|
||||
migrate_source_host,
|
||||
parse_raw_url,
|
||||
rewrite_url_host,
|
||||
)
|
||||
|
||||
|
||||
def test_parse_raw_url_legacy_form():
|
||||
parts = parse_raw_url("https://gitea.coulomb.social/coulomb/activity-core/raw/main/registry/indexes/capabilities.yaml")
|
||||
assert parts.scheme == "https"
|
||||
assert parts.host == "gitea.coulomb.social"
|
||||
assert parts.org == "coulomb"
|
||||
assert parts.repo == "activity-core"
|
||||
assert parts.branch == "main"
|
||||
assert parts.path == "registry/indexes/capabilities.yaml"
|
||||
|
||||
|
||||
def test_parse_raw_url_branch_qualified_form():
|
||||
parts = parse_raw_url("https://forgejo.coulomb.social/coulomb/core-hub/raw/branch/main/registry/indexes/capabilities.yaml")
|
||||
assert parts.host == "forgejo.coulomb.social"
|
||||
assert parts.repo == "core-hub"
|
||||
assert parts.branch == "main"
|
||||
|
||||
|
||||
def test_parse_raw_url_rejects_unrecognized():
|
||||
with pytest.raises(ValueError):
|
||||
parse_raw_url("https://example.com/not/a/raw/url")
|
||||
|
||||
|
||||
def test_derive_raw_url_uses_branch_qualified_form():
|
||||
url = derive_raw_url("https://forgejo.coulomb.social", "coulomb", "core-hub")
|
||||
assert url == "https://forgejo.coulomb.social/coulomb/core-hub/raw/branch/main/registry/indexes/capabilities.yaml"
|
||||
|
||||
|
||||
def test_derive_raw_url_strips_trailing_slash():
|
||||
url = derive_raw_url("https://forgejo.coulomb.social/", "coulomb", "core-hub")
|
||||
assert url.startswith("https://forgejo.coulomb.social/coulomb/")
|
||||
|
||||
|
||||
def test_rewrite_url_host_swaps_host_and_normalizes_form():
|
||||
old = "https://gitea.coulomb.social/coulomb/activity-core/raw/main/registry/indexes/capabilities.yaml"
|
||||
new = rewrite_url_host(old, "https://forgejo.coulomb.social")
|
||||
assert new == "https://forgejo.coulomb.social/coulomb/activity-core/raw/branch/main/registry/indexes/capabilities.yaml"
|
||||
|
||||
|
||||
def test_forge_base_url_env_fallback(monkeypatch):
|
||||
monkeypatch.setenv("REUSE_SURFACE_FORGE_BASE_URL", "https://forgejo.coulomb.social")
|
||||
assert forge_base_url() == "https://forgejo.coulomb.social"
|
||||
|
||||
|
||||
def test_forge_base_url_none_when_unset(monkeypatch):
|
||||
monkeypatch.delenv("REUSE_SURFACE_FORGE_BASE_URL", raising=False)
|
||||
assert forge_base_url() is None
|
||||
|
||||
|
||||
def test_forge_base_url_explicit_wins(monkeypatch):
|
||||
monkeypatch.setenv("REUSE_SURFACE_FORGE_BASE_URL", "https://env-value")
|
||||
assert forge_base_url("https://explicit-value") == "https://explicit-value"
|
||||
|
||||
|
||||
def test_migrate_source_host_mutates_matching_entry(monkeypatch):
|
||||
monkeypatch.setattr("reuse_surface.forge_host.probe_url", lambda url, **k: (True, 200))
|
||||
sources = [
|
||||
{"repo": "activity-core", "url": "https://gitea.coulomb.social/coulomb/activity-core/raw/main/registry/indexes/capabilities.yaml"},
|
||||
{"repo": "other-repo", "url": "https://gitea.coulomb.social/coulomb/other-repo/raw/main/registry/indexes/capabilities.yaml"},
|
||||
]
|
||||
report = migrate_source_host(sources, "activity-core", "https://forgejo.coulomb.social")
|
||||
assert report["verified"] is True
|
||||
assert sources[0]["url"] == "https://forgejo.coulomb.social/coulomb/activity-core/raw/branch/main/registry/indexes/capabilities.yaml"
|
||||
# other entries untouched
|
||||
assert sources[1]["url"] == "https://gitea.coulomb.social/coulomb/other-repo/raw/main/registry/indexes/capabilities.yaml"
|
||||
|
||||
|
||||
def test_migrate_source_host_repo_not_found():
|
||||
sources = [{"repo": "activity-core", "url": "https://gitea.coulomb.social/coulomb/activity-core/raw/main/registry/indexes/capabilities.yaml"}]
|
||||
with pytest.raises(ValueError, match="not found"):
|
||||
migrate_source_host(sources, "does-not-exist", "https://forgejo.coulomb.social")
|
||||
|
||||
|
||||
def test_migrate_source_host_refuses_unverified_url(monkeypatch):
|
||||
monkeypatch.setattr("reuse_surface.forge_host.probe_url", lambda url, **k: (False, 404))
|
||||
sources = [{"repo": "activity-core", "url": "https://gitea.coulomb.social/coulomb/activity-core/raw/main/registry/indexes/capabilities.yaml"}]
|
||||
original_url = sources[0]["url"]
|
||||
with pytest.raises(ValueError, match="did not resolve"):
|
||||
migrate_source_host(sources, "activity-core", "https://forgejo.coulomb.social")
|
||||
# must not have mutated the entry when verification failed
|
||||
assert sources[0]["url"] == original_url
|
||||
|
||||
|
||||
def test_migrate_source_host_skips_verification_when_disabled():
|
||||
sources = [{"repo": "activity-core", "url": "https://gitea.coulomb.social/coulomb/activity-core/raw/main/registry/indexes/capabilities.yaml"}]
|
||||
report = migrate_source_host(sources, "activity-core", "https://forgejo.coulomb.social", verify=False)
|
||||
assert report["verified"] is None
|
||||
assert sources[0]["url"].startswith("https://forgejo.coulomb.social/")
|
||||
|
||||
|
||||
def test_cmd_federation_migrate_host_dry_run(tmp_path, monkeypatch):
|
||||
from reuse_surface.cli import main
|
||||
|
||||
sources_path = tmp_path / "sources.yaml"
|
||||
sources_path.write_text(
|
||||
"version: 1\n"
|
||||
"domain: helix_forge\n"
|
||||
"collision_policy: warn\n"
|
||||
"sources:\n"
|
||||
"- repo: activity-core\n"
|
||||
" url: https://gitea.coulomb.social/coulomb/activity-core/raw/main/registry/indexes/capabilities.yaml\n"
|
||||
" enabled: true\n"
|
||||
)
|
||||
monkeypatch.setattr("reuse_surface.cli.migrate_source_host", lambda sources, repo, to, verify=True: {
|
||||
"repo": repo, "old_url": sources[0]["url"], "new_url": "https://forgejo.coulomb.social/x", "verified": True,
|
||||
})
|
||||
exit_code = main([
|
||||
"federation", "migrate-host",
|
||||
"--repo", "activity-core",
|
||||
"--to", "https://forgejo.coulomb.social",
|
||||
"--sources", str(sources_path),
|
||||
"--dry-run",
|
||||
])
|
||||
assert exit_code == 0
|
||||
# dry-run must not write
|
||||
assert "gitea.coulomb.social" in sources_path.read_text()
|
||||
|
||||
|
||||
def test_cmd_federation_migrate_host_writes_file(tmp_path, monkeypatch):
|
||||
from reuse_surface.cli import main
|
||||
|
||||
sources_path = tmp_path / "sources.yaml"
|
||||
sources_path.write_text(
|
||||
"version: 1\n"
|
||||
"domain: helix_forge\n"
|
||||
"collision_policy: warn\n"
|
||||
"sources:\n"
|
||||
"- repo: activity-core\n"
|
||||
" url: https://gitea.coulomb.social/coulomb/activity-core/raw/main/registry/indexes/capabilities.yaml\n"
|
||||
" enabled: true\n"
|
||||
)
|
||||
monkeypatch.setattr("reuse_surface.forge_host.probe_url", lambda url, **k: (True, 200))
|
||||
exit_code = main([
|
||||
"federation", "migrate-host",
|
||||
"--repo", "activity-core",
|
||||
"--to", "https://forgejo.coulomb.social",
|
||||
"--sources", str(sources_path),
|
||||
])
|
||||
assert exit_code == 0
|
||||
written = sources_path.read_text()
|
||||
assert "forgejo.coulomb.social" in written
|
||||
assert "gitea.coulomb.social" not in written
|
||||
|
||||
|
||||
def test_cmd_federation_migrate_host_repo_not_found_errors(tmp_path):
|
||||
from reuse_surface.cli import main
|
||||
|
||||
sources_path = tmp_path / "sources.yaml"
|
||||
sources_path.write_text("version: 1\ndomain: helix_forge\ncollision_policy: warn\nsources: []\n")
|
||||
exit_code = main([
|
||||
"federation", "migrate-host",
|
||||
"--repo", "does-not-exist",
|
||||
"--to", "https://forgejo.coulomb.social",
|
||||
"--sources", str(sources_path),
|
||||
"--dry-run",
|
||||
])
|
||||
assert exit_code == 1
|
||||
|
||||
|
||||
def test_cmd_federation_migrate_host_from_mismatch_errors(tmp_path):
|
||||
from reuse_surface.cli import main
|
||||
|
||||
sources_path = tmp_path / "sources.yaml"
|
||||
sources_path.write_text(
|
||||
"version: 1\ndomain: helix_forge\ncollision_policy: warn\n"
|
||||
"sources:\n- repo: activity-core\n url: https://forgejo.coulomb.social/coulomb/activity-core/raw/branch/main/registry/indexes/capabilities.yaml\n"
|
||||
)
|
||||
exit_code = main([
|
||||
"federation", "migrate-host",
|
||||
"--repo", "activity-core",
|
||||
"--to", "https://forgejo.coulomb.social",
|
||||
"--from", "gitea.coulomb.social",
|
||||
"--sources", str(sources_path),
|
||||
"--dry-run",
|
||||
])
|
||||
assert exit_code == 1
|
||||
Reference in New Issue
Block a user