chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,734 @@
|
||||
# Copyright (C) CVAT.ai Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
import json
|
||||
from copy import deepcopy
|
||||
from functools import partial
|
||||
from http import HTTPStatus
|
||||
from itertools import product
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
import urllib3
|
||||
from cvat_sdk.api_client import exceptions, models
|
||||
from cvat_sdk.api_client.api_client import ApiClient, Endpoint
|
||||
from cvat_sdk.core.helpers import get_paginated_collection
|
||||
from deepdiff import DeepDiff
|
||||
|
||||
from shared.utils.config import make_api_client
|
||||
|
||||
from .utils import (
|
||||
CollectionSimpleFilterTestBase,
|
||||
compare_annotations,
|
||||
invite_user_to_org,
|
||||
register_new_user,
|
||||
wait_background_request,
|
||||
)
|
||||
|
||||
|
||||
class _PermissionTestBase:
|
||||
def merge(
|
||||
self,
|
||||
*,
|
||||
task_id: int | None = None,
|
||||
job_id: int | None = None,
|
||||
user: str,
|
||||
raise_on_error: bool = True,
|
||||
wait_result: bool = True,
|
||||
) -> urllib3.HTTPResponse:
|
||||
assert task_id is not None or job_id is not None
|
||||
|
||||
kwargs = {}
|
||||
if task_id is not None:
|
||||
kwargs["task_id"] = task_id
|
||||
if job_id is not None:
|
||||
kwargs["job_id"] = job_id
|
||||
|
||||
with make_api_client(user) as api_client:
|
||||
_, response = api_client.consensus_api.create_merge(
|
||||
consensus_merge_create_request=models.ConsensusMergeCreateRequest(**kwargs),
|
||||
_parse_response=False,
|
||||
_check_status=raise_on_error,
|
||||
)
|
||||
|
||||
if not raise_on_error and response.status != HTTPStatus.ACCEPTED:
|
||||
return response
|
||||
assert response.status == HTTPStatus.ACCEPTED
|
||||
|
||||
if wait_result:
|
||||
rq_id = json.loads(response.data)["rq_id"]
|
||||
background_request, _ = wait_background_request(api_client, rq_id)
|
||||
assert (
|
||||
background_request.status.value
|
||||
== models.RequestStatus.allowed_values[("value",)]["FINISHED"]
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
def request_merge(
|
||||
self,
|
||||
*,
|
||||
task_id: int | None = None,
|
||||
job_id: int | None = None,
|
||||
user: str,
|
||||
) -> str:
|
||||
response = self.merge(user=user, task_id=task_id, job_id=job_id, wait_result=False)
|
||||
return json.loads(response.data)["rq_id"]
|
||||
|
||||
@pytest.fixture
|
||||
def find_sandbox_task(self, tasks, jobs, users, is_task_staff):
|
||||
def _find(
|
||||
is_staff: bool, *, has_consensus_jobs: bool | None = None
|
||||
) -> tuple[dict[str, Any], dict[str, Any]]:
|
||||
task = next(
|
||||
t
|
||||
for t in tasks
|
||||
if t["organization"] is None
|
||||
and not users[t["owner"]["id"]]["is_superuser"]
|
||||
and (
|
||||
has_consensus_jobs is None
|
||||
or has_consensus_jobs
|
||||
== any(
|
||||
j
|
||||
for j in jobs
|
||||
if j["task_id"] == t["id"] and j["type"] == "consensus_replica"
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
if is_staff:
|
||||
user = task["owner"]
|
||||
else:
|
||||
user = next(u for u in users if not is_task_staff(u["id"], task["id"]))
|
||||
|
||||
return task, user
|
||||
|
||||
return _find
|
||||
|
||||
@pytest.fixture
|
||||
def find_sandbox_task_with_consensus(self, find_sandbox_task):
|
||||
return partial(find_sandbox_task, has_consensus_jobs=True)
|
||||
|
||||
@pytest.fixture
|
||||
def find_org_task(
|
||||
self, restore_db_per_function, tasks, jobs, users, is_org_member, is_task_staff, admin_user
|
||||
):
|
||||
def _find(
|
||||
is_staff: bool, user_org_role: str, *, has_consensus_jobs: bool | None = None
|
||||
) -> tuple[dict[str, Any], dict[str, Any]]:
|
||||
for user in users:
|
||||
if user["is_superuser"]:
|
||||
continue
|
||||
|
||||
task = next(
|
||||
(
|
||||
t
|
||||
for t in tasks
|
||||
if t["organization"] is not None
|
||||
and is_task_staff(user["id"], t["id"]) == is_staff
|
||||
and is_org_member(user["id"], t["organization"], role=user_org_role)
|
||||
and (
|
||||
has_consensus_jobs is None
|
||||
or has_consensus_jobs
|
||||
== any(
|
||||
j
|
||||
for j in jobs
|
||||
if j["task_id"] == t["id"] and j["type"] == "consensus_replica"
|
||||
)
|
||||
)
|
||||
),
|
||||
None,
|
||||
)
|
||||
if task is not None:
|
||||
break
|
||||
|
||||
if not task:
|
||||
task = next(
|
||||
t
|
||||
for t in tasks
|
||||
if t["organization"] is not None
|
||||
if has_consensus_jobs is None or has_consensus_jobs == t["consensus_enabled"]
|
||||
)
|
||||
user = next(
|
||||
u
|
||||
for u in users
|
||||
if is_org_member(u["id"], task["organization"], role=user_org_role)
|
||||
)
|
||||
|
||||
if is_staff:
|
||||
with make_api_client(admin_user) as api_client:
|
||||
api_client.tasks_api.partial_update(
|
||||
task["id"],
|
||||
patched_task_write_request=models.PatchedTaskWriteRequest(
|
||||
assignee_id=user["id"]
|
||||
),
|
||||
)
|
||||
|
||||
return task, user
|
||||
|
||||
return _find
|
||||
|
||||
@pytest.fixture
|
||||
def find_org_task_with_consensus(self, find_org_task):
|
||||
return partial(find_org_task, has_consensus_jobs=True)
|
||||
|
||||
_default_sandbox_cases = ("is_staff, allow", [(True, True), (False, False)])
|
||||
|
||||
_default_org_cases = (
|
||||
"org_role, is_staff, allow",
|
||||
[
|
||||
("owner", True, True),
|
||||
("owner", False, True),
|
||||
("maintainer", True, True),
|
||||
("maintainer", False, True),
|
||||
("supervisor", True, True),
|
||||
("supervisor", False, False),
|
||||
("worker", True, True),
|
||||
("worker", False, False),
|
||||
],
|
||||
)
|
||||
|
||||
_default_org_roles = ("owner", "maintainer", "supervisor", "worker")
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("restore_db_per_function")
|
||||
@pytest.mark.usefixtures("restore_redis_inmem_per_function")
|
||||
class TestPostConsensusMerge(_PermissionTestBase):
|
||||
def test_can_merge_task_with_consensus_jobs(self, admin_user, tasks):
|
||||
task_id = next(t["id"] for t in tasks if t["consensus_enabled"])
|
||||
|
||||
self.merge(user=admin_user, task_id=task_id)
|
||||
|
||||
def test_can_merge_consensus_job(self, admin_user, jobs):
|
||||
job_id = next(
|
||||
j["id"] for j in jobs if j["type"] == "annotation" and j["consensus_replicas"] > 0
|
||||
)
|
||||
|
||||
self.merge(user=admin_user, job_id=job_id)
|
||||
|
||||
def test_cannot_merge_task_without_consensus_jobs(self, admin_user, tasks):
|
||||
task_id = next(t["id"] for t in tasks if not t["consensus_enabled"])
|
||||
|
||||
with pytest.raises(exceptions.ApiException) as capture:
|
||||
self.merge(user=admin_user, task_id=task_id)
|
||||
|
||||
assert "Consensus is not enabled in this task" in capture.value.body
|
||||
|
||||
def test_cannot_merge_task_without_mergeable_parent_jobs(self, admin_user, tasks, jobs):
|
||||
task_id = next(t["id"] for t in tasks if t["consensus_enabled"])
|
||||
|
||||
for j in jobs:
|
||||
if (j["stage"] != "annotation" or j["state"] != "new") and (
|
||||
j["task_id"] == task_id and j["type"] in ("annotation", "consensus_replica")
|
||||
):
|
||||
with make_api_client(admin_user) as api_client:
|
||||
api_client.jobs_api.partial_update(
|
||||
j["id"],
|
||||
patched_job_write_request=models.PatchedJobWriteRequest(
|
||||
state="new", stage="annotation"
|
||||
),
|
||||
)
|
||||
|
||||
with pytest.raises(exceptions.ApiException) as capture:
|
||||
self.merge(user=admin_user, task_id=task_id)
|
||||
|
||||
assert "No annotation jobs in the annotation stage" in capture.value.body
|
||||
|
||||
def test_cannot_merge_replica_job(self, admin_user, tasks, jobs):
|
||||
job_id = next(
|
||||
j["id"]
|
||||
for j in jobs
|
||||
if j["type"] == "consensus_replica"
|
||||
if tasks.map[j["task_id"]]["consensus_enabled"]
|
||||
)
|
||||
|
||||
with pytest.raises(exceptions.ApiException) as capture:
|
||||
self.merge(user=admin_user, job_id=job_id)
|
||||
|
||||
assert "No annotated consensus jobs found for parent job" in capture.value.body
|
||||
|
||||
def _test_merge_200(self, user: str, *, task_id: int | None = None, job_id: int | None = None):
|
||||
return self.merge(user=user, task_id=task_id, job_id=job_id)
|
||||
|
||||
def _test_merge_403(self, user: str, *, task_id: int | None = None, job_id: int | None = None):
|
||||
response = self.merge(user=user, task_id=task_id, job_id=job_id, raise_on_error=False)
|
||||
assert response.status == HTTPStatus.FORBIDDEN
|
||||
return response
|
||||
|
||||
@pytest.mark.parametrize(*_PermissionTestBase._default_sandbox_cases)
|
||||
def test_user_merge_in_sandbox_task(self, is_staff, allow, find_sandbox_task_with_consensus):
|
||||
task, user = find_sandbox_task_with_consensus(is_staff)
|
||||
|
||||
if allow:
|
||||
self._test_merge_200(user["username"], task_id=task["id"])
|
||||
else:
|
||||
self._test_merge_403(user["username"], task_id=task["id"])
|
||||
|
||||
@pytest.mark.parametrize(*_PermissionTestBase._default_org_cases)
|
||||
def test_user_merge_in_org_task(
|
||||
self,
|
||||
find_org_task_with_consensus,
|
||||
org_role,
|
||||
is_staff,
|
||||
allow,
|
||||
):
|
||||
task, user = find_org_task_with_consensus(is_staff, org_role)
|
||||
|
||||
if allow:
|
||||
self._test_merge_200(user["username"], task_id=task["id"])
|
||||
else:
|
||||
self._test_merge_403(user["username"], task_id=task["id"])
|
||||
|
||||
# users with task:view rights can check status of report creation
|
||||
def _test_check_merge_status(
|
||||
self,
|
||||
rq_id: str,
|
||||
*,
|
||||
staff_user: str,
|
||||
another_user: str,
|
||||
another_user_status: int = HTTPStatus.FORBIDDEN,
|
||||
):
|
||||
with make_api_client(another_user) as api_client:
|
||||
_, response = api_client.requests_api.retrieve(
|
||||
rq_id, _parse_response=False, _check_status=False
|
||||
)
|
||||
assert response.status == another_user_status
|
||||
|
||||
with make_api_client(staff_user) as api_client:
|
||||
wait_background_request(api_client, rq_id)
|
||||
|
||||
def test_user_without_rights_cannot_check_status_of_merge_in_sandbox(
|
||||
self,
|
||||
find_sandbox_task_with_consensus,
|
||||
users,
|
||||
):
|
||||
task, task_staff = find_sandbox_task_with_consensus(is_staff=True)
|
||||
|
||||
another_user = next(
|
||||
u
|
||||
for u in users
|
||||
if (
|
||||
u["id"] != task_staff["id"]
|
||||
and not u["is_superuser"]
|
||||
and u["id"] != task["owner"]["id"]
|
||||
and u["id"] != (task["assignee"] or {}).get("id")
|
||||
)
|
||||
)
|
||||
|
||||
rq_id = self.request_merge(task_id=task["id"], user=task_staff["username"])
|
||||
self._test_check_merge_status(
|
||||
rq_id, staff_user=task_staff["username"], another_user=another_user["username"]
|
||||
)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"same_org, role",
|
||||
[
|
||||
pair
|
||||
for pair in product([True, False], _PermissionTestBase._default_org_roles)
|
||||
if not (pair[0] and pair[1] in ["owner", "maintainer"])
|
||||
],
|
||||
)
|
||||
def test_user_without_rights_cannot_check_status_of_merge_in_org(
|
||||
self,
|
||||
find_org_task_with_consensus,
|
||||
same_org: bool,
|
||||
role: str,
|
||||
organizations,
|
||||
):
|
||||
task, task_staff = find_org_task_with_consensus(is_staff=True, user_org_role="supervisor")
|
||||
|
||||
# create a new user that passes the requirements
|
||||
another_user = register_new_user(f"{same_org}{role}")
|
||||
org_id = (
|
||||
task["organization"]
|
||||
if same_org
|
||||
else next(o for o in organizations if o["id"] != task["organization"])["id"]
|
||||
)
|
||||
invite_user_to_org(another_user["email"], org_id, role)
|
||||
|
||||
rq_id = self.request_merge(task_id=task["id"], user=task_staff["username"])
|
||||
self._test_check_merge_status(
|
||||
rq_id, staff_user=task_staff["username"], another_user=another_user["username"]
|
||||
)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"role",
|
||||
# owner and maintainer have rights even without being assigned to a task
|
||||
("supervisor", "worker"),
|
||||
)
|
||||
def test_task_assignee_can_check_status_of_merge_in_org(
|
||||
self,
|
||||
find_org_task_with_consensus,
|
||||
role: str,
|
||||
):
|
||||
task, another_user = find_org_task_with_consensus(is_staff=False, user_org_role=role)
|
||||
task_owner = task["owner"]
|
||||
|
||||
rq_id = self.request_merge(task_id=task["id"], user=task_owner["username"])
|
||||
self._test_check_merge_status(
|
||||
rq_id,
|
||||
staff_user=task_owner["username"],
|
||||
another_user=another_user["username"],
|
||||
)
|
||||
|
||||
with make_api_client(task_owner["username"]) as api_client:
|
||||
api_client.tasks_api.partial_update(
|
||||
task["id"],
|
||||
patched_task_write_request=models.PatchedTaskWriteRequest(
|
||||
assignee_id=another_user["id"]
|
||||
),
|
||||
)
|
||||
|
||||
self._test_check_merge_status(
|
||||
rq_id,
|
||||
staff_user=task_owner["username"],
|
||||
another_user=another_user["username"],
|
||||
another_user_status=HTTPStatus.OK,
|
||||
)
|
||||
|
||||
@pytest.mark.parametrize("is_sandbox", (True, False))
|
||||
def test_admin_can_check_status_of_merge(
|
||||
self,
|
||||
find_org_task_with_consensus,
|
||||
find_sandbox_task_with_consensus,
|
||||
users,
|
||||
is_sandbox: bool,
|
||||
):
|
||||
if is_sandbox:
|
||||
task, task_staff = find_sandbox_task_with_consensus(is_staff=True)
|
||||
else:
|
||||
task, task_staff = find_org_task_with_consensus(is_staff=True, user_org_role="owner")
|
||||
|
||||
admin = next(
|
||||
u
|
||||
for u in users
|
||||
if (
|
||||
u["is_superuser"]
|
||||
and u["id"] != task_staff["id"]
|
||||
and u["id"] != task["owner"]["id"]
|
||||
and u["id"] != (task["assignee"] or {}).get("id")
|
||||
)
|
||||
)
|
||||
|
||||
rq_id = self.request_merge(task_id=task["id"], user=task_staff["username"])
|
||||
|
||||
with make_api_client(admin["username"]) as api_client:
|
||||
wait_background_request(api_client, rq_id)
|
||||
|
||||
|
||||
class TestSimpleConsensusSettingsFilters(CollectionSimpleFilterTestBase):
|
||||
@pytest.fixture(autouse=True)
|
||||
def setup(self, admin_user, consensus_settings):
|
||||
self.user = admin_user
|
||||
self.samples = consensus_settings
|
||||
|
||||
def _get_endpoint(self, api_client: ApiClient) -> Endpoint:
|
||||
return api_client.consensus_api.list_settings_endpoint
|
||||
|
||||
@pytest.mark.parametrize("field", ("task_id",))
|
||||
def test_can_use_simple_filter_for_object_list(self, field):
|
||||
return super()._test_can_use_simple_filter_for_object_list(field)
|
||||
|
||||
|
||||
class TestListSettings(_PermissionTestBase):
|
||||
def _test_list_settings_200(
|
||||
self, user: str, task_id: int, *, expected_data: dict[str, Any] | None = None, **kwargs
|
||||
):
|
||||
with make_api_client(user) as api_client:
|
||||
actual = get_paginated_collection(
|
||||
api_client.consensus_api.list_settings_endpoint,
|
||||
task_id=task_id,
|
||||
**kwargs,
|
||||
return_json=True,
|
||||
)
|
||||
|
||||
if expected_data is not None:
|
||||
assert DeepDiff(expected_data, actual, ignore_order=True) == {}
|
||||
|
||||
def _test_list_settings_403(self, user: str, task_id: int, **kwargs):
|
||||
with make_api_client(user) as api_client:
|
||||
_, response = api_client.consensus_api.list_settings(
|
||||
task_id=task_id, **kwargs, _parse_response=False, _check_status=False
|
||||
)
|
||||
assert response.status == HTTPStatus.FORBIDDEN
|
||||
|
||||
return response
|
||||
|
||||
@pytest.mark.parametrize(*_PermissionTestBase._default_sandbox_cases)
|
||||
def test_user_list_settings_in_sandbox_task(
|
||||
self, is_staff, allow, find_sandbox_task_with_consensus, consensus_settings
|
||||
):
|
||||
task, user = find_sandbox_task_with_consensus(is_staff)
|
||||
settings = next(s for s in consensus_settings if s["task_id"] == task["id"])
|
||||
|
||||
if allow:
|
||||
self._test_list_settings_200(user["username"], task["id"], expected_data=[settings])
|
||||
else:
|
||||
self._test_list_settings_403(user["username"], task["id"])
|
||||
|
||||
@pytest.mark.parametrize(*_PermissionTestBase._default_org_cases)
|
||||
def test_user_list_settings_in_org_task(
|
||||
self,
|
||||
consensus_settings,
|
||||
find_org_task_with_consensus,
|
||||
org_role: str,
|
||||
is_staff,
|
||||
allow: bool,
|
||||
):
|
||||
task, user = find_org_task_with_consensus(is_staff, org_role)
|
||||
settings = next(s for s in consensus_settings if s["task_id"] == task["id"])
|
||||
org_id = task["organization"]
|
||||
|
||||
if allow:
|
||||
self._test_list_settings_200(
|
||||
user["username"], task["id"], expected_data=[settings], org_id=org_id
|
||||
)
|
||||
else:
|
||||
self._test_list_settings_403(user["username"], task["id"], org_id=org_id)
|
||||
|
||||
|
||||
class TestGetSettings(_PermissionTestBase):
|
||||
def _test_get_settings_200(
|
||||
self, user: str, obj_id: int, *, expected_data: dict[str, Any] | None = None, **kwargs
|
||||
):
|
||||
with make_api_client(user) as api_client:
|
||||
_, response = api_client.consensus_api.retrieve_settings(obj_id, **kwargs)
|
||||
assert response.status == HTTPStatus.OK
|
||||
|
||||
if expected_data is not None:
|
||||
assert DeepDiff(expected_data, json.loads(response.data), ignore_order=True) == {}
|
||||
|
||||
return response
|
||||
|
||||
def _test_get_settings_403(self, user: str, obj_id: int, **kwargs):
|
||||
with make_api_client(user) as api_client:
|
||||
_, response = api_client.consensus_api.retrieve_settings(
|
||||
obj_id, **kwargs, _parse_response=False, _check_status=False
|
||||
)
|
||||
assert response.status == HTTPStatus.FORBIDDEN
|
||||
|
||||
return response
|
||||
|
||||
def test_can_get_settings(self, admin_user, consensus_settings):
|
||||
settings = next(iter(consensus_settings))
|
||||
self._test_get_settings_200(admin_user, settings["id"], expected_data=settings)
|
||||
|
||||
@pytest.mark.parametrize(*_PermissionTestBase._default_sandbox_cases)
|
||||
def test_user_get_settings_in_sandbox_task(
|
||||
self, is_staff, allow, find_sandbox_task_with_consensus, consensus_settings
|
||||
):
|
||||
task, user = find_sandbox_task_with_consensus(is_staff)
|
||||
settings = next(s for s in consensus_settings if s["task_id"] == task["id"])
|
||||
|
||||
if allow:
|
||||
self._test_get_settings_200(user["username"], settings["id"], expected_data=settings)
|
||||
else:
|
||||
self._test_get_settings_403(user["username"], settings["id"])
|
||||
|
||||
@pytest.mark.parametrize(*_PermissionTestBase._default_org_cases)
|
||||
def test_user_get_settings_in_org_task(
|
||||
self,
|
||||
consensus_settings,
|
||||
find_org_task_with_consensus,
|
||||
org_role: str,
|
||||
is_staff,
|
||||
allow: bool,
|
||||
):
|
||||
task, user = find_org_task_with_consensus(is_staff, org_role)
|
||||
settings = next(s for s in consensus_settings if s["task_id"] == task["id"])
|
||||
|
||||
if allow:
|
||||
self._test_get_settings_200(user["username"], settings["id"], expected_data=settings)
|
||||
else:
|
||||
self._test_get_settings_403(user["username"], settings["id"])
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("restore_db_per_function")
|
||||
class TestPatchSettings(_PermissionTestBase):
|
||||
def _test_patch_settings_200(
|
||||
self,
|
||||
user: str,
|
||||
obj_id: int,
|
||||
data: dict[str, Any],
|
||||
*,
|
||||
expected_data: dict[str, Any] | None = None,
|
||||
**kwargs,
|
||||
):
|
||||
with make_api_client(user) as api_client:
|
||||
_, response = api_client.consensus_api.partial_update_settings(
|
||||
obj_id, patched_consensus_settings_request=data, **kwargs
|
||||
)
|
||||
assert response.status == HTTPStatus.OK
|
||||
|
||||
if expected_data is not None:
|
||||
assert DeepDiff(expected_data, json.loads(response.data), ignore_order=True) == {}
|
||||
|
||||
return response
|
||||
|
||||
def _test_patch_settings_403(self, user: str, obj_id: int, data: dict[str, Any], **kwargs):
|
||||
with make_api_client(user) as api_client:
|
||||
_, response = api_client.consensus_api.partial_update_settings(
|
||||
obj_id,
|
||||
patched_consensus_settings_request=data,
|
||||
**kwargs,
|
||||
_parse_response=False,
|
||||
_check_status=False,
|
||||
)
|
||||
assert response.status == HTTPStatus.FORBIDDEN
|
||||
|
||||
return response
|
||||
|
||||
def _get_request_data(self, data: dict[str, Any]) -> tuple[dict[str, Any], dict[str, Any]]:
|
||||
patched_data = deepcopy(data)
|
||||
|
||||
for field, value in data.items():
|
||||
if isinstance(value, bool):
|
||||
patched_data[field] = not value
|
||||
elif isinstance(value, float):
|
||||
patched_data[field] = 1 - value
|
||||
|
||||
expected_data = deepcopy(patched_data)
|
||||
|
||||
return patched_data, expected_data
|
||||
|
||||
def test_can_patch_settings(self, admin_user, consensus_settings):
|
||||
settings = next(iter(consensus_settings))
|
||||
data, expected_data = self._get_request_data(settings)
|
||||
self._test_patch_settings_200(admin_user, settings["id"], data, expected_data=expected_data)
|
||||
|
||||
@pytest.mark.parametrize(*_PermissionTestBase._default_sandbox_cases)
|
||||
def test_user_patch_settings_in_sandbox_task(
|
||||
self, consensus_settings, find_sandbox_task_with_consensus, is_staff: bool, allow: bool
|
||||
):
|
||||
task, user = find_sandbox_task_with_consensus(is_staff)
|
||||
settings = next(s for s in consensus_settings if s["task_id"] == task["id"])
|
||||
request_data, expected_data = self._get_request_data(settings)
|
||||
|
||||
if allow:
|
||||
self._test_patch_settings_200(
|
||||
user["username"], settings["id"], request_data, expected_data=expected_data
|
||||
)
|
||||
else:
|
||||
self._test_patch_settings_403(user["username"], settings["id"], request_data)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"org_role, is_staff, allow",
|
||||
[
|
||||
("owner", True, True),
|
||||
("owner", False, True),
|
||||
("maintainer", True, True),
|
||||
("maintainer", False, True),
|
||||
("supervisor", True, True),
|
||||
("supervisor", False, False),
|
||||
("worker", True, True),
|
||||
("worker", False, False),
|
||||
],
|
||||
)
|
||||
def test_user_patch_settings_in_org_task(
|
||||
self,
|
||||
consensus_settings,
|
||||
find_org_task_with_consensus,
|
||||
org_role: str,
|
||||
is_staff: bool,
|
||||
allow: bool,
|
||||
):
|
||||
task, user = find_org_task_with_consensus(is_staff, org_role)
|
||||
settings = next(s for s in consensus_settings if s["task_id"] == task["id"])
|
||||
request_data, expected_data = self._get_request_data(settings)
|
||||
|
||||
if allow:
|
||||
self._test_patch_settings_200(
|
||||
user["username"], settings["id"], request_data, expected_data=expected_data
|
||||
)
|
||||
else:
|
||||
self._test_patch_settings_403(user["username"], settings["id"], request_data)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("restore_db_per_function")
|
||||
@pytest.mark.usefixtures("restore_redis_inmem_per_function")
|
||||
class TestMerging(_PermissionTestBase):
|
||||
@pytest.mark.parametrize("task_id", [31])
|
||||
def test_merged_annotations_have_scores(self, admin_user, jobs, labels, task_id: int):
|
||||
"""Test that merged annotations have score attributes based on agreement."""
|
||||
task_labels = [l for l in labels if l.get("task_id") == task_id]
|
||||
|
||||
task_jobs = [j for j in jobs if j["task_id"] == task_id]
|
||||
parent_job = next(
|
||||
j for j in task_jobs if j["type"] == "annotation" if j["consensus_replicas"] > 0
|
||||
)
|
||||
replicas = [
|
||||
j
|
||||
for j in task_jobs
|
||||
if j["type"] == "consensus_replica"
|
||||
if j["parent_job_id"] == parent_job["id"]
|
||||
]
|
||||
|
||||
with make_api_client(admin_user) as api_client:
|
||||
api_client.tasks_api.destroy_annotations(task_id)
|
||||
|
||||
for replica in replicas:
|
||||
api_client.jobs_api.destroy_annotations(replica["id"])
|
||||
|
||||
bbox_full_agreement = models.LabeledShapeRequest(
|
||||
type="rectangle",
|
||||
frame=parent_job["start_frame"],
|
||||
label_id=task_labels[0]["id"],
|
||||
points=[0, 0, 2, 2],
|
||||
attributes=[
|
||||
{"spec_id": attr["id"], "value": attr["default_value"]}
|
||||
for attr in task_labels[0]["attributes"]
|
||||
],
|
||||
rotation=0,
|
||||
z_order=0,
|
||||
occluded=False,
|
||||
outside=False,
|
||||
group=0,
|
||||
)
|
||||
|
||||
bbox_partial_agreement = models.LabeledShapeRequest(
|
||||
type="rectangle",
|
||||
frame=parent_job["start_frame"],
|
||||
label_id=task_labels[0]["id"],
|
||||
points=[4, 0, 6, 2],
|
||||
rotation=0,
|
||||
z_order=0,
|
||||
occluded=False,
|
||||
outside=False,
|
||||
group=0,
|
||||
)
|
||||
|
||||
for replica in replicas:
|
||||
api_client.jobs_api.update_annotations(
|
||||
replica["id"],
|
||||
labeled_data_request=models.LabeledDataRequest(shapes=[bbox_full_agreement]),
|
||||
)
|
||||
|
||||
api_client.jobs_api.update_annotations(
|
||||
replicas[0]["id"],
|
||||
labeled_data_request=models.LabeledDataRequest(
|
||||
shapes=[bbox_full_agreement, bbox_partial_agreement]
|
||||
),
|
||||
)
|
||||
|
||||
self.merge(job_id=parent_job["id"], user=admin_user)
|
||||
|
||||
merged_annotations = json.loads(
|
||||
api_client.jobs_api.retrieve_annotations(parent_job["id"])[1].data
|
||||
)
|
||||
|
||||
assert compare_annotations(
|
||||
merged_annotations,
|
||||
{
|
||||
"shapes": [
|
||||
{**bbox_full_agreement.to_dict(), "source": "consensus", "score": 1.0},
|
||||
{
|
||||
**bbox_partial_agreement.to_dict(),
|
||||
"source": "consensus",
|
||||
"score": 1 / len(replicas),
|
||||
},
|
||||
]
|
||||
},
|
||||
ignore_source=False,
|
||||
ignore_score=False,
|
||||
)
|
||||
Reference in New Issue
Block a user