"""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': """
|
<View>
|
<Text name="text" value="$text"></Text>
|
<Choices name="choice" toName="text">
|
<Choice value="A" />
|
<Choice value="B" />
|
</Choices>
|
<Labels name="labels" toName="text">
|
<Label value="Label 1" />
|
<Label value="Label 2" />
|
</Labels>
|
</View>
|
""",
|
}
|
),
|
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': """
|
<View>
|
<Text name="text" value="$text"></Text>
|
<Choices name="choice" toName="text">
|
<Choice value="A" />
|
<Choice value="B" />
|
</Choices>
|
<Labels name="labels" toName="text">
|
<Label value="Label 1" />
|
<Label value="Label 2" />
|
</Labels>
|
</View>
|
""",
|
}
|
),
|
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']
|