[CalendarServer-changes] [10133] CalDAVTester/trunk

source_changes at macosforge.org source_changes at macosforge.org
Fri Dec 7 09:50:06 PST 2012


Revision: 10133
          http://trac.calendarserver.org//changeset/10133
Author:   cdaboo at apple.com
Date:     2012-12-07 09:50:06 -0800 (Fri, 07 Dec 2012)
Log Message:
-----------
JSON pointer based verifier.

Added Paths:
-----------
    CalDAVTester/trunk/src/jsonPointer.py
    CalDAVTester/trunk/src/unittest/
    CalDAVTester/trunk/src/unittest/__init__.py
    CalDAVTester/trunk/src/unittest/test_jsonPointer.py
    CalDAVTester/trunk/verifiers/jsonPointerMatch.py

Added: CalDAVTester/trunk/src/jsonPointer.py
===================================================================
--- CalDAVTester/trunk/src/jsonPointer.py	                        (rev 0)
+++ CalDAVTester/trunk/src/jsonPointer.py	2012-12-07 17:50:06 UTC (rev 10133)
@@ -0,0 +1,171 @@
+##
+# Copyright (c) 2012 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+import json
+
+
+class JSONPointerMatchError(Exception):
+    """
+    Exception for failed pointer matches
+    """
+    pass
+
+
+
+class JSONPointer(object):
+    """
+    Represents a JSON Pointer that can match a specific JSON object.
+    """
+
+    def __init__(self, pointer):
+
+        if not pointer or pointer[0] != "/":
+            raise ValueError("Invalid JSON pointer: %s" % (pointer,))
+        self.segments = self._splitSegments(pointer)
+
+
+    def _splitSegments(self, pointer):
+        """
+        Split a pointer up into segments.
+
+        @param pointer: the pointer
+        @type pointer: C{str}
+
+        @return: list of segments
+        @rtype C{list}
+        """
+        splits = pointer[1:].split("/")
+        if splits == [""]:
+            return None
+        if any(map(lambda x: len(x) == 0, splits)):
+            raise TypeError("Pointer segment is empty: %s" % (pointer,))
+        return map(self._unescape, splits)
+
+
+    def _unescape(self, segment):
+        """
+        Unescape ~0 and ~1 in a path segment.
+
+        @param segment: the segment to unescape
+        @type segment: C{str}
+
+        @return: the unescaped segment
+        @rtype: C{str}
+        """
+        return segment.replace("~1", "/").replace("~0", "~")
+
+
+    def matchs(self, s):
+        """
+        Match this pointer against the string representation of a JSON object.
+
+        @param s: a string representation of a JSON object
+        @type s: C{str}
+        """
+
+        return self.match(json.loads(s))
+
+
+    def match(self, j):
+        """
+        Match this pointer against the JSON object.
+
+        @param j: a JSON object
+        @type j: C{dict} or C{list}
+        """
+
+        try:
+            return self.walk(j, self.segments)
+        except Exception:
+            raise JSONPointerMatchError
+
+
+    def walk(self, j, segments):
+        """
+        Recursively match the next portion of a pointer segment.
+
+        @param j: JSON object to match
+        @type j: C{dict} or C{list}
+        @param segments: list of pointer segments
+        @type segments: C{list}
+        """
+
+        if not segments:
+            return j
+
+        if isinstance(j, dict):
+            return self.walk(j[segments[0]], segments[1:])
+        elif isinstance(j, list):
+            index = -1 if segments[0] == "-" else int(segments[0])
+            return self.walk(j[index], segments[1:])
+        else:
+            raise JSONPointerMatchError
+
+
+
+class JSONMatcher(JSONPointer):
+    """
+    Represents a JSON pointer with syntax allowing a match against multiple JSON objects. If any
+    segment of a path is a single ".", then all object or array members are matched. The result of
+    the match is the array of objects that match. Missing keys and index past the end are ignored.
+    """
+
+    def __init__(self, pointer):
+
+        if not pointer or pointer[0] != "/":
+            raise ValueError("Invalid JSON pointer: %s" % (pointer,))
+        self.segments = self._splitSegments(pointer)
+
+
+    def walk(self, j, segments):
+        """
+        Recursively match the next portion of a pointer segment, talking wildcard "."
+        segment matching into account.
+
+        @param j: JSON object to match
+        @type j: C{dict} or C{list}
+        @param segments: list of pointer segments
+        @type segments: C{list}
+        """
+
+        if not segments:
+            return [j, ]
+
+        results = []
+        if isinstance(j, dict):
+            if segments[0] == ".":
+                keys = j.keys()
+            else:
+                keys = [segments[0]]
+            for k in keys:
+                try:
+                    results.extend(self.walk(j[k], segments[1:]))
+                except KeyError:
+                    pass
+        elif isinstance(j, list):
+            if segments[0] == ".":
+                r = range(len(j))
+            else:
+                r = [-1 if segments[0] == "-" else int(segments[0])]
+            for index in r:
+                try:
+                    results.extend(self.walk(j[index], segments[1:]))
+                except IndexError:
+                    pass
+        else:
+            raise JSONPointerMatchError
+
+        return results

