"""This file and its contents are licensed under the Apache License 2.0. Please see the included NOTICE for copyright information and LICENSE for a copy of the license. """ import json import pytest from rest_framework import status from ..utils import project_id # noqa pytestmark = pytest.mark.django_db def test_views_api(business_client, project_id): # create payload = dict(project=project_id, data={'test': 1}) response = business_client.post( '/api/dm/views/', data=json.dumps(payload), content_type='application/json', ) assert response.status_code == 201, response.content # list response = business_client.get( '/api/dm/views/', ) assert response.status_code == 200, response.content assert response.json()[0]['project'] == project_id view_id = response.json()[0]['id'] # partial update updated_payload = dict(data={'test': 2}) response = business_client.patch( f'/api/dm/views/{view_id}/', data=json.dumps(updated_payload), content_type='application/json', ) assert response.status_code == 200, response.content # retrieve response = business_client.get( f'/api/dm/views/{view_id}/', ) assert response.status_code == 200, response.content assert response.json()['data'] == updated_payload['data'] # reset response = business_client.delete( '/api/dm/views/reset', data=json.dumps(dict(project=project_id)), content_type='application/json', ) assert response.status_code == 204, response.content response = business_client.get('/api/dm/views/') assert response.json() == [] def test_views_api_filter_project(business_client): # create project response = business_client.post( '/api/projects/', data=json.dumps(dict(title='test_project1')), content_type='application/json', ) project1_id = response.json()['id'] business_client.post( '/api/dm/views/', data=json.dumps(dict(project=project1_id)), content_type='application/json', ) response = business_client.post( '/api/projects/', data=json.dumps(dict(title='test_project2')), content_type='application/json', ) project2_id = response.json()['id'] business_client.post( '/api/dm/views/', data=json.dumps(dict(project=project2_id)), content_type='application/json', ) # list all response = business_client.get('/api/dm/views/') assert response.status_code == 200, response.content assert len(response.json()) == 2 # filtered list response = business_client.get(f'/api/dm/views/?project={project1_id}') assert response.status_code == 200, response.content assert response.json()[0]['project'] == project1_id # filtered reset response = business_client.delete( '/api/dm/views/reset/', data=json.dumps(dict(project=project1_id)), content_type='application/json', ) assert response.status_code == 204, response.content # filtered list response = business_client.get(f'/api/dm/views/?project={project2_id}') assert len(response.json()) == 1 assert response.json()[0]['project'] == project2_id def test_views_api_filters(business_client, project_id): # create payload = dict( project=project_id, data={ 'filters': { 'conjunction': 'or', 'items': [ { 'filter': 'filter:tasks:data.image', 'operator': 'contains', 'type': 'Image', 'value': {}, }, { 'filter': 'filter:tasks:data.image', 'operator': 'equal', 'type': 'Image', 'value': {}, }, ], } }, ) response = business_client.post( '/api/dm/views/', data=json.dumps(payload), content_type='application/json', ) assert response.status_code == 201, response.content view_id = response.json()['id'] # retrieve response = business_client.get( f'/api/dm/views/{view_id}/', ) assert response.status_code == 200, response.content assert response.json()['data'] == payload['data'] updated_payload = dict( project=project_id, data={ 'filters': { 'conjunction': 'and', 'items': [ { 'filter': 'filter:tasks:data.text', 'operator': 'equal', 'type': 'Text', 'value': {}, }, { 'filter': 'filter:tasks:data.text', 'operator': 'contains', 'type': 'Text', 'value': {}, }, ], } }, ) response = business_client.put( f'/api/dm/views/{view_id}/', data=json.dumps(updated_payload), content_type='application/json', ) assert response.status_code == 200, response.content # check after update response = business_client.get( f'/api/dm/views/{view_id}/', ) assert response.status_code == 200, response.content assert response.json()['data'] == updated_payload['data'] def test_views_api_nested_filters(business_client, project_id): """Test creating views with nested filters using child filters. This test validates the nested filter structure where a parent filter can have child filters that are AND-merged with the parent. This is similar to the enterprise annotations_results_json filters but uses regular task data filters. """ # Create a project with specific label config for testing project_response = business_client.post( '/api/projects/', data=json.dumps( { 'title': 'test_nested_filters', 'label_config': """ """, } ), content_type='application/json', ) assert project_response.status_code == 201 project = project_response.json() # Create tasks with different data task1_data = {'text': 'task1', 'category': 'A'} task1_response = business_client.post( f'/api/projects/{project["id"]}/tasks', data=json.dumps({'data': task1_data}), content_type='application/json', ) assert task1_response.status_code == 201 task1 = task1_response.json() task2_data = {'text': 'task2', 'category': 'B'} task2_response = business_client.post( f'/api/projects/{project["id"]}/tasks', data=json.dumps({'data': task2_data}), content_type='application/json', ) assert task2_response.status_code == 201 task2 = task2_response.json() task3_data = {'text': 'task3', 'category': 'A'} task3_response = business_client.post( f'/api/projects/{project["id"]}/tasks', data=json.dumps({'data': task3_data}), content_type='application/json', ) assert task3_response.status_code == 201 task3 = task3_response.json() # Create a view with nested filters # Parent filter: tasks with category 'A' # Child filter: tasks with text containing 'task1' nested_filter_payload = { 'project': project['id'], 'data': { 'filters': { 'conjunction': 'and', 'items': [ { 'filter': 'filter:tasks:data.category', 'operator': 'equal', 'type': 'String', 'value': 'A', 'child_filter': { 'filter': 'filter:tasks:data.text', 'operator': 'contains', 'type': 'String', 'value': 'task1', }, } ], } }, } response = business_client.post( '/api/dm/views/', data=json.dumps(nested_filter_payload), content_type='application/json', ) assert response.status_code == 201, response.content view_id = response.json()['id'] # Retrieve the created view and verify the nested structure response = business_client.get(f'/api/dm/views/{view_id}/') assert response.status_code == 200, response.content view_data = response.json()['data'] filter_data = view_data['filters'] # Verify the filter structure assert filter_data['conjunction'] == 'and' assert len(filter_data['items']) == 1 root_filter = filter_data['items'][0] assert root_filter['filter'] == 'filter:tasks:data.category' assert root_filter['operator'] == 'equal' assert root_filter['type'] == 'String' assert root_filter['value'] == 'A' # Verify child filter structure assert 'child_filter' in root_filter child_filter = root_filter['child_filter'] assert child_filter['filter'] == 'filter:tasks:data.text' assert child_filter['operator'] == 'contains' assert child_filter['type'] == 'String' assert child_filter['value'] == 'task1' # Test that the view filters tasks correctly # Only task1 should match: category='A' AND text contains 'task1' response = business_client.get(f'/api/tasks?view={view_id}') assert response.status_code == 200, response.content tasks = response.json()['tasks'] assert len(tasks) == 1 assert tasks[0]['id'] == task1['id'] # Test with a different nested filter structure # Parent filter: tasks with category 'A' or 'B' # Child filter: tasks with text containing 'task' complex_nested_payload = { 'project': project['id'], 'data': { 'filters': { 'conjunction': 'or', 'items': [ { 'filter': 'filter:tasks:data.category', 'operator': 'equal', 'type': 'String', 'value': 'A', 'child_filter': { 'filter': 'filter:tasks:data.text', 'operator': 'contains', 'type': 'String', 'value': 'task', }, }, { 'filter': 'filter:tasks:data.category', 'operator': 'equal', 'type': 'String', 'value': 'B', 'child_filter': { 'filter': 'filter:tasks:data.text', 'operator': 'contains', 'type': 'String', 'value': 'task', }, }, ], } }, } response = business_client.post( '/api/dm/views/', data=json.dumps(complex_nested_payload), content_type='application/json', ) assert response.status_code == 201, response.content complex_view_id = response.json()['id'] # Test the complex nested filter response = business_client.get(f'/api/tasks?view={complex_view_id}') assert response.status_code == 200, response.content tasks = response.json()['tasks'] # Should match all tasks: (category='A' AND text contains 'task') OR (category='B' AND text contains 'task') assert len(tasks) == 3 task_ids = [task['id'] for task in tasks] assert task1['id'] in task_ids assert task2['id'] in task_ids assert task3['id'] in task_ids def test_views_api_patch_add_child_filter(business_client, project_id): """Test creating a view with a non-nested filter, then PATCHing it to add a child filter. This test validates the behavior of updating a view's filter structure by adding child filters to existing filters through PATCH requests. """ # Create a project with specific label config for testing project_response = business_client.post( '/api/projects/', data=json.dumps( { 'title': 'test_patch_child_filter', 'label_config': """ """, } ), content_type='application/json', ) assert project_response.status_code == 201 project = project_response.json() # Create tasks with different data task1_data = {'text': 'task1', 'category': 'A'} task1_response = business_client.post( f'/api/projects/{project["id"]}/tasks', data=json.dumps({'data': task1_data}), content_type='application/json', ) assert task1_response.status_code == 201 task1 = task1_response.json() task2_data = {'text': 'task2', 'category': 'A'} task2_response = business_client.post( f'/api/projects/{project["id"]}/tasks', data=json.dumps({'data': task2_data}), content_type='application/json', ) assert task2_response.status_code == 201 task2 = task2_response.json() task3_data = {'text': 'task3', 'category': 'B'} task3_response = business_client.post( f'/api/projects/{project["id"]}/tasks', data=json.dumps({'data': task3_data}), content_type='application/json', ) assert task3_response.status_code == 201 task3 = task3_response.json() # Step 1: Create a view with a non-nested filter # Filter: tasks with category 'A' simple_filter_payload = { 'project': project['id'], 'data': { 'filters': { 'conjunction': 'and', 'items': [ { 'filter': 'filter:tasks:data.category', 'operator': 'equal', 'type': 'String', 'value': 'A', } ], } }, } response = business_client.post( '/api/dm/views/', data=json.dumps(simple_filter_payload), content_type='application/json', ) assert response.status_code == 201, response.content view_id = response.json()['id'] # Verify the initial view has no child filters response = business_client.get(f'/api/dm/views/{view_id}/') assert response.status_code == 200, response.content view_data = response.json()['data'] filter_data = view_data['filters'] # Verify the initial filter structure (no child filters) assert filter_data['conjunction'] == 'and' assert len(filter_data['items']) == 1 root_filter = filter_data['items'][0] assert root_filter['filter'] == 'filter:tasks:data.category' assert root_filter['operator'] == 'equal' assert root_filter['type'] == 'String' assert root_filter['value'] == 'A' # Verify no child filter exists initially assert 'child_filter' not in root_filter # Test that the initial view filters tasks correctly # Should match task1 and task2 (both have category='A') response = business_client.get(f'/api/tasks?view={view_id}') assert response.status_code == 200, response.content tasks = response.json()['tasks'] assert len(tasks) == 2 task_ids = [task['id'] for task in tasks] assert task1['id'] in task_ids assert task2['id'] in task_ids assert task3['id'] not in task_ids # Step 2: PATCH the view to add a child filter # Add child filter: tasks with text containing 'task1' patch_payload = { 'data': { 'filters': { 'conjunction': 'and', 'items': [ { 'filter': 'filter:tasks:data.category', 'operator': 'equal', 'type': 'String', 'value': 'A', 'child_filter': { 'filter': 'filter:tasks:data.text', 'operator': 'contains', 'type': 'String', 'value': 'task1', }, } ], } }, } response = business_client.patch( f'/api/dm/views/{view_id}/', data=json.dumps(patch_payload), content_type='application/json', ) assert response.status_code == 200, response.content # Step 3: Verify the PATCHed view has the child filter response = business_client.get(f'/api/dm/views/{view_id}/') assert response.status_code == 200, response.content view_data = response.json()['data'] filter_data = view_data['filters'] # Verify the updated filter structure (now has child filter) assert filter_data['conjunction'] == 'and' assert len(filter_data['items']) == 1 root_filter = filter_data['items'][0] assert root_filter['filter'] == 'filter:tasks:data.category' assert root_filter['operator'] == 'equal' assert root_filter['type'] == 'String' assert root_filter['value'] == 'A' # Verify child filter was added assert 'child_filter' in root_filter child_filter = root_filter['child_filter'] assert child_filter['filter'] == 'filter:tasks:data.text' assert child_filter['operator'] == 'contains' assert child_filter['type'] == 'String' assert child_filter['value'] == 'task1' # Step 4: Test that the PATCHed view filters tasks correctly # Should now only match task1: category='A' AND text contains 'task1' response = business_client.get(f'/api/tasks?view={view_id}') assert response.status_code == 200, response.content tasks = response.json()['tasks'] assert len(tasks) == 1 assert tasks[0]['id'] == task1['id'] # Step 5: PATCH again to modify the child filter # Change child filter to: tasks with text containing 'task' patch_payload_2 = { 'data': { 'filters': { 'conjunction': 'and', 'items': [ { 'filter': 'filter:tasks:data.category', 'operator': 'equal', 'type': 'String', 'value': 'A', 'child_filter': { 'filter': 'filter:tasks:data.text', 'operator': 'contains', 'type': 'String', 'value': 'task', }, } ], } }, } response = business_client.patch( f'/api/dm/views/{view_id}/', data=json.dumps(patch_payload_2), content_type='application/json', ) assert response.status_code == 200, response.content # Step 6: Verify the child filter was updated response = business_client.get(f'/api/dm/views/{view_id}/') assert response.status_code == 200, response.content view_data = response.json()['data'] filter_data = view_data['filters'] root_filter = filter_data['items'][0] child_filter = root_filter['child_filter'] assert child_filter['value'] == 'task' # Updated value # Test that the updated view filters tasks correctly # Should now match task1 and task2: category='A' AND text contains 'task' response = business_client.get(f'/api/tasks?view={view_id}') assert response.status_code == 200, response.content tasks = response.json()['tasks'] assert len(tasks) == 2 task_ids = [task['id'] for task in tasks] assert task1['id'] in task_ids assert task2['id'] in task_ids assert task3['id'] not in task_ids def test_views_ordered_by_id(business_client, project_id): views = [{'view_data': 1}, {'view_data': 2}, {'view_data': 3}] for view in views: payload = dict(project=project_id, data=view) business_client.post( '/api/dm/views/', data=json.dumps(payload), content_type='application/json', ) response = business_client.get('/api/dm/views/') data = response.json() assert response.status_code == status.HTTP_200_OK ids = [view['id'] for view in data] assert ids == sorted(ids) def test_update_views_order(business_client, project_id): # Create views views = [{'view_data': 1}, {'view_data': 2}, {'view_data': 3}] view_ids = [] for view in views: payload = dict(project=project_id, data=view) response = business_client.post( '/api/dm/views/', data=json.dumps(payload), content_type='application/json', ) assert response.status_code == status.HTTP_201_CREATED view_ids.append(response.json()['id']) # Update the order of views new_order = {'project': project_id, 'ids': [view_ids[2], view_ids[0], view_ids[1]]} response = business_client.post( '/api/dm/views/order/', data=json.dumps(new_order), content_type='application/json', ) assert response.status_code == status.HTTP_200_OK # Verify the new order response = business_client.get('/api/dm/views/') data = response.json() assert response.status_code == status.HTTP_200_OK returned_ids = [view['id'] for view in data] assert returned_ids == new_order['ids']