diff --git a/tests/rest_api/assets/annotations.json b/tests/rest_api/assets/annotations.json index 32bfa80a6a0..a80822b7ff1 100644 --- a/tests/rest_api/assets/annotations.json +++ b/tests/rest_api/assets/annotations.json @@ -295,10 +295,39 @@ "version": 3 }, "3": { - "shapes": [], + "shapes": [ + { + "attributes": [], + "frame": 0, + "group": 0, + "id": 28, + "label_id": 6, + "occluded": false, + "points": [ + 155.5, + 634.4814453125, + 222.17951945080313, + 575.8846681922205, + 214.09713958810244, + 384.9384439359292, + 160.55137299771377, + 255.6203661327254, + 96.90263157894879, + 137.41556064073302, + 284.8179633867294, + 65.68443935926916, + 354.52848970251944, + 264.713043478263 + ], + "rotation": 0.0, + "source": "manual", + "type": "polyline", + "z_order": 0 + } + ], "tags": [], "tracks": [], - "version": 0 + "version": 6 }, "4": { "shapes": [ @@ -1434,16 +1463,72 @@ "version": 3 }, "7": { - "shapes": [], + "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 + "version": 3 }, "8": { "shapes": [], "tags": [], "tracks": [], "version": 0 + }, + "9": { + "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": 3 } } } \ No newline at end of file diff --git a/tests/rest_api/assets/cvat_data.tar.bz2 b/tests/rest_api/assets/cvat_data.tar.bz2 index 07ffd46a868..624ee1ab9cf 100644 Binary files a/tests/rest_api/assets/cvat_data.tar.bz2 and b/tests/rest_api/assets/cvat_data.tar.bz2 differ diff --git a/tests/rest_api/assets/cvat_db/cvat_db.sql b/tests/rest_api/assets/cvat_db/cvat_db.sql index a4a3b6c8e48..06c9e60a5f0 100644 --- a/tests/rest_api/assets/cvat_db/cvat_db.sql +++ b/tests/rest_api/assets/cvat_db/cvat_db.sql @@ -2,8 +2,8 @@ -- PostgreSQL database dump -- --- Dumped from database version 10.19 --- Dumped by pg_dump version 10.19 +-- Dumped from database version 10.20 +-- Dumped by pg_dump version 10.20 SET statement_timeout = 0; SET lock_timeout = 0; @@ -2640,7 +2640,6 @@ COPY public.auth_user (id, password, last_login, is_superuser, username, first_n 7 pbkdf2_sha256$260000$EtdCZTYNPHPX50kM67A7kA$JxuHkmB25RkeMszSV9Pd58E9AFdSu3Rx2UYSAKu/q4k= \N f worker2 Worker Second worker2@cvat.org f t 2021-12-14 18:30:43+00 8 pbkdf2_sha256$260000$PNnmu5EKgxZCY9HQgNLfta$JjsE9zrFYJ3ISUHANjjwnxuiGNRGIyVamqnqrkqR9fQ= \N f worker3 Worker Third worker3@cvat.org f t 2021-12-14 18:31:25+00 9 pbkdf2_sha256$260000$ipL3D6HSba1Cn7pzb5Q7bh$OFx/xO6Q3Q5sBGq3W3MBsmqFhVjkPmVVfaQdnJ8FAtg= \N f worker4 Worker Fourth worker4@cvat.org f t 2021-12-14 18:32:01+00 -11 pbkdf2_sha256$260000$Zw76ANIvIsDngZGsTv2G8O$piTVoqHrpTskW8rI1FBT9rzM2dcpjhrcOfI3pDgtjbo= \N f business2 Business Second business2@cvat.org f t 2021-12-14 18:34:01+00 12 pbkdf2_sha256$260000$KcNRm6RwodBaGfWhh7ngqB$SsKUBx7vF1Ee0WBDwExd/JI39w1Ee0cBoox7lqNbhCk= \N f business3 Business Third business3@cvat.org f t 2021-12-14 18:34:34+00 13 pbkdf2_sha256$260000$BbdWU6TKtVfIAW00Dk4Qyb$mkqf3VrZULPrzfH5NFEeEYCnzBPLaBBsVgdeTCve4lA= \N f business4 Business Fourth business4@cvat.org f t 2021-12-14 18:35:15+00 14 pbkdf2_sha256$260000$47xWwGNsT3dvBc1qZDcLvC$fLC2y72acILlhGFiVp3nA/bnCxY/mRb7xOqOh7Es95k= \N f dummy1 Dummy First dummy1@cvat.org f t 2021-12-14 18:36:00+00 @@ -2650,8 +2649,9 @@ COPY public.auth_user (id, password, last_login, is_superuser, username, first_n 18 pbkdf2_sha256$260000$uOqP32bk2zHuvO0sdGBGmu$hMbzA1yBWcY5rIU670sZ3SHXRLUa7bCkbrMnrEDGSRM= \N t admin2 Admin Second admin2@cvat.org t t 2021-12-14 18:38:46+00 6 pbkdf2_sha256$260000$15iUjDNh5gPg5683u1HhOG$fF8hW6AR90o9SCsO/MomzdQFkgQsMUW3YQUlwwiC1vA= 2021-12-14 19:11:21.04874+00 f worker1 Worker First worker1@cvat.org f t 2021-12-14 18:30:00+00 10 pbkdf2_sha256$260000$X4F89IRqnBtojZuHidrwQG$j1+EpXfyvMesHdod4N+dNUfF4WKS2NWFfeGDec/43as= 2022-01-19 13:52:59.477881+00 f business1 Business First business1@cvat.org f t 2021-12-14 18:33:06+00 -1 pbkdf2_sha256$260000$DevmxlmLwciP1P6sZs2Qag$U9DFtjTWx96Sk95qY6UXVcvpdQEP2LcoFBftk5D2RKY= 2022-02-11 14:54:28.083729+00 t admin1 Admin First admin1@cvat.org t t 2021-12-14 18:04:57+00 2 pbkdf2_sha256$260000$Pf2xYWXBedoAJ504jyDD8e$8sJ244Ai0xhZrUTelapPNHlEg7CV0cCUaxbcxZtfaug= 2022-02-16 06:24:53.910205+00 f user1 User First user1@cvat.org f t 2021-12-14 18:21:09+00 +11 pbkdf2_sha256$260000$Zw76ANIvIsDngZGsTv2G8O$piTVoqHrpTskW8rI1FBT9rzM2dcpjhrcOfI3pDgtjbo= 2022-02-21 10:29:16.518442+00 f business2 Business Second business2@cvat.org f t 2021-12-14 18:34:01+00 +1 pbkdf2_sha256$260000$DevmxlmLwciP1P6sZs2Qag$U9DFtjTWx96Sk95qY6UXVcvpdQEP2LcoFBftk5D2RKY= 2022-02-21 10:37:08.94795+00 t admin1 Admin First admin1@cvat.org t t 2021-12-14 18:04:57+00 \. @@ -2691,8 +2691,8 @@ COPY public.auth_user_user_permissions (id, user_id, permission_id) FROM stdin; COPY public.authtoken_token (key, created, user_id) FROM stdin; a959159194c4b5238d95ef4e07919110fab346b0 2021-12-14 19:44:48.519942+00 10 -f952dc4730c58346882176f775ccaaf2a1c72416 2021-12-22 08:11:58.498742+00 1 e28ce88d18614314476dec601b5c29b9093045f1 2022-02-16 06:24:53.902374+00 2 +48372a6aac494a9932803913ec9088216778b57c 2022-02-21 10:37:08.935479+00 1 \. @@ -2919,6 +2919,8 @@ po0rbd1yhywmc0i2jfam69r419a66aj8 .eJxVjMsOwiAQRf-FtSE8pB1cuvcbCMMMUjWQlHZl_HdD0o v28l0efbrv9x06z97ilwcf7lwtuf4ctc .eJxVjDsOwjAQRO_iGlm22fhDSc8ZrLV3gwPIluKkQtydREoBzRTz3sxbRFyXEtfOc5xIXIRW4vRbJsxPrjuhB9Z7k7nVZZ6S3BV50C5vjfh1Pdy_g4K9bGuLXqMDQqdDTtYN6AHIIoGGMIJlQwxB-VFn3gLPzjil3ABkAIBZfL7_vTer:1nABOV:0UAK9VV6D18QF1-189XQ2T9LrQUSdioGNoHdRUzzt7o 2022-02-02 13:52:59.489923+00 wf6d6vzf4u74l08o0qgbqehei21hibea .eJxVjDEOwjAMRe-SGUUkpHZgZO8ZIttxSAG1UtNOiLtDpQ6w_vfef5lE61LT2nROQzYX48zhd2OSh44byHcab5OVaVzmge2m2J02209Zn9fd_Tuo1Oq3DrGwD040Ro_-nJmJgkgsqAAIioCi0KGKMhU4Mgip6wjRF6JyMu8PBAI5Mw:1nIXJc:oovNJRods5cbviWOWush4H3jDdP8XklEignva_EnQ8Q 2022-02-25 14:54:28.092369+00 9rh2r15lb3xra3kdqjtll5n4zw7ebw95 .eJxVjDsOwjAQBe_iGln-xJ9Q0ucM1q69xgFkS3FSIe5OIqWA9s3Me7MA21rC1mkJc2JXptjld0OIT6oHSA-o98Zjq-syIz8UftLOp5bodTvdv4MCvey1tOiNIpGNATlq55IZvaABd4Ao1RCzARLWJ5dRA2kJGqxU1kfjPRL7fAHVPjfN:1nKDkD:rZM4YHHS9MckfKB5KrsvNBVMegYSRdlMqL3uDJRE0Cg 2022-03-02 06:24:53.914069+00 +khn67dfajnzkr0tlusuyyub011gt0aqb .eJxVjMEOwiAQRP-FsyEsobL16N1vIAvLStVAUtqT8d9tkx70OPPezFsFWpcS1p7nMLG6KFCn3y5Seua6A35QvTedWl3mKepd0Qft-tY4v66H-3dQqJdt7UU4MedxGKwFh0RoCBFi4niOTsSAHQFA_JaYmI2nTBEdGRRrSX2-CY443A:1nM5q9:nf3N_nhzYu-XfEC1O2ICYP2Zt9XXPPVziSLrP5uHSVw 2022-03-07 10:22:45.432248+00 +5x9v6r58e4l9if78anupog0ittsq2w3j .eJxVjMEOwiAQRP-FsyEsobL16N1vIAvLStVAUtqT8d9tkx70OPPezFsFWpcS1p7nMLG6KFCn3y5Seua6A35QvTedWl3mKepd0Qft-tY4v66H-3dQqJdt7UU4MedxGKwFh0RoCBFi4niOTsSAHQFA_JaYmI2nTBEdGRRrSX2-CY443A:1nM644:zfo0j_Zkrm04UxrDj8g_nnsXrpWtRzL4oRx3hAdqyqI 2022-03-07 10:37:08.963511+00 \. @@ -3306,6 +3308,17 @@ COPY public.engine_clientfile (id, file, data_id) FROM stdin; 359 /home/django/data/data/4/raw/134.png 4 360 /home/django/data/data/5/raw/test_video_1.mp4 5 361 /home/django/data/data/6/raw/test_pointcloud_pcd.zip 6 +362 /home/django/data/data/7/raw/10.png 7 +363 /home/django/data/data/7/raw/3.png 7 +364 /home/django/data/data/7/raw/9.png 7 +365 /home/django/data/data/7/raw/6.png 7 +366 /home/django/data/data/7/raw/0.png 7 +367 /home/django/data/data/7/raw/8.png 7 +368 /home/django/data/data/7/raw/2.png 7 +369 /home/django/data/data/7/raw/7.png 7 +370 /home/django/data/data/7/raw/5.png 7 +371 /home/django/data/data/7/raw/1.png 7 +372 /home/django/data/data/7/raw/4.png 7 \. @@ -3342,6 +3355,7 @@ COPY public.engine_data (id, chunk_size, size, image_quality, start_frame, stop_ 4 72 58 70 0 57 imageset imageset cache local \N lexicographical 5 72 25 70 0 24 imageset video cache local \N lexicographical 6 72 1 70 0 0 imageset imageset cache local \N lexicographical +7 72 11 70 0 10 imageset imageset cache local \N lexicographical \. @@ -3710,6 +3724,17 @@ COPY public.engine_image (id, path, frame, height, width, data_id) FROM stdin; 358 98.png 56 370 615 4 359 99.png 57 545 343 4 360 test_pointcloud_pcd/pointcloud/000001.pcd 0 1 100 6 +361 0.png 0 983 827 7 +362 1.png 1 547 467 7 +363 10.png 2 202 598 7 +364 2.png 3 276 449 7 +365 3.png 4 999 170 7 +366 4.png 5 471 473 7 +367 5.png 6 745 607 7 +368 6.png 7 578 853 7 +369 7.png 8 270 823 7 +370 8.png 9 179 545 7 +371 9.png 10 932 827 7 \. @@ -3730,14 +3755,15 @@ COPY public.engine_issue (id, frame, "position", created_date, updated_date, job -- COPY public.engine_job (id, segment_id, assignee_id, status, stage, state) FROM stdin; -3 3 \N annotation annotation new 5 5 \N validation acceptance new 2 2 6 annotation annotation new -1 1 \N annotation annotation new 6 6 7 annotation annotation new 4 4 \N validation validation new -7 7 \N annotation annotation new 8 8 \N annotation annotation new +1 1 1 annotation annotation new +9 9 \N annotation annotation in progress +3 3 \N annotation annotation in progress +7 7 9 annotation annotation in progress \. @@ -3761,6 +3787,18 @@ COPY public.engine_jobcommit (id, version, "timestamp", message, owner_id, job_i 13 4 2021-12-22 07:22:30.331021+00 Changes: tags - 0; shapes - 0; tracks - 0 \N 4 14 5 2021-12-22 07:22:30.362857+00 Changes: tags - 0; shapes - 0; tracks - 0 \N 4 15 6 2021-12-22 07:22:30.388715+00 Changes: tags - 0; shapes - 0; tracks - 0 \N 4 +16 1 2022-02-21 10:32:04.068136+00 Changes: tags - 0; shapes - 1; tracks - 0 \N 9 +17 2 2022-02-21 10:32:04.169838+00 Changes: tags - 0; shapes - 0; tracks - 0 \N 9 +18 3 2022-02-21 10:32:04.256121+00 Changes: tags - 0; shapes - 0; tracks - 0 \N 9 +19 1 2022-02-21 10:37:22.961448+00 Changes: tags - 0; shapes - 0; tracks - 0 \N 3 +20 2 2022-02-21 10:37:23.075321+00 Changes: tags - 0; shapes - 0; tracks - 0 \N 3 +21 3 2022-02-21 10:37:23.187161+00 Changes: tags - 0; shapes - 0; tracks - 0 \N 3 +22 4 2022-02-21 10:37:27.7082+00 Changes: tags - 0; shapes - 1; tracks - 0 \N 3 +23 5 2022-02-21 10:37:27.834371+00 Changes: tags - 0; shapes - 0; tracks - 0 \N 3 +24 6 2022-02-21 10:37:27.95231+00 Changes: tags - 0; shapes - 0; tracks - 0 \N 3 +25 1 2022-02-21 10:40:21.267763+00 Changes: tags - 0; shapes - 1; tracks - 0 \N 7 +26 2 2022-02-21 10:40:21.354689+00 Changes: tags - 0; shapes - 0; tracks - 0 \N 7 +27 3 2022-02-21 10:40:21.435822+00 Changes: tags - 0; shapes - 0; tracks - 0 \N 7 \. @@ -3779,6 +3817,8 @@ COPY public.engine_label (id, name, task_id, color, project_id) FROM stdin; 8 dog \N #406040 2 9 car 5 #2080c0 \N 10 car 6 #2080c0 \N +11 cat 7 #6080c0 \N +12 dog 7 #406040 \N \. @@ -3829,6 +3869,9 @@ COPY public.engine_labeledshape (id, frame, "group", type, occluded, z_order, po 24 52 0 rectangle f 0 85.9462890625,138.1123046875,166.75294494628906,391.78382873535156 4 5 manual 0 25 52 0 rectangle f 0 45.03125,638.294921875,224.0333251953125,770.2450256347656 4 5 manual 0 26 53 0 rectangle f 0 34.384765625,283.845703125,108.30784606933594,382.30724334716797 4 5 manual 0 +27 0 0 polygon f 0 448.3779296875,356.4892578125,438.2558352402775,761.3861556064112,744.1780320366161,319.37356979405195,446.1288329519466,163.03832951945333 9 11 manual 0 +28 0 0 polyline f 0 155.5,634.4814453125,222.17951945080313,575.8846681922205,214.09713958810244,384.9384439359292,160.55137299771377,255.6203661327254,96.90263157894879,137.41556064073302,284.8179633867294,65.68443935926916,354.52848970251944,264.713043478263 3 6 manual 0 +29 0 0 polygon f 0 364.0361328125,528.87890625,609.5286041189956,586.544622425632,835.2494279176244,360.0000000000018,543.6247139588122,175.4691075514893,326.9656750572103,192.76887871853796,244.58581235698148,319.63386727689067 7 9 manual 0 \. @@ -3935,6 +3978,7 @@ COPY public.engine_segment (id, start_frame, stop_frame, task_id) FROM stdin; 6 0 57 4 7 0 24 5 8 0 0 6 +9 0 10 7 \. @@ -3954,9 +3998,10 @@ COPY public.engine_task (id, name, mode, created_date, updated_date, status, bug 2 task2 annotation 2021-12-14 18:50:29.458488+00 2021-12-22 07:14:15.234748+00 annotation 2 0 \N 23 \N 2 2d 1 1 task1 annotation 2021-12-14 18:43:47.601289+00 2021-12-22 07:15:22.942484+00 annotation 2 0 \N 130 \N 1 2d \N 4 task1_in_project2 annotation 2021-12-14 19:55:57.475273+00 2021-12-22 07:17:34.836384+00 annotation 10 0 \N 58 2 4 2d train 2 -3 task1_in_project1 annotation 2021-12-14 19:48:33.089778+00 2021-12-22 07:19:33.85476+00 annotation 10 0 \N 50 1 3 2d Train \N -5 task2 interpolation 2022-02-16 06:25:48.168612+00 2022-02-16 06:25:48.889352+00 annotation 2 5 \N 25 \N 5 2d \N 6 task3 annotation 2022-02-16 06:26:54.631217+00 2022-02-16 06:26:54.836403+00 annotation 2 0 \N 1 \N 6 3d \N +3 task1_in_project1 annotation 2021-12-14 19:48:33.089778+00 2022-02-21 10:37:27.697705+00 annotation 10 0 \N 50 1 3 2d Train \N +5 task2 interpolation 2022-02-16 06:25:48.168612+00 2022-02-21 10:40:21.257604+00 annotation 2 5 \N 25 \N 5 2d \N +7 task_2_org2 annotation 2022-02-21 10:31:52.429478+00 2022-02-21 10:41:38.540427+00 annotation 11 0 7 11 \N 7 2d 2 \. @@ -4102,7 +4147,7 @@ SELECT pg_catalog.setval('public.auth_permission_id_seq', 88, true); -- Name: auth_user_groups_id_seq; Type: SEQUENCE SET; Schema: public; Owner: root -- -SELECT pg_catalog.setval('public.auth_user_groups_id_seq', 44, true); +SELECT pg_catalog.setval('public.auth_user_groups_id_seq', 47, true); -- @@ -4158,7 +4203,7 @@ SELECT pg_catalog.setval('public.engine_attributespec_id_seq', 1, true); -- Name: engine_clientfile_id_seq; Type: SEQUENCE SET; Schema: public; Owner: root -- -SELECT pg_catalog.setval('public.engine_clientfile_id_seq', 361, true); +SELECT pg_catalog.setval('public.engine_clientfile_id_seq', 372, true); -- @@ -4179,14 +4224,14 @@ SELECT pg_catalog.setval('public.engine_comment_id_seq', 6, true); -- Name: engine_data_id_seq; Type: SEQUENCE SET; Schema: public; Owner: root -- -SELECT pg_catalog.setval('public.engine_data_id_seq', 6, true); +SELECT pg_catalog.setval('public.engine_data_id_seq', 7, true); -- -- Name: engine_image_id_seq; Type: SEQUENCE SET; Schema: public; Owner: root -- -SELECT pg_catalog.setval('public.engine_image_id_seq', 360, true); +SELECT pg_catalog.setval('public.engine_image_id_seq', 371, true); -- @@ -4200,21 +4245,21 @@ SELECT pg_catalog.setval('public.engine_issue_id_seq', 4, true); -- Name: engine_job_id_seq; Type: SEQUENCE SET; Schema: public; Owner: root -- -SELECT pg_catalog.setval('public.engine_job_id_seq', 8, true); +SELECT pg_catalog.setval('public.engine_job_id_seq', 9, true); -- -- Name: engine_jobcommit_id_seq; Type: SEQUENCE SET; Schema: public; Owner: root -- -SELECT pg_catalog.setval('public.engine_jobcommit_id_seq', 15, true); +SELECT pg_catalog.setval('public.engine_jobcommit_id_seq', 27, true); -- -- Name: engine_label_id_seq; Type: SEQUENCE SET; Schema: public; Owner: root -- -SELECT pg_catalog.setval('public.engine_label_id_seq', 10, true); +SELECT pg_catalog.setval('public.engine_label_id_seq', 12, true); -- @@ -4235,7 +4280,7 @@ SELECT pg_catalog.setval('public.engine_labeledimageattributeval_id_seq', 1, fal -- Name: engine_labeledshape_id_seq; Type: SEQUENCE SET; Schema: public; Owner: root -- -SELECT pg_catalog.setval('public.engine_labeledshape_id_seq', 26, true); +SELECT pg_catalog.setval('public.engine_labeledshape_id_seq', 29, true); -- @@ -4298,7 +4343,7 @@ SELECT pg_catalog.setval('public.engine_remotefile_id_seq', 1, false); -- Name: engine_segment_id_seq; Type: SEQUENCE SET; Schema: public; Owner: root -- -SELECT pg_catalog.setval('public.engine_segment_id_seq', 8, true); +SELECT pg_catalog.setval('public.engine_segment_id_seq', 9, true); -- @@ -4312,7 +4357,7 @@ SELECT pg_catalog.setval('public.engine_serverfile_id_seq', 1, false); -- Name: engine_task_id_seq; Type: SEQUENCE SET; Schema: public; Owner: root -- -SELECT pg_catalog.setval('public.engine_task_id_seq', 6, true); +SELECT pg_catalog.setval('public.engine_task_id_seq', 7, true); -- diff --git a/tests/rest_api/assets/jobs.json b/tests/rest_api/assets/jobs.json index 7da9c015a3e..d742c3c4756 100644 --- a/tests/rest_api/assets/jobs.json +++ b/tests/rest_api/assets/jobs.json @@ -1,10 +1,16 @@ { - "count": 8, + "count": 9, "next": null, "previous": null, "results": [ { - "assignee": null, + "assignee": { + "first_name": "Admin", + "id": 1, + "last_name": "First", + "url": "http://localhost:8080/api/users/1", + "username": "admin1" + }, "bug_tracker": null, "data_chunk_size": 72, "data_compressed_chunk_type": "imageset", @@ -109,7 +115,7 @@ "project_id": 1, "stage": "annotation", "start_frame": 0, - "state": "new", + "state": "in progress", "status": "annotation", "stop_frame": 49, "task_id": 3, @@ -241,7 +247,13 @@ "url": "http://localhost:8080/api/jobs/6" }, { - "assignee": null, + "assignee": { + "first_name": "Worker", + "id": 9, + "last_name": "Fourth", + "url": "http://localhost:8080/api/users/9", + "username": "worker4" + }, "bug_tracker": null, "data_chunk_size": 72, "data_compressed_chunk_type": "imageset", @@ -259,7 +271,7 @@ "project_id": null, "stage": "annotation", "start_frame": 0, - "state": "new", + "state": "in progress", "status": "annotation", "stop_frame": 24, "task_id": 5, @@ -289,6 +301,37 @@ "stop_frame": 0, "task_id": 6, "url": "http://localhost:8080/api/jobs/8" + }, + { + "assignee": null, + "bug_tracker": null, + "data_chunk_size": 72, + "data_compressed_chunk_type": "imageset", + "dimension": "2d", + "id": 9, + "labels": [ + { + "attributes": [], + "color": "#6080c0", + "id": 11, + "name": "cat" + }, + { + "attributes": [], + "color": "#406040", + "id": 12, + "name": "dog" + } + ], + "mode": "annotation", + "project_id": null, + "stage": "annotation", + "start_frame": 0, + "state": "in progress", + "status": "annotation", + "stop_frame": 10, + "task_id": 7, + "url": "http://localhost:8080/api/jobs/9" } ] } \ No newline at end of file diff --git a/tests/rest_api/assets/tasks.json b/tests/rest_api/assets/tasks.json index fc7247156bb..e68fb4257dd 100644 --- a/tests/rest_api/assets/tasks.json +++ b/tests/rest_api/assets/tasks.json @@ -1,8 +1,74 @@ { - "count": 6, + "count": 7, "next": null, "previous": null, "results": [ + { + "assignee": { + "first_name": "Worker", + "id": 7, + "last_name": "Second", + "url": "http://localhost:8080/api/users/7", + "username": "worker2" + }, + "bug_tracker": "", + "created_date": "2022-02-21T10:31:52.429478Z", + "data": 7, + "data_chunk_size": 72, + "data_compressed_chunk_type": "imageset", + "data_original_chunk_type": "imageset", + "dimension": "2d", + "id": 7, + "image_quality": 70, + "labels": [ + { + "attributes": [], + "color": "#6080c0", + "id": 11, + "name": "cat" + }, + { + "attributes": [], + "color": "#406040", + "id": 12, + "name": "dog" + } + ], + "mode": "annotation", + "name": "task_2_org2", + "organization": 2, + "overlap": 0, + "owner": { + "first_name": "Business", + "id": 11, + "last_name": "Second", + "url": "http://localhost:8080/api/users/11", + "username": "business2" + }, + "project_id": null, + "segment_size": 11, + "segments": [ + { + "jobs": [ + { + "assignee": null, + "id": 9, + "stage": "annotation", + "state": "in progress", + "status": "annotation", + "url": "http://localhost:8080/api/jobs/9" + } + ], + "start_frame": 0, + "stop_frame": 10 + } + ], + "size": 11, + "status": "annotation", + "subset": "", + "updated_date": "2022-02-21T10:41:38.540427Z", + "url": "http://localhost:8080/api/tasks/7" + }, { "assignee": null, "bug_tracker": "", @@ -93,10 +159,16 @@ { "jobs": [ { - "assignee": null, + "assignee": { + "first_name": "Worker", + "id": 9, + "last_name": "Fourth", + "url": "http://localhost:8080/api/users/9", + "username": "worker4" + }, "id": 7, "stage": "annotation", - "state": "new", + "state": "in progress", "status": "annotation", "url": "http://localhost:8080/api/jobs/7" } @@ -108,7 +180,7 @@ "size": 25, "status": "annotation", "subset": "", - "updated_date": "2022-02-16T06:25:48.889352Z", + "updated_date": "2022-02-21T10:40:21.257604Z", "url": "http://localhost:8080/api/tasks/5" }, { @@ -235,7 +307,7 @@ "assignee": null, "id": 3, "stage": "annotation", - "state": "new", + "state": "in progress", "status": "annotation", "url": "http://localhost:8080/api/jobs/3" } @@ -275,7 +347,7 @@ "size": 148, "status": "annotation", "subset": "Train", - "updated_date": "2021-12-22T07:19:33.854760Z", + "updated_date": "2022-02-21T10:37:27.697705Z", "url": "http://localhost:8080/api/tasks/3" }, { @@ -386,7 +458,13 @@ { "jobs": [ { - "assignee": null, + "assignee": { + "first_name": "Admin", + "id": 1, + "last_name": "First", + "url": "http://localhost:8080/api/users/1", + "username": "admin1" + }, "id": 1, "stage": "annotation", "state": "new", diff --git a/tests/rest_api/assets/users.json b/tests/rest_api/assets/users.json index 65d4ffba9bf..3e7a0b38be7 100644 --- a/tests/rest_api/assets/users.json +++ b/tests/rest_api/assets/users.json @@ -14,7 +14,7 @@ "is_active": true, "is_staff": true, "is_superuser": true, - "last_login": "2022-02-11T14:54:28.083729Z", + "last_login": "2022-02-21T10:37:08.947950Z", "last_name": "First", "url": "http://localhost:8080/api/users/1", "username": "admin1" @@ -174,7 +174,7 @@ "is_active": true, "is_staff": false, "is_superuser": false, - "last_login": null, + "last_login": "2022-02-21T10:29:16.518442Z", "last_name": "Second", "url": "http://localhost:8080/api/users/11", "username": "business2" diff --git a/tests/rest_api/conftest.py b/tests/rest_api/conftest.py index 1ca201a6249..5d3e587ab7b 100644 --- a/tests/rest_api/conftest.py +++ b/tests/rest_api/conftest.py @@ -31,7 +31,7 @@ def drop_test_db(): def create_test_db(): docker_cp(source=osp.join(ASSETS_DIR, 'cvat_db'), target='cvat_db:/') cvat_db_container('createdb test_db') - cvat_db_container('psql -U root -d test_db -f /cvat_db/cvat_db.sql') + cvat_db_container('psql -U root -q -d test_db -f /cvat_db/cvat_db.sql') @pytest.fixture(scope='session', autouse=True) def init_test_db(): @@ -118,6 +118,53 @@ def annotations(): def users_by_name(users): return {user['username']: user for user in users} +@pytest.fixture(scope='module') +def jobs_by_org(tasks, jobs): + data = {} + for job in jobs: + data.setdefault(tasks[job['task_id']]['organization'], []).append(job) + data[''] = data.pop(None, []) + return data + +@pytest.fixture(scope='module') +def assignee_id(): + def get_id(data): + if data.get('assignee') is not None: + return data['assignee']['id'] + return get_id + +def ownership(func): + def wrap(user_id, resource_id): + if resource_id is None: + return False + return func(user_id, resource_id) + return wrap + +@pytest.fixture(scope='module') +def is_project_staff(projects, assignee_id): + @ownership + def check(user_id, pid): + return user_id == projects[pid]['owner']['id'] or \ + user_id == assignee_id(projects[pid]) + return check + +@pytest.fixture(scope='module') +def is_task_staff(tasks, is_project_staff, assignee_id): + @ownership + def check(user_id, tid): + return user_id == tasks[tid]['owner']['id'] or \ + user_id == assignee_id(tasks[tid]) or \ + is_project_staff(user_id, tasks[tid]['project_id']) + return check + +@pytest.fixture(scope='module') +def is_job_staff(jobs, is_task_staff, assignee_id): + @ownership + def check(user_id, jid): + return user_id == assignee_id(jobs[jid]) or \ + is_task_staff(user_id, jobs[jid]['task_id']) + return check + @pytest.fixture(scope='module') def find_users(test_db): def find(**kwargs): @@ -158,3 +205,34 @@ def add_row(**kwargs): membership_id=membership['id']) return data + +@pytest.fixture(scope='module') +def org_staff(memberships): + def find(org_id): + if org_id in ['', None]: + return set() + else: + return set(m['user']['id'] for m in memberships + if m['role'] in ['maintainer', 'owner'] and m['user'] != None + and m['organization'] == org_id) + return find + +@pytest.fixture(scope='module') +def is_org_member(memberships): + def check(user_id, org_id): + if org_id in ['', None]: + return True + else: + return user_id in set(m['user']['id'] for m in memberships + if m['user'] != None and m['organization'] == org_id) + return check + +@pytest.fixture(scope='module') +def find_job_staff_user(is_job_staff): + def find(jobs, users, is_staff): + for job in jobs: + for user in users: + if is_staff == is_job_staff(user['id'], job['id']): + return user['username'], job['id'] + return None, None + return find \ No newline at end of file diff --git a/tests/rest_api/test_0100_jobs.py b/tests/rest_api/test_0100_jobs.py deleted file mode 100644 index 887d768cbb6..00000000000 --- a/tests/rest_api/test_0100_jobs.py +++ /dev/null @@ -1,170 +0,0 @@ -# Copyright (C) 2021 Intel Corporation -# -# SPDX-License-Identifier: MIT - -from http import HTTPStatus -from deepdiff import DeepDiff -import pytest -from .utils.config import get_method - -def get_job_staff(job, tasks, projects): - job_staff = [] - job_staff.append(job['assignee']) - tid = job['task_id'] - job_staff.append(tasks[tid]['owner']) - job_staff.append(tasks[tid]['assignee']) - - pid = job['project_id'] - if pid: - job_staff.append(projects[pid]['owner']) - job_staff.append(projects[pid]['assignee']) - job_staff = set(u['id'] for u in job_staff if u is not None) - - return job_staff - -def get_org_staff(org_id, memberships): - if org_id in ['', None]: - return set() - else: - return set(m['user']['id'] for m in memberships - if m['role'] in ['maintainer', 'owner'] and m['user'] != None - and m['organization'] == org_id) - -def filter_jobs(jobs, tasks, org): - if org is None: - kwargs = {} - jobs = jobs.raw - elif org == '': - kwargs = {'org': ''} - jobs = [job for job in jobs - if tasks[job['task_id']]['organization'] is None] - else: - kwargs = {'org_id': org} - jobs = [job for job in jobs - if tasks[job['task_id']]['organization'] == org] - - return jobs, kwargs - -def is_org_member(memberships, user, org_id): - if org_id in ['', None]: - return True - else: - return user['id'] in set(m['user']['id'] for m in memberships - if m['user'] != None and m['organization'] == org_id) - -class TestGetJobs: - def _test_get_job_200(self, user, jid, data, **kwargs): - response = get_method(user, f'jobs/{jid}', **kwargs) - - assert response.status_code == HTTPStatus.OK - assert DeepDiff(data, response.json()) == {} - - def _test_get_job_403(self, user, jid, **kwargs): - response = get_method(user, f'jobs/{jid}', **kwargs) - assert response.status_code == HTTPStatus.FORBIDDEN - - @pytest.mark.parametrize('org', [None, '', 1, 2]) - def test_admin_get_job(self, jobs, tasks, org): - jobs, kwargs = filter_jobs(jobs, tasks, org) - - # keep only the reasonable amount of jobs - for job in jobs[:8]: - self._test_get_job_200('admin2', job['id'], job, **kwargs) - - @pytest.mark.parametrize('org_id', ['', None, 1, 2]) - @pytest.mark.parametrize('groups', [['business'], ['user'], ['worker'], []]) - def test_non_admin_get_job(self, org_id, groups, users, jobs, tasks, projects, - memberships): - # keep the reasonable amount of users and jobs - users = [u for u in users if u['groups'] == groups][:4] - jobs, kwargs = filter_jobs(jobs, tasks, org_id) - org_staff = get_org_staff(org_id, memberships) - - for job in jobs[:8]: - job_staff = get_job_staff(job, tasks, projects) - - # check if the specific user in job_staff to see the job - for user in users: - if user['id'] in job_staff | org_staff: - self._test_get_job_200(user['username'], job['id'], job, **kwargs) - else: - self._test_get_job_403(user['username'], job['id'], **kwargs) - -class TestListJobs: - def _test_list_jobs_200(self, user, data, **kwargs): - response = get_method(user, 'jobs', **kwargs, page_size=all) - - assert response.status_code == HTTPStatus.OK - assert DeepDiff(data, response.json()['results']) == {} - - def _test_list_jobs_403(self, user, **kwargs): - response = get_method(user, 'jobs', **kwargs) - assert response.status_code == HTTPStatus.FORBIDDEN - - @pytest.mark.parametrize('org', [None, '', 1, 2]) - def test_admin_list_jobs(self, jobs, tasks, org): - jobs, kwargs = filter_jobs(jobs, tasks, org) - self._test_list_jobs_200('admin1', jobs, **kwargs) - - @pytest.mark.parametrize('org_id', ['', None, 1, 2]) - @pytest.mark.parametrize('groups', [['business'], ['user'], ['worker'], []]) - def test_non_admin_list_jobs(self, org_id, groups, users, jobs, tasks, - projects, memberships): - # keep the reasonable amount of users and jobs - users = [u for u in users if u['groups'] == groups][:2] - jobs, kwargs = filter_jobs(jobs, tasks, org_id) - org_staff = get_org_staff(org_id, memberships) - - for user in users: - user_jobs = [] - for job in jobs: - job_staff = get_job_staff(job, tasks, projects) - if user['id'] in job_staff | org_staff: - user_jobs.append(job) - if is_org_member(memberships, user, org_id): - self._test_list_jobs_200(user['username'], user_jobs, **kwargs) - else: - self._test_list_jobs_403(user['username'], **kwargs) - - -class TestGetAnnotations: - def _test_get_job_annotations_200(self, user, jid, data, **kwargs): - response = get_method(user, f'jobs/{jid}/annotations', **kwargs) - - assert response.status_code == HTTPStatus.OK - assert DeepDiff(data, response.json()) == {} - - def _test_get_job_annotations_403(self, user, jid, **kwargs): - response = get_method(user, f'jobs/{jid}/annotations', **kwargs) - assert response.status_code == HTTPStatus.FORBIDDEN - - @pytest.mark.parametrize('org', [None, '', 1, 2]) - def test_admin_get_job_annotations(self, jobs, tasks, annotations, org): - jobs, kwargs = filter_jobs(jobs, tasks, org) - - # keep only the reasonable amount of jobs - for job in jobs[:8]: - jid = str(job['id']) - self._test_get_job_annotations_200('admin2', jid, - annotations['job'][jid], **kwargs) - - @pytest.mark.parametrize('org_id', ['', None]) - @pytest.mark.parametrize('groups', [['business'], ['user'], ['worker'], []]) - def test_non_admin_get_job_annotations(self, org_id, groups, users, jobs, tasks, - projects, annotations, memberships): - users = [u for u in users if u['groups'] == groups][:4] - jobs, kwargs = filter_jobs(jobs, tasks, org_id) - org_staff = get_org_staff(org_id, memberships) - - # keep only the reasonable amount of jobs - for job in jobs[:8]: - job_staff = get_job_staff(job, tasks, projects) - jid = str(job['id']) - - for user in users: - if user['id'] in job_staff | org_staff: - self._test_get_job_annotations_200(user['username'], - jid, annotations['job'][jid], **kwargs) - else: - self._test_get_job_annotations_403(user['username'], - jid, **kwargs) diff --git a/tests/rest_api/test_0004_analytics.py b/tests/rest_api/test_analytics.py similarity index 100% rename from tests/rest_api/test_0004_analytics.py rename to tests/rest_api/test_analytics.py diff --git a/tests/rest_api/test_0005_chache_policy.py b/tests/rest_api/test_chache_policy.py similarity index 100% rename from tests/rest_api/test_0005_chache_policy.py rename to tests/rest_api/test_chache_policy.py diff --git a/tests/rest_api/test_0000_check_objects_integrity.py b/tests/rest_api/test_check_objects_integrity.py similarity index 100% rename from tests/rest_api/test_0000_check_objects_integrity.py rename to tests/rest_api/test_check_objects_integrity.py diff --git a/tests/rest_api/test_0050_invitations.py b/tests/rest_api/test_invitations.py similarity index 100% rename from tests/rest_api/test_0050_invitations.py rename to tests/rest_api/test_invitations.py diff --git a/tests/rest_api/test_jobs.py b/tests/rest_api/test_jobs.py new file mode 100644 index 00000000000..e17775afec6 --- /dev/null +++ b/tests/rest_api/test_jobs.py @@ -0,0 +1,306 @@ +# Copyright (C) 2021 Intel Corporation +# +# SPDX-License-Identifier: MIT + +from http import HTTPStatus +from deepdiff import DeepDiff +import pytest +from .utils.config import get_method, patch_method + +def get_job_staff(job, tasks, projects): + job_staff = [] + job_staff.append(job['assignee']) + tid = job['task_id'] + job_staff.append(tasks[tid]['owner']) + job_staff.append(tasks[tid]['assignee']) + + pid = job['project_id'] + if pid: + job_staff.append(projects[pid]['owner']) + job_staff.append(projects[pid]['assignee']) + job_staff = set(u['id'] for u in job_staff if u is not None) + + return job_staff + +def filter_jobs(jobs, tasks, org): + if org is None: + kwargs = {} + jobs = jobs.raw + elif org == '': + kwargs = {'org': ''} + jobs = [job for job in jobs + if tasks[job['task_id']]['organization'] is None] + else: + kwargs = {'org_id': org} + jobs = [job for job in jobs + if tasks[job['task_id']]['organization'] == org] + + return jobs, kwargs + +class TestGetJobs: + def _test_get_job_200(self, user, jid, data, **kwargs): + response = get_method(user, f'jobs/{jid}', **kwargs) + + assert response.status_code == HTTPStatus.OK + assert DeepDiff(data, response.json()) == {} + + def _test_get_job_403(self, user, jid, **kwargs): + response = get_method(user, f'jobs/{jid}', **kwargs) + assert response.status_code == HTTPStatus.FORBIDDEN + + @pytest.mark.parametrize('org', [None, '', 1, 2]) + def test_admin_get_job(self, jobs, tasks, org): + jobs, kwargs = filter_jobs(jobs, tasks, org) + + # keep only the reasonable amount of jobs + for job in jobs[:8]: + self._test_get_job_200('admin2', job['id'], job, **kwargs) + + @pytest.mark.parametrize('org_id', ['', None, 1, 2]) + @pytest.mark.parametrize('groups', [['business'], ['user'], ['worker'], []]) + def test_non_admin_get_job(self, org_id, groups, users, jobs, tasks, projects, + org_staff): + # keep the reasonable amount of users and jobs + users = [u for u in users if u['groups'] == groups][:4] + jobs, kwargs = filter_jobs(jobs, tasks, org_id) + org_staff = org_staff(org_id) + + for job in jobs[:8]: + job_staff = get_job_staff(job, tasks, projects) + + # check if the specific user in job_staff to see the job + for user in users: + if user['id'] in job_staff | org_staff: + self._test_get_job_200(user['username'], job['id'], job, **kwargs) + else: + self._test_get_job_403(user['username'], job['id'], **kwargs) + +class TestListJobs: + def _test_list_jobs_200(self, user, data, **kwargs): + response = get_method(user, 'jobs', **kwargs, page_size=all) + + assert response.status_code == HTTPStatus.OK + assert DeepDiff(data, response.json()['results']) == {} + + def _test_list_jobs_403(self, user, **kwargs): + response = get_method(user, 'jobs', **kwargs) + assert response.status_code == HTTPStatus.FORBIDDEN + + @pytest.mark.parametrize('org', [None, '', 1, 2]) + def test_admin_list_jobs(self, jobs, tasks, org): + jobs, kwargs = filter_jobs(jobs, tasks, org) + self._test_list_jobs_200('admin1', jobs, **kwargs) + + @pytest.mark.parametrize('org_id', ['', None, 1, 2]) + @pytest.mark.parametrize('groups', [['business'], ['user'], ['worker'], []]) + def test_non_admin_list_jobs(self, org_id, groups, users, jobs, tasks, + projects, org_staff, is_org_member): + users = [u for u in users if u['groups'] == groups][:2] + jobs, kwargs = filter_jobs(jobs, tasks, org_id) + org_staff = org_staff(org_id) + + for user in users: + user_jobs = [] + for job in jobs: + job_staff = get_job_staff(job, tasks, projects) + if user['id'] in job_staff | org_staff: + user_jobs.append(job) + if is_org_member(user['id'], org_id): + self._test_list_jobs_200(user['username'], user_jobs, **kwargs) + else: + self._test_list_jobs_403(user['username'], **kwargs) + + +class TestGetAnnotations: + def _test_get_job_annotations_200(self, user, jid, data, **kwargs): + response = get_method(user, f'jobs/{jid}/annotations', **kwargs) + + assert response.status_code == HTTPStatus.OK + assert DeepDiff(data, response.json()) == {} + + def _test_get_job_annotations_403(self, user, jid, **kwargs): + response = get_method(user, f'jobs/{jid}/annotations', **kwargs) + assert response.status_code == HTTPStatus.FORBIDDEN + + @pytest.mark.parametrize('org', ['']) + @pytest.mark.parametrize('groups, 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_get_job_annotations(self, org, groups, job_staff, + is_allow, users, jobs, tasks, annotations, find_job_staff_user): + users = [u for u in users if u['groups'] == groups] + jobs, kwargs = filter_jobs(jobs, tasks, org) + username, job_id = find_job_staff_user(jobs, users, job_staff) + + if is_allow: + self._test_get_job_annotations_200(username, + job_id, annotations['job'][str(job_id)], **kwargs) + else: + self._test_get_job_annotations_403(username, job_id, **kwargs) + + @pytest.mark.parametrize('org', [2]) + @pytest.mark.parametrize('role, job_staff, is_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_member_get_job_annotations(self, org, role, job_staff, is_allow, + jobs, tasks, find_job_staff_user, annotations, find_users): + users = find_users(org=org, role=role) + jobs, kwargs = filter_jobs(jobs, tasks, org) + username, jid = find_job_staff_user(jobs, users, job_staff) + + if is_allow: + self._test_get_job_annotations_200(username, + jid, annotations['job'][str(jid)], **kwargs) + else: + self._test_get_job_annotations_403(username, jid, **kwargs) + + @pytest.mark.parametrize('org', [1]) + @pytest.mark.parametrize('privilege, is_allow', [ + ('admin', True), ('business', False), ('worker', False), ('user', False) + ]) + def test_non_member_get_job_annotations(self, org, privilege, is_allow, + jobs, tasks, find_job_staff_user, annotations, find_users): + users = find_users(privilege=privilege, exclude_org=org) + jobs, kwargs = filter_jobs(jobs, tasks, org) + username, job_id = find_job_staff_user(jobs, users, False) + + kwargs = {'org_id': org} + if is_allow: + self._test_get_job_annotations_200(username, + job_id, annotations['job'][str(job_id)], **kwargs) + else: + self._test_get_job_annotations_403(username, job_id, **kwargs) + + +class TestPatchJobAnnotations: + _ORG = 2 + + def _test_check_respone(self, is_allow, response, data=None): + if is_allow: + assert response.status_code == HTTPStatus.OK + assert DeepDiff(data, response.json()) == {} + else: + assert response.status_code == HTTPStatus.FORBIDDEN + + @pytest.fixture(scope='class') + def request_data(self, annotations): + def get_data(jid): + data = annotations['job'][str(jid)].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', [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_update_job_annotations(self, org, role, job_staff, is_allow, + find_job_staff_user, find_users, request_data, jobs_by_org): + users = find_users(role=role, org=org) + jobs = jobs_by_org[org] + username, jid = find_job_staff_user(jobs, users, job_staff) + + data = request_data(jid) + response = patch_method(username, f'jobs/{jid}/annotations', + data, org_id=org, action='update') + + self._test_check_respone(is_allow, response, data) + + + @pytest.mark.parametrize('org', [2]) + @pytest.mark.parametrize('privilege, is_allow', [ + ('admin', True), ('business', False), ('worker', False), ('user', False) + ]) + def test_non_member_update_job_annotations(self, org, privilege, is_allow, + find_job_staff_user, find_users, request_data, jobs_by_org): + users = find_users(privilege=privilege, exclude_org=org) + jobs = jobs_by_org[org] + username, jid = find_job_staff_user(jobs, users, False) + + data = request_data(jid) + response = patch_method(username, f'jobs/{jid}/annotations', data, + org_id=org, action='update') + + self._test_check_respone(is_allow, response, data) + + @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_update_job_annotations(self, org, privilege, job_staff, is_allow, + find_job_staff_user, find_users, request_data, jobs_by_org): + users = find_users(privilege=privilege) + jobs = jobs_by_org[org] + username, jid = find_job_staff_user(jobs, users, job_staff) + + data = request_data(jid) + response = patch_method(username, f'jobs/{jid}/annotations', data, + org_id=org, action='update') + + self._test_check_respone(is_allow, response, data) + +class TestPatchJob: + _ORG = 2 + + @pytest.fixture(scope='class') + def find_task_staff_user(self, is_task_staff): + def find(jobs, users, is_staff): + for job in jobs: + for user in users: + if is_staff == is_task_staff(user['id'], job['task_id']): + return user, job['id'] + return None, None + return find + + @pytest.fixture(scope='class') + def expected_data(self, jobs, users): + keys = ['url', 'id', 'username', 'first_name', 'last_name'] + def find(job_id, assignee_id): + data = jobs[job_id].copy() + data['assignee'] = dict(filter(lambda a: a[0] in keys, + users[assignee_id].items())) + return data + return find + + @pytest.fixture(scope='class') + def new_assignee(self, jobs, tasks, assignee_id, org_staff): + def find_new_assignee(jid, user_id): + members = org_staff(tasks[jobs[jid]['task_id']]['organization']) + members -= {assignee_id(jobs[jid]), user_id} + return members.pop() + return find_new_assignee + + @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_job_assignee(self, org, role, task_staff, is_allow, + find_task_staff_user, find_users, jobs_by_org, new_assignee, expected_data): + users, jobs = find_users(role=role, org=org), jobs_by_org[org] + user, jid = find_task_staff_user(jobs, users, task_staff) + + assignee = new_assignee(jid, user['id']) + response = patch_method(user['username'], f'jobs/{jid}', + {'assignee': assignee}, org_id=self._ORG) + + if is_allow: + assert response.status_code == HTTPStatus.OK + assert DeepDiff(expected_data(jid, assignee), response.json()) == {} + else: + assert response.status_code == HTTPStatus.FORBIDDEN diff --git a/tests/rest_api/test_0003_membership.py b/tests/rest_api/test_memberships.py similarity index 100% rename from tests/rest_api/test_0003_membership.py rename to tests/rest_api/test_memberships.py diff --git a/tests/rest_api/test_0002_organizations.py b/tests/rest_api/test_organizations.py similarity index 100% rename from tests/rest_api/test_0002_organizations.py rename to tests/rest_api/test_organizations.py diff --git a/tests/rest_api/test_0001_users.py b/tests/rest_api/test_users.py similarity index 100% rename from tests/rest_api/test_0001_users.py rename to tests/rest_api/test_users.py