Skip to content
Open
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
18 changes: 18 additions & 0 deletions erclient/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -1054,6 +1054,15 @@ def get_sources(self, page_size=100):
def get_users(self):
return self._get('users')

def get_task_status(self, task_id):
"""
Get the status of an async background task.

:param task_id: the task ID returned by an async operation (e.g. GPX upload)
:return: dict with task_id, status, result, and location
"""
return self._get(f'core/taskstatus/{task_id}/')


class AsyncERClient(object):
"""
Expand Down Expand Up @@ -1613,6 +1622,15 @@ async def get_feature_group(self, feature_group_id: str):
"""
return await self._get(f"spatialfeaturegroup/{feature_group_id}", params={})

async def get_task_status(self, task_id):
"""
Get the status of an async background task.

:param task_id: the task ID returned by an async operation (e.g. GPX upload)
:return: dict with task_id, status, result, and location
"""
return await self._get(f'core/taskstatus/{task_id}/')

async def _get_data(self, endpoint, params, batch_size=0):
if "page" not in params: # Use cursor paginator unless the user has specified a page
params["use_cursor"] = "true"
Expand Down
139 changes: 139 additions & 0 deletions tests/async_client/test_task_status.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
"""Tests for get_task_status in the async AsyncERClient."""
import httpx
import pytest
import respx


SERVICE_ROOT = "https://fake-site.erdomain.org/api/v1.0"
TASK_ID = "abc12345-def6-7890-ghij-klmnopqrstuv"


@pytest.fixture
def task_pending_response():
return {
"data": {
"task_id": TASK_ID,
"status": "PENDING",
"result": None,
"location": f"/api/v1.0/core/taskstatus/{TASK_ID}/",
}
}


@pytest.fixture
def task_success_response():
return {
"data": {
"task_id": TASK_ID,
"status": "SUCCESS",
"result": {"imported": 42, "errors": 0},
"location": f"/api/v1.0/core/taskstatus/{TASK_ID}/",
}
}


@pytest.fixture
def task_failure_response():
return {
"data": {
"task_id": TASK_ID,
"status": "FAILURE",
"result": "File format not recognized",
"location": f"/api/v1.0/core/taskstatus/{TASK_ID}/",
}
}


@pytest.fixture
def task_started_response():
return {
"data": {
"task_id": TASK_ID,
"status": "STARTED",
"result": None,
"location": f"/api/v1.0/core/taskstatus/{TASK_ID}/",
}
}


@pytest.mark.asyncio
async def test_get_task_status_pending(er_client, task_pending_response):
async with respx.mock(assert_all_called=False) as respx_mock:
route = respx_mock.get(
f"{SERVICE_ROOT}/core/taskstatus/{TASK_ID}/"
).respond(httpx.codes.OK, json=task_pending_response)

result = await er_client.get_task_status(TASK_ID)
assert route.called
assert result["task_id"] == TASK_ID
assert result["status"] == "PENDING"
assert result["result"] is None
await er_client.close()


@pytest.mark.asyncio
async def test_get_task_status_success(er_client, task_success_response):
async with respx.mock(assert_all_called=False) as respx_mock:
route = respx_mock.get(
f"{SERVICE_ROOT}/core/taskstatus/{TASK_ID}/"
).respond(httpx.codes.OK, json=task_success_response)

result = await er_client.get_task_status(TASK_ID)
assert route.called
assert result["status"] == "SUCCESS"
assert result["result"]["imported"] == 42
await er_client.close()


@pytest.mark.asyncio
async def test_get_task_status_failure(er_client, task_failure_response):
async with respx.mock(assert_all_called=False) as respx_mock:
route = respx_mock.get(
f"{SERVICE_ROOT}/core/taskstatus/{TASK_ID}/"
).respond(httpx.codes.OK, json=task_failure_response)

result = await er_client.get_task_status(TASK_ID)
assert route.called
assert result["status"] == "FAILURE"
assert result["result"] == "File format not recognized"
await er_client.close()


@pytest.mark.asyncio
async def test_get_task_status_started(er_client, task_started_response):
async with respx.mock(assert_all_called=False) as respx_mock:
route = respx_mock.get(
f"{SERVICE_ROOT}/core/taskstatus/{TASK_ID}/"
).respond(httpx.codes.OK, json=task_started_response)

result = await er_client.get_task_status(TASK_ID)
assert route.called
assert result["status"] == "STARTED"
await er_client.close()


