--- test_name: tasks-all-fields-postgre strict: false marks: - usefixtures: - django_live_url - skipif: "'default' not in '{tavern.env_vars.DJANGO_DB}'" stages: - id: signup type: ref - id: create_project name: create_project request: data: title: Test Draft 1 show_collab_predictions: true label_config: '' method: POST url: '{django_live_url}/api/projects' response: save: json: project_pk: id created_by: created_by.id status_code: 201 - name: create_filter_tasks request: files: json_file: tests/data_manager/tasks_annotations_predictions.json headers: content-type: multipart/form-data method: POST url: '{django_live_url}/api/projects/{project_pk}/import' response: json: annotation_count: 4 prediction_count: 3 task_count: 6 status_code: 201 - name: setup_views request: method: POST url: '{django_live_url}/api/dm/views/?project={project_pk}' json: { "data": { "filters": { "conjunction": "or", "items": [ { "filter": "filter:tasks:data.text", "operator": "equal", "type": "String", "value": "Test example phrase 1" }, { "filter": "filter:tasks:data.text", "operator": "contains", "type": "String", "value": "x" }, { "filter": "filter:tasks:annotations_results", "operator": "contains", "type": "String", "value": "TESTING" }, { "filter": "filter:tasks:predictions_results", "operator": "contains", "type": "String", "value": "PREDICTIONS" } ] }, "ordering": [ "tasks:data.text" ] }, "project": !int "{project_pk}" } response: save: json: view: "@" status_code: 201 - name: get_tasks request: method: GET url: '{django_live_url}/api/tasks?fields=all&view={view.id}' response: status_code: 200 verify_response_with: function: label_studio.tests.utils:save_response json: { "total_annotations": 4, "total_predictions": 3, "total": 4, "tasks": [ { "cancelled_annotations": 0, "storage_filename": null, "annotations_results": "", "data": { "text": "opop", "int_field": 42 }, "predictions_results": !raw "[{type:choices, value:{choices:[class_PREDICTIONS_TESTING]}, to_name:text, from_name:text_class}]", "predictions_score": null, "total_annotations": 0, "total_predictions": 1, "annotations_ids": "", "annotations": [], "predictions": [ { "model_version": "undefined", "created_ago": "0\u00a0minutes", "result": [ { "type": "choices", "value": { "choices": [ "class_PREDICTIONS_TESTING" ] }, "to_name": "text", "from_name": "text_class" } ], "score": null, "cluster": null, "neighbors": null, "mislabeling": 0.0 } ], "drafts": [], "annotators": [], "avg_lead_time": null }, { "cancelled_annotations": 0, "storage_filename": null, "annotations_results": !raw "[{type:choices, value:{choices:[class_A]}, to_name:text, from_name:text_class}]", "data": { "text": "Test example phrase 1", "int_field": 1 }, "predictions_results": !raw "[{type:choices, value:{choices:[class_A]}, to_name:text, from_name:text_class}]", "predictions_score": null, "total_annotations": 1, "total_predictions": 1, "annotations_ids": !anystr "", "annotations": [ { "created_username": " test_suites_user@heartex.com, {created_by}", "created_ago": "0\u00a0minutes", "completed_by": !int "{created_by}", "result": [ { "type": "choices", "value": { "choices": [ "class_A" ] }, "to_name": "text", "from_name": "text_class" } ], "was_cancelled": false, "ground_truth": true, "lead_time": 3.0 } ], "predictions": [ { "model_version": "model_version_A", "created_ago": "0\u00a0minutes", "result": [ { "type": "choices", "value": { "choices": [ "class_A" ] }, "to_name": "text", "from_name": "text_class" } ], "score": null, "cluster": null, "neighbors": null, "mislabeling": 0.0 } ], "drafts": [], "annotators": [ !int "{created_by}" ], "avg_lead_time": 3.0 }, { "cancelled_annotations": 0, "storage_filename": null, "annotations_results": !anystr "", "data": { "text": "x2", "int_field": 20 }, "predictions_results": !anystr "", "predictions_score": null, "total_annotations": 2, "total_predictions": 1, "annotations_ids": !anystr "", "annotations": [ { "created_username": " test_suites_user@heartex.com, {created_by}", "created_ago": "0\u00a0minutes", "result": [ { "type": "choices", "value": { "choices": [ "class_A" ] }, "to_name": "text", "from_name": "text_class" } ], "was_cancelled": false, "ground_truth": true, "lead_time": 3.0 }, { "created_username": " test_suites_user@heartex.com, {created_by}", "created_ago": "0\u00a0minutes", "result": [ { "type": "choices", "value": { "choices": [ "class_A" ] }, "to_name": "text", "from_name": "text_class" } ], "was_cancelled": false, "ground_truth": true, "lead_time": 5.0 } ], "predictions": [ { "model_version": "model_version_A", "created_ago": "0\u00a0minutes", "result": [ { "type": "choices", "value": { "choices": [ "class_A" ] }, "to_name": "text", "from_name": "text_class" } ], "score": null, "cluster": null, "neighbors": null, "mislabeling": 0.0 } ], "drafts": [], "annotators": [ !int "{created_by}" ], "avg_lead_time": 4.0 }, { "cancelled_annotations": 0, "storage_filename": null, "annotations_results": !raw "[{type:choices, value:{choices:[class_TESTING]}, to_name:text, from_name:text_class}]", "data": { "text": "yoyo", "int_field": "99" }, "predictions_results": "", "predictions_score": null, "total_annotations": 1, "total_predictions": 0, "annotations_ids": !anystr "", "annotations": [ { "created_ago": "0\u00a0minutes", "completed_by": !int "{created_by}", "result": [ { "type": "choices", "value": { "choices": [ "class_TESTING" ] }, "to_name": "text", "from_name": "text_class" } ], "was_cancelled": false, "ground_truth": true, "lead_time": null } ], "predictions": [], "drafts": [], "annotators": [ !int "{created_by}" ] } ] } --- test_name: tasks-all-fields-sqlite strict: false marks: - skipif: "'default' in '{tavern.env_vars.DJANGO_DB}'" - usefixtures: - django_live_url stages: - id: signup type: ref - id: create_project name: create_project request: data: title: Test Draft 1 show_collab_predictions: true label_config: '' method: POST url: '{django_live_url}/api/projects' response: save: json: project_pk: id created_by: created_by.id status_code: 201 - name: create_filter_tasks request: files: json_file: tests/data_manager/tasks_annotations_predictions.json headers: content-type: multipart/form-data method: POST url: '{django_live_url}/api/projects/{project_pk}/import' response: json: annotation_count: 4 prediction_count: 3 task_count: 6 status_code: 201 - name: setup_views request: method: POST url: '{django_live_url}/api/dm/views/?project={project_pk}' json: { "data": { "filters": { "conjunction": "or", "items": [ { "filter": "filter:tasks:data.text", "operator": "equal", "type": "String", "value": "Test example phrase 1" }, { "filter": "filter:tasks:data.text", "operator": "contains", "type": "String", "value": "x" }, { "filter": "filter:tasks:annotations_results", "operator": "contains", "type": "String", "value": "TESTING" }, { "filter": "filter:tasks:predictions_results", "operator": "contains", "type": "String", "value": "PREDICTIONS" } ] }, "ordering": [ "tasks:data.text" ] }, "project": !int "{project_pk}" } response: save: json: view: "@" status_code: 201 - name: get_tasks request: method: GET url: '{django_live_url}/api/tasks?fields=all&view={view.id}' response: status_code: 200 # verify_response_with: # function: label_studio.tests.utils:save_response json: { "total_annotations": 4, "total_predictions": 3, "total": 4, "tasks": [ { "cancelled_annotations": 0, "storage_filename": null, "annotations_results": !raw "[{from_name: text_class, to_name: text, type: choices, value: {choices: [class_A]}}]", "data": { "text": "Test example phrase 1", "int_field": 1 }, "predictions_results": !raw "[{from_name: text_class, to_name: text, type: choices, value: {choices: [class_A]}}]", "predictions_score": null, "total_annotations": 1, "total_predictions": 1, "annotations_ids": !anystr "", "annotations": [ { "created_username": " test_suites_user@heartex.com, {created_by}", "created_ago": "0\u00a0minutes", "completed_by": !int "{created_by}", "result": [ { "from_name": "text_class", "to_name": "text", "type": "choices", "value": { "choices": [ "class_A" ] } } ], "was_cancelled": false, "ground_truth": true, "lead_time": 3.0 } ], "predictions": [ { "model_version": "model_version_A", "created_ago": "0\u00a0minutes", "result": [ { "from_name": "text_class", "to_name": "text", "type": "choices", "value": { "choices": [ "class_A" ] } } ], "score": null, "cluster": null, "neighbors": null, "mislabeling": 0.0, } ], "drafts": [], "annotators": [ !int "{created_by}" ], "project": !int "{project_pk}" }, { "cancelled_annotations": 0, "storage_filename": null, "annotations_results": "", "data": { "int_field": 42, "text": "opop" }, "predictions_results": !raw "[{from_name: text_class, to_name: text, type: choices, value: {choices: [class_PREDICTIONS_TESTING]}}]", "predictions_score": null, "total_annotations": 0, "total_predictions": 1, "annotations_ids": !anystr "", "annotations": [], "predictions": [ { "model_version": "undefined", "created_ago": "0\u00a0minutes", "result": [ { "from_name": "text_class", "to_name": "text", "type": "choices", "value": { "choices": [ "class_PREDICTIONS_TESTING" ] } } ], "score": null, "cluster": null, "neighbors": null, "mislabeling": 0.0, } ], "drafts": [], "annotators": [], "project": !int "{project_pk}" }, { "cancelled_annotations": 0, "storage_filename": null, "annotations_results": !anystr "", "data": { "int_field": 20, "text": "x2" }, "predictions_results": !anystr "", "predictions_score": null, "total_annotations": 2, "total_predictions": 1, "annotations_ids": !anystr "", "annotations": [ { "created_username": " test_suites_user@heartex.com, {created_by}", "created_ago": "0\u00a0minutes", "completed_by": !int "{created_by}", "result": [ { "from_name": "text_class", "to_name": "text", "type": "choices", "value": { "choices": [ "class_A" ] } } ], "was_cancelled": false, "ground_truth": true, "lead_time": 3.0 }, { "created_username": " test_suites_user@heartex.com, {created_by}", "created_ago": "0\u00a0minutes", "completed_by": !int "{created_by}", "result": [ { "from_name": "text_class", "to_name": "text", "type": "choices", "value": { "choices": [ "class_A" ] } } ], "was_cancelled": false, "ground_truth": true, "lead_time": 5.0 } ], "predictions": [ { "model_version": "model_version_A", "created_ago": "0\u00a0minutes", "result": [ { "from_name": "text_class", "to_name": "text", "type": "choices", "value": { "choices": [ "class_A" ] } } ], "score": null, "cluster": null, "neighbors": null, "mislabeling": 0.0, } ], "drafts": [], "annotators": [ !int "{created_by}" ], "project": !int "{project_pk}" }, { "cancelled_annotations": 0, "storage_filename": null, "annotations_results": !raw "[{from_name: text_class, to_name: text, type: choices, value: {choices: [class_TESTING]}}]", "data": { "int_field": "99", "text": "yoyo" }, "predictions_results": "", "predictions_score": null, "total_annotations": 1, "total_predictions": 0, "annotations_ids": !anystr "", "annotations": [ { "created_username": " test_suites_user@heartex.com, {created_by}", "created_ago": "0\u00a0minutes", "completed_by": !int "{created_by}", "result": [ { "from_name": "text_class", "to_name": "text", "type": "choices", "value": { "choices": [ "class_TESTING" ] } } ], "was_cancelled": false, "ground_truth": true, "lead_time": null } ], "predictions": [], "drafts": [], "annotators": [ !int "{created_by}" ], "project": !int "{project_pk}" } ] } --- test_name: tasks-annotators strict: false marks: - usefixtures: - django_live_url stages: - id: signup type: ref - id: create_project name: create_project request: data: title: Test Draft 1 show_collab_predictions: true label_config: '' method: POST url: '{django_live_url}/api/projects' response: save: json: project_pk: id created_by: created_by.id status_code: 201 - name: create_filter_tasks request: files: json_file: tests/data_manager/tasks_annotations_predictions.json headers: content-type: multipart/form-data method: POST url: '{django_live_url}/api/projects/{project_pk}/import' response: json: annotation_count: 4 prediction_count: 3 task_count: 6 status_code: 201 - name: setup_views request: method: POST url: '{django_live_url}/api/dm/views/?project={project_pk}' json: { "data": { "filters": { "conjunction": "or", "items": [ { "filter": "filter:tasks:annotators", "operator": "not_contains", "type": "List", "value": "{created_by}" } ] }, "ordering": [ "tasks:data.text" ] }, "project": !int "{project_pk}" } response: save: json: view: "@" status_code: 201 - name: get_tasks request: method: GET url: '{django_live_url}/api/tasks?fields=all&view={view.id}' response: status_code: 200 verify_response_with: function: label_studio.tests.utils:save_response json: { "total_annotations": 0, "total_predictions": 1, "total": 3, "tasks": [ { "cancelled_annotations": 0, "storage_filename": null, "annotations_results": "", "data": { "int_field": 42, "text": "opop" }, "predictions_score": null, "total_annotations": 0, "total_predictions": 1, "annotations_ids": !anystr "", "annotations": [], "predictions": [ { "model_version": "undefined", "created_ago": "0\u00a0minutes", "result": [ { "from_name": "text_class", "to_name": "text", "type": "choices", "value": { "choices": [ "class_PREDICTIONS_TESTING" ] } } ], "score": null, "cluster": null, "neighbors": null, "mislabeling": 0.0, } ], "drafts": [], "annotators": [], "project": !int "{project_pk}" }, { "cancelled_annotations": 0, "storage_filename": null, "annotations_results": "", "data": { "text": "vivi" }, "predictions_results": "", "predictions_score": null, "total_annotations": 0, "total_predictions": 0, "annotations_ids": !anystr "", "annotations": [], "predictions": [], "drafts": [], "annotators": [], "project": !int "{project_pk}" }, { "cancelled_annotations": 0, "storage_filename": null, "annotations_results": "", "data": { "text": "zzz" }, "predictions_results": "", "predictions_score": null, "total_annotations": 0, "total_predictions": 0, "annotations_ids": !anystr "", "annotations": [], "predictions": [], "drafts": [], "annotators": [], "project": !int "{project_pk}" } ] } - name: setup_views request: method: POST url: '{django_live_url}/api/dm/views/?project={project_pk}' json: { "data": { "filters": { "conjunction": "or", "items": [ { "filter": "filter:tasks:annotators", "operator": "contains", "type": "List", "value": "{created_by}" } ] }, "ordering": [ "tasks:data.text" ] }, "project": !int "{project_pk}" } response: save: json: view: "@" status_code: 201 - name: get_tasks_after_setup request: method: GET url: '{django_live_url}/api/tasks?fields=all&view={view.id}' response: status_code: 200 verify_response_with: function: label_studio.tests.utils:save_response json: { "total_annotations": 4, "total_predictions": 2, "total": 3, "tasks": [ { "cancelled_annotations": 0, "storage_filename": null, "data": { "text": "Test example phrase 1", "int_field": 1 }, "predictions_score": null, "total_annotations": 1, "total_predictions": 1, "annotations_ids": !anystr "", "annotations": [ { "created_username": " test_suites_user@heartex.com, {created_by}", "created_ago": "0\u00a0minutes", "completed_by": !int "{created_by}", "result": [ { "from_name": "text_class", "to_name": "text", "type": "choices", "value": { "choices": [ "class_A" ] } } ], "was_cancelled": false, "ground_truth": true, "lead_time": 3.0 } ], "predictions": [ { "model_version": "model_version_A", "created_ago": "0\u00a0minutes", "result": [ { "from_name": "text_class", "to_name": "text", "type": "choices", "value": { "choices": [ "class_A" ] } } ], "score": null, "cluster": null, "neighbors": null, "mislabeling": 0.0, } ], "drafts": [], "annotators": [ !int "{created_by}" ], "project": !int "{project_pk}" }, { "cancelled_annotations": 0, "storage_filename": null, "data": { "int_field": 20, "text": "x2" }, "predictions_score": null, "total_annotations": 2, "total_predictions": 1, "annotations_ids": !anystr "", "annotations": [ { "created_username": " test_suites_user@heartex.com, {created_by}", "created_ago": "0\u00a0minutes", "completed_by": !int "{created_by}", "result": [ { "from_name": "text_class", "to_name": "text", "type": "choices", "value": { "choices": [ "class_A" ] } } ], "was_cancelled": false, "ground_truth": true, "lead_time": 3.0 }, { "created_username": " test_suites_user@heartex.com, {created_by}", "created_ago": "0\u00a0minutes", "completed_by": !int "{created_by}", "result": [ { "from_name": "text_class", "to_name": "text", "type": "choices", "value": { "choices": [ "class_A" ] } } ], "was_cancelled": false, "ground_truth": true, "lead_time": 5.0 } ], "predictions": [ { "model_version": "model_version_A", "created_ago": "0\u00a0minutes", "result": [ { "from_name": "text_class", "to_name": "text", "type": "choices", "value": { "choices": [ "class_A" ] } } ], "score": null, "cluster": null, "neighbors": null, "mislabeling": 0.0, } ], "drafts": [], "annotators": [ !int "{created_by}" ], "project": !int "{project_pk}" }, { "cancelled_annotations": 0, "storage_filename": null, "data": { "int_field": "99", "text": "yoyo" }, "predictions_results": "", "predictions_score": null, "total_annotations": 1, "total_predictions": 0, "annotations_ids": !anystr "", "annotations": [ { "created_username": " test_suites_user@heartex.com, {created_by}", "created_ago": "0\u00a0minutes", "completed_by": !int "{created_by}", "result": [ { "from_name": "text_class", "to_name": "text", "type": "choices", "value": { "choices": [ "class_TESTING" ] } } ], "was_cancelled": false, "ground_truth": true, "lead_time": null } ], "predictions": [], "drafts": [], "annotators": [ !int "{created_by}" ], "project": !int "{project_pk}" } ] } --- test_name: tasks-minimal-fields strict: false marks: - usefixtures: - django_live_url stages: - id: signup type: ref - id: create_project name: create_project request: data: title: Test Draft 1 show_collab_predictions: true label_config: '' method: POST url: '{django_live_url}/api/projects' response: save: json: project_pk: id created_by: created_by.id status_code: 201 - name: create_filter_tasks request: files: json_file: tests/data_manager/tasks_annotations_predictions.json headers: content-type: multipart/form-data method: POST url: '{django_live_url}/api/projects/{project_pk}/import' response: json: annotation_count: 4 prediction_count: 3 task_count: 6 status_code: 201 - name: setup_views request: method: POST url: '{django_live_url}/api/dm/views/?project={project_pk}' json: { "data": { "filters": { "conjunction": "or", "items": [ { "filter": "filter:tasks:annotators", "operator": "empty", "type": "List", "value": false } ] }, "ordering": [ "tasks:data.text" ] }, "project": !int "{project_pk}" } response: save: json: view: "@" status_code: 201 - name: get_tasks request: method: GET url: '{django_live_url}/api/tasks?view={view.id}' response: status_code: 200 verify_response_with: function: label_studio.tests.utils:save_response json: { "total_annotations": 4, "total_predictions": 2, "total": 3, "tasks": [ { "annotations_results": "", "data": { "text": "Test example phrase 1", "int_field": 1 }, "predictions_results": "", "total_annotations": 1, "annotations_ids": !anystr "", "drafts": [], "file_upload": null, "annotators": [ !int "{created_by}" ], "project": !int "{project_pk}" }, { "annotations_results": "", "data": { "int_field": 20, "text": "x2" }, "predictions_results": "", "total_annotations": 2, "annotations_ids": !anystr "", "drafts": [], "file_upload": null, "annotators": [ !int "{created_by}" ], "project": !int "{project_pk}" }, { "annotations_results": "", "data": { "int_field": "99", "text": "yoyo" }, "predictions_results": "", "total_annotations": 1, "annotations_ids": !anystr "", "drafts": [], "file_upload": null, "annotators": [ !int "{created_by}" ], "project": !int "{project_pk}" } ] } - name: setup_views request: method: POST url: '{django_live_url}/api/dm/views/?project={project_pk}' json: { "data": { "filters": { "conjunction": "and", "items": [ { "filter": "filter:tasks:predictions_results", "operator": "equal", "type": "String", "value": "PREDICTIONS" }, { "filter": "filter:tasks:annotations_results", "operator": "not_equal", "type": "String", "value": "PREDICTIONS" } ] }, "ordering": [ "tasks:data.text" ] }, "project": !int "{project_pk}" } response: save: json: view: "@" status_code: 201 - name: get_tasks request: method: GET url: '{django_live_url}/api/tasks?view={view.id}' response: status_code: 200 verify_response_with: function: label_studio.tests.utils:save_response json: { "total_annotations": 0, "total_predictions": 0, "total": 0, } - name: get_tasks_with_page request: method: GET url: '{django_live_url}/api/tasks?view={view.id}&page=1' response: status_code: 200 verify_response_with: function: label_studio.tests.utils:save_response json: { "total_annotations": 0, "total_predictions": 0, "total": 0, } - name: get_first_task request: method: GET url: '{django_live_url}/api/projects/{project_pk}/next' response: save: json: first_task_id: id status_code: 200 - name: create_annotation_first_task request: headers: content-type: application/json json: lead_time: 5 result: - from_name: label to_name: text type: choices value: choices: - class_B method: POST url: '{django_live_url}/api/tasks/{first_task_id}/annotations' response: save: json: annotation_pk: id status_code: 201 - name: setup_views request: method: POST url: '{django_live_url}/api/dm/views/?project={project_pk}' json: { "data": { "filters": { "conjunction": "or", "items": [ { "filter": "filter:tasks:updated_by", "operator": "contains", "type": "List", "value": !int "{created_by}", } ] }, "ordering": [ "tasks:data.text" ] }, "project": !int "{project_pk}" } response: save: json: view: "@" status_code: 201 - name: get_tasks request: method: GET url: '{django_live_url}/api/tasks?view={view.id}' response: status_code: 200 verify_response_with: function: label_studio.tests.utils:save_response json: { "total_annotations": 1, "total_predictions": 0, "total": 1, } - name: setup_views request: method: POST url: '{django_live_url}/api/dm/views/?project={project_pk}' json: { "data": { "filters": { "conjunction": "or", "items": [ { "filter": "filter:tasks:updated_by", "operator": "not_contains", "type": "List", "value": !int "{created_by}", } ] }, "ordering": [ "tasks:data.text" ] }, "project": !int "{project_pk}" } response: save: json: view: "@" status_code: 201 - name: get_tasks request: method: GET url: '{django_live_url}/api/tasks?view={view.id}' response: status_code: 200 verify_response_with: function: label_studio.tests.utils:save_response json: { "total_annotations": 4, "total_predictions": 3, "total": 5, } - name: setup_views request: method: POST url: '{django_live_url}/api/dm/views/?project={project_pk}' json: { "data": { "filters": { "conjunction": "or", "items": [ { "filter": "filter:tasks:updated_by", "operator": "equal", "type": "List", "value": !int "{created_by}", } ] }, "ordering": [ "tasks:data.text" ] }, "project": !int "{project_pk}" } response: save: json: view: "@" status_code: 201 - name: get_tasks request: method: GET url: '{django_live_url}/api/tasks?view={view.id}' response: status_code: 200 verify_response_with: function: label_studio.tests.utils:save_response json: { "total_annotations": 1, "total_predictions": 0, "total": 1, } --- test_name: tasks-storage-filename-s3 strict: false marks: - usefixtures: - django_live_url stages: - id: signup type: ref - id: create_project name: create_project request: data: title: Test Draft 1 show_collab_predictions: true label_config: '' method: POST url: '{django_live_url}/api/projects' response: save: json: project_pk: id created_by: created_by.id status_code: 201 - name: Add S3 storage request: data: bucket: pytest-s3-images project: '{project_pk}' title: Testing S3 storage (bucket from conftest.py) use_blob_urls: true presign_ttl: 3600 recursive_scan: true method: POST url: '{django_live_url}/api/storages/s3' response: save: json: storage_pk: id status_code: 201 - name: Sync S3 storage request: method: POST url: '{django_live_url}/api/storages/s3/{storage_pk}/sync' response: json: last_sync_count: 4 status_code: 200 - name: setup_views request: method: POST url: '{django_live_url}/api/dm/views/?project={project_pk}' json: { "data": { "filters": {}, "ordering": [ "tasks:storage_filename" ] }, "project": !int "{project_pk}" } response: save: json: view: "@" status_code: 201 - name: get_tasks request: method: GET url: '{django_live_url}/api/tasks?fields=all&view={view.id}' response: status_code: 200 json: { "total_annotations": 0, "total_predictions": 0, "total": 4, "tasks": [ { "storage_filename": "image1.jpg", "project": !int "{project_pk}" }, { "storage_filename": "subdir/another/image2.jpg", "project": !int "{project_pk}" }, { "storage_filename": "subdir/image1.jpg", "project": !int "{project_pk}" }, { "storage_filename": "subdir/image2.jpg", "project": !int "{project_pk}" } ] } --- test_name: tasks-storage-filename-local strict: false marks: - skipif: '"Windows" in platform.system()' - usefixtures: - django_live_url - local_files_storage - local_files_document_root_tempdir stages: - id: signup name: Sign up request: url: "{django_live_url}/user/signup" data: email: test_suites_user@heartex.com password: 12345678 method: POST response: status_code: 302 save: $ext: function: tests.utils:os_independent_path extra_kwargs: path: 'files/subdir/test_image2.png' add_tempdir: true - id: create_project name: create_project request: data: title: Test Draft 1 show_collab_predictions: true label_config: '' method: POST url: '{django_live_url}/api/projects' response: save: json: project_pk: id created_by: created_by.id status_code: 201 - name: create local storage request: json: project: "{project_pk}" title: test regex_filter: ".*png" path: "{os_independent_path_parent}" use_blob_urls: true method: POST url: '{django_live_url}/api/storages/localfiles' response: save: json: storage_pk: id status_code: 201 - name: sync local storage request: method: POST url: '{django_live_url}/api/storages/localfiles/{storage_pk}/sync' response: json: last_sync_count: 1 status_code: 200 - name: setup_views request: method: POST url: '{django_live_url}/api/dm/views/?project={project_pk}' json: { "data": { "filters":{ "conjunction": "and", "items": [{ "filter": "filter:tasks:storage_filename", "operator": "contains", "type": "String", "value": ".png" }] }, "ordering": [] }, "project": !int "{project_pk}" } response: save: json: view: "@" status_code: 201 - name: get_tasks request: method: GET url: '{django_live_url}/api/tasks?fields=all&view={view.id}' response: status_code: 200 json: { "total_annotations": 0, "total_predictions": 0, "total": 1, "tasks": [ { "storage_filename": "/tmp/files/subdir/test_image2.png", "project": !int "{project_pk}" } ] } --- # See FilterSerializer; test logic preventing exploit that traverses the ORM # to leak sensitive data character-by-character test_name: tasks_api_filter_security_restrictions strict: false marks: - usefixtures: - django_live_url stages: - id: signup type: ref - id: create_project name: create_project request: data: title: Test Draft 1 show_collab_predictions: true label_config: '' method: POST url: '{django_live_url}/api/projects' response: save: json: project_pk: id created_by: created_by.id status_code: 201 - name: create view with exploit filter should fail request: method: POST url: '{django_live_url}/api/dm/views' json: project: '{project_pk}' data: filters: conjunction: and items: - filter: "filter:tasks:updated_by__sensitive_field" operator: empty value: "true" type: String response: status_code: 400 json: validation_errors: filter_group: filters: - column: - '"__" is not generally allowed in filters. Consider asking your administrator to add "updated_by__sensitive_field" to DATA_MANAGER_FILTER_ALLOWLIST, but note that some filter expressions may pose a security risk' - name: create view with allowlisted filter should succeed request: method: POST url: '{django_live_url}/api/dm/views' json: project: '{project_pk}' data: filters: conjunction: and items: - filter: "filter:tasks:updated_by__active_organization" operator: empty value: "true" type: String response: status_code: 201 save: json: view_pk: id - name: change filter to typical column succeeds request: method: PUT url: '{django_live_url}/api/dm/views/{view_pk}?interaction=filter&project={project_pk}' json: project: '{project_pk}' data: filters: conjunction: and items: - filter: "filter:tasks:updated_by" operator: empty value: "true" type: String response: status_code: 200 - name: change filter to unexpected prefix fails request: method: PUT url: '{django_live_url}/api/dm/views/{view_pk}?interaction=filter&project={project_pk}' json: project: '{project_pk}' data: filters: conjunction: and items: - filter: "tasks:filter:annotations_results" operator: empty value: "true" type: String response: status_code: 400 json: validation_errors: filter_group: filters: - column: - 'Filter "tasks:filter:annotations_results" should start with "filter:tasks:"' - name: change filter to include direction marker succeeds request: method: PUT url: '{django_live_url}/api/dm/views/{view_pk}?interaction=filter&project={project_pk}' json: project: '{project_pk}' data: filters: conjunction: and items: - filter: "filter:tasks:-annotations_results" operator: empty value: "true" type: String response: status_code: 200 - name: invalid filter with direction marker fails, but suggests correct allowlist request: method: PUT url: '{django_live_url}/api/dm/views/{view_pk}?interaction=filter&project={project_pk}' json: project: '{project_pk}' data: filters: conjunction: and items: - filter: "filter:tasks:-updated_by__sensitive_field" operator: empty value: "true" type: String response: status_code: 400 json: validation_errors: filter_group: filters: - column: - '"__" is not generally allowed in filters. Consider asking your administrator to add "updated_by__sensitive_field" to DATA_MANAGER_FILTER_ALLOWLIST, but note that some filter expressions may pose a security risk' - name: change filter to dotted data field with underscores succeeds request: method: PUT url: '{django_live_url}/api/dm/views/{view_pk}?interaction=filter&project={project_pk}' json: project: '{project_pk}' data: filters: conjunction: and items: - filter: "filter:tasks:data.images__0" operator: empty value: "true" type: String response: status_code: 200 - name: change filter to valid column without prefix fails request: method: PUT url: '{django_live_url}/api/dm/views/{view_pk}?interaction=filter&project={project_pk}' json: project: '{project_pk}' data: filters: conjunction: and items: - filter: "annotations_results" operator: empty value: "true" type: String response: status_code: 400 json: validation_errors: filter_group: filters: - column: - 'Filter "annotations_results" should start with "filter:tasks:"' --- # Regression test preventing replace-all of prefixes from being used to construct exploit filters test_name: tasks_api_prevent_prefix_regression strict: false marks: - usefixtures: - django_live_url stages: - id: signup type: ref - id: create_project name: create_project request: data: title: Test Draft 1 show_collab_predictions: true label_config: '' method: POST url: '{django_live_url}/api/projects' response: save: json: project_pk: id created_by: created_by.id status_code: 201 # Perhaps our validation should be further improved, but for now, it's # garbage in, garbage out. - name: create view with prefix in middle of filter should succeed request: method: POST url: '{django_live_url}/api/dm/views' json: project: '{project_pk}' data: filters: conjunction: and items: # in an earlier version, preprocess_field_name would have # transformed this to updated_by__sensitive_field - filter: "filter:tasks:updated_by_tasks:_sensitive_field" operator: empty value: "true" type: String response: status_code: 201 save: json: view_pk: id - name: get_tasks fails because "updated_by_tasks:_sensitive_field" doesn't resolve to a field request: method: GET url: '{django_live_url}/api/tasks?view={view_pk}' response: # 500 actually expected here status_code: 500