tests for examples listed in spec

This commit is contained in:
S. Andrew Sheppard 2015-03-12 18:11:14 -05:00
parent 7fa40cfa57
commit 19fa92ebd3
2 changed files with 230 additions and 13 deletions

View File

@ -18,7 +18,7 @@ def parse_json_form(dictionary, prefix=''):
""" """
# Step 1: Initialize output object # Step 1: Initialize output object
output = {} output = {}
for name, value in dictionary.items(): for name, value in get_all_items(dictionary):
# TODO: implement is_file flag # TODO: implement is_file flag
# Step 2: Compute steps array # Step 2: Compute steps array
@ -40,6 +40,8 @@ def parse_json_form(dictionary, prefix=''):
entry_value=value, entry_value=value,
is_file=False, is_file=False,
) )
# Convert any remaining Undefined array entries to None
output = clean_undefined(output)
# Account for DRF prefix (not part of JSON form spec) # Account for DRF prefix (not part of JSON form spec)
result = get_value(output, prefix, Undefined()) result = get_value(output, prefix, Undefined())
@ -101,7 +103,7 @@ def parse_json_path(path):
# Step 8 - Loop # Step 8 - Loop
while path: while path:
# Step 8.1 - Check for single-item array # Step 8.1 - Check for single-item array
if path[:1] == "[]": if path[:2] == "[]":
steps[-1].append = True steps[-1].append = True
path = path[2:] path = path[2:]
if path: if path:
@ -153,8 +155,6 @@ def set_json_value(context, step, current_value, entry_value, is_file):
# TODO: handle is_file # TODO: handle is_file
# Add empty values to array so indexing works like JavaScript # Add empty values to array so indexing works like JavaScript
# TODO: According to spec, these should turn into nulls if they are never
# overriden in a later step.
if isinstance(context, list) and isinstance(step.key, int): if isinstance(context, list) and isinstance(step.key, int):
while len(context) <= step.key: while len(context) <= step.key:
context.append(Undefined()) context.append(Undefined())
@ -163,13 +163,22 @@ def set_json_value(context, step, current_value, entry_value, is_file):
if step.last: if step.last:
if isinstance(current_value, Undefined): if isinstance(current_value, Undefined):
# Step 7.1: No existing value # Step 7.1: No existing value
key = step.key
if isinstance(context, dict) and isinstance(key, int):
key = str(key)
if step.append: if step.append:
context[step.key] = [entry_value] context[key] = [entry_value]
else: else:
context[step.key] = entry_value context[key] = entry_value
elif isinstance(current_value, list): elif isinstance(current_value, list):
# Step 7.2: Existing value is an Array # Step 7.2: Existing value is an Array, assume multi-valued field
# and add entry to end.
# FIXME: What if the other items in the array had explicit keys and
# this one is supposed to be the "" value?
# (See step 8.4 and Example 7)
context[step.key].append(entry_value) context[step.key].append(entry_value)
elif isinstance(current_value, dict) and not is_file: elif isinstance(current_value, dict) and not is_file:
# Step 7.3: Existing value is an Object # Step 7.3: Existing value is an Object
return set_json_value( return set_json_value(
@ -195,20 +204,21 @@ def set_json_value(context, step, current_value, entry_value, is_file):
context[step.key] = {} context[step.key] = {}
return context[step.key] return context[step.key]
elif isinstance(current_value, dict): elif isinstance(current_value, dict):
# Step 7.2: Existing value is an Object # Step 8.2: Existing value is an Object
return get_value(context, step.key, Undefined()) return get_value(context, step.key, Undefined())
elif isinstance(current_value, list): elif isinstance(current_value, list):
# Step 7.3: Existing value is an Array # Step 8.3: Existing value is an Array
if step.next_type == "array": if step.next_type == "array":
return current_value return current_value
# Convert array to object to facilitate mixed keys
obj = {} obj = {}
for i, item in enumerate(current_value): for i, item in enumerate(current_value):
if not isinstance(item, Undefined): if not isinstance(item, Undefined):
obj[i] = item obj[str(i)] = item
context[step.key] = obj context[step.key] = obj
return obj return obj
else: else:
# 7.4: Existing value is a scalar; convert to Object, preserving # 8.4: Existing value is a scalar; convert to Object, preserving
# current value via an empty key # current value via an empty key
obj = {'': current_value} obj = {'': current_value}
context[step.key] = obj context[step.key] = obj
@ -255,3 +265,32 @@ class JsonStep(object):
append = None append = None
last = None last = None
failed = None failed = None
def clean_undefined(obj):
"""
Convert Undefined array entries to None (null)
"""
if isinstance(obj, list):
return [
None if isinstance(item, Undefined) else item
for item in obj
]
if isinstance(obj, dict):
for key in obj:
obj[key] = clean_undefined(obj[key])
return obj
def get_all_items(obj):
"""
dict.items() but with a separate row for each value in a MultiValueDict
"""
if hasattr(obj, 'getlist'):
items = []
for key in obj:
for value in obj.getlist(key):
items.append((key, value))
return items
else:
return obj.items()

178
tests/test_json_forms.py Normal file
View File

@ -0,0 +1,178 @@
from rest_framework.utils import html
from django.utils.datastructures import MultiValueDict
class TestHtmlJsonExamples:
"""
Tests for the HTML JSON form algorithm
Examples 1-10 from http://www.w3.org/TR/html-json-forms/
"""
def test_basic_keys(self):
"""
Example 1: Basic Keys
"""
input_data = {
'name': "Bender",
'hind': "Bitable",
'shiny': True
}
expected_output = input_data
result = html.parse_json_form(input_data)
assert result == expected_output
def test_multiple_values(self):
"""
Example 2: Multiple Values
"""
input_data = MultiValueDict()
input_data.setlist('bottle-on-wall', [1, 2, 3])
expected_output = {
'bottle-on-wall': [1, 2, 3]
}
result = html.parse_json_form(input_data)
assert result == expected_output
def test_deeper_structure(self):
"""
Example 3: Deeper Structure
"""
input_data = {
'pet[species]': "Dahut",
'pet[name]': "Hypatia",
'kids[1]': "Thelma",
'kids[0]': "Ashley",
}
expected_output = {
'pet': {
'species': "Dahut",
'name': "Hypatia",
},
'kids': ["Ashley", "Thelma"],
}
result = html.parse_json_form(input_data)
assert result == expected_output
def test_sparse_arrays(self):
"""
Example 4: Sparse Arrays
"""
input_data = {
"hearbeat[0]": "thunk",
"hearbeat[2]": "thunk",
}
expected_output = {
"hearbeat": ["thunk", None, "thunk"]
}
result = html.parse_json_form(input_data)
assert result == expected_output
def test_even_deeper(self):
"""
Example 5: Even Deeper
"""
input_data = {
'pet[0][species]': "Dahut",
'pet[0][name]': "Hypatia",
'pet[1][species]': "Felis Stultus",
'pet[1][name]': "Billie",
}
expected_output = {
'pet': [
{
'species': "Dahut",
'name': "Hypatia",
},
{
'species': "Felis Stultus",
'name': "Billie",
}
]
}
result = html.parse_json_form(input_data)
assert result == expected_output
def test_such_deep(self):
"""
Example 6: Such Deep
"""
input_data = {
'wow[such][deep][3][much][power][!]': "Amaze",
}
expected_output = {
'wow': {
'such': {
'deep': [
None,
None,
None,
{
'much': {
'power': {
'!': "Amaze",
}
}
}
]
}
}
}
result = html.parse_json_form(input_data)
assert result == expected_output
def test_merge_behaviour(self):
"""
Example 7: Merge Behaviour
"""
# FIXME: Shouldn't this work regardless of key order?
from rest_framework.compat import OrderedDict
input_data = OrderedDict([
('mix', "scalar"),
('mix[0]', "array 1"),
('mix[2]', "array 2"),
('mix[key]', "key key"),
('mix[car]', "car key"),
])
expected_output = {
'mix': {
'': "scalar",
'0': "array 1",
'2': "array 2",
'key': "key key",
'car': "car key",
}
}
result = html.parse_json_form(input_data)
assert result == expected_output
def test_append(self):
"""
Example 8: Append
"""
input_data = {
'highlander[]': "one",
}
expected_output = {
'highlander': ["one"],
}
result = html.parse_json_form(input_data)
assert result == expected_output
# TODO: Example 9: Files
def test_invalid(self):
"""
Example 10: Invalid
"""
input_data = {
"error[good]": "BOOM!",
"error[bad": "BOOM BOOM!",
}
expected_output = {
'error': {
'good': "BOOM!",
},
'error[bad': "BOOM BOOM!",
}
result = html.parse_json_form(input_data)
assert result == expected_output