@pytest.mark.asyncio
async def test_get_task_status_not_found(er_client):
from erclient.er_errors import ERClientNotFound
async with respx.mock(assert_all_called=False) as respx_mock:
respx_mock.get(
f"{SERVICE_ROOT}/core/taskstatus/nonexistent-task-id/"
).respond(httpx.codes.NOT_FOUND, json={"status": {"code": 404}})

with pytest.raises(ERClientNotFound):
await er_client.get_task_status("nonexistent-task-id")
await er_client.close()


@pytest.mark.asyncio
async def test_get_task_status_url_construction(er_client, task_pending_response):
async with respx.mock(assert_all_called=False) as respx_mock:
route = respx_mock.get(
f"{SERVICE_ROOT}/core/taskstatus/{TASK_ID}/"
).respond(httpx.codes.OK, json=task_pending_response)

await er_client.get_task_status(TASK_ID)
assert route.called
req = route.calls[0].request
assert str(req.url).startswith(f"{SERVICE_ROOT}/core/taskstatus/{TASK_ID}/")
await er_client.close()
123 changes: 123 additions & 0 deletions tests/sync_client/test_task_status.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
"""Tests for get_task_status in the sync ERClient."""
import json
from unittest.mock import patch, MagicMock

import pytest


SERVICE_ROOT = "https://fake-site.erdomain.org/api/v1.0"
TASK_ID = "abc12345-def6-7890-ghij-klmnopqrstuv"


def _mock_response(json_data=None, status_code=200, ok=True):
"""Helper to build a mock requests.Response."""
resp = MagicMock()
resp.ok = ok
resp.status_code = status_code
resp.text = json.dumps(json_data) if json_data is not None else ""
resp.json.return_value = json_data
return resp


@pytest.fixture
def task_pending_response():
return {
"data": {
"task_id": TASK_ID,
"status": "PENDING",
"result": None,
"location": f"/api/v1.0/core/taskstatus/{TASK_ID}/",
}
}


@pytest.fixture
def task_success_response():
return {
"data": {
"task_id": TASK_ID,
"status": "SUCCESS",
"result": {"imported": 42, "errors": 0},
"location": f"/api/v1.0/core/taskstatus/{TASK_ID}/",
}
}


@pytest.fixture
def task_failure_response():
return {
"data": {
"task_id": TASK_ID,
"status": "FAILURE",
"result": "File format not recognized",
"location": f"/api/v1.0/core/taskstatus/{TASK_ID}/",
}
}


@pytest.fixture
def task_started_response():
return {
"data": {
"task_id": TASK_ID,
"status": "STARTED",
"result": None,
"location": f"/api/v1.0/core/taskstatus/{TASK_ID}/",
}
}


def test_get_task_status_pending(er_client, task_pending_response):
with patch.object(er_client._http_session, "get") as mock_get:
mock_get.return_value = _mock_response(task_pending_response)
result = er_client.get_task_status(TASK_ID)

assert mock_get.called
call_url = mock_get.call_args[0][0]
assert f"core/taskstatus/{TASK_ID}/" in call_url
assert result["task_id"] == TASK_ID
assert result["status"] == "PENDING"
assert result["result"] is None


def test_get_task_status_success(er_client, task_success_response):
with patch.object(er_client._http_session, "get") as mock_get:
mock_get.return_value = _mock_response(task_success_response)
result = er_client.get_task_status(TASK_ID)

assert result["status"] == "SUCCESS"
assert result["result"]["imported"] == 42


def test_get_task_status_failure(er_client, task_failure_response):
with patch.object(er_client._http_session, "get") as mock_get:
mock_get.return_value = _mock_response(task_failure_response)
result = er_client.get_task_status(TASK_ID)

assert result["status"] == "FAILURE"
assert result["result"] == "File format not recognized"


def test_get_task_status_started(er_client, task_started_response):
with patch.object(er_client._http_session, "get") as mock_get:
mock_get.return_value = _mock_response(task_started_response)
result = er_client.get_task_status(TASK_ID)

assert result["status"] == "STARTED"


def test_get_task_status_url_construction(er_client, task_pending_response):
with patch.object(er_client._http_session, "get") as mock_get:
mock_get.return_value = _mock_response(task_pending_response)
er_client.get_task_status(TASK_ID)

call_url = mock_get.call_args[0][0]
assert call_url == f"{SERVICE_ROOT}/core/taskstatus/{TASK_ID}/"


def test_get_task_status_not_found(er_client):
from erclient.er_errors import ERClientNotFound
with patch.object(er_client._http_session, "get") as mock_get:
mock_get.return_value = _mock_response(status_code=404, ok=False)
with pytest.raises(ERClientNotFound):
er_client.get_task_status("nonexistent-task-id")
Loading