Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions .github/workflows/api-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: API Tests

on:
push:
paths:
- 'tests/api/**'
- '.github/workflows/api-tests.yml'
pull_request:
paths:
- 'tests/api/**'

jobs:
test:
runs-on: ubuntu-latest

steps:
- name: Checkout repo
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pytest requests

- name: Run tests
env:
API_TOKEN: ${{ secrets.GITEA_API_TOKEN }}
run: |
echo "BASE_URL = 'http://54.194.27.12:3000/'" > config.py
echo "API_TOKEN = '${API_TOKEN}'" >> config.py
pytest tests/api -v
27 changes: 27 additions & 0 deletions gitea-tests.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAtpFEkGJbDhqrrjAk44uAf5Wr5/nPTso1hGdXVyeXKY/f44kk
G03FUdcydLtO9SeMZeuHjDNOZIDf4N5eEMEYQWZazJRRESzFG1XzokqO4NqjqlxX
o/67hxM+UGiZAlgOyPwQj9ewhg5ZMrv2kcHbTmEyxiTkbkb+d9+ryqKkAlO3KqJW
P9SU3mtuCLG01Tqm0dBaZgxnpLqjBK/OOMW4G8KKl6q92+7GEF/XNP8G5gWDdMiI
fEMzRVorqALGxKTjcvMgIKG9dmeI5hE7cgnVzdlQ6xSZ2i/awGPNGF33VIaHCU3S
Upve8nxsybizEijkXuhbhnhco2w+E1iEBgmnRQIDAQABAoIBAFArjItG1atk5N0S
ATD28o+UPzAYEAQOYd/prX31Qbkbl/qIH5Xp2ettb5e5JRwcqNeczSIw6YzS1v0d
SPtcf/VAKEFMJClBmrC9VsZ+rS1qdZJ7CHVYiCrxtVyEAiT4XE2/+tnfooHLzTmt
NsKc+Vv67Nv8GV+fx2EGlJ7gOttV/8Hy3oT4jqkvTJWlenzxOJdGQiVXtos8YpX8
MM/PkS5Sauu6OU5ArSXXHg7C/bNOYgWKXfDP9MQn8IxflSzVAOn+Ad7dXkjTC3Ua
a4xv9PeRVekfEi3WmahQxhXDVTrVZwiGUJ8hxZMvNteNPMWcSJdlo014Ly/Qjx5M
jlBKhOUCgYEA4UygCtTbSf5zeilqESqfnAzH+yhieAKA2E6g67zX0h76UKMiU1Vc
efNQEiFYFeIbhXUBLCdiT7C8gpgm5QvUWpsNFjqSKV844ZBc2TZXsB6J/JEZKUuN
6x3SRKNmuFtiW7J78hdLCHj/l+FukdtSRBNQco+cOFs5HT55qLD8LvcCgYEAz3H6
WxM876n7npxJyCtXkyYuMQGafqUk4WHWtlPTWIQw7DwqgeF0p4SOkwYIabI6ILEY
gAAbQVrFmChvh/tQmDk29gubKdMe7JM3nIRXBPaDwSxnBs0EjgAJiXdYlpCCHVIX
4Za2Vi1M9N7cGZecVT0sBUuMaBmya/IBeyOWQKMCgYBX46pzT0IUhXzK5SkJdVU6
bQn+gmyXYHKe7117WPngcFE578nONHiU4kQULonMT55o25IPhXWmnM2NLInPxGOc
zOu4BjVKimkIJWbzHW3ruJ4ftwLXxy+fzsxeFlhWBuBB4UjU0h1lOr6Ko1ic8bAP
+nDhoABTQ9LuA5c2JYTbVQKBgC7CDiBBMdcDhYe2ypqnylGMpZS+O8iYCLwUhYUL
V/P3t99HoH0uCFFJ+6kADx1j4t5DjLYtT/dnMmqdkqYf64akPtMuwoam462HcV2C
JusjdYcxLvfFdmVbdMrbb8hgQjPBsUhT5D2AcHwxT4MlPUOpSibXZIqCYEkcf2D2
IIPpAoGADaSAsjcGFBWgQco2i/gUHxMuNE425mcNcXQ+zutSNyZ2TVpLXJukIjsx
cB8YqTQ16X0wls79fu92A4QfYpG4POXn+usD9T5r4R/b+ofZCTHEYi1j4MCcUhcQ
AK6vnjryobdhniZCFREdcX0yPjT0ZC0pZcVarqVu4tLoRHFLqcs=
-----END RSA PRIVATE KEY-----
Binary file not shown.
10 changes: 10 additions & 0 deletions tests/api/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import os

