forgejo-integration-testing/tests/test_main.py
Michał Szczepanik b89f2afb0d Stop skipping the repeated HEAD requests test
Forgejo-aneksajo 16.0.1-git-annex-2 now bundles a more recent git-annex,
which has the fix for the double HEAD request (likely 10.20260717; still
based on alpine 3.23 but now installing git-annex from edge).

See: https://codeberg.org/forgejo-aneksajo/forgejo-aneksajo/pulls/123
2026-07-29 15:14:21 +02:00

296 lines
11 KiB
Python

from pathlib import Path
import random
import subprocess
import tempfile
from datalad_core.create import create_annexrepo
from datalad_core.git_utils import get_current_branch
from datalad_core.runners import call_annex_json_lines, call_git, call_git_oneline
from furl import furl
import pytest
import requests
def test_push_to_create(pytestconfig, forgejo, words):
"""Test the push to create functionality
This tests:
- whether push to create creates the forgejo repo (this depends on
a feature which is off by default, but became a de facto
standard for us)
- whether the default branch in forgejo matches the local branch
(ie. not git-annex); this most likely depends on the order in
which branches are pushed, changed in git-annex 10.20250605
("sync: Push the current branch first, rather than a synced
branch, to better support git forges (gitlab, gitea, forgejo,
etc.) which use push-to-create with the first pushed branch
becoming the default branch.")
- whether the UUID becomes known after a single push; this
probably changed in git-annex 10.20260213 ("When used with git
forges that allow Push to Create, the remote's annex-uuid is
re-probed after the initial push.")
This uses a single call to `git annex push`.
See:
https://forgejo.org/docs/latest/user/push-to-create/
https://git-annex.branchable.com/todo/Pushing_current_branch_before_synced__42___or_git-annex/
https://git-annex.branchable.com/todo/support_push_to_create/
"""
if pytestconfig.getoption("org") is not None:
owner = pytestconfig.getoption("org")
else:
# we have a token, we need to check which user it belongs to
r = forgejo.get_user()
owner = r.json().get("login")
assert owner is not None and len(owner) > 0
remote_repo_name = words.random_name()
push_url = furl(
pytestconfig.getoption("instance_url"), path=f"{owner}/{remote_repo_name}"
).tostr()
# the name is random, but best make sure repo doesn't exist
r = forgejo.get_repo(owner, remote_repo_name, do_raise=False)
assert r.status_code == 404
with tempfile.TemporaryDirectory() as tmpdir:
repo_path = Path(tmpdir)
wt = create_annexrepo(repo_path)
(wt.path / "foo.dat").write_bytes(random.randbytes(1024))
_ = list(call_annex_json_lines(annex_args=["add", "foo.dat"], cwd=wt.path))
_ = call_git(args=["commit", "-m", "Add foo"], cwd=wt.path)
_ = call_git(args=["remote", "add", "origin", push_url], cwd=wt.path)
_ = subprocess.run(
args=["git", "annex", "push", "origin"],
cwd=wt.path,
check=True,
)
# check if the remote exists
r = forgejo.get_repo(
owner, remote_repo_name
) # 200 OK or raises for 404 Not Found
# check if the default branch is the branch that got pushed (not git-annex)
current_branch = get_current_branch(repo_path)
remote_branch = r.json().get("default_branch")
assert current_branch == remote_branch
# check if the remote got initialized with a UUID, and local knows it
res = list(call_annex_json_lines(annex_args=["info", "origin"], cwd=wt.path))
assert len(res[0]["uuid"]) > 0
@pytest.mark.xfail(reason="Known issue, forgejo-aneksajo/issues/103")
def test_pull_does_not_create(pytestconfig, forgejo, words):
"""Test git annex pull from a non-existing repo
Pulling from a remote URL which points to a non-existing repo
should not auto-create that repo. Pull creating a repo has been
observed as an unwanted side-effect of push-to-create workarounds
(what happens when git-annex requests /config).
A test for
https://codeberg.org/forgejo-aneksajo/forgejo-aneksajo/issues/103
"""
remote_repo_name = words.random_name()
if pytestconfig.getoption("org") is not None:
owner = pytestconfig.getoption("org")
else:
# we have a token, we need to check which user it belongs to
r = forgejo.get_user()
owner = r.json().get("login")
assert owner is not None and len(owner) > 0
# the name is random, but best make sure repo doesn't exist
clone_url = furl(
pytestconfig.getoption("instance_url"), path=f"{owner}/{remote_repo_name}"
).tostr()
r = forgejo.get_repo(owner, remote_repo_name, do_raise=False)
assert r.status_code == 404
with tempfile.TemporaryDirectory(delete=True) as tmpdir:
repo_path = Path(tmpdir)
wt = create_annexrepo(repo_path)
_ = call_git(args=["remote", "add", "foo", clone_url], cwd=wt.path)
_ = subprocess.run(
args=["git", "annex", "pull", "foo"],
cwd=wt.path,
check=False, # ok to return non-zero, hide error
)
# the repo should not get created
r = forgejo.get_repo(owner, remote_repo_name, do_raise=False)
assert r.status_code == 404
def test_create_same_name(pytestconfig, forgejo, words):
"""Test deleting and recreating a repo
Deleting an annex-initialized forgejo repo and creating a new one
under the same name should not cause any issues.
A test for
https://codeberg.org/forgejo-aneksajo/forgejo-aneksajo/issues/83
"""
# create a Forgejo repo (push-to-create to be tested elsewhere)
remote_repo_name = words.random_name()
r = forgejo.create_repo(remote_repo_name, org=pytestconfig.getoption("org"))
create_response = r.json()
# create a local repo with an annexed file & push
with tempfile.TemporaryDirectory(delete=True) as tmpdir:
repo_path = Path(tmpdir)
wt = create_annexrepo(repo_path)
(wt.path / "bigstuff").write_bytes(random.randbytes(1024))
# git annex add, see https://hub.datalad.org/datalad/datalad-core/commit/a1feef64
_ = list(call_annex_json_lines(annex_args=["add", "bigstuff"], cwd=wt.path))
_ = call_git(args=["commit", "-m", "dummy"], cwd=wt.path)
_ = call_git(
args=["remote", "add", "origin", create_response["clone_url"]], cwd=wt.path
)
# git annex sync
# note: does not support --json, thus basic subprocess.run
_ = subprocess.run(
args=["git", "annex", "sync", "--content"],
cwd=wt.path,
check=True, # raises CalledProcessError on non-zero exit
)
# check if origin got a UUID
res = list(call_annex_json_lines(annex_args=["info", "origin"], cwd=wt.path))
uuid_there_first_try = res[0]["uuid"]
assert len(uuid_there_first_try) > 0
# now, delete the repo
create_owner = create_response["owner"]["login"] # login works for user and org
r = forgejo.delete_repo(create_owner, remote_repo_name)
# create a repo again under the same name
r = forgejo.create_repo(remote_repo_name, org=pytestconfig.getoption("org"))
recreate_response = r.json()
# repeate create & sync
with tempfile.TemporaryDirectory(delete=True) as tmpdir:
repo_path = Path(tmpdir)
wt = create_annexrepo(repo_path)
(wt.path / "bigstuff").write_bytes(random.randbytes(1024))
_ = list(call_annex_json_lines(annex_args=["add", "bigstuff"], cwd=wt.path))
_ = call_git(args=["commit", "-m", "dummy"], cwd=wt.path)
_ = call_git(
args=["remote", "add", "origin", recreate_response["clone_url"]],
cwd=wt.path,
)
_ = subprocess.run(
args=["git", "annex", "sync", "--content"],
cwd=wt.path,
check=True, # raises CalledProcessError on non-zero exit
)
# check if origin got a (new) UUID
for res in call_annex_json_lines(annex_args=["info", "origin"], cwd=wt.path):
assert len(res["uuid"]) > 0
assert res["uuid"] != uuid_there_first_try
def test_annex_init_priv_pub(pytestconfig, forgejo, words):
"""Test whether remote annex gets initialized (priv/publ)
We expect that git-annex sync for an empty forgejo repo would
initialize it with an annex UUID. This used not to be the case for
public repositories, so we test two cases (private and
public). This is related to forgejo-aneksajo's method of
initializing an annex.
A test for
https://codeberg.org/forgejo-aneksajo/forgejo-aneksajo/issues/93
"""
# create a Forgejo repo (push-to-create to be tested elsewhere)
public_repo_name = words.random_name()
org: str | None = pytestconfig.getoption("org")
r = forgejo.create_repo(name=public_repo_name, private=False, org=org)
public_clone_url = r.json()["clone_url"]
private_repo_name = words.random_name()
r = forgejo.create_repo(name=private_repo_name, private=True, org=org)
private_clone_url = r.json()["clone_url"]
with tempfile.TemporaryDirectory(delete=True) as tmpdir:
repo_path = Path(tmpdir)
wt = create_annexrepo(repo_path)
(wt.path / "foo.dat").write_bytes(random.randbytes(1024))
_ = list(call_annex_json_lines(annex_args=["add", "foo.dat"], cwd=wt.path))
_ = call_git(args=["commit", "-m", "Add foo"], cwd=wt.path)
_ = call_git(args=["remote", "add", "publ", public_clone_url], cwd=wt.path)
_ = call_git(args=["remote", "add", "priv", private_clone_url], cwd=wt.path)
_ = subprocess.run(
args=["git", "annex", "sync", "--content"],
cwd=wt.path,
check=True, # raises CalledProcessError on non-zero exit
)
# check if both remotes have an UUID (according to local info)
for remote in ("priv", "publ"):
for res in call_annex_json_lines(annex_args=["info", remote], cwd=wt.path):
assert len(res["uuid"]) > 0
def test_double_head(pytestconfig, forgejo, words):
"""Make sure p2phttps does not hang on HEAD requests
A test for
https://git-annex.branchable.com/bugs/p2phttp_key_endpoints_stuck_after_2_head_requests/
The behavior should have been fixed in git-annex 10.20260525
"""
repo_name = words.random_name()
org: str | None = pytestconfig.getoption("org")
r = forgejo.create_repo(name=repo_name, private=False, org=org)
clone_url = r.json()["clone_url"]
with tempfile.TemporaryDirectory() as tmpdir:
repo_path = Path(tmpdir)
wt = create_annexrepo(repo_path)
(wt.path / "foo.dat").write_bytes(random.randbytes(1024))
_ = list(call_annex_json_lines(annex_args=["add", "foo.dat"], cwd=wt.path))
_ = call_git(args=["commit", "-m", "Add foo"], cwd=wt.path)
_ = call_git(args=["remote", "add", "origin", clone_url], cwd=wt.path)
_ = call_git(args=["annex", "push", "origin"], cwd=wt.path)
key = call_git_oneline(args=["annex", "lookupkey", "foo.dat"], cwd=wt.path)
info = next(call_annex_json_lines(annex_args=["info", "origin"], cwd=wt.path))
# build p2phttp file download URL
f = furl(clone_url)
f.path.segments = ["git-annex-p2phttp", "git-annex", info["uuid"], "key", key]
f = furl(clone_url)
f.path.segments = ["git-annex-p2phttp", "git-annex", info["uuid"], "key", key]
# make three requests in succession; lockup raises requests.exceptions.ReadTimeout
_ = requests.head(f.tostr(), timeout=15)
_ = requests.head(f.tostr(), timeout=15)
_ = requests.head(f.tostr(), timeout=15)