diff --git a/tests/integration/cassettes/test_live_readwrite/test_clear_team_iteration_lands_on_a_child_of_a_scheduled_parent.yaml b/tests/integration/cassettes/test_live_readwrite/test_clear_team_iteration_lands_on_a_child_of_a_scheduled_parent.yaml new file mode 100644 index 0000000..9d7f888 --- /dev/null +++ b/tests/integration/cassettes/test_live_readwrite/test_clear_team_iteration_lands_on_a_child_of_a_scheduled_parent.yaml @@ -0,0 +1,891 @@ +interactions: +- request: + body: '' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + host: + - example.tpondemand.com + user-agent: + - python-httpx/0.28.1 + x-request-id: + - b1f11ddde8754f6390315deb82675e18 + method: GET + uri: https://example.tpondemand.com/api/v1/TeamProject?access_token=REDACTED&format=json&take=25&where=Project.Id+eq+49938 + response: + body: + string: '{"Items": [{"ResourceType": "TeamProject", "Id": 501, "StartDate": + "/Date(1789644135000+0200)/", "EndDate": null, "IsProjectAccessed": true, + "IsFullProjectAccess": false, "Team": {"ResourceType": "Team", "Id": 75876, + "Name": "Sanitised Name", "EmojiIcon": null}, "Project": {"ResourceType": + "Project", "Id": 49938, "Name": "Sanitised Name"}}]}' + headers: + cache-control: + - no-store, must-revalidate, proxy-revalidate, no-cache + connection: + - keep-alive + content-length: + - '343' + content-type: + - application/json + date: + - Mon, 21 Sep 2026 09:26:03 GMT + expires: + - Sun, 20 Sep 2026 09:26:03 GMT + server: + - nginx + strict-transport-security: + - max-age=31536000; includeSubDomains + traceparent: + - 00-37ba37ce3fb30594b3948692b1db88c8-d6649b11c39a517c-01 + transfer-encoding: + - chunked + vary: + - Accept-Encoding + via: + - 1.1 kong/3.9.1 + x-api-v1-without-rest: + - 'true' + x-content-type-options: + - nosniff + x-correlationid: + - 09045d8c-f6ac-421e-a63d-ba580a057e5b + x-frame-options: + - SAMEORIGIN + x-kong-proxy-latency: + - '0' + x-kong-request-id: + - ddc3a6788bf83b084566581fe1052250 + x-kong-upstream-latency: + - '17' + x-loaddbreader: + - '1' + x-loadresourcedbreaderdata: + - '0' + x-permitted-cross-domain-policies: + - none + x-sql-stopwatch: + - '9' + x-stopwatch: + - '5' + x-tp-handled-by: + - tp-next + status: + code: 200 + message: OK +- request: + body: '' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + host: + - example.tpondemand.com + user-agent: + - python-httpx/0.28.1 + x-request-id: + - c400bf44d9994392ae83c106c6e7378a + method: GET + uri: https://example.tpondemand.com/api/v1/TeamIteration?access_token=REDACTED&format=json&take=25&where=Team.Id+eq+75876 + response: + body: + string: '{"Items": [{"ResourceType": "TeamIteration", "Id": 75930, "Name": "Sanitised + Name", "Description": null, "StartDate": "/Date(1790632800000+0200)/", "EndDate": + "/Date(1791842399000+0200)/", "CreateDate": "/Date(1789933054000+0200)/", + "ModifyDate": "/Date(1789933054000+0200)/", "LastCommentDate": null, "Tags": + "", "NumericPriority": 718.0, "EntityVersion": 49796361, "EntityType": {"ResourceType": + "EntityType", "Id": 24, "Name": "Sanitised Name"}, "LastEditor": {"ResourceType": + "GeneralUser", "Id": 1, "FirstName": "Sanitised FirstName", "LastName": "Sanitised + LastName", "Login": "Sanitised Login", "FullName": "Sanitised FullName"}, + "Owner": {"ResourceType": "GeneralUser", "Id": 1, "FirstName": "Sanitised + FirstName", "LastName": "Sanitised LastName", "Login": "Sanitised Login", + "FullName": "Sanitised FullName"}, "Creator": {"ResourceType": "GeneralUser", + "Id": 1, "FirstName": "Sanitised FirstName", "LastName": "Sanitised LastName", + "Login": "Sanitised Login", "FullName": "Sanitised FullName"}, "LastCommentedUser": + null, "Project": null, "LinkedTestPlan": null, "Milestone": null, "Effort": + 0.0, "EffortCompleted": 0.0, "EffortToDo": 0.0, "IsCurrent": false, "Capacity": + 0.0, "Velocity": 0.0, "Duration": 14, "Progress": 0.0, "Units": "h", "CanBeFinished": + false, "Team": {"ResourceType": "Team", "Id": 75876, "Name": "Sanitised Name", + "EmojiIcon": null}, "Release": null, "CustomFields": []}, {"ResourceType": + "TeamIteration", "Id": 75929, "Name": "Sanitised Name", "Description": null, + "StartDate": "/Date(1789423200000+0200)/", "EndDate": "/Date(1790632799000+0200)/", + "CreateDate": "/Date(1789933054000+0200)/", "ModifyDate": "/Date(1789933054000+0200)/", + "LastCommentDate": null, "Tags": "", "NumericPriority": 717.0, "EntityVersion": + 49796360, "EntityType": {"ResourceType": "EntityType", "Id": 24, "Name": "Sanitised + Name"}, "LastEditor": {"ResourceType": "GeneralUser", "Id": 1, "FirstName": + "Sanitised FirstName", "LastName": "Sanitised LastName", "Login": "Sanitised + Login", "FullName": "Sanitised FullName"}, "Owner": {"ResourceType": "GeneralUser", + "Id": 1, "FirstName": "Sanitised FirstName", "LastName": "Sanitised LastName", + "Login": "Sanitised Login", "FullName": "Sanitised FullName"}, "Creator": + {"ResourceType": "GeneralUser", "Id": 1, "FirstName": "Sanitised FirstName", + "LastName": "Sanitised LastName", "Login": "Sanitised Login", "FullName": + "Sanitised FullName"}, "LastCommentedUser": null, "Project": null, "LinkedTestPlan": + null, "Milestone": null, "Effort": 0.0, "EffortCompleted": 0.0, "EffortToDo": + 0.0, "IsCurrent": true, "Capacity": 0.0, "Velocity": 0.0, "Duration": 14, + "Progress": 0.0, "Units": "h", "CanBeFinished": true, "Team": {"ResourceType": + "Team", "Id": 75876, "Name": "Sanitised Name", "EmojiIcon": null}, "Release": + null, "CustomFields": []}, {"ResourceType": "TeamIteration", "Id": 75928, + "Name": "Sanitised Name", "Description": null, "StartDate": "/Date(1788213600000+0200)/", + "EndDate": "/Date(1789423199000+0200)/", "CreateDate": "/Date(1789933052000+0200)/", + "ModifyDate": "/Date(1789933052000+0200)/", "LastCommentDate": null, "Tags": + "", "NumericPriority": 715.0, "EntityVersion": 49796358, "EntityType": {"ResourceType": + "EntityType", "Id": 24, "Name": "Sanitised Name"}, "LastEditor": {"ResourceType": + "GeneralUser", "Id": 1, "FirstName": "Sanitised FirstName", "LastName": "Sanitised + LastName", "Login": "Sanitised Login", "FullName": "Sanitised FullName"}, + "Owner": {"ResourceType": "GeneralUser", "Id": 1, "FirstName": "Sanitised + FirstName", "LastName": "Sanitised LastName", "Login": "Sanitised Login", + "FullName": "Sanitised FullName"}, "Creator": {"ResourceType": "GeneralUser", + "Id": 1, "FirstName": "Sanitised FirstName", "LastName": "Sanitised LastName", + "Login": "Sanitised Login", "FullName": "Sanitised FullName"}, "LastCommentedUser": + null, "Project": null, "LinkedTestPlan": null, "Milestone": null, "Effort": + 0.0, "EffortCompleted": 0.0, "EffortToDo": 0.0, "IsCurrent": false, "Capacity": + 0.0, "Velocity": 0.0, "Duration": 14, "Progress": 0.0, "Units": "h", "CanBeFinished": + true, "Team": {"ResourceType": "Team", "Id": 75876, "Name": "Sanitised Name", + "EmojiIcon": null}, "Release": null, "CustomFields": []}]}' + headers: + cache-control: + - no-store, must-revalidate, proxy-revalidate, no-cache + connection: + - keep-alive + content-length: + - '4190' + content-type: + - application/json + date: + - Mon, 21 Sep 2026 09:26:04 GMT + expires: + - Sun, 20 Sep 2026 09:26:04 GMT + server: + - nginx + strict-transport-security: + - max-age=31536000; includeSubDomains + traceparent: + - 00-77fcf880aae66b72c6e2f9b12843fe26-5c96270079608612-01 + transfer-encoding: + - chunked + vary: + - Accept-Encoding + via: + - 1.1 kong/3.9.1 + x-api-v1-without-rest: + - 'true' + x-content-type-options: + - nosniff + x-correlationid: + - 38316837-1316-4c63-8dec-b9cda92b5433 + x-frame-options: + - SAMEORIGIN + x-kong-proxy-latency: + - '1' + x-kong-request-id: + - c613860009a6b46153b57b144bc0772d + x-kong-upstream-latency: + - '70' + x-loaddbreader: + - '7' + x-loadresourcedbreaderdata: + - '0' + x-permitted-cross-domain-policies: + - none + x-sql-stopwatch: + - '30' + x-stopwatch: + - '61' + x-tp-handled-by: + - tp-next + status: + code: 200 + message: OK +- request: + body: '{"Name":"Synthetic recording cascade parent story","Description":"Synthetic + record created by an integration recording run.","Project":{"Id":49938},"Team":{"Id":75876},"TeamIteration":{"Id":75928}}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '197' + content-type: + - application/json + host: + - example.tpondemand.com + user-agent: + - python-httpx/0.28.1 + x-request-id: + - 1c754e9190f546999915cfa8e8ecb88b + method: POST + uri: https://example.tpondemand.com/api/v1/UserStory?access_token=REDACTED&format=json + response: + body: + string: '{"ResourceType": "UserStory", "Id": 75944, "Name": "Sanitised Name", + "Description": "Sanitised Description", "StartDate": null, "EndDate": null, + "CreateDate": "/Date(1789982764000+0200)/", "ModifyDate": "/Date(1789982764000+0200)/", + "LastCommentDate": null, "Tags": "", "NumericPriority": 64040.32281790183, + "EntityVersion": 49860156, "EntityType": {"ResourceType": "EntityType", "Id": + 4, "Name": "Sanitised Name", "IsUnitInHourOnly": false}, "LastEditor": {"ResourceType": + "GeneralUser", "Id": 1, "FirstName": "Sanitised FirstName", "LastName": "Sanitised + LastName", "Login": "Sanitised Login", "FullName": "Sanitised FullName"}, + "Owner": {"ResourceType": "GeneralUser", "Id": 1, "FirstName": "Sanitised + FirstName", "LastName": "Sanitised LastName", "Login": "Sanitised Login", + "FullName": "Sanitised FullName"}, "Creator": {"ResourceType": "GeneralUser", + "Id": 1, "FirstName": "Sanitised FirstName", "LastName": "Sanitised LastName", + "Login": "Sanitised Login", "FullName": "Sanitised FullName"}, "LastCommentedUser": + null, "Project": {"ResourceType": "Project", "Id": 49938, "Name": "Sanitised + Name", "Process": {"ResourceType": "Process", "Id": 2}}, "LinkedTestPlan": + null, "Milestone": null, "Effort": 0.0, "EffortCompleted": 0.0, "EffortToDo": + 0.0, "Progress": 0.0, "TimeSpent": 0.0, "TimeRemain": 0.0, "LastStateChangeDate": + "/Date(1789982764000+0200)/", "PlannedStartDate": null, "PlannedEndDate": + null, "Units": "h", "Release": null, "Iteration": null, "TeamIteration": {"ResourceType": + "TeamIteration", "Id": 75928, "Name": "Sanitised Name"}, "Team": {"ResourceType": + "Team", "Id": 75876, "Name": "Sanitised Name", "EmojiIcon": null}, "Priority": + {"ResourceType": "Priority", "Id": 5, "Name": "Sanitised Name", "Importance": + 5}, "EntityState": {"ResourceType": "EntityState", "Id": 78, "Name": "Sanitised + Name", "NumericPriority": 78.0}, "ResponsibleTeam": {"ResourceType": "TeamAssignment", + "Id": 82543}, "InitialEstimate": 0.0, "Feature": null, "Build": null, "CustomFields": + []}' + headers: + cache-control: + - no-store, must-revalidate, proxy-revalidate, no-cache + connection: + - keep-alive + content-length: + - '1991' + content-type: + - application/json + date: + - Mon, 21 Sep 2026 09:26:04 GMT + expires: + - Sun, 20 Sep 2026 09:26:04 GMT + server: + - nginx + strict-transport-security: + - max-age=31536000; includeSubDomains + traceparent: + - 00-a8e0a305ed6c698bcc50048f4eafd762-159a612938d9c519-01 + via: + - 1.1 kong/3.9.1 + x-accountlockwaittime: + - '2' + x-api-v1-without-rest: + - 'true' + x-content-type-options: + - nosniff + x-correlationid: + - 795b7777-ba27-4f1c-ace1-0a6527536766 + x-diagnostic-tags: + - create-resource,get-resource + x-frame-options: + - SAMEORIGIN + x-kong-proxy-latency: + - '1' + x-kong-request-id: + - b6d7b1668f6f191cfa4d8768dc7a373d + x-kong-upstream-latency: + - '260' + x-loaddbreader: + - 1;3 + x-loadresourcedbreaderdata: + - 0;0 + x-permitted-cross-domain-policies: + - none + x-sql-stopwatch: + - 1;160 + x-stopwatch: + - '253' + x-tp-handled-by: + - tp-next + status: + code: 201 + message: Created +- request: + body: '' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + host: + - example.tpondemand.com + user-agent: + - python-httpx/0.28.1 + x-request-id: + - be51a61e52b84e7f8e5174b8b106ee42 + method: GET + uri: https://example.tpondemand.com/api/v1/UserStory/75944?access_token=REDACTED&format=json&include=%5BTeamIteration%5D + response: + body: + string: '{"ResourceType": "UserStory", "Id": 75944, "TeamIteration": {"ResourceType": + "TeamIteration", "Id": 75928, "Name": "Sanitised Name"}}' + headers: + cache-control: + - no-store, must-revalidate, proxy-revalidate, no-cache + connection: + - keep-alive + content-length: + - '133' + content-type: + - application/json + date: + - Mon, 21 Sep 2026 09:26:04 GMT + expires: + - Sun, 20 Sep 2026 09:26:04 GMT + server: + - nginx + strict-transport-security: + - max-age=31536000; includeSubDomains + traceparent: + - 00-e320cf295568413e61c53d1c6944e138-327efb04025fa309-01 + transfer-encoding: + - chunked + vary: + - Accept-Encoding + via: + - 1.1 kong/3.9.1 + x-api-v1-without-rest: + - 'true' + x-content-type-options: + - nosniff + x-correlationid: + - cb6d2796-3f1a-444e-9c93-fd321c893625 + x-diagnostic-tags: + - get-resource + x-frame-options: + - SAMEORIGIN + x-kong-proxy-latency: + - '1' + x-kong-request-id: + - 5a7c91755e225d4d5ef1fe4743902514 + x-kong-upstream-latency: + - '7' + x-loaddbreader: + - '0' + x-loadresourcedbreaderdata: + - '0' + x-permitted-cross-domain-policies: + - none + x-sql-stopwatch: + - '3' + x-stopwatch: + - '1' + x-tp-handled-by: + - tp-next + status: + code: 200 + message: OK +- request: + body: '{"Name":"Synthetic recording cascade child task","UserStory":{"Id":75944},"Team":{"Id":75876},"TeamIteration":{"Id":75928}}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '123' + content-type: + - application/json + host: + - example.tpondemand.com + user-agent: + - python-httpx/0.28.1 + x-request-id: + - 7bc404f30b7042d4abd8e4fbdca1aaed + method: POST + uri: https://example.tpondemand.com/api/v1/Task?access_token=REDACTED&format=json + response: + body: + string: '{"ResourceType": "Task", "Id": 75945, "Name": "Sanitised Name", "Description": + null, "StartDate": null, "EndDate": null, "CreateDate": "/Date(1789982764000+0200)/", + "ModifyDate": "/Date(1789982764000+0200)/", "LastCommentDate": null, "Tags": + "", "NumericPriority": 64072.34297931078, "EntityVersion": 49860159, "EntityType": + {"ResourceType": "EntityType", "Id": 5, "Name": "Sanitised Name", "IsUnitInHourOnly": + true}, "LastEditor": {"ResourceType": "GeneralUser", "Id": 1, "FirstName": + "Sanitised FirstName", "LastName": "Sanitised LastName", "Login": "Sanitised + Login", "FullName": "Sanitised FullName"}, "Owner": {"ResourceType": "GeneralUser", + "Id": 1, "FirstName": "Sanitised FirstName", "LastName": "Sanitised LastName", + "Login": "Sanitised Login", "FullName": "Sanitised FullName"}, "Creator": + {"ResourceType": "GeneralUser", "Id": 1, "FirstName": "Sanitised FirstName", + "LastName": "Sanitised LastName", "Login": "Sanitised Login", "FullName": + "Sanitised FullName"}, "LastCommentedUser": null, "Project": {"ResourceType": + "Project", "Id": 49938, "Name": "Sanitised Name", "Process": {"ResourceType": + "Process", "Id": 2}}, "LinkedTestPlan": null, "Milestone": null, "Effort": + 0.0, "EffortCompleted": 0.0, "EffortToDo": 0.0, "Progress": 0.0, "TimeSpent": + 0.0, "TimeRemain": 0.0, "LastStateChangeDate": "/Date(1789982764000+0200)/", + "PlannedStartDate": null, "PlannedEndDate": null, "Units": "h", "Release": + null, "Iteration": null, "TeamIteration": {"ResourceType": "TeamIteration", + "Id": 75928, "Name": "Sanitised Name"}, "Team": {"ResourceType": "Team", "Id": + 75876, "Name": "Sanitised Name", "EmojiIcon": null}, "Priority": {"ResourceType": + "Priority", "Id": 11, "Name": "Sanitised Name", "Importance": 1}, "EntityState": + {"ResourceType": "EntityState", "Id": 88, "Name": "Sanitised Name", "NumericPriority": + 88.0}, "ResponsibleTeam": {"ResourceType": "TeamAssignment", "Id": 82544}, + "UserStory": {"ResourceType": "UserStory", "Id": 75944, "Name": "Sanitised + Name"}, "CustomFields": []}' + headers: + cache-control: + - no-store, must-revalidate, proxy-revalidate, no-cache + connection: + - keep-alive + content-length: + - '1994' + content-type: + - application/json + date: + - Mon, 21 Sep 2026 09:26:05 GMT + expires: + - Sun, 20 Sep 2026 09:26:04 GMT + server: + - nginx + strict-transport-security: + - max-age=31536000; includeSubDomains + traceparent: + - 00-f8656ddbad3ef1ec241fc9916eb0d99f-0017957fdb73a1c8-01 + via: + - 1.1 kong/3.9.1 + x-accountlockwaittime: + - '1' + x-api-v1-without-rest: + - 'true' + x-content-type-options: + - nosniff + x-correlationid: + - f08b19de-dcea-4a15-a2ae-f7854f508ce5 + x-diagnostic-tags: + - create-resource,get-resource + x-frame-options: + - SAMEORIGIN + x-kong-proxy-latency: + - '0' + x-kong-request-id: + - 65ddad46ae1a50679f8bc01300098c60 + x-kong-upstream-latency: + - '155' + x-loaddbreader: + - '2' + x-loadresourcedbreaderdata: + - '0' + x-permitted-cross-domain-policies: + - none + x-sql-stopwatch: + - '62' + x-stopwatch: + - '149' + x-tp-handled-by: + - tp-next + status: + code: 201 + message: Created +- request: + body: '' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + host: + - example.tpondemand.com + user-agent: + - python-httpx/0.28.1 + x-request-id: + - 182e58d3fe0f46a8a6c6e8f1f2249d3a + method: GET + uri: https://example.tpondemand.com/api/v1/Task/75945?access_token=REDACTED&format=json&include=%5BTeamIteration%5D + response: + body: + string: '{"ResourceType": "Task", "Id": 75945, "TeamIteration": {"ResourceType": + "TeamIteration", "Id": 75928, "Name": "Sanitised Name"}}' + headers: + cache-control: + - no-store, must-revalidate, proxy-revalidate, no-cache + connection: + - keep-alive + content-length: + - '128' + content-type: + - application/json + date: + - Mon, 21 Sep 2026 09:26:05 GMT + expires: + - Sun, 20 Sep 2026 09:26:05 GMT + server: + - nginx + strict-transport-security: + - max-age=31536000; includeSubDomains + traceparent: + - 00-2160fe3526bd3d65101585d877b33a7e-2488ea6268329034-01 + transfer-encoding: + - chunked + vary: + - Accept-Encoding + via: + - 1.1 kong/3.9.1 + x-api-v1-without-rest: + - 'true' + x-content-type-options: + - nosniff + x-correlationid: + - 83fda5cb-5951-4d27-9eea-06e451c8310e + x-diagnostic-tags: + - get-resource + x-frame-options: + - SAMEORIGIN + x-kong-proxy-latency: + - '0' + x-kong-request-id: + - cd599300bb9d8d3068a0672819cce119 + x-kong-upstream-latency: + - '15' + x-loaddbreader: + - '1' + x-loadresourcedbreaderdata: + - '0' + x-permitted-cross-domain-policies: + - none + x-sql-stopwatch: + - 1;10 + x-stopwatch: + - '1' + x-tp-handled-by: + - tp-next + status: + code: 200 + message: OK +- request: + body: '{"TeamIteration":null}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '22' + content-type: + - application/json + host: + - example.tpondemand.com + user-agent: + - python-httpx/0.28.1 + x-request-id: + - 214d51efdc3b473689ee142cfbfbff4d + method: POST + uri: https://example.tpondemand.com/api/v1/Task/75945?access_token=REDACTED&format=json + response: + body: + string: '{"ResourceType": "Task", "Id": 75945, "Name": "Sanitised Name", "Description": + null, "StartDate": null, "EndDate": null, "CreateDate": "/Date(1789982764000+0200)/", + "ModifyDate": "/Date(1789982765000+0200)/", "LastCommentDate": null, "Tags": + "", "NumericPriority": 64072.34297931078, "EntityVersion": 49860160, "EntityType": + {"ResourceType": "EntityType", "Id": 5, "Name": "Sanitised Name", "IsUnitInHourOnly": + true}, "LastEditor": {"ResourceType": "GeneralUser", "Id": 1, "FirstName": + "Sanitised FirstName", "LastName": "Sanitised LastName", "Login": "Sanitised + Login", "FullName": "Sanitised FullName"}, "Owner": {"ResourceType": "GeneralUser", + "Id": 1, "FirstName": "Sanitised FirstName", "LastName": "Sanitised LastName", + "Login": "Sanitised Login", "FullName": "Sanitised FullName"}, "Creator": + {"ResourceType": "GeneralUser", "Id": 1, "FirstName": "Sanitised FirstName", + "LastName": "Sanitised LastName", "Login": "Sanitised Login", "FullName": + "Sanitised FullName"}, "LastCommentedUser": null, "Project": {"ResourceType": + "Project", "Id": 49938, "Name": "Sanitised Name", "Process": {"ResourceType": + "Process", "Id": 2}}, "LinkedTestPlan": null, "Milestone": null, "Effort": + 0.0, "EffortCompleted": 0.0, "EffortToDo": 0.0, "Progress": 0.0, "TimeSpent": + 0.0, "TimeRemain": 0.0, "LastStateChangeDate": "/Date(1789982764000+0200)/", + "PlannedStartDate": null, "PlannedEndDate": null, "Units": "h", "Release": + null, "Iteration": null, "TeamIteration": null, "Team": {"ResourceType": "Team", + "Id": 75876, "Name": "Sanitised Name", "EmojiIcon": null}, "Priority": {"ResourceType": + "Priority", "Id": 11, "Name": "Sanitised Name", "Importance": 1}, "EntityState": + {"ResourceType": "EntityState", "Id": 88, "Name": "Sanitised Name", "NumericPriority": + 88.0}, "ResponsibleTeam": {"ResourceType": "TeamAssignment", "Id": 82544}, + "UserStory": {"ResourceType": "UserStory", "Id": 75944, "Name": "Sanitised + Name"}, "CustomFields": []}' + headers: + cache-control: + - no-store, must-revalidate, proxy-revalidate, no-cache + connection: + - keep-alive + content-length: + - '1926' + content-type: + - application/json + date: + - Mon, 21 Sep 2026 09:26:05 GMT + expires: + - Sun, 20 Sep 2026 09:26:05 GMT + server: + - nginx + strict-transport-security: + - max-age=31536000; includeSubDomains + traceparent: + - 00-11fae370b0476906859731b28fef4913-6bc4058481f6c34a-01 + transfer-encoding: + - chunked + vary: + - Accept-Encoding + via: + - 1.1 kong/3.9.1 + x-api-v1-without-rest: + - 'true' + x-content-type-options: + - nosniff + x-correlationid: + - aa9f5c0d-2218-4914-82fd-b3143845e3a1 + x-diagnostic-tags: + - update-resource,get-resource + x-frame-options: + - SAMEORIGIN + x-kong-proxy-latency: + - '1' + x-kong-request-id: + - a9d9bd72bbf7856f6db2162ca4de9542 + x-kong-upstream-latency: + - '124' + x-loaddbreader: + - '2' + x-loadresourcedbreaderdata: + - '0' + x-permitted-cross-domain-policies: + - none + x-sql-stopwatch: + - 2;40 + x-stopwatch: + - '119' + x-tp-handled-by: + - tp-next + status: + code: 200 + message: OK +- request: + body: '' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + host: + - example.tpondemand.com + user-agent: + - python-httpx/0.28.1 + x-request-id: + - 0323fd8d84134397aaeee82578d20197 + method: GET + uri: https://example.tpondemand.com/api/v1/Task/75945?access_token=REDACTED&format=json&include=%5BTeamIteration%5D + response: + body: + string: '{"ResourceType": "Task", "Id": 75945, "TeamIteration": null}' + headers: + cache-control: + - no-store, must-revalidate, proxy-revalidate, no-cache + connection: + - keep-alive + content-length: + - '60' + content-type: + - application/json + date: + - Mon, 21 Sep 2026 09:26:05 GMT + expires: + - Sun, 20 Sep 2026 09:26:05 GMT + server: + - nginx + strict-transport-security: + - max-age=31536000; includeSubDomains + traceparent: + - 00-f2cbb677f1ddba82c2d00defc89e852c-fe67715224b46c49-01 + transfer-encoding: + - chunked + vary: + - Accept-Encoding + via: + - 1.1 kong/3.9.1 + x-api-v1-without-rest: + - 'true' + x-content-type-options: + - nosniff + x-correlationid: + - 9036399f-bb81-4058-b81d-374f06338ce2 + x-diagnostic-tags: + - get-resource + x-frame-options: + - SAMEORIGIN + x-kong-proxy-latency: + - '0' + x-kong-request-id: + - 28393677c252ff3b79af5f78240ae24d + x-kong-upstream-latency: + - '10' + x-loaddbreader: + - '1' + x-loadresourcedbreaderdata: + - '0' + x-permitted-cross-domain-policies: + - none + x-sql-stopwatch: + - '6' + x-stopwatch: + - '4' + x-tp-handled-by: + - tp-next + status: + code: 200 + message: OK +- request: + body: '' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + host: + - example.tpondemand.com + user-agent: + - python-httpx/0.28.1 + x-request-id: + - 9b3e8a3c939c4b2bbc4774000fccb309 + method: DELETE + uri: https://example.tpondemand.com/api/v1/Task/75945?access_token=REDACTED&format=json + response: + body: + string: '{"ResourceType": "Task", "Id": 75945}' + headers: + cache-control: + - no-store, must-revalidate, proxy-revalidate, no-cache + connection: + - keep-alive + content-length: + - '37' + content-type: + - application/json + date: + - Mon, 21 Sep 2026 09:26:06 GMT + expires: + - Sun, 20 Sep 2026 09:26:05 GMT + server: + - nginx + strict-transport-security: + - max-age=31536000; includeSubDomains + traceparent: + - 00-d630ed67ef628bf2204a38c59cf01e02-c724699aca52a9c9-01 + transfer-encoding: + - chunked + vary: + - Accept-Encoding + via: + - 1.1 kong/3.9.1 + x-api-v1-without-rest: + - 'true' + x-content-type-options: + - nosniff + x-correlationid: + - 1bab180e-a165-4c66-b396-555ebfa30fc7 + x-diagnostic-tags: + - delete-resource + x-frame-options: + - SAMEORIGIN + x-kong-proxy-latency: + - '0' + x-kong-request-id: + - f9eccf2e73edfa705328d9409688fe4c + x-kong-upstream-latency: + - '125' + x-permitted-cross-domain-policies: + - none + x-sql-stopwatch: + - '90' + x-stopwatch: + - '117' + x-tp-handled-by: + - tp-next + status: + code: 200 + message: OK +- request: + body: '' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + host: + - example.tpondemand.com + user-agent: + - python-httpx/0.28.1 + x-request-id: + - 6a8594fba33343cf9bd27ea063bc0989 + method: DELETE + uri: https://example.tpondemand.com/api/v1/UserStory/75944?access_token=REDACTED&format=json + response: + body: + string: '{"ResourceType": "UserStory", "Id": 75944}' + headers: + cache-control: + - no-store, must-revalidate, proxy-revalidate, no-cache + connection: + - keep-alive + content-length: + - '42' + content-type: + - application/json + date: + - Mon, 21 Sep 2026 09:26:06 GMT + expires: + - Sun, 20 Sep 2026 09:26:06 GMT + server: + - nginx + strict-transport-security: + - max-age=31536000; includeSubDomains + traceparent: + - 00-441b58fbbefbdb12f872d51d627706a7-5704c41101c26f2c-01 + transfer-encoding: + - chunked + vary: + - Accept-Encoding + via: + - 1.1 kong/3.9.1 + x-api-v1-without-rest: + - 'true' + x-content-type-options: + - nosniff + x-correlationid: + - 5b8dc584-7b2c-4a42-8e54-f502d1bd925c + x-diagnostic-tags: + - delete-resource + x-frame-options: + - SAMEORIGIN + x-kong-proxy-latency: + - '1' + x-kong-request-id: + - 3e65f170f02c4043aec6fd0fc5616e3b + x-kong-upstream-latency: + - '149' + x-permitted-cross-domain-policies: + - none + x-sql-stopwatch: + - '111' + x-stopwatch: + - '140' + x-tp-handled-by: + - tp-next + status: + code: 200 + message: OK +version: 1 diff --git a/tests/integration/test_live_readwrite.py b/tests/integration/test_live_readwrite.py index 2aa5c72..1be8577 100644 --- a/tests/integration/test_live_readwrite.py +++ b/tests/integration/test_live_readwrite.py @@ -408,3 +408,122 @@ async def test_custom_field_set_and_clear_round_trip(live_credentials) -> None: with pytest.raises(NotFoundError): await client.user_stories.get(story.id) + + +@pytest.mark.asyncio +async def test_clear_team_iteration_lands_on_a_child_of_a_scheduled_parent( + live_credentials, +) -> None: + """``clear_team_iteration`` on a child whose parent holds a ``TeamIteration``. + + Recorded to settle what TargetProcess does with a child's explicit + ``null`` while its parent is scheduled, which no cassette covered and no + unit test can answer: the unit suite drives a mock transport, so it + asserts what the client does with a discarded ``null`` rather than whether + TargetProcess discards one. + + On this instance it does not. The child is a Task under a UserStory, both + in the same team and the same sprint; the clear is answered with the field + genuinely unset, and the verifying re-read carries ``"TeamIteration": + null`` - the key *present* and null, so the "a field the re-read does not + carry counts as cleared" lenience is not what passed it. So + ``clear_team_iteration`` returns the re-read rather than raising, and this + test asserts that. + + What this does **not** establish is that the cascade never happens. Two + configurations were tried live and neither produced it: a child created + with no ``Team`` came back with no ``TeamIteration`` at all (nothing to + cascade onto), and the child recorded here was given the sprint + explicitly, so the value it cleared was one TargetProcess had *stored* for + it. The case left untested is a child that holds a team assignment but was + never given a sprint of its own - where a value shown against it could be + the parent's rather than its own, and a ``null`` would have nothing to + unset. ``TeamIterationCascadeError`` and the prose describing it are + deliberately untouched by this test; it records an observation, and + whether that observation narrows the documented behaviour is not a + question a fixture should answer on its own. + + The run also never re-read the *parent* after the child's clear, so the + cassette shows the parent scheduled before the clear and not after. + Nothing in the run writes to the parent in between. + + Two pieces of instance state are read, never written, as everywhere else + on this path. The team comes out of the sandbox project's own + ``TeamProject`` row rather than being named, so the test binds to whatever + is linked at record time; the sprint is that team's lowest-Id + ``TeamIteration``. Both are structural selections, because a cassette + replays every ``Name`` as a placeholder and a lookup by name would find + nothing (``test_live_readwrite_surfaces.py`` states the same rule). + + Re-recording needs a team linked to the sandbox project for the duration + of the run, exactly as ``test_team_assignment_create_and_delete`` does: + TargetProcess will not put a card in a team's sprint unless that team is + assigned to the card's project. Every other run replays offline and needs + no link. + """ + domain, token = live_credentials + async with TargetProcessClient(domain=domain, token=token, mode=ClientMode.READWRITE) as client: + links = [ + link + async for link in client.entities.list( + "TeamProject", where=f"Project.Id eq {_SANDBOX_PROJECT_ID}" + ) + ] + assert links, ( + "the sandbox project has no TeamProject row - link a team to it for the " + "duration of a re-recording run (see this test's docstring)" + ) + # TeamProject is not one of the typed resources, so its references + # arrive as raw wire dicts in ``model_extra`` under their PascalCase + # names rather than as parsed model fields. + team_id = min(links, key=lambda link: link.id).Team["Id"] + + sprints = [ + sprint async for sprint in client.team_iterations.list(where=f"Team.Id eq {team_id}") + ] + assert sprints, f"team {team_id} owns no TeamIteration to schedule the parent into" + sprint = min(sprints, key=lambda sprint: sprint.id) + + parent = await client.user_stories.create( + Name=f"{_NAME_PREFIX} cascade parent story", + Description=_CREATED_DESCRIPTION, + Project={"Id": _SANDBOX_PROJECT_ID}, + Team={"Id": team_id}, + TeamIteration={"Id": sprint.id}, + ) + try: + # Asserted before the child is made: a TeamIteration TP declined + # to store would leave the parent unscheduled, and a clear under an + # unscheduled parent is a different scenario entirely - so this + # fails here rather than as a puzzling pass three calls later. + scheduled = await client.user_stories.get(parent.id, include=["TeamIteration"]) + assert scheduled.team_iteration is not None + assert scheduled.team_iteration.id == sprint.id + + # The child carries the same team and the same sprint as its + # parent. The ``Team`` is not optional: TargetProcess holds a + # card's sprint through a team assignment, so a child created + # without one comes back with no ``TeamIteration`` to clear. + child = await client.tasks.create( + Name=f"{_NAME_PREFIX} cascade child task", + UserStory={"Id": parent.id}, + Team={"Id": team_id}, + TeamIteration={"Id": sprint.id}, + ) + try: + before = await client.tasks.get(child.id, include=["TeamIteration"]) + assert before.team_iteration is not None + assert before.team_iteration.id == sprint.id + + cleared = await client.tasks.clear_team_iteration(child.id) + + # The narrowed re-read, not TP's echo of the write: the echo + # carries EntityVersion and a re-read narrowed to one field + # does not, which is how the two are told apart. + assert cleared.id == child.id + assert cleared.team_iteration is None + assert cleared.entity_version is None + finally: + await client.tasks.delete(child.id) + finally: + await client.user_stories.delete(parent.id)