Added: CalDAVTester/trunk/src/unittest/__init__.py
===================================================================
--- CalDAVTester/trunk/src/unittest/__init__.py	                        (rev 0)
+++ CalDAVTester/trunk/src/unittest/__init__.py	2012-12-07 17:50:06 UTC (rev 10133)
@@ -0,0 +1,15 @@
+##
+# Copyright (c) 2012 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##

Added: CalDAVTester/trunk/src/unittest/test_jsonPointer.py
===================================================================
--- CalDAVTester/trunk/src/unittest/test_jsonPointer.py	                        (rev 0)
+++ CalDAVTester/trunk/src/unittest/test_jsonPointer.py	2012-12-07 17:50:06 UTC (rev 10133)
@@ -0,0 +1,260 @@
+##
+#    Copyright (c) 2012 Cyrus Daboo. All rights reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License");
+#    you may not use this file except in compliance with the License.
+#    You may obtain a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS,
+#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#    See the License for the specific language governing permissions and
+#    limitations under the License.
+##
+
+import unittest
+from src.jsonPointer import JSONPointer, JSONPointerMatchError, JSONMatcher
+
+class TestJSONPointer(unittest.TestCase):
+
+    def testValidPointers(self):
+        data = (
+            (None, False),
+            ("", False),
+            (1, False),
+            ("/", True),
+            ("//", False),
+            ("/abc", True),
+            ("/abc/", False),
+        )
+
+        for pointer, result in data:
+            try:
+                JSONPointer(pointer)
+                ok = True
+            except (ValueError, TypeError):
+                ok = False
+            self.assertEqual(ok, result, "Failed test: %s" % (pointer,))
+
+
+    def testUnescape(self):
+        data = (
+            ("/", None),
+            ("/~0", ["~", ]),
+            ("/abc~1def", ["abc/def", ]),
+            ("/abc/~0~1a", ["abc", "~/a", ]),
+            ("/~0ab~1c/~0~1a", ["~ab/c", "~/a", ]),
+        )
+
+        for pointer, result in data:
+            j = JSONPointer(pointer)
+            self.assertEqual(j.segments, result, "Failed test: %s" % (pointer,))
+
+
+    def testMatchOK(self):
+        data = (
+            # Objects
+            ("/", '{"1": "foo"}', {"1": "foo"}),
+            ("/1", '{"1": "foo", "2": "bar"}', "foo"),
+            ("/2", '{"1": "foo", "2": "bar"}', "bar"),
+            ("/1", '{"1": {"1.1": "foo"}}', {"1.1": "foo"}),
+            ("/1/1.1", '{"1": {"1.1": "foo", "1.2": "bar"}}', "foo"),
+            ("/1/1.2", '{"1": {"1.1": "foo", "1.2": "bar"}}', "bar"),
+
+            # Arrays
+            ("/", '["1", "2"]', ["1", "2"]),
+            ("/0", '["1", "2"]', "1"),
+            ("/1", '["1", "2"]', "2"),
+            ("/-", '["1", "2"]', "2"),
+            ("/0", '[["1", "2"]]', ["1", "2"]),
+            ("/-", '[["1", "2"]]', ["1", "2"]),
+            ("/0/0", '[["1", "2"]]', "1"),
+            ("/0/1", '[["1", "2"]]', "2"),
+            ("/0/-", '[["1", "2"]]', "2"),
+
+            # Both
+            ("/", '{"1": ["foo", "bar"]}', {"1": ["foo", "bar"]}),
+            ("/1", '{"1": ["foo", "bar"]}', ["foo", "bar"]),
+            ("/1/0", '{"1": ["foo", "bar"]}', "foo"),
+            ("/1/1", '{"1": ["foo", "bar"]}', "bar"),
+            ("/1/-", '{"1": ["foo", "bar"]}', "bar"),
+            ("/", '[{"1.1": "foo", "1.2": "bar"}]', [{"1.1": "foo", "1.2": "bar"}]),
+            ("/0", '[{"1.1": "foo", "1.2": "bar"}]', {"1.1": "foo", "1.2": "bar"}),
+            ("/-", '[{"1.1": "foo", "1.2": "bar"}]', {"1.1": "foo", "1.2": "bar"}),
+            ("/0/1.1", '[{"1.1": "foo", "1.2": "bar"}]', "foo"),
+            ("/0/1.2", '[{"1.1": "foo", "1.2": "bar"}]', "bar"),
+        )
+
+        for pointer, jobj, result in data:
+            j = JSONPointer(pointer)
+            self.assertEqual(j.matchs(jobj), result, "Failed test: %s" % (pointer,))
+
+
+    def testMatchBad(self):
+        data = (
+            # Objects
+            ("/3", '{"1": "foo", "2": "bar"}'),
+            ("/a", '{"1": "foo", "2": "bar"}'),
+            ("/-", '{"1": "foo", "2": "bar"}'),
+            ("/1/3", '{"1": {"1.1": "foo", "1.2": "bar"}}'),
+            ("/1/a", '{"1": {"1.1": "foo", "1.2": "bar"}}'),
+            ("/1/-", '{"1": {"1.1": "foo", "1.2": "bar"}}'),
+
+            # Arrays
+            ("/2", '["1", "2"]'),
+            ("/0/2", '[["1", "2"]]'),
+
+            # Both
+            ("/1/3", '{"1": ["foo", "bar"]}'),
+            ("/0/3", '[{"1.1": "foo", "1.2": "bar"}]'),
+            ("/0/a", '[{"1.1": "foo", "1.2": "bar"}]'),
+            ("/0/-", '[{"1.1": "foo", "1.2": "bar"}]'),
+
+            # Wrong Object
+            ("/1/0", '{"1": "foo"}'),
+            ("/1/0", '{"1": 1}'),
+            ("/1/0", '{"1": true}'),
+            ("/1/0", '{"1": null}'),
+        )
+
+        for pointer, jobj in data:
+            j = JSONPointer(pointer)
+            self.assertRaises(JSONPointerMatchError, j.matchs, jobj)
+
+
+
+class TestJSONMatcher(unittest.TestCase):
+
+    def testMatchOK(self):
+        data = (
+            # Objects
+            ("/", '{"1": "foo"}', [{"1": "foo"}]),
+            ("/1", '{"1": "foo", "2": "bar"}', ["foo"]),
+            ("/2", '{"1": "foo", "2": "bar"}', ["bar"]),
+            ("/1", '{"1": {"1.1": "foo"}}', [{"1.1": "foo"}]),
+            ("/1/1.1", '{"1": {"1.1": "foo", "1.2": "bar"}}', ["foo"]),
+            ("/1/1.2", '{"1": {"1.1": "foo", "1.2": "bar"}}', ["bar"]),
+
+            # Arrays
+            ("/", '["1", "2"]', [["1", "2"]]),
+            ("/0", '["1", "2"]', ["1"]),
+            ("/1", '["1", "2"]', ["2"]),
+            ("/-", '["1", "2"]', ["2"]),
+            ("/0", '[["1", "2"]]', [["1", "2"]]),
+            ("/-", '[["1", "2"]]', [["1", "2"]]),
+            ("/0/0", '[["1", "2"]]', ["1"]),
+            ("/0/1", '[["1", "2"]]', ["2"]),
+            ("/0/-", '[["1", "2"]]', ["2"]),
+
+            # Both
+            ("/", '{"1": ["foo", "bar"]}', [{"1": ["foo", "bar"]}]),
+            ("/1", '{"1": ["foo", "bar"]}', [["foo", "bar"]]),
+            ("/1/0", '{"1": ["foo", "bar"]}', ["foo"]),
+            ("/1/1", '{"1": ["foo", "bar"]}', ["bar"]),
+            ("/1/-", '{"1": ["foo", "bar"]}', ["bar"]),
+            ("/", '[{"1.1": "foo", "1.2": "bar"}]', [[{"1.1": "foo", "1.2": "bar"}]]),
+            ("/0", '[{"1.1": "foo", "1.2": "bar"}]', [{"1.1": "foo", "1.2": "bar"}]),
+            ("/-", '[{"1.1": "foo", "1.2": "bar"}]', [{"1.1": "foo", "1.2": "bar"}]),
+            ("/0/1.1", '[{"1.1": "foo", "1.2": "bar"}]', ["foo"]),
+            ("/0/1.2", '[{"1.1": "foo", "1.2": "bar"}]', ["bar"]),
+        )
+
+        for pointer, jobj, result in data:
+            j = JSONMatcher(pointer)
+            self.assertEqual(j.matchs(jobj), result, "Failed test: %s" % (pointer,))
+
+
+    def testMatchBad(self):
+        data = (
+            # Objects
+            ("/3", '{"1": "foo", "2": "bar"}', False),
+            ("/a", '{"1": "foo", "2": "bar"}', False),
+            ("/-", '{"1": "foo", "2": "bar"}', False),
+            ("/1/3", '{"1": {"1.1": "foo", "1.2": "bar"}}', False),
+            ("/1/a", '{"1": {"1.1": "foo", "1.2": "bar"}}', False),
+            ("/1/-", '{"1": {"1.1": "foo", "1.2": "bar"}}', False),
+
+            # Arrays
+            ("/2", '["1", "2"]', False),
+            ("/0/2", '[["1", "2"]]', False),
+
+            # Both
+            ("/1/3", '{"1": ["foo", "bar"]}', False),
+            ("/0/3", '[{"1.1": "foo", "1.2": "bar"}]', False),
+            ("/0/a", '[{"1.1": "foo", "1.2": "bar"}]', False),
+            ("/0/-", '[{"1.1": "foo", "1.2": "bar"}]', False),
+
+            # Wrong Object
+            ("/1/0", '{"1": "foo"}', True),
+            ("/1/0", '{"1": 1}', True),
+            ("/1/0", '{"1": true}', True),
+            ("/1/0", '{"1": null}', True),
+        )
+
+        for pointer, jobj, willRaise in data:
+            j = JSONMatcher(pointer)
+            if willRaise:
+                self.assertRaises(JSONPointerMatchError, j.matchs, jobj)
+            else:
+                self.assertEqual(j.matchs(jobj), [], "Failed test: %s" % (pointer,))
+
+
+    def testMatchingOK(self):
+        data = (
+            (
+                "/",
+                '{"1":"foo", "2": "bar"}',
+                [{"1":"foo", "2": "bar"}, ],
+            ),
+            (
+                "/.",
+                '{"1":"foo", "2": "bar"}',
+                ["foo", "bar", ],
+            ),
+            (
+                "/./0",
+                '{"1":["foo1", "foo2"], "2": ["bar1", "bar2"]}',
+                ["foo1", "bar1", ],
+            ),
+            (
+                "/./1",
+                '{"1":["foo1", "foo2"], "2": ["bar1", "bar2"]}',
+                ["foo2", "bar2", ],
+            ),
+            (
+                "/./-",
+                '{"1":["foo1", "foo2"], "2": ["bar1", "bar2"]}',
+                ["foo2", "bar2", ],
+            ),
+            (
+                "/./2",
+                '{"1":["foo1", "foo2"], "2": ["bar1", "bar2"]}',
+                [],
+            ),
+            (
+                "/./foo1",
+                '{"1":{"foo1": "bar1", "foo2": "bar2"}, "2": {"foo1": "bar3", "foo4": "bar4"}}',
+                ["bar1", "bar3", ],
+            ),
+            (
+                "/./foo2",
+                '{"1":{"foo1": "bar1", "foo2": "bar2"}, "2": {"foo1": "bar3", "foo4": "bar4"}}',
+                ["bar2", ],
+            ),
+            (
+                "/./foo4",
+                '{"1":{"foo1": "bar1", "foo2": "bar2"}, "2": {"foo1": "bar3", "foo4": "bar4"}}',
+                ["bar4", ],
+            ),
+            (
+                "/./foo3",
+                '{"1":{"foo1": "bar1", "foo2": "bar2"}, "2": {"foo1": "bar3", "foo4": "bar4"}}',
+                [],
+            ),
+        )
+
+        for pointer, jobj, result in data:
+            j = JSONMatcher(pointer)
+            self.assertEqual(j.matchs(jobj), result, "Failed test: %s" % (pointer,))

