Skip to content
Merged
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
24 changes: 23 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,33 @@ ignore = [
"src/dstack/_internal/server/migrations/versions",
]

[tool.pytest.ini_options]
testpaths = ["src/tests"]
addopts = [
"--disable-socket",
"--allow-hosts=127.0.0.1,localhost",
# unix socket for Docker/testcontainers
"--allow-unix-socket",
]
markers = [
"shim_version",
"dockerized",
]
env = [
"DSTACK_CLI_RICH_FORCE_TERMINAL=0",
]
filterwarnings = [
# testcontainers modules use deprecated decorators – nothing we can do:
# https://github.com/testcontainers/testcontainers-python/issues/874
"ignore:^The @wait_container_is_ready decorator:DeprecationWarning"
]

[dependency-groups]
dev = [
"pre-commit>=4.2.0",
"pytest~=7.2",
"pytest~=8.0",
"pytest-asyncio>=0.23.8",
"pytest-mock>=3.14.0",
"pytest-httpbin>=2.1.0",
"pytest-socket>=0.7.0",
"pytest-env>=1.1.0",
Expand Down
12 changes: 0 additions & 12 deletions pytest.ini

This file was deleted.

2 changes: 1 addition & 1 deletion src/tests/_internal/server/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@


@pytest.fixture
def client(event_loop):
def client():
transport = httpx.ASGITransport(app=app)
return httpx.AsyncClient(transport=transport, base_url="http://test")

Expand Down
2 changes: 1 addition & 1 deletion src/tests/_internal/server/services/test_logs.py
Original file line number Diff line number Diff line change
Expand Up @@ -798,8 +798,8 @@ async def test_poll_logs_descending_malformed_lines(
class TestCloudWatchLogStorage:
FAKE_NOW = datetime(2023, 10, 6, 10, 1, 54, tzinfo=timezone.utc)

@freeze_time(FAKE_NOW)
@pytest_asyncio.fixture
@freeze_time(FAKE_NOW)
async def project(self, test_db, session: AsyncSession) -> ProjectModel:
project = await create_project(session=session, name="test-proj")
return project
Expand Down
57 changes: 28 additions & 29 deletions src/tests/plugins/test_rest_plugin.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import json
import os
from contextlib import nullcontext as does_not_raise
from unittest import mock
from unittest.mock import Mock

import pytest
Expand Down Expand Up @@ -101,14 +100,14 @@ async def test_on_run_apply_plugin_service_uri_not_set(self):
CustomApplyPolicy()

@pytest.mark.asyncio
@mock.patch.dict(os.environ, {PLUGIN_SERVICE_URI_ENV_VAR_NAME: "http://mock"})
@pytest.mark.parametrize("test_db", ["sqlite", "postgres"], indirect=True)
@pytest.mark.parametrize(
"spec", ["run_spec", "fleet_spec", "volume_spec", "gateway_spec"], indirect=True
)
async def test_on_apply_plugin_service_returns_mutated_spec(
self, test_db, user, project, spec
self, mocker, test_db, user, project, spec
):
mocker.patch.dict(os.environ, {PLUGIN_SERVICE_URI_ENV_VAR_NAME: "http://mock"})
policy = CustomApplyPolicy()
mock_response = Mock()
response_dict = {"spec": spec.dict(), "error": None}
Expand All @@ -120,55 +119,54 @@ async def test_on_apply_plugin_service_returns_mutated_spec(

mock_response.text = json.dumps(response_dict)
mock_response.raise_for_status = Mock()
with mock.patch("requests.post", return_value=mock_response):
result = policy.on_apply(user=user.name, project=project.name, spec=spec)
assert result == type(spec)(**response_dict["spec"])
mocker.patch("requests.post", return_value=mock_response)
result = policy.on_apply(user=user.name, project=project.name, spec=spec)
assert result == type(spec)(**response_dict["spec"])

@pytest.mark.asyncio
@mock.patch.dict(os.environ, {PLUGIN_SERVICE_URI_ENV_VAR_NAME: "http://mock"})
@pytest.mark.parametrize("test_db", ["sqlite", "postgres"], indirect=True)
@pytest.mark.parametrize(
"spec", ["run_spec", "fleet_spec", "volume_spec", "gateway_spec"], indirect=True
)
async def test_on_apply_plugin_service_call_fails(self, test_db, user, project, spec):
async def test_on_apply_plugin_service_call_fails(self, mocker, test_db, user, project, spec):
mocker.patch.dict(os.environ, {PLUGIN_SERVICE_URI_ENV_VAR_NAME: "http://mock"})
policy = CustomApplyPolicy()
with mock.patch("requests.post", side_effect=requests.RequestException("fail")):
with pytest.raises(ServerClientError):
policy.on_apply(user=user.name, project=project.name, spec=spec)
mocker.patch("requests.post", side_effect=requests.RequestException("fail"))
with pytest.raises(ServerClientError):
policy.on_apply(user=user.name, project=project.name, spec=spec)

@pytest.mark.asyncio
@mock.patch.dict(os.environ, {PLUGIN_SERVICE_URI_ENV_VAR_NAME: "http://mock"})
@pytest.mark.parametrize("test_db", ["sqlite", "postgres"], indirect=True)
@pytest.mark.parametrize(
"spec", ["run_spec", "fleet_spec", "volume_spec", "gateway_spec"], indirect=True
)
async def test_on_apply_plugin_service_connection_fails(self, test_db, user, project, spec):
async def test_on_apply_plugin_service_connection_fails(
self, mocker, test_db, user, project, spec
):
mocker.patch.dict(os.environ, {PLUGIN_SERVICE_URI_ENV_VAR_NAME: "http://mock"})
policy = CustomApplyPolicy()
with mock.patch(
"requests.post", side_effect=requests.ConnectionError("Failed to connect")
):
with pytest.raises(ServerClientError):
policy.on_apply(user=user.name, project=project.name, spec=spec)
mocker.patch("requests.post", side_effect=requests.ConnectionError("Failed to connect"))
with pytest.raises(ServerClientError):
policy.on_apply(user=user.name, project=project.name, spec=spec)

@pytest.mark.asyncio
@mock.patch.dict(os.environ, {PLUGIN_SERVICE_URI_ENV_VAR_NAME: "http://mock"})
@pytest.mark.parametrize("test_db", ["sqlite", "postgres"], indirect=True)
@pytest.mark.parametrize(
"spec", ["run_spec", "fleet_spec", "volume_spec", "gateway_spec"], indirect=True
)
async def test_on_apply_plugin_service_returns_invalid_spec(
self, test_db, user, project, spec
self, mocker, test_db, user, project, spec
):
mocker.patch.dict(os.environ, {PLUGIN_SERVICE_URI_ENV_VAR_NAME: "http://mock"})
policy = CustomApplyPolicy()
mock_response = Mock()
mock_response.text = json.dumps({"invalid-key": "abc"})
mock_response.raise_for_status = Mock()
with mock.patch("requests.post", return_value=mock_response):
with pytest.raises(ServerClientError):
policy.on_apply(user.name, project=project.name, spec=spec)
mocker.patch("requests.post", return_value=mock_response)
with pytest.raises(ServerClientError):
policy.on_apply(user.name, project=project.name, spec=spec)

@pytest.mark.asyncio
@mock.patch.dict(os.environ, {PLUGIN_SERVICE_URI_ENV_VAR_NAME: "http://mock"})
@pytest.mark.parametrize("test_db", ["sqlite", "postgres"], indirect=True)
@pytest.mark.parametrize(
"spec", ["run_spec", "fleet_spec", "volume_spec", "gateway_spec"], indirect=True
Expand All @@ -194,14 +192,15 @@ async def test_on_apply_plugin_service_returns_invalid_spec(
],
)
async def test_on_apply_plugin_service_error_handling(
self, test_db, user, project, spec, error, expectation
self, mocker, test_db, user, project, spec, error, expectation
):
mocker.patch.dict(os.environ, {PLUGIN_SERVICE_URI_ENV_VAR_NAME: "http://mock"})
policy = CustomApplyPolicy()
mock_response = Mock()
response_dict = {"spec": spec.dict(), "error": error}
mock_response.text = json.dumps(response_dict)
mock_response.raise_for_status = Mock()
with mock.patch("requests.post", return_value=mock_response):
with expectation:
result = policy.on_apply(user=user.name, project=project.name, spec=spec)
assert result == type(spec)(**response_dict["spec"])
mocker.patch("requests.post", return_value=mock_response)
with expectation:
result = policy.on_apply(user=user.name, project=project.name, spec=spec)
assert result == type(spec)(**response_dict["spec"])