Skip to content

Commit ee4ce12

Browse files
committed
Add integration tests
1 parent 2f9c669 commit ee4ce12

13 files changed

Lines changed: 479 additions & 0 deletions
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
name: Integration Tests
2+
3+
on:
4+
push:
5+
branches: [ main, master ]
6+
pull_request:
7+
branches: [ main, master ]
8+
9+
jobs:
10+
integration-tests:
11+
runs-on: ubuntu-24.04
12+
13+
steps:
14+
- uses: actions/checkout@v4
15+
16+
- name: Set up Python
17+
uses: actions/setup-python@v5
18+
with:
19+
python-version: '3.x'
20+
21+
- name: Install urbackup-server
22+
run: |
23+
echo 'deb http://download.opensuse.org/repositories/home:/uroni/xUbuntu_24.04/ /' | sudo tee /etc/apt/sources.list.d/home:uroni.list
24+
curl -fsSL https://download.opensuse.org/repositories/home:uroni/xUbuntu_24.04/Release.key | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/home_uroni.gpg > /dev/null
25+
sudo apt-get update
26+
sudo apt-get install -y urbackup-server
27+
28+
- name: Start urbackup-server
29+
run: sudo systemctl start urbackupsrv
30+
31+
- name: Install Python dependencies
32+
run: |
33+
pip install pytest
34+
pip install -e .
35+
36+
- name: Run integration tests
37+
run: pytest

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ urbackup_api/__pycache__/*
44

55
# IDE
66
.idea
7+
tests/__pycache__

pytest.ini

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[pytest]
2+
testpaths = tests

tests/conftest.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import subprocess
2+
import time
3+
4+
import pytest
5+
6+
import urbackup_api
7+
8+
9+
SERVER_URL = "http://127.0.0.1:55414/x"
10+
ADMIN_USER = "admin"
11+
ADMIN_PASSWORD = "test1234"
12+
13+
14+
def _restart_clean_server():
15+
"""Stop urbackupsrv, wipe /var/urbackup/, start fresh."""
16+
subprocess.run(["sudo", "systemctl", "stop", "urbackupsrv"], check=True)
17+
subprocess.run(["sudo", "rm", "-rf", "/var/urbackup/"], check=True)
18+
subprocess.run(["sudo", "mkdir", "-p", "/var/urbackup/"], check=True)
19+
subprocess.run(["sudo", "chown", "urbackup:urbackup", "/var/urbackup/"], check=True)
20+
subprocess.run(["sudo", "systemctl", "start", "urbackupsrv"], check=True)
21+
# Wait for the server to be ready
22+
for _ in range(30):
23+
try:
24+
s = urbackup_api.urbackup_server(SERVER_URL, ADMIN_USER, "")
25+
if s.login():
26+
break
27+
except Exception:
28+
pass
29+
time.sleep(1)
30+
else:
31+
raise RuntimeError("urbackupsrv did not become ready after restart")
32+
# Set admin password
33+
subprocess.run(
34+
["sudo", "urbackupsrv", "reset-admin-pw", "-p", ADMIN_PASSWORD],
35+
check=True,
36+
)
37+
38+
39+
@pytest.fixture(scope="module", autouse=True)
40+
def clean_server():
41+
"""Restart urbackupsrv with clean data before each test module."""
42+
_restart_clean_server()
43+
yield
44+
# No teardown needed; next module will restart anyway
45+
46+
47+
@pytest.fixture()
48+
def server():
49+
"""Return a logged-in urbackup_server instance."""
50+
s = urbackup_api.urbackup_server(SERVER_URL, ADMIN_USER, ADMIN_PASSWORD)
51+
assert s.login(), "Failed to login to urbackup server"
52+
return s

tests/test_backups.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
"""Tests for backup-related operations (backups listing, actions, start/stop)."""
2+
3+
import pytest
4+
5+
6+
class TestBackupLists:
7+
8+
def test_get_clientbackups_empty(self, server):
9+
server.add_client("pytest-backup-client")
10+
status = server.get_client_status("pytest-backup-client")
11+
backups = server.get_clientbackups(status["id"])
12+
assert isinstance(backups, list)
13+
assert backups == []
14+
15+
def test_get_clientimagebackups_empty(self, server):
16+
server.add_client("pytest-imgbackup-client")
17+
status = server.get_client_status("pytest-imgbackup-client")
18+
backups = server.get_clientimagebackups(status["id"])
19+
assert isinstance(backups, list)
20+
assert backups == []
21+
22+
23+
class TestStartBackup:
24+
25+
@pytest.fixture(autouse=True)
26+
def _create_client(self, server):
27+
server.add_client("pytest-startbackup-client")
28+
29+
def test_start_incr_file_backup_no_online_client(self, server):
30+
# Client exists but is not online, so backup start should fail
31+
result = server.start_incr_file_backup("pytest-startbackup-client")
32+
assert result is False
33+
34+
def test_start_full_file_backup_no_online_client(self, server):
35+
result = server.start_full_file_backup("pytest-startbackup-client")
36+
assert result is False
37+
38+
def test_start_incr_image_backup_no_online_client(self, server):
39+
result = server.start_incr_image_backup("pytest-startbackup-client")
40+
assert result is False
41+
42+
def test_start_full_image_backup_no_online_client(self, server):
43+
result = server.start_full_image_backup("pytest-startbackup-client")
44+
assert result is False
45+
46+
def test_start_backup_nonexistent_client(self, server):
47+
result = server.start_incr_file_backup("nonexistent-client-xyz")
48+
assert result is False
49+
50+
51+
class TestActions:
52+
53+
def test_get_actions_empty(self, server):
54+
actions = server.get_actions()
55+
assert isinstance(actions, list)
56+
57+
def test_stop_action_missing_fields(self, server):
58+
# action dict missing required keys
59+
result = server.stop_action({})
60+
assert result is False
61+
62+
result = server.stop_action({"clientid": 1})
63+
assert result is False
64+
65+
result = server.stop_action({"id": 1})
66+
assert result is False
67+
68+
69+
class TestActionConstants:
70+
71+
def test_action_constants_exist(self):
72+
from urbackup_api import urbackup_server
73+
74+
assert urbackup_server.action_incr_file == 1
75+
assert urbackup_server.action_full_file == 2
76+
assert urbackup_server.action_incr_image == 3
77+
assert urbackup_server.action_full_image == 4
78+
assert urbackup_server.action_resumed_incr_file == 5
79+
assert urbackup_server.action_resumed_full_file == 6
80+
assert urbackup_server.action_file_restore == 8
81+
assert urbackup_server.action_image_restore == 9
82+
assert urbackup_server.action_client_update == 10
83+
assert urbackup_server.action_check_db_integrity == 11
84+
assert urbackup_server.action_backup_db == 12
85+
assert urbackup_server.action_recalc_stats == 13

tests/test_clients.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
"""Tests for client management (add, status, settings, authkey)."""
2+
3+
import pytest
4+
5+
6+
class TestAddClient:
7+
8+
def test_add_client(self, server):
9+
ret = server.add_client("pytest-client-1")
10+
assert ret is not None
11+
assert ret["added_new_client"] is True
12+
assert "new_authkey" in ret
13+
assert ret["new_clientname"] == "pytest-client-1"
14+
15+
def test_add_client_duplicate_returns_none(self, server):
16+
server.add_client("pytest-dup")
17+
ret = server.add_client("pytest-dup")
18+
assert ret is None
19+
20+
def test_add_client_with_group(self, server):
21+
ret = server.add_client("pytest-grouped", groupname="testgroup")
22+
assert ret is not None
23+
assert ret["added_new_client"] is True
24+
25+
26+
class TestGetClientStatus:
27+
28+
@pytest.fixture(autouse=True)
29+
def _create_client(self, server):
30+
server.add_client("pytest-status-client")
31+
32+
def test_get_client_status(self, server):
33+
status = server.get_client_status("pytest-status-client")
34+
assert status is not None
35+
assert status["name"] == "pytest-status-client"
36+
assert "id" in status
37+
38+
def test_get_client_status_nonexistent(self, server):
39+
status = server.get_client_status("nonexistent-client-xyz")
40+
assert status is None
41+
42+
def test_client_appears_in_status_list(self, server):
43+
all_status = server.get_status()
44+
names = [c["name"] for c in all_status]
45+
assert "pytest-status-client" in names
46+
47+
48+
class TestClientSettings:
49+
50+
@pytest.fixture(autouse=True)
51+
def _create_client(self, server):
52+
server.add_client("pytest-settings-client")
53+
54+
def test_get_client_settings(self, server):
55+
settings = server.get_client_settings("pytest-settings-client")
56+
assert settings is not None
57+
assert isinstance(settings, dict)
58+
assert "internet_authkey" in settings
59+
60+
def test_get_client_settings_nonexistent(self, server):
61+
settings = server.get_client_settings("nonexistent-client-xyz")
62+
assert settings is None
63+
64+
def test_change_client_setting(self, server):
65+
result = server.change_client_setting(
66+
"pytest-settings-client", "internet_authkey", "my-custom-key"
67+
)
68+
assert result is True
69+
70+
authkey = server.get_client_authkey("pytest-settings-client")
71+
assert authkey is not None
72+
assert authkey["value"] == "my-custom-key"
73+
74+
75+
class TestClientAuthkey:
76+
77+
@pytest.fixture(autouse=True)
78+
def _create_client(self, server):
79+
server.add_client("pytest-authkey-client")
80+
81+
def test_get_client_authkey(self, server):
82+
authkey = server.get_client_authkey("pytest-authkey-client")
83+
assert authkey is not None
84+
85+
def test_get_client_authkey_nonexistent(self, server):
86+
authkey = server.get_client_authkey("nonexistent-client-xyz")
87+
assert authkey is None

tests/test_extra_clients.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
"""Tests for extra clients management."""
2+
3+
4+
class TestExtraClients:
5+
6+
def test_get_extra_clients_empty(self, server):
7+
extras = server.get_extra_clients()
8+
assert isinstance(extras, list)
9+
assert extras == []
10+
11+
def test_add_extra_client(self, server):
12+
result = server.add_extra_client("10.0.0.100")
13+
assert result is True
14+
15+
extras = server.get_extra_clients()
16+
hostnames = [e["hostname"] for e in extras]
17+
assert "10.0.0.100" in hostnames
18+
19+
def test_add_multiple_extra_clients(self, server):
20+
server.add_extra_client("10.0.0.101")
21+
server.add_extra_client("10.0.0.102")
22+
23+
extras = server.get_extra_clients()
24+
hostnames = [e["hostname"] for e in extras]
25+
assert "10.0.0.101" in hostnames
26+
assert "10.0.0.102" in hostnames
27+
28+
def test_remove_extra_client(self, server):
29+
server.add_extra_client("10.0.0.200")
30+
extras = server.get_extra_clients()
31+
target = [e for e in extras if e["hostname"] == "10.0.0.200"]
32+
assert len(target) == 1
33+
34+
result = server.remove_extra_client(target[0]["id"])
35+
assert result is True
36+
37+
extras_after = server.get_extra_clients()
38+
hostnames_after = [e["hostname"] for e in extras_after]
39+
assert "10.0.0.200" not in hostnames_after

tests/test_installer.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
"""Tests for installer download functionality."""
2+
3+
import os
4+
import tempfile
5+
6+
from urbackup_api import installer_os
7+
8+
9+
class TestDownloadInstaller:
10+
11+
def test_download_linux_installer(self, server):
12+
with tempfile.NamedTemporaryFile(suffix=".sh", delete=False) as tmp:
13+
tmpname = tmp.name
14+
15+
try:
16+
result = server.download_installer(tmpname, "pytest-installer-client", installer_os.Linux)
17+
assert result is True
18+
assert os.path.exists(tmpname)
19+
assert os.path.getsize(tmpname) > 0
20+
finally:
21+
os.unlink(tmpname)
22+
23+
def test_download_installer_existing_client(self, server):
24+
# First call creates the client
25+
server.add_client("pytest-installer-existing")
26+
27+
with tempfile.NamedTemporaryFile(suffix=".sh", delete=False) as tmp:
28+
tmpname = tmp.name
29+
30+
try:
31+
result = server.download_installer(tmpname, "pytest-installer-existing", installer_os.Linux)
32+
assert result is True
33+
assert os.path.getsize(tmpname) > 0
34+
finally:
35+
os.unlink(tmpname)
36+
37+
38+
class TestInstallerOsEnum:
39+
40+
def test_installer_os_values(self):
41+
assert installer_os.Windows.value == ("windows",)
42+
assert installer_os.Linux.value == "linux"

tests/test_livelog.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
"""Tests for live log retrieval."""
2+
3+
4+
class TestLiveLog:
5+
6+
def test_get_livelog(self, server):
7+
log = server.get_livelog()
8+
# Fresh server should have some log entries (startup messages)
9+
# or None if no entries exist yet
10+
if log is not None:
11+
assert isinstance(log, list)
12+
for entry in log:
13+
assert "id" in entry
14+
15+
def test_get_livelog_updates_lastlogid(self, server):
16+
log = server.get_livelog()
17+
if log is not None:
18+
assert server._lastlogid > 0

0 commit comments

Comments
 (0)