#local API_TOKEN
#API_TOKEN = 'b32712d989aeee1e78f112ba4fc8e9f93ae7fe78'

#remote API_TOKEN:
API_TOKEN = 'f1600abdf735b9e11dbfc45e57db04319d58235e'

#BASE_URL = os.getenv('GITEA_URL', 'http://localhost:3000')
BASE_URL = 'http://54.194.27.12:3000'
16 changes: 16 additions & 0 deletions tests/api/test_auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import requests
from config import BASE_URL, API_TOKEN # uses the same config.py you already generate in CI

def test_me_endpoint_returns_current_user_and_is_admin():
headers = {"Authorization": f"token {API_TOKEN}"}

r = requests.get(f"{BASE_URL}/api/v1/user", headers=headers)
assert r.status_code == 200, f"Unexpected status: {r.status_code} body={r.text}"

me = r.json()
# Basic shape checks
for key in ["id", "login", "email", "is_admin", "created"]:
assert key in me, f"Missing key '{key}' in /user response: {me}"

# If the token is an admin (your token is), this should be True
assert me["is_admin"] is True
50 changes: 50 additions & 0 deletions tests/api/test_branches.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import requests
import random
import string
from config import BASE_URL, API_TOKEN

def _headers():
return {"Authorization": f"token {API_TOKEN}"}

def _rand(prefix):
return prefix + "_" + "".join(random.choices(string.ascii_lowercase + string.digits, k=6))

def test_list_branches_contains_default_branch():
headers = _headers()

# 1) Create owner user
username = _rand("owner")
user_payload = {
"email": f"{username}@example.com",
"username": username,
"password": "TestPass123!",
"must_change_password": False,
"send_notify": False,
}
r_user = requests.post(f"{BASE_URL}/api/v1/admin/users", headers=headers, json=user_payload)
assert r_user.status_code == 201, f"User create failed: {r_user.status_code} {r_user.text}"

# 2) Create repo with auto_init so default branch exists
repo_name = _rand("repo")
repo_payload = {"name": repo_name, "description": "Branches test", "private": False, "auto_init": True}
r_repo = requests.post(f"{BASE_URL}/api/v1/admin/users/{username}/repos", headers=headers, json=repo_payload)
assert r_repo.status_code == 201, f"Repo create failed: {r_repo.status_code} {r_repo.text}"

# 3) Get repo to read its default_branch
r_get = requests.get(f"{BASE_URL}/api/v1/repos/{username}/{repo_name}", headers=headers)
assert r_get.status_code == 200, f"Get repo failed: {r_get.status_code} {r_get.text}"
default_branch = r_get.json().get("default_branch")
assert default_branch, "Expected default_branch to be set"

# 4) List branches and verify default branch is present
r_branches = requests.get(f"{BASE_URL}/api/v1/repos/{username}/{repo_name}/branches", headers=headers)
assert r_branches.status_code == 200, f"List branches failed: {r_branches.status_code} {r_branches.text}"
branches = r_branches.json()
assert isinstance(branches, list) and branches, "Expected non-empty branches list"

names = {b.get("name") for b in branches}
assert default_branch in names, f"default branch '{default_branch}' not found in {names}"

