diff --git a/.vscode/settings.json b/.vscode/settings.json index a639b285..76614f36 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -18,13 +18,9 @@ } ], "npm.exclude": "**/.env/**", - "python.linting.enabled": true, - "python.linting.pylintEnabled": true, - "python.linting.pycodestyleEnabled": false, "licenser.license": "Custom", "licenser.customHeader": "Copyright (C) @YEAR@ Intel Corporation\n\nSPDX-License-Identifier: MIT", "files.trimTrailingWhitespace": true, - "python.pythonPath": ".env/bin/python", "sqltools.connections": [ { "previewLimit": 50, @@ -33,9 +29,15 @@ "database": "${workspaceFolder:cvat}/db.sqlite3" } ], + "python.defaultInterpreterPath": "${workspaceFolder}/.env/", + "python.linting.enabled": true, + "python.linting.pylintEnabled": true, + "python.linting.pycodestyleEnabled": false, "python.testing.pytestArgs": [ - "tests" + "--rootdir","${workspaceFolder}/tests/" ], "python.testing.unittestEnabled": false, - "python.testing.pytestEnabled": true + "python.testing.pytestEnabled": true, + "python.testing.pytestPath": "${workspaceFolder}/.env/bin/pytest", + "python.testing.cwd": "${workspaceFolder}/tests" } diff --git a/tests/rest_api/README.md b/tests/rest_api/README.md index f7802c7b..5b384260 100644 --- a/tests/rest_api/README.md +++ b/tests/rest_api/README.md @@ -84,7 +84,7 @@ python utils/dump_objects.py To restore DB and data volume, please use commands below. ```console -cat assets/cvat_db/data.json | docker exec -i cvat python manage.py --format=json loaddata - +cat assets/cvat_db/data.json | docker exec -i cvat python manage.py loaddata --format=json - cat assets/cvat_db/cvat_data.tar.bz2 | docker exec -i cvat tar --strip 3 -C /home/django/data/ -xj ``` diff --git a/tests/rest_api/assets/annotations.json b/tests/rest_api/assets/annotations.json index 77abf902..d68fc662 100644 --- a/tests/rest_api/assets/annotations.json +++ b/tests/rest_api/assets/annotations.json @@ -298,5 +298,293 @@ "tracks": [], "version": 0 } + }, + "task": { + "2": { + "shapes": [ + { + "attributes": [], + "frame": 0, + "group": 0, + "id": 1, + "label_id": 3, + "occluded": false, + "points": [ + 223.39453125, + 226.0751953125, + 513.7663269042969, + 377.9619903564453 + ], + "rotation": 0.0, + "source": "manual", + "type": "rectangle", + "z_order": 0 + }, + { + "attributes": [], + "frame": 1, + "group": 0, + "id": 2, + "label_id": 3, + "occluded": false, + "points": [ + 63.0791015625, + 139.75390625, + 132.19337349397574, + 112.3867469879533, + 189.71144578313397, + 159.23614457831354, + 191.1030120481937, + 246.9048192771097, + 86.73554216867524, + 335.5012048192784, + 32.00060240964012, + 250.15180722891637 + ], + "rotation": 0.0, + "source": "manual", + "type": "polygon", + "z_order": 0 + }, + { + "attributes": [], + "frame": 1, + "group": 0, + "id": 3, + "label_id": 4, + "occluded": false, + "points": [ + 83.0244140625, + 216.75390625, + 112.24759036144678, + 162.48313253012202, + 167.44638554216908, + 183.35662650602535, + 149.35602409638705, + 252.0072289156633, + 84.41626506024113, + 292.8265060240974, + 72.81987951807241, + 258.9650602409638 + ], + "rotation": 0.0, + "source": "manual", + "type": "polygon", + "z_order": 0 + }, + { + "attributes": [], + "frame": 2, + "group": 0, + "id": 4, + "label_id": 3, + "occluded": false, + "points": [ + 24.443359375, + 107.2275390625, + 84.91109877913368, + 61.125083240844106, + 169.4316315205324, + 75.1561598224198, + 226.5581576026634, + 113.90865704772477, + 240.5892341842391, + 205.77880133185317, + 210.52264150943483, + 270.9230854605994 + ], + "rotation": 0.0, + "source": "manual", + "type": "polyline", + "z_order": 0 + }, + { + "attributes": [], + "frame": 22, + "group": 0, + "id": 5, + "label_id": 3, + "occluded": false, + "points": [ + 148.94921875, + 285.6865234375, + 313.515094339622, + 400.32830188679145, + 217.36415094339463, + 585.2339622641503, + 64.81698113207494, + 499.25283018867776 + ], + "rotation": 0.0, + "source": "manual", + "type": "points", + "z_order": 0 + } + ], + "tags": [], + "tracks": [], + "version": 0 + }, + "5": { + "shapes": [ + { + "attributes": [], + "frame": 0, + "group": 0, + "id": 29, + "label_id": 9, + "occluded": false, + "points": [ + 364.0361328125, + 528.87890625, + 609.5286041189956, + 586.544622425632, + 835.2494279176244, + 360.0000000000018, + 543.6247139588122, + 175.4691075514893, + 326.9656750572103, + 192.76887871853796, + 244.58581235698148, + 319.63386727689067 + ], + "rotation": 0.0, + "source": "manual", + "type": "polygon", + "z_order": 0 + } + ], + "tags": [], + "tracks": [], + "version": 0 + }, + "6": { + "shapes": [], + "tags": [], + "tracks": [], + "version": 0 + }, + "7": { + "shapes": [ + { + "attributes": [], + "frame": 0, + "group": 0, + "id": 27, + "label_id": 11, + "occluded": false, + "points": [ + 448.3779296875, + 356.4892578125, + 438.2558352402775, + 761.3861556064112, + 744.1780320366161, + 319.37356979405195, + 446.1288329519466, + 163.03832951945333 + ], + "rotation": 0.0, + "source": "manual", + "type": "polygon", + "z_order": 0 + } + ], + "tags": [], + "tracks": [], + "version": 0 + }, + "8": { + "shapes": [ + { + "attributes": [], + "frame": 0, + "group": 0, + "id": 30, + "label_id": 13, + "occluded": false, + "points": [ + 440.0439453125, + 84.0791015625, + 71.83311938382576, + 249.81514762516053, + 380.4441591784325, + 526.585365853658, + 677.6251604621302, + 260.42875481386363, + 629.4557124518615, + 127.35044929396645 + ], + "rotation": 0.0, + "source": "manual", + "type": "polygon", + "z_order": 0 + } + ], + "tags": [], + "tracks": [], + "version": 0 + }, + "9": { + "shapes": [ + { + "attributes": [], + "frame": 0, + "group": 0, + "id": 31, + "label_id": 6, + "occluded": false, + "points": [ + 65.6189987163034, + 100.96585365853753, + 142.12734274711147, + 362.6243902439037 + ], + "rotation": 0.0, + "source": "manual", + "type": "rectangle", + "z_order": 0 + } + ], + "tags": [], + "tracks": [], + "version": 0 + }, + "11": { + "shapes": [ + { + "attributes": [], + "frame": 0, + "group": 0, + "id": 33, + "label_id": 7, + "occluded": false, + "points": [ + 100.14453125, + 246.03515625, + 408.8692551505537, + 327.5483359746413, + 588.5839936608554, + 289.0380348652925, + 623.8851030110927, + 183.77654516640177, + 329.2812995245622, + 71.45483359746322 + ], + "rotation": 0.0, + "source": "manual", + "type": "polyline", + "z_order": 0 + } + ], + "tags": [], + "tracks": [], + "version": 0 + }, + "12": { + "shapes": [], + "tags": [], + "tracks": [], + "version": 0 + } } } \ No newline at end of file diff --git a/tests/rest_api/assets/cvat_db/data.json b/tests/rest_api/assets/cvat_db/data.json index 100cd515..54ea9ce0 100644 --- a/tests/rest_api/assets/cvat_db/data.json +++ b/tests/rest_api/assets/cvat_db/data.json @@ -3600,7 +3600,7 @@ "name": "task2", "mode": "annotation", "owner": 2, - "assignee": null, + "assignee": 7, "bug_tracker": "", "created_date": "2021-12-14T18:50:29.458Z", "updated_date": "2021-12-22T07:14:15.234Z", @@ -3621,7 +3621,7 @@ "name": "task2", "mode": "interpolation", "owner": 2, - "assignee": null, + "assignee": 15, "bug_tracker": "", "created_date": "2022-02-16T06:25:48.168Z", "updated_date": "2022-02-21T10:40:21.257Z", @@ -3684,7 +3684,7 @@ "name": "task1", "mode": "annotation", "owner": 2, - "assignee": null, + "assignee": 9, "bug_tracker": "", "created_date": "2022-03-05T08:30:48.612Z", "updated_date": "2022-03-05T08:52:34.908Z", @@ -3705,7 +3705,7 @@ "name": "task1_in_project1", "mode": "annotation", "owner": 10, - "assignee": 20, + "assignee": 1, "bug_tracker": "", "created_date": "2022-03-05T09:33:10.420Z", "updated_date": "2022-03-05T09:47:49.667Z", @@ -4567,7 +4567,7 @@ "pk": 11, "fields": { "segment": 11, - "assignee": null, + "assignee": 9, "status": "annotation", "stage": "annotation", "state": "in progress" @@ -5628,5 +5628,141 @@ "user": 20, "rating": 0.0 } +}, +{ + "model": "engine.issue", + "pk": 1, + "fields": { + "frame": 0, + "position": "[244.58581235698148, 319.63386727689067, 326.9656750572103, 192.76887871853796, 543.6247139588122, 175.4691075514893, 835.2494279176244, 360.0000000000018, 609.5286041189956, 586.544622425632, 364.0361328125, 528.87890625, 244.58581235698148, 319.63386727689067]", + "job": 7, + "owner": 2, + "assignee": null, + "created_date": "2022-03-16T11:04:39.444Z", + "updated_date": null, + "resolved": false + } +}, +{ + "model": "engine.issue", + "pk": 2, + "fields": { + "frame": 0, + "position": "[98.48046875, 696.72265625, 326.1220703125, 841.5859375]", + "job": 9, + "owner": 11, + "assignee": null, + "created_date": "2022-03-16T11:07:22.170Z", + "updated_date": null, + "resolved": false + } +}, +{ + "model": "engine.issue", + "pk": 3, + "fields": { + "frame": 5, + "position": "[108.1845703125, 235.0, 720.0087890625, 703.3505859375]", + "job": 16, + "owner": 11, + "assignee": null, + "created_date": "2022-03-16T11:08:18.367Z", + "updated_date": null, + "resolved": false + } +}, +{ + "model": "engine.issue", + "pk": 4, + "fields": { + "frame": 5, + "position": "[295.36328125, 243.6044921875, 932.23046875, 561.4921875]", + "job": 10, + "owner": 2, + "assignee": null, + "created_date": "2022-03-16T12:40:00.764Z", + "updated_date": null, + "resolved": false + } +}, +{ + "model": "engine.issue", + "pk": 5, + "fields": { + "frame": 0, + "position": "[65.6189987163034, 100.96585365853753, 142.12734274711147, 362.6243902439037]", + "job": 11, + "owner": 20, + "assignee": null, + "created_date": "2022-03-16T12:49:29.369Z", + "updated_date": null, + "resolved": false + } +}, +{ + "model": "engine.comment", + "pk": 1, + "fields": { + "issue": 1, + "owner": 2, + "message": "Why are we still here?", + "created_date": "2022-03-16T11:04:39.447Z", + "updated_date": "2022-03-16T11:04:39.447Z" + } +}, +{ + "model": "engine.comment", + "pk": 2, + "fields": { + "issue": 1, + "owner": 2, + "message": "Just to suffer?", + "created_date": "2022-03-16T11:04:49.821Z", + "updated_date": "2022-03-16T11:04:49.821Z" + } +}, +{ + "model": "engine.comment", + "pk": 3, + "fields": { + "issue": 2, + "owner": 11, + "message": "Something should be here", + "created_date": "2022-03-16T11:07:22.173Z", + "updated_date": "2022-03-16T11:07:22.173Z" + } +}, +{ + "model": "engine.comment", + "pk": 4, + "fields": { + "issue": 3, + "owner": 11, + "message": "Another one issue", + "created_date": "2022-03-16T11:08:18.370Z", + "updated_date": "2022-03-16T11:08:18.370Z" + } +}, +{ + "model": "engine.comment", + "pk": 5, + "fields": { + "issue": 4, + "owner": 2, + "message": "Issue with empty frame", + "created_date": "2022-03-16T12:40:00.767Z", + "updated_date": "2022-03-16T12:40:00.767Z" + } +}, +{ + "model": "engine.comment", + "pk": 6, + "fields": { + "issue": 5, + "owner": 20, + "message": "Wrong position", + "created_date": "2022-03-16T12:49:29.372Z", + "updated_date": "2022-03-16T12:49:29.372Z" + } } ] diff --git a/tests/rest_api/assets/issues.json b/tests/rest_api/assets/issues.json new file mode 100644 index 00000000..689c95cb --- /dev/null +++ b/tests/rest_api/assets/issues.json @@ -0,0 +1,221 @@ +{ + "count": 5, + "next": null, + "previous": null, + "results": [ + { + "assignee": null, + "comments": [ + { + "created_date": "2022-03-16T12:49:29.372000Z", + "id": 6, + "issue": 5, + "message": "Wrong position", + "owner": { + "first_name": "User", + "id": 20, + "last_name": "Sixth", + "url": "http://localhost:8080/api/users/20", + "username": "user6" + }, + "updated_date": "2022-03-16T12:49:29.372000Z" + } + ], + "created_date": "2022-03-16T12:49:29.369000Z", + "frame": 0, + "id": 5, + "job": 11, + "owner": { + "first_name": "User", + "id": 20, + "last_name": "Sixth", + "url": "http://localhost:8080/api/users/20", + "username": "user6" + }, + "position": [ + 65.6189987163034, + 100.96585365853753, + 142.12734274711147, + 362.6243902439037 + ], + "resolved": false, + "updated_date": null + }, + { + "assignee": null, + "comments": [ + { + "created_date": "2022-03-16T12:40:00.767000Z", + "id": 5, + "issue": 4, + "message": "Issue with empty frame", + "owner": { + "first_name": "User", + "id": 2, + "last_name": "First", + "url": "http://localhost:8080/api/users/2", + "username": "user1" + }, + "updated_date": "2022-03-16T12:40:00.767000Z" + } + ], + "created_date": "2022-03-16T12:40:00.764000Z", + "frame": 5, + "id": 4, + "job": 10, + "owner": { + "first_name": "User", + "id": 2, + "last_name": "First", + "url": "http://localhost:8080/api/users/2", + "username": "user1" + }, + "position": [ + 295.36328125, + 243.6044921875, + 932.23046875, + 561.4921875 + ], + "resolved": false, + "updated_date": null + }, + { + "assignee": null, + "comments": [ + { + "created_date": "2022-03-16T11:08:18.370000Z", + "id": 4, + "issue": 3, + "message": "Another one issue", + "owner": { + "first_name": "Business", + "id": 11, + "last_name": "Second", + "url": "http://localhost:8080/api/users/11", + "username": "business2" + }, + "updated_date": "2022-03-16T11:08:18.370000Z" + } + ], + "created_date": "2022-03-16T11:08:18.367000Z", + "frame": 5, + "id": 3, + "job": 16, + "owner": { + "first_name": "Business", + "id": 11, + "last_name": "Second", + "url": "http://localhost:8080/api/users/11", + "username": "business2" + }, + "position": [ + 108.1845703125, + 235.0, + 720.0087890625, + 703.3505859375 + ], + "resolved": false, + "updated_date": null + }, + { + "assignee": null, + "comments": [ + { + "created_date": "2022-03-16T11:07:22.173000Z", + "id": 3, + "issue": 2, + "message": "Something should be here", + "owner": { + "first_name": "Business", + "id": 11, + "last_name": "Second", + "url": "http://localhost:8080/api/users/11", + "username": "business2" + }, + "updated_date": "2022-03-16T11:07:22.173000Z" + } + ], + "created_date": "2022-03-16T11:07:22.170000Z", + "frame": 0, + "id": 2, + "job": 9, + "owner": { + "first_name": "Business", + "id": 11, + "last_name": "Second", + "url": "http://localhost:8080/api/users/11", + "username": "business2" + }, + "position": [ + 98.48046875, + 696.72265625, + 326.1220703125, + 841.5859375 + ], + "resolved": false, + "updated_date": null + }, + { + "assignee": null, + "comments": [ + { + "created_date": "2022-03-16T11:04:39.447000Z", + "id": 1, + "issue": 1, + "message": "Why are we still here?", + "owner": { + "first_name": "User", + "id": 2, + "last_name": "First", + "url": "http://localhost:8080/api/users/2", + "username": "user1" + }, + "updated_date": "2022-03-16T11:04:39.447000Z" + }, + { + "created_date": "2022-03-16T11:04:49.821000Z", + "id": 2, + "issue": 1, + "message": "Just to suffer?", + "owner": { + "first_name": "User", + "id": 2, + "last_name": "First", + "url": "http://localhost:8080/api/users/2", + "username": "user1" + }, + "updated_date": "2022-03-16T11:04:49.821000Z" + } + ], + "created_date": "2022-03-16T11:04:39.444000Z", + "frame": 0, + "id": 1, + "job": 7, + "owner": { + "first_name": "User", + "id": 2, + "last_name": "First", + "url": "http://localhost:8080/api/users/2", + "username": "user1" + }, + "position": [ + 244.58581235698148, + 319.63386727689067, + 326.9656750572103, + 192.76887871853796, + 543.6247139588122, + 175.4691075514893, + 835.2494279176244, + 360.0000000000018, + 609.5286041189956, + 586.544622425632, + 364.0361328125, + 528.87890625, + 244.58581235698148, + 319.63386727689067 + ], + "resolved": false, + "updated_date": null + } + ] +} \ No newline at end of file diff --git a/tests/rest_api/assets/jobs.json b/tests/rest_api/assets/jobs.json index a5369d6b..fbb9d2f5 100644 --- a/tests/rest_api/assets/jobs.json +++ b/tests/rest_api/assets/jobs.json @@ -173,7 +173,13 @@ "url": "http://localhost:8080/api/jobs/12" }, { - "assignee": null, + "assignee": { + "first_name": "Worker", + "id": 9, + "last_name": "Fourth", + "url": "http://localhost:8080/api/users/9", + "username": "worker4" + }, "bug_tracker": "", "data_chunk_size": 72, "data_compressed_chunk_type": "imageset", diff --git a/tests/rest_api/assets/tasks.json b/tests/rest_api/assets/tasks.json index 2c6f7aef..12c3381d 100644 --- a/tests/rest_api/assets/tasks.json +++ b/tests/rest_api/assets/tasks.json @@ -110,11 +110,11 @@ }, { "assignee": { - "first_name": "User", - "id": 20, - "last_name": "Sixth", - "url": "http://localhost:8080/api/users/20", - "username": "user6" + "first_name": "Admin", + "id": 1, + "last_name": "First", + "url": "http://localhost:8080/api/users/1", + "username": "admin1" }, "bug_tracker": "", "created_date": "2022-03-05T09:33:10.420000Z", @@ -169,7 +169,13 @@ { "jobs": [ { - "assignee": null, + "assignee": { + "first_name": "Worker", + "id": 9, + "last_name": "Fourth", + "url": "http://localhost:8080/api/users/9", + "username": "worker4" + }, "id": 11, "stage": "annotation", "state": "in progress", @@ -230,7 +236,13 @@ "url": "http://localhost:8080/api/tasks/9" }, { - "assignee": null, + "assignee": { + "first_name": "Worker", + "id": 9, + "last_name": "Fourth", + "url": "http://localhost:8080/api/users/9", + "username": "worker4" + }, "bug_tracker": "", "created_date": "2022-03-05T08:30:48.612000Z", "data": 8, @@ -416,7 +428,13 @@ "url": "http://localhost:8080/api/tasks/6" }, { - "assignee": null, + "assignee": { + "first_name": "Dummy", + "id": 15, + "last_name": "Second", + "url": "http://localhost:8080/api/users/15", + "username": "dummy2" + }, "bug_tracker": "", "created_date": "2022-02-16T06:25:48.168000Z", "data": 5, @@ -476,7 +494,13 @@ "url": "http://localhost:8080/api/tasks/5" }, { - "assignee": null, + "assignee": { + "first_name": "Worker", + "id": 7, + "last_name": "Second", + "url": "http://localhost:8080/api/users/7", + "username": "worker2" + }, "bug_tracker": "", "created_date": "2021-12-14T18:50:29.458000Z", "data": 2, diff --git a/tests/rest_api/conftest.py b/tests/rest_api/conftest.py index 5469847c..688845c5 100644 --- a/tests/rest_api/conftest.py +++ b/tests/rest_api/conftest.py @@ -103,6 +103,11 @@ def annotations(): with open(osp.join(ASSETS_DIR, 'annotations.json')) as f: return json.load(f) +@pytest.fixture(scope='module') +def issues(): + with open(osp.join(ASSETS_DIR, 'issues.json')) as f: + return Container(json.load(f)['results']) + @pytest.fixture(scope='module') def users_by_name(users): return {user['username']: user for user in users} @@ -115,6 +120,22 @@ def jobs_by_org(tasks, jobs): data[''] = data.pop(None, []) return data +@pytest.fixture(scope='module') +def tasks_by_org(tasks): + data = {} + for task in tasks: + data.setdefault(task['organization'], []).append(task) + data[''] = data.pop(None, []) + return data + +@pytest.fixture(scope='module') +def issues_by_org(tasks, jobs, issues): + data = {} + for issue in issues: + data.setdefault(tasks[jobs[issue['job']]['task_id']]['organization'], []).append(issue) + data[''] = data.pop(None, []) + return data + @pytest.fixture(scope='module') def assignee_id(): def get_id(data): @@ -154,6 +175,22 @@ def is_job_staff(jobs, is_task_staff, assignee_id): is_task_staff(user_id, jobs[jid]['task_id']) return check +@pytest.fixture(scope='module') +def is_issue_staff(issues, jobs, assignee_id): + @ownership + def check(user_id, issue_id): + return user_id == issues[issue_id]['owner']['id'] or \ + user_id == assignee_id(issues[issue_id]) or \ + user_id == assignee_id(jobs[issues[issue_id]['job']]) + return check + +@pytest.fixture(scope='module') +def is_issue_admin(issues, jobs, is_task_staff): + @ownership + def check(user_id, issue_id): + return is_task_staff(user_id, jobs[issues[issue_id]['job']]['task_id']) + return check + @pytest.fixture(scope='module') def find_users(test_db): def find(**kwargs): @@ -225,8 +262,36 @@ def find_job_staff_user(is_job_staff): return None, None return find +@pytest.fixture(scope='module') +def find_task_staff_user(is_task_staff): + def find(tasks, users, is_staff): + for task in tasks: + for user in users: + if is_staff == is_task_staff(user['id'], task['id']): + return user['username'], task['id'] + return None, None + return find + +@pytest.fixture(scope='module') +def find_issue_staff_user(is_issue_staff, is_issue_admin): + def find(issues, users, is_staff, is_admin): + for issue in issues: + for user in users: + i_admin, i_staff = is_issue_admin(user['id'], issue['id']), is_issue_staff(user['id'], issue['id']) + if (is_admin is None and (i_staff or i_admin) == is_staff) \ + or (is_admin == i_admin and is_staff == i_staff): + return user['username'], issue['id'] + return None, None + return find + @pytest.fixture(scope='module') def filter_jobs_with_shapes(annotations): def find(jobs): return list(filter(lambda j: annotations['job'][str(j['id'])]['shapes'], jobs)) return find + +@pytest.fixture(scope='module') +def filter_tasks_with_shapes(annotations): + def find(tasks): + return list(filter(lambda t: annotations['task'][str(t['id'])]['shapes'], tasks)) + return find diff --git a/tests/rest_api/test_issues.py b/tests/rest_api/test_issues.py new file mode 100644 index 00000000..e3c6ec65 --- /dev/null +++ b/tests/rest_api/test_issues.py @@ -0,0 +1,140 @@ +# Copyright (C) 2022 Intel Corporation +# +# SPDX-License-Identifier: MIT + +import pytest +from http import HTTPStatus +from deepdiff import DeepDiff +from copy import deepcopy + +from .utils.config import post_method, patch_method + +class TestPostIssues: + def _test_check_response(self, user, data, is_allow, **kwargs): + response = post_method(user, 'issues', data, **kwargs) + + if is_allow: + assert response.status_code == HTTPStatus.CREATED + assert user == response.json()['owner']['username'] + assert data['message'] == response.json()['comments'][0]['message'] + assert DeepDiff(data, response.json(), + exclude_regex_paths=r"root\['created_date|updated_date|comments|id|owner|message'\]") == {} + else: + assert response.status_code == HTTPStatus.FORBIDDEN + + @pytest.mark.parametrize('org', ['']) + @pytest.mark.parametrize('privilege, job_staff, is_allow', [ + ('admin', True, True), ('admin', False, True), + ('business', True, True), ('business', False, False), + ('worker', True, True), ('worker', False, False), + ('user', True, True), ('user', False, False) + ]) + def test_user_create_issue(self, org, privilege, job_staff, is_allow, + find_job_staff_user, find_users, jobs_by_org): + users = find_users(privilege=privilege) + jobs = jobs_by_org[org] + username, jid = find_job_staff_user(jobs, users, job_staff) + job, = filter(lambda job: job['id'] == jid, jobs) + + data = { + "assignee": None, + "comments": [], + "job": jid, + "frame": job['start_frame'], + "position": [ + 0., 0., 1., 1., + ], + "resolved": False, + "message": "lorem ipsum", + } + + self._test_check_response(username, data, is_allow) + + + @pytest.mark.parametrize('org', [2]) + @pytest.mark.parametrize('role, job_staff, is_allow', [ + ('maintainer', False, True), ('owner', False, True), + ('supervisor', False, False), ('worker', False, False), + ('maintainer', True, True), ('owner', True, True), + ('supervisor', True, True), ('worker', True, True) + ]) + def test_member_create_issue(self, org, role, job_staff, is_allow, + find_job_staff_user, find_users, jobs_by_org, jobs): + users = find_users(role=role, org=org) + username, jid = find_job_staff_user(jobs_by_org[org], users, job_staff) + job = jobs[jid] + + data = { + "assignee": None, + "comments": [], + "job": jid, + "frame": job['start_frame'], + "position": [ + 0., 0., 1., 1., + ], + "resolved": False, + "message": "lorem ipsum", + } + + self._test_check_response(username, data, is_allow, org_id=org) + + +class TestPatchIssues: + def _test_check_response(self, user, issue_id, data, is_allow, **kwargs): + response = patch_method(user, f'issues/{issue_id}', data, + action='update', **kwargs) + + if is_allow: + assert response.status_code == HTTPStatus.OK + assert DeepDiff(data, response.json(), + exclude_regex_paths=r"root\['created_date|updated_date|comments|id|owner'\]") == {} + else: + assert response.status_code == HTTPStatus.FORBIDDEN + + @pytest.fixture(scope='class') + def request_data(self, issues): + def get_data(issue_id): + data = deepcopy(issues[issue_id]) + data['resolved'] = not data['resolved'] + data.pop('comments') + data.pop('updated_date') + data.pop('id') + data.pop('owner') + return data + return get_data + + @pytest.mark.parametrize('org', ['']) + @pytest.mark.parametrize('privilege, issue_staff, issue_admin, is_allow', [ + ('admin', True, None, True), ('admin', False, None, True), + ('business', True, None, True), ('business', False, None, False), + ('user', True, None, True), ('user', False, None, False), + ('worker', False, True, True), ('worker', True, False, False), + ('worker', False, False, False) + ]) + def test_user_update_issue(self, org, privilege, issue_staff, issue_admin, is_allow, + find_issue_staff_user, find_users, issues_by_org, request_data): + users = find_users(privilege=privilege) + issues = issues_by_org[org] + username, issue_id = find_issue_staff_user(issues, users, issue_staff, issue_admin) + + data = request_data(issue_id) + self._test_check_response(username, issue_id, data, is_allow) + + @pytest.mark.parametrize('org', [2]) + @pytest.mark.parametrize('role, issue_staff, issue_admin, is_allow', [ + ('maintainer', True, None, True), ('maintainer', False, None, True), + ('supervisor', True, None, True), ('supervisor', False, None, False), + ('owner', True, None, True), ('owner', False, None, True), + ('worker', False, True, True), ('worker', True, False, False), + ('worker', False, False, False) + ]) + def test_member_update_issue(self, org, role, issue_staff, issue_admin, is_allow, + find_issue_staff_user, find_users, issues_by_org, request_data): + users = find_users(role=role, org=org) + issues = issues_by_org[org] + username, issue_id = find_issue_staff_user(issues, users, issue_staff, issue_admin) + + data = request_data(issue_id) + self._test_check_response(username, issue_id, data, is_allow, org_id=org) + + diff --git a/tests/rest_api/test_tasks.py b/tests/rest_api/test_tasks.py index 4698a75d..c8984aab 100644 --- a/tests/rest_api/test_tasks.py +++ b/tests/rest_api/test_tasks.py @@ -6,7 +6,7 @@ from http import HTTPStatus from deepdiff import DeepDiff import pytest -from .utils.config import get_method, post_method +from .utils.config import get_method, post_method, patch_method class TestGetTasks: def _test_task_list_200(self, user, project_id, data, exclude_paths = '', **kwargs): @@ -154,4 +154,62 @@ class TestGetData: def test_frame_content_type(self, content_type, task_id): response = get_method(self._USERNAME, f'tasks/{task_id}/data', type='frame', quality='original', number=0) assert response.status_code == HTTPStatus.OK - assert response.headers['Content-Type'] == content_type \ No newline at end of file + assert response.headers['Content-Type'] == content_type + + +class TestPatchTaskAnnotations: + def _test_check_respone(self, is_allow, response, data=None): + if is_allow: + assert response.status_code == HTTPStatus.OK + assert DeepDiff(data, response.json(), + exclude_paths="root['version']") == {} + else: + assert response.status_code == HTTPStatus.FORBIDDEN + + @pytest.fixture(scope='class') + def request_data(self, annotations): + def get_data(tid): + data = annotations['task'][str(tid)].copy() + data['shapes'][0].update({'points': [2.0, 3.0, 4.0, 5.0, 6.0, 7.0]}) + data['version'] += 1 + return data + return get_data + + @pytest.mark.parametrize('org', ['']) + @pytest.mark.parametrize('privilege, task_staff, is_allow', [ + ('admin', True, True), ('admin', False, True), + ('business', True, True), ('business', False, False), + ('worker', True, True), ('worker', False, False), + ('user', True, True), ('user', False, False) + ]) + def test_user_update_task_annotations(self, org, privilege, task_staff, is_allow, + find_task_staff_user, find_users, request_data, tasks_by_org, filter_tasks_with_shapes): + users = find_users(privilege=privilege) + tasks = tasks_by_org[org] + filtered_tasks = filter_tasks_with_shapes(tasks) + username, tid = find_task_staff_user(filtered_tasks, users, task_staff) + + data = request_data(tid) + response = patch_method(username, f'tasks/{tid}/annotations', data, + org_id=org, action='update') + + self._test_check_respone(is_allow, response, data) + + @pytest.mark.parametrize('org', [2]) + @pytest.mark.parametrize('role, task_staff, is_allow', [ + ('maintainer', False, True), ('owner', False, True), + ('supervisor', False, False), ('worker', False, False), + ('maintainer', True, True), ('owner', True, True), + ('supervisor', True, True), ('worker', True, True) + ]) + def test_member_update_task_annotation(self, org, role, task_staff, is_allow, + find_task_staff_user, find_users, tasks_by_org, request_data): + users = find_users(role=role, org=org) + tasks = tasks_by_org[org] + username, tid = find_task_staff_user(tasks, users, task_staff) + + data = request_data(tid) + response = patch_method(username, f'tasks/{tid}/annotations', data, + org_id=org, action='update') + + self._test_check_respone(is_allow, response, data) diff --git a/tests/rest_api/utils/dump_objects.py b/tests/rest_api/utils/dump_objects.py index c7e56645..45fad7de 100644 --- a/tests/rest_api/utils/dump_objects.py +++ b/tests/rest_api/utils/dump_objects.py @@ -4,17 +4,17 @@ import json annotations = {} for obj in ['user', 'project', 'task', 'job', 'organization', 'membership', - 'invitation']: + 'invitation', 'issue']: response = get_method('admin1', f'{obj}s', page_size='all') with open(osp.join(ASSETS_DIR, f'{obj}s.json'), 'w') as f: json.dump(response.json(), f, indent=2, sort_keys=True) - if obj == 'job': + if obj in ['job', 'task']: annotations[obj] = {} - for job in response.json()['results']: - jid = job["id"] - response = get_method('admin1', f'jobs/{jid}/annotations') - annotations[obj][jid] = response.json() + for _obj in response.json()['results']: + oid = _obj["id"] + response = get_method('admin1', f'{obj}s/{oid}/annotations') + annotations[obj][oid] = response.json() with open(osp.join(ASSETS_DIR, f'annotations.json'), 'w') as f: json.dump(annotations, f, indent=2, sort_keys=True)