diff --git a/tests/rest_api/assets/cvat_db/cvat_data.tar.bz2 b/tests/rest_api/assets/cvat_db/cvat_data.tar.bz2 index 5e665d09..021a0344 100644 Binary files a/tests/rest_api/assets/cvat_db/cvat_data.tar.bz2 and b/tests/rest_api/assets/cvat_db/cvat_data.tar.bz2 differ diff --git a/tests/rest_api/assets/cvat_db/data.json b/tests/rest_api/assets/cvat_db/data.json index 9a0d30d6..d691c6bf 100644 --- a/tests/rest_api/assets/cvat_db/data.json +++ b/tests/rest_api/assets/cvat_db/data.json @@ -1453,7 +1453,7 @@ "pk": 3, "fields": { "password": "pbkdf2_sha256$260000$9YZSJ0xF4Kvjsm2Fwflciy$zRpcqAMLaJBbqTRS09NkZovOHtcdy6haZxu++AeoWFo=", - "last_login": null, + "last_login": "2022-03-28T13:05:05.561Z", "is_superuser": false, "username": "user2", "first_name": "User", @@ -3603,7 +3603,21 @@ "assignee": 3, "bug_tracker": "", "created_date": "2021-12-14T19:52:37.278Z", - "updated_date": "2022-03-05T10:32:23.813Z", + "updated_date": "2022-03-28T13:04:54.669Z", + "status": "annotation", + "organization": 2 + } +}, +{ + "model": "engine.project", + "pk": 3, + "fields": { + "name": "project 3", + "owner": 3, + "assignee": 19, + "bug_tracker": "", + "created_date": "2022-03-28T13:05:24.659Z", + "updated_date": "2022-03-28T13:06:09.283Z", "status": "annotation", "organization": 2 } @@ -5645,56 +5659,6 @@ "rating": 0.0 } }, -{ - "model": "engine.manifest", - "pk": 1, - "fields": { - "filename": "manifest.jsonl", - "cloud_storage": 1 - } -}, -{ - "model": "engine.manifest", - "pk": 2, - "fields": { - "filename": "manifest.jsonl", - "cloud_storage": 2 - } -}, -{ - "model": "engine.cloudstorage", - "pk": 1, - "fields": { - "provider_type": "AWS_S3_BUCKET", - "resource": "public", - "display_name": "Bucket 1", - "owner": 2, - "created_date": "2022-03-17T07:22:49.519Z", - "updated_date": "2022-03-17T07:22:49.529Z", - "credentials": "", - "credentials_type": "ANONYMOUS_ACCESS", - "specific_attributes": "endpoint_url=http%3A%2F%2Fminio%3A9000", - "description": "", - "organization": null - } -}, -{ - "model": "engine.cloudstorage", - "pk": 2, - "fields": { - "provider_type": "AWS_S3_BUCKET", - "resource": "private", - "display_name": "Bucket 2", - "owner": 11, - "created_date": "2022-03-17T07:23:59.305Z", - "updated_date": "2022-03-17T07:23:59.309Z", - "credentials": "minio_access_key minio_secret_key", - "credentials_type": "KEY_SECRET_KEY_PAIR", - "specific_attributes": "endpoint_url=http%3A%2F%2Fminio%3A9000", - "description": "", - "organization": 2 - } -}, { "model": "engine.issue", "pk": 1, @@ -5830,5 +5794,55 @@ "created_date": "2022-03-16T12:49:29.372Z", "updated_date": "2022-03-16T12:49:29.372Z" } +}, +{ + "model": "engine.manifest", + "pk": 1, + "fields": { + "filename": "manifest.jsonl", + "cloud_storage": 1 + } +}, +{ + "model": "engine.manifest", + "pk": 2, + "fields": { + "filename": "manifest.jsonl", + "cloud_storage": 2 + } +}, +{ + "model": "engine.cloudstorage", + "pk": 1, + "fields": { + "provider_type": "AWS_S3_BUCKET", + "resource": "public", + "display_name": "Bucket 1", + "owner": 2, + "created_date": "2022-03-17T07:22:49.519Z", + "updated_date": "2022-03-17T07:22:49.529Z", + "credentials": "", + "credentials_type": "ANONYMOUS_ACCESS", + "specific_attributes": "endpoint_url=http%3A%2F%2Fminio%3A9000", + "description": "", + "organization": null + } +}, +{ + "model": "engine.cloudstorage", + "pk": 2, + "fields": { + "provider_type": "AWS_S3_BUCKET", + "resource": "private", + "display_name": "Bucket 2", + "owner": 11, + "created_date": "2022-03-17T07:23:59.305Z", + "updated_date": "2022-03-17T07:23:59.309Z", + "credentials": "minio_access_key minio_secret_key", + "credentials_type": "KEY_SECRET_KEY_PAIR", + "specific_attributes": "endpoint_url=http%3A%2F%2Fminio%3A9000", + "description": "", + "organization": 2 + } } ] diff --git a/tests/rest_api/assets/projects.json b/tests/rest_api/assets/projects.json index ab3692de..a0042fb0 100644 --- a/tests/rest_api/assets/projects.json +++ b/tests/rest_api/assets/projects.json @@ -1,8 +1,36 @@ { - "count": 2, + "count": 3, "next": null, "previous": null, "results": [ + { + "assignee": { + "first_name": "User", + "id": 19, + "last_name": "Fifth", + "url": "http://localhost:8080/api/users/19", + "username": "user5" + }, + "bug_tracker": "", + "created_date": "2022-03-28T13:05:24.659000Z", + "dimension": null, + "id": 3, + "labels": [], + "name": "project 3", + "organization": 2, + "owner": { + "first_name": "User", + "id": 3, + "last_name": "Second", + "url": "http://localhost:8080/api/users/3", + "username": "user2" + }, + "status": "annotation", + "task_subsets": [], + "tasks": [], + "updated_date": "2022-03-28T13:06:09.283000Z", + "url": "http://localhost:8080/api/projects/3" + }, { "assignee": { "first_name": "User", @@ -45,7 +73,7 @@ "tasks": [ 11 ], - "updated_date": "2022-03-05T10:32:23.813000Z", + "updated_date": "2022-03-28T13:04:54.669000Z", "url": "http://localhost:8080/api/projects/2" }, { diff --git a/tests/rest_api/assets/users.json b/tests/rest_api/assets/users.json index 6822ac01..232616ba 100644 --- a/tests/rest_api/assets/users.json +++ b/tests/rest_api/assets/users.json @@ -278,7 +278,7 @@ "is_active": true, "is_staff": false, "is_superuser": false, - "last_login": null, + "last_login": "2022-03-28T13:05:05.561000Z", "last_name": "Second", "url": "http://localhost:8080/api/users/3", "username": "user2" diff --git a/tests/rest_api/test_projects.py b/tests/rest_api/test_projects.py new file mode 100644 index 00000000..381c48e8 --- /dev/null +++ b/tests/rest_api/test_projects.py @@ -0,0 +1,197 @@ +# Copyright (C) 2022 Intel Corporation +# +# SPDX-License-Identifier: MIT + +from http import HTTPStatus +from itertools import groupby, product +import pytest + +from .utils.config import get_method, post_method + + +class TestGetProjects: + def _find_project_by_user_org(self, user, projects, is_project_staff_flag, is_project_staff): + if is_project_staff_flag: + for p in projects: + if is_project_staff(user['id'], p['id']): + return p['id'] + else: + for p in projects: + if not is_project_staff(user['id'], p['id']): + return p['id'] + + def _test_response_200(self, username, project_id, **kwargs): + response = get_method(username, f'projects/{project_id}', **kwargs) + assert response.status_code == HTTPStatus.OK + project = response.json() + assert project_id == project['id'] + + def _test_response_403(self, username, project_id): + response = get_method(username, f'projects/{project_id}') + assert response.status_code == HTTPStatus.FORBIDDEN + + # Admin can see any project even he has no ownerships for this project. + def test_project_admin_accessibility(self, projects, find_users, is_project_staff, org_staff): + users = find_users(privilege='admin') + + user, project = next( + (user, project) + for user, project in product(users, projects) + if not is_project_staff(user['id'], project['organization']) + and user['id'] not in org_staff(project['organization']) + ) + self._test_response_200(user['username'], project['id']) + + # Project owner or project assignee can see project. + def test_project_owner_accessibility(self, projects): + for p in projects: + if p['owner'] is not None: + project_with_owner = p + if p['assignee'] is not None: + project_with_assignee = p + + assert project_with_owner is not None + assert project_with_assignee is not None + + self._test_response_200(project_with_owner['owner']['username'], project_with_owner['id']) + self._test_response_200(project_with_assignee['assignee']['username'], project_with_assignee['id']) + + def test_user_cannot_see_project(self, projects, find_users, is_project_staff, org_staff): + users = find_users(exclude_privilege='admin') + + user, project = next( + (user, project) + for user, project in product(users, projects) + if not is_project_staff(user['id'], project['organization']) + and user['id'] not in org_staff(project['organization']) + ) + self._test_response_403(user['username'], project['id']) + + # Member of organization that has role supervisor or worker cannot see + # project if this member not in [project:owner, project:assignee] + @pytest.mark.parametrize('role', ('supervisor', 'worker')) + def test_if_supervisor_or_worker_cannot_see_project(self, projects, is_project_staff, + find_users, role): + non_admins = find_users(role=role, exclude_privilege='admin') + assert non_admins is not None + + project_id = self._find_project_by_user_org(non_admins[0], projects, False, is_project_staff) + assert project_id is not None + + self._test_response_403(non_admins[0]['username'], project_id) + + # Member of organization that has role maintainer or owner can see any + # project even this member not in [project:owner, project:assignee] + @pytest.mark.parametrize('role', ('maintainer', 'owner')) + def test_if_maintainer_or_owner_can_see_project(self, find_users, projects, is_project_staff, role): + non_admins = find_users(role=role, exclude_privilege='admin') + assert non_admins is not None + + project_id = self._find_project_by_user_org(non_admins[0], projects, False, is_project_staff) + assert project_id is not None + + self._test_response_200(non_admins[0]['username'], project_id, org_id=non_admins[0]['org']) + + # Member of organization that has role supervisor or worker can see + # project if this member in [project:owner, project:assignee] + @pytest.mark.parametrize('role', ('supervisor', 'worker')) + def test_if_org_member_supervisor_or_worker_can_see_project(self, projects, + find_users, is_project_staff, role): + non_admins = find_users(role=role, exclude_privilege='admin') + assert len(non_admins) + + for u in non_admins: + project_id = self._find_project_by_user_org(u, projects, True, is_project_staff) + if project_id: + user_in_project = u + break + + assert project_id is not None + + self._test_response_200(user_in_project['username'], project_id, org_id=user_in_project['org']) + +class TestPostProjects: + def _test_create_project_201(self, user, spec, **kwargs): + response = post_method(user, '/projects', spec, **kwargs) + assert response.status_code == HTTPStatus.CREATED + + def _test_create_project_403(self, user, spec, **kwargs): + response = post_method(user, '/projects', spec, **kwargs) + assert response.status_code == HTTPStatus.FORBIDDEN + + def test_if_worker_cannot_create_project(self, find_users): + workers = find_users(privilege='worker') + assert len(workers) + + username = workers[0]['username'] + spec = { + 'name': f'test {username} tries to create a project' + } + self._test_create_project_403(username, spec) + + @pytest.mark.parametrize('privilege', ('admin', 'business', 'user')) + def test_is_user_can_create_project(self, find_users, privilege): + privileged_users = find_users(privilege=privilege) + assert len(privileged_users) + + username = privileged_users[0]['username'] + spec = { + 'name': f'test {username} tries to create a project' + } + self._test_create_project_201(username, spec) + + def test_if_user_cannot_have_more_than_3_projects(self, projects, find_users): + users = find_users(privilege='user') + + user_id, user_projects = next( + (user_id, len(list(projects))) + for user_id, projects in groupby(projects, lambda a: a['owner']['id']) + if len(list(projects)) < 3 + ) + user = users[user_id] + + for i in range(1, 4 - user_projects): + spec = { + 'name': f'test: {user["username"]} tries to create a project number {user_projects + i}' + } + self._test_create_project_201(user['username'], spec) + + spec = { + 'name': f'test {user["username"]} tries to create more than 3 projects' + } + self._test_create_project_403(user['username'], spec) + + @pytest.mark.parametrize('privilege', ('admin', 'business')) + def test_if_user_can_have_more_than_3_projects(self, find_users, privilege): + privileged_users = find_users(privilege=privilege) + assert len(privileged_users) + + user = privileged_users[0] + + for i in range(1, 5): + spec = { + 'name': f'test: {user["username"]} with privilege {privilege} tries to create a project number {i}' + } + self._test_create_project_201(user['username'], spec) + + def test_if_org_worker_cannot_crate_project(self, find_users): + workers = find_users(role='worker') + + worker = next(u for u in workers if u['org']) + + spec = { + 'name': f'test: worker {worker["username"]} creating a project for his organization', + } + self._test_create_project_403(worker['username'], spec, org_id=worker['org']) + + @pytest.mark.parametrize('role', ('supervisor', 'maintainer', 'owner')) + def test_if_org_role_can_crate_project(self, find_users, role): + privileged_users = find_users(role=role) + assert len(privileged_users) + + user = next(u for u in privileged_users if u['org']) + + spec = { + 'name': f'test: worker {user["username"]} creating a project for his organization', + } + self._test_create_project_201(user['username'], spec, org_id=user['org'])