Added: CalDAVTester/trunk/verifiers/jsonPointerMatch.py
===================================================================
--- CalDAVTester/trunk/verifiers/jsonPointerMatch.py	                        (rev 0)
+++ CalDAVTester/trunk/verifiers/jsonPointerMatch.py	2012-12-07 17:50:06 UTC (rev 10133)
@@ -0,0 +1,103 @@
+##
+# Copyright (c) 2010 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+
+"""
+Verifier that matches JSON content using extended JSON pointer syntax.
+
+JSON pointer syntax is extended as follows:
+
+1) A ~$xxx at the end will result in a test for the string "xxx" in the matching JSON object.
+2) A "." as a path segment will match any JSON object member or array item.
+"""
+
+import json
+from src.jsonPointer import JSONMatcher, JSONPointerMatchError
+
+class Verifier(object):
+
+    def verify(self, manager, uri, response, respdata, args): #@UnusedVariable
+        # Get arguments
+        statusCodes = args.get("status", ["200", ])
+        exists = args.get("exists", [])
+        notexists = args.get("notexists", [])
+
+        # status code must match
+        if str(response.status) not in statusCodes:
+            return False, "        HTTP Status Code Wrong: %d" % (response.status,)
+
+        # look for response data
+        if not respdata:
+            return False, "        No response body"
+
+        # Must be application/json
+        ct = response.msg.getheaders("content-type")
+        if ct[0] != "application/json":
+            return False, "        Wrong Content-Type: %s" % (ct,)
+
+        # Read in json
+        try:
+            j = json.loads(respdata)
+        except Exception, e:
+            return False, "        Response data is not JSON data: %s" % (e,)
+
+        def _splitPathTests(path):
+            if '[' in path:
+                return path.split('[', 1)
+            else:
+                return path, None
+
+        result = True
+        resulttxt = ""
+        for jpath in exists:
+            if jpath.find("~$") != -1:
+                path, value = jpath.split("~$")
+            else:
+                path, value = jpath, None
+            try:
+                jp = JSONMatcher(path)
+            except Exception:
+                result = False
+                resulttxt += "        Invalid JSON pointer for %s\n" % (path,)
+            else:
+                try:
+                    jobjs = jp.match(j)
+                    if not jobjs:
+                        result = False
+                        resulttxt += "        Items not returned in JSON for %s\n" % (path,)
+                    if value and value not in map(str, jobjs):
+                        result = False
+                        resulttxt += "        Item values not returned in JSON for %s\n" % (jpath,)
+                except JSONPointerMatchError:
+                    result = False
+                    resulttxt += "        Items not returned in JSON for %s\n" % (path,)
+
+        for jpath in notexists:
+            try:
+                jp = JSONMatcher(jpath)
+            except Exception:
+                result = False
+                resulttxt += "        Invalid JSON pointer for %s\n" % (jpath,)
+            else:
+                try:
+                    jp.match(j)
+                except JSONPointerMatchError:
+                    pass
+                else:
+                    resulttxt += "        Items returned in JSON for %s\n" % (jpath,)
+                    result = False
+
+        return result, resulttxt
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20121207/544b2fa6/attachment-0001.html>


More information about the calendarserver-changes mailing list