# 5) Cleanup (best-effort)
requests.delete(f"{BASE_URL}/api/v1/repos/{username}/{repo_name}", headers=headers)
requests.delete(f"{BASE_URL}/api/v1/admin/users/{username}", headers=headers)
108 changes: 108 additions & 0 deletions tests/api/test_create_repo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# tests/test_create_repo.py
import random
import string
import unittest
import requests
from config import BASE_URL, API_TOKEN


def _headers() -> dict:
return {"Authorization": f"token {API_TOKEN}"}


def _rand(prefix: str = "user") -> str:
return prefix + "_" + "".join(random.choices(string.ascii_lowercase + string.digits, k=6))


class TestCreateRepoThenGetDetails(unittest.TestCase):
@classmethod
def setUp(self):
#try CI
# Runs before each test
if not BASE_URL:
raise RuntimeError("BASE_URL is not set")
if not API_TOKEN:
raise RuntimeError("API_TOKEN is not set")
self.base_url = BASE_URL.rstrip("/")
self.headers = {"Authorization": f"token {API_TOKEN}"}

# Track created resources for cleanup
self._created_user = None
self._created_repo = None

def tearDown(self) -> None:
# Delete repo first if it was created
if self._created_repo:
owner, repo_name = self._created_repo
try:
url = f"{self.base_url}/api/v1/repos/{owner}/{repo_name}"
r = requests.delete(url, headers=self.headers, timeout=30)
print(f"[teardown] DELETE repo {owner}/{repo_name} -> {r.status_code}")
except Exception as e:
print(f"[teardown] Failed to delete repo {owner}/{repo_name}: {e}")

# Delete user if it was created
if self._created_user:
try:
url = f"{self.base_url}/api/v1/admin/users/{self._created_user}"
r = requests.delete(url, headers=self.headers, timeout=30)
print(f"[teardown] DELETE user {self._created_user} -> {r.status_code}")
except Exception as e:
print(f"[teardown] Failed to delete user {self._created_user}: {e}")

def test_create_repo_then_get_details(self) -> None:
# 1) Create a user (owner)
username = _rand("owner")
user_payload = {
"email": f"{username}@example.com",
"username": username,
"password": "TestPass123!",
"must_change_password": False,
"send_notify": False,
}
r_user = requests.post(
f"{self.base_url}/api/v1/admin/users",
headers=self.headers,
json=user_payload,
timeout=30,
)
self.assertEqual(r_user.status_code, 201, f"User create failed: {r_user.status_code} {r_user.text}")
self._created_user = username # mark for cleanup

# 2) Create a repository for that user
repo_name = _rand("repo")
repo_payload = {
"name": repo_name,
"description": "API test repo",
"private": False,
"auto_init": True,
}
r_repo = requests.post(
f"{self.base_url}/api/v1/admin/users/{username}/repos",
headers=self.headers,
json=repo_payload,
timeout=30,
)
self.assertEqual(r_repo.status_code, 201, f"Repo create failed: {r_repo.status_code} {r_repo.text}")
created = r_repo.json()
self.assertEqual(created["name"], repo_name)
self.assertEqual(created["owner"]["login"], username)
self._created_repo = (username, repo_name) # mark for cleanup

# 3) GET repo details and verify fields
r_get = requests.get(
f"{self.base_url}/api/v1/repos/{username}/{repo_name}",
headers=self.headers,
timeout=30,
)
self.assertEqual(r_get.status_code, 200, f"Get repo failed: {r_get.status_code} {r_get.text}")
repo = r_get.json()

self.assertEqual(repo["name"], repo_name)
self.assertEqual(repo["owner"]["login"], username)
self.assertIs(repo["private"], False)
self.assertTrue(repo.get("default_branch"), "Expected default_branch to be set")


