1- from datetime import datetime , timedelta , timezone
1+ from datetime import datetime , timedelta
22from unittest .mock import patch
33
4+ import pytest
45from fastapi .testclient import TestClient
56
67from src .core .types import AssignmentStatuses , JobStatuses
8+ from src .utils .time import utcnow
79
10+ from tests .utils .constants import WALLET_ADDRESS1 , WALLET_ADDRESS2
811from tests .utils .setup_cvat import (
9- add_asignment_to_db ,
12+ add_assignment_to_db ,
1013 add_cvat_job_to_db ,
1114 add_cvat_project_to_db ,
1215 add_cvat_task_to_db ,
1316 generate_cvat_signature ,
1417 get_cvat_job_from_db ,
1518)
1619
17- api_url = "http://localhost:8080/api/"
20+ API_URL = "http://localhost:8080/api/"
1821
22+ PING_EVENT_DATA = {
23+ "event" : "ping" ,
24+ }
1925
20- def test_ping_incoming_webhook (client : TestClient ) -> None :
21- data = {
22- "event" : "ping" ,
23- }
24- signature = generate_cvat_signature (data )
2526
27+ def test_ping_incoming_webhook (client : TestClient ) -> None :
2628 # Should respond with 200 status to a "ping" event
2729 response = client .post (
2830 "/cvat-webhook" ,
29- headers = {"X-Signature-256" : signature },
30- json = data ,
31+ headers = {"X-Signature-256" : generate_cvat_signature ( PING_EVENT_DATA ) },
32+ json = PING_EVENT_DATA ,
3133 )
3234
3335 assert response .status_code == 200
@@ -36,13 +38,13 @@ def test_ping_incoming_webhook(client: TestClient) -> None:
3638def test_incoming_webhook_200 (client : TestClient ) -> None :
3739 # Create some entities in test DB
3840 add_cvat_project_to_db (cvat_id = 1 )
39- add_cvat_task_to_db (cvat_id = 1 , cvat_project_id = 1 , status = "annotation" )
41+ add_cvat_task_to_db (cvat_id = 1 , cvat_project_id = 1 )
4042
4143 # Payload for "create:job" event
4244 data = {
4345 "event" : "create:job" ,
4446 "job" : {
45- "url" : api_url + "jobs/1" ,
47+ "url" : API_URL + "jobs/1" ,
4648 "id" : 1 ,
4749 "task_id" : 1 ,
4850 "project_id" : 1 ,
@@ -71,107 +73,130 @@ def test_incoming_webhook_200(client: TestClient) -> None:
7173 assert job .cvat_project_id == 1
7274
7375
74- def test_incoming_webhook_200_update_expired_assignmets (client : TestClient ) -> None :
76+ @pytest .mark .parametrize ("is_last_assignment" , [True , False ])
77+ def test_incoming_webhook_can_update_expired_assignment (
78+ client : TestClient , is_last_assignment : bool
79+ ):
80+ # Check if an "update:job" event can update an expired assignment,
81+ # if the assignment is the last one for the job. Updates to other assignments should be ignored.
82+
7583 add_cvat_project_to_db (cvat_id = 1 )
76- add_cvat_task_to_db (cvat_id = 1 , cvat_project_id = 1 , status = "annotation" )
77- add_cvat_job_to_db (cvat_id = 1 , cvat_task_id = 1 , cvat_project_id = 1 , status = "new" )
78- (job , _ ) = get_cvat_job_from_db (1 )
79- # Check if "update:job" event works with expired assignments
80- wallet_address = "0x86e83d346041E8806e352681f3F14549C0d2BC68"
81- add_asignment_to_db (wallet_address , 1 , job .cvat_id , datetime .now (tz = timezone .utc ))
84+ add_cvat_task_to_db (cvat_id = 1 , cvat_project_id = 1 )
85+ job = add_cvat_job_to_db (
86+ cvat_id = 1 , cvat_task_id = 1 , cvat_project_id = 1 , status = JobStatuses .in_progress
87+ )
88+
89+ user_cvat_id = 1
90+ add_assignment_to_db (WALLET_ADDRESS1 , user_cvat_id , job .cvat_id , expires_at = utcnow ())
91+
92+ if not is_last_assignment :
93+ user_cvat_id += 1
94+ add_assignment_to_db (WALLET_ADDRESS2 , user_cvat_id , job .cvat_id , expires_at = utcnow ())
8295
8396 data = {
8497 "event" : "update:job" ,
8598 "job" : {
86- "url" : api_url + "jobs/1" ,
99+ "url" : API_URL + "jobs/1" ,
87100 "id" : 1 ,
88101 "task_id" : 1 ,
89102 "project_id" : 1 ,
90103 "state" : "completed" ,
91104 "start_frame" : 0 ,
92105 "stop_frame" : 1 ,
93106 "assignee" : {
94- "url" : api_url + "users/1 " ,
95- "id" : 1 ,
107+ "url" : API_URL + f "users/{ user_cvat_id } " ,
108+ "id" : user_cvat_id ,
96109 },
97- "updated_date" : datetime . now ( timezone . utc ).strftime ("%Y-%m-%dT%H:%M:%S.%f" ) + "Z" ,
110+ "updated_date" : ( utcnow () + timedelta ( hours = 1 ) ).strftime ("%Y-%m-%dT%H:%M:%S.%f" ) + "Z" ,
98111 },
99112 "before_update" : {"state" : "new" , "assignee" : None },
100113 "webhook_id" : 1 ,
101114 }
102115
103- signature = generate_cvat_signature (data )
104-
105- with patch ("src.handlers.cvat_events.cvat_api" ):
116+ with patch ("src.handlers.cvat_events.cvat_api.update_job_assignee" ) as mock_update_job_assignee :
106117 response = client .post (
107118 "/cvat-webhook" ,
108- headers = {"X-Signature-256" : signature },
119+ headers = {"X-Signature-256" : generate_cvat_signature ( data ) },
109120 json = data ,
110121 )
111122
112123 assert response .status_code == 200
113124
114- (job , asignees ) = get_cvat_job_from_db (1 )
115- assert job .status == JobStatuses .new .value
116- assert asignees [0 ].status == AssignmentStatuses .expired .value
125+ (job , assignments ) = get_cvat_job_from_db (1 )
126+ assert job .status == JobStatuses .new
127+ assert assignments [- 1 ].status == AssignmentStatuses .expired
128+ mock_update_job_assignee .assert_called_once_with (job .cvat_id , assignee_id = None )
129+
130+ if not is_last_assignment :
131+ for assignment in assignments [:- 1 ]:
132+ assert assignment .status == AssignmentStatuses .created
117133
118134
119- def test_incoming_webhook_200_update (client : TestClient ) -> None :
135+ @pytest .mark .parametrize ("assignment_status" , AssignmentStatuses )
136+ def test_incoming_webhook_can_update_active_assignment (
137+ client : TestClient , assignment_status : AssignmentStatuses
138+ ):
120139 add_cvat_project_to_db (cvat_id = 1 )
121- add_cvat_task_to_db (cvat_id = 1 , cvat_project_id = 1 , status = "annotation" )
122- add_cvat_job_to_db (cvat_id = 1 , cvat_task_id = 1 , cvat_project_id = 1 , status = "new" )
123- (job , _ ) = get_cvat_job_from_db (1 )
124- # Check if "update:job" event works correctly
125- wallet_address = "0x86e83d346041E8806e352681f3F14549C0d2BC69"
126- add_asignment_to_db (wallet_address , 2 , job .cvat_id , datetime .now () + timedelta (hours = 1 ))
140+ add_cvat_task_to_db (cvat_id = 1 , cvat_project_id = 1 )
141+ job = add_cvat_job_to_db (
142+ cvat_id = 1 , cvat_task_id = 1 , cvat_project_id = 1 , status = JobStatuses .in_progress
143+ )
144+ add_assignment_to_db (
145+ WALLET_ADDRESS1 ,
146+ 1 ,
147+ job .cvat_id ,
148+ status = assignment_status ,
149+ expires_at = datetime .now ()
150+ if assignment_status == AssignmentStatuses .expired
151+ else datetime .now () + timedelta (hours = 1 ),
152+ )
127153
128154 data = {
129155 "event" : "update:job" ,
130156 "job" : {
131- "url" : api_url + "jobs/1" ,
157+ "url" : API_URL + "jobs/1" ,
132158 "id" : 1 ,
133159 "task_id" : 1 ,
134160 "project_id" : 1 ,
135161 "state" : "completed" ,
136162 "start_frame" : 0 ,
137163 "stop_frame" : 1 ,
138164 "assignee" : {
139- "url" : api_url + "users/1" ,
140- "id" : 2 ,
165+ "url" : API_URL + "users/1" ,
166+ "id" : 1 ,
141167 },
142- "updated_date" : datetime . now ( timezone . utc ).strftime ("%Y-%m-%dT%H:%M:%S.%f" ) + "Z" ,
168+ "updated_date" : utcnow ( ).strftime ("%Y-%m-%dT%H:%M:%S.%f" ) + "Z" ,
143169 },
144- "before_update" : {"state" : "new " , "assignee" : None },
170+ "before_update" : {"state" : "in_progress " , "assignee" : None },
145171 "webhook_id" : 1 ,
146172 }
147173
148- signature = generate_cvat_signature (data )
149-
150- with patch ("src.handlers.cvat_events.cvat_api" ):
174+ with patch ("src.handlers.cvat_events.cvat_api.update_job_assignee" ) as mock_update_job_assignee :
151175 response = client .post (
152176 "/cvat-webhook" ,
153- headers = {"X-Signature-256" : signature },
177+ headers = {"X-Signature-256" : generate_cvat_signature ( data ) },
154178 json = data ,
155179 )
156180
157181 assert response .status_code == 200
158182
159- (job , asignees ) = get_cvat_job_from_db (1 )
160- assert job .status == JobStatuses .completed .value
161- assert asignees [0 ].status == AssignmentStatuses .completed .value
162-
163-
164- data = {
165- "event" : "ping" ,
166- }
183+ (job , assignments ) = get_cvat_job_from_db (1 )
184+ if assignment_status == AssignmentStatuses .created :
185+ assert job .status == JobStatuses .completed
186+ assert assignments [0 ].status == AssignmentStatuses .completed
187+ mock_update_job_assignee .assert_called_once_with (job .cvat_id , assignee_id = None )
188+ else :
189+ assert job .status == JobStatuses .in_progress
190+ assert assignments [0 ].status == assignment_status
191+ mock_update_job_assignee .assert_not_called ()
167192
168193
169194def test_incoming_webhook_401_bad_signature (client : TestClient ) -> None :
170195 # Send a request with bad signature
171196 response = client .post (
172197 "/cvat-webhook" ,
173198 headers = {"X-Signature-256" : "dummy_signature" },
174- json = data ,
199+ json = PING_EVENT_DATA ,
175200 )
176201 assert response .status_code == 401
177202 assert response .json () == {"message" : "Unauthorized" }
@@ -180,7 +205,7 @@ def test_incoming_webhook_401_bad_signature(client: TestClient) -> None:
180205def test_incoming_webhook_401_without_signature (client : TestClient ) -> None :
181206 response = client .post (
182207 "/cvat-webhook" ,
183- json = data ,
208+ json = PING_EVENT_DATA ,
184209 )
185210
186211 # Send a request without a signature
0 commit comments