if __name__ == "__main__":
unittest.main(verbosity=2)
38 changes: 38 additions & 0 deletions tests/api/test_duplicate_users.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import requests
import string
import random
from config import BASE_URL, API_TOKEN

def _headers():
return {"Authorization": f"token {API_TOKEN}"}

def _rand_username(prefix="dupuser"):
return prefix + "_" + "".join(random.choices(string.ascii_lowercase + string.digits, k=6))

def test_create_duplicate_user_should_fail():
headers = _headers()
username = _rand_username()
payload = {
"email": f"{username}@example.com",
"username": username,
"password": "TestPass123!",
"must_change_password": False,
"send_notify": False,
}

# First creation should succeed
r1 = requests.post(f"{BASE_URL}/api/v1/admin/users", headers=headers, json=payload)
assert r1.status_code == 201, f"First create failed: {r1.status_code} {r1.text}"

# Second creation with the same username should fail
r2 = requests.post(f"{BASE_URL}/api/v1/admin/users", headers=headers, json=payload)

# Gitea typically returns 422 for duplicate username; some setups may return 409
assert r2.status_code in (422, 409), f"Expected 422/409 on duplicate, got {r2.status_code} {r2.text}"

# Error body should hint user exists
err_text = r2.text.lower()
assert ("exist" in err_text) or ("already" in err_text), f"Unexpected error message: {r2.text}"

# Cleanup: best-effort delete the user we created (ignore errors)
requests.delete(f"{BASE_URL}/api/v1/admin/users/{username}", headers=headers)
58 changes: 58 additions & 0 deletions tests/api/test_issues.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import requests
import random
import string
from config import BASE_URL, API_TOKEN

def _headers():
return {"Authorization": f"token {API_TOKEN}"}

def _rand(prefix):
return prefix + "_" + "".join(random.choices(string.ascii_lowercase + string.digits, k=6))

def test_create_issue_and_list_it():
headers = _headers()

# 1) Create owner user
username = _rand("owner")
user_payload = {
"email": f"{username}@example.com",
"username": username,
"password": "TestPass123!",
"must_change_password": False,
"send_notify": False,
}
r_user = requests.post(f"{BASE_URL}/api/v1/admin/users", headers=headers, json=user_payload)
assert r_user.status_code == 201, f"User create failed: {r_user.status_code} {r_user.text}"

# 2) Create repo
repo_name = _rand("repo")
repo_payload = {"name": repo_name, "description": "Issues test", "private": False, "auto_init": True}
r_repo = requests.post(f"{BASE_URL}/api/v1/admin/users/{username}/repos", headers=headers, json=repo_payload)
assert r_repo.status_code == 201, f"Repo create failed: {r_repo.status_code} {r_repo.text}"

# 3) Create an issue
title = f"Issue { _rand('t') }"
body = "Created by API test"
issue_payload = {"title": title, "body": body}
r_issue = requests.post(
f"{BASE_URL}/api/v1/repos/{username}/{repo_name}/issues",
headers=headers,
json=issue_payload
)
assert r_issue.status_code == 201, f"Issue create failed: {r_issue.status_code} {r_issue.text}"
issue = r_issue.json()
number = issue.get("number")
assert number, f"Expected issue number, got {issue}"
assert issue["title"] == title
assert issue["state"] == "open"

# 4) List issues and verify the created one is present
r_list = requests.get(f"{BASE_URL}/api/v1/repos/{username}/{repo_name}/issues", headers=headers)
assert r_list.status_code == 200, f"Issues list failed: {r_list.status_code} {r_list.text}"
issues = r_list.json()
numbers = {i.get("number") for i in issues}
assert number in numbers, f"Issue #{number} not found in list: {numbers}"

# 5) Cleanup (best-effort)
requests.delete(f"{BASE_URL}/api/v1/repos/{username}/{repo_name}", headers=headers)
requests.delete(f"{BASE_URL}/api/v1/admin/users/{username}", headers=headers)
Loading
Loading