Added tests for resources and their methods. Also added tests for the json model.
diff --git a/Makefile b/Makefile
index 9825ad9..debf9c3 100644
--- a/Makefile
+++ b/Makefile
@@ -1,2 +1,5 @@
 pep8:
 	find apiclient samples -name "*.py" | xargs pep8 --ignore=E111,E202
+
+test:
+	python runtests.py
diff --git a/apiclient/discovery.py b/apiclient/discovery.py
index 7d884af..93133a8 100644
--- a/apiclient/discovery.py
+++ b/apiclient/discovery.py
@@ -80,12 +80,12 @@
   def request(self, headers, params):
     model = params.get('body', None)
     query = '?alt=json&prettyprint=true'
-    headers['Accept'] = 'application/json'
+    headers['accept'] = 'application/json'
     if model == None:
       return (headers, params, query, None)
     else:
       model = {'data': model}
-      headers['Content-Type'] = 'application/json'
+      headers['content-type'] = 'application/json'
       del params['body']
       return (headers, params, query, simplejson.dumps(model))
 
@@ -176,7 +176,7 @@
       for name, regex in pattern.iteritems():
         if name in kwargs:
           if re.match(regex, kwargs[name]) is None:
-            raise TypeError('Parameter "%s" value "%s" does match the pattern "%s"' % (name, kwargs[name], regex))
+            raise TypeError('Parameter "%s" value "%s" does not match the pattern "%s"' % (name, kwargs[name], regex))
 
       params = {}
       for key, value in kwargs.iteritems():
diff --git a/tests/data/buzz.json b/tests/data/buzz.json
new file mode 100644
index 0000000..0b25911
--- /dev/null
+++ b/tests/data/buzz.json
@@ -0,0 +1,988 @@
+{
+ "data": {
+  "buzz": {
+   "v1": {
+    "baseUrl": "https://www.googleapis.com/",
+    "resources": {
+     "photos": {
+      "methods": {
+       "insert": {
+        "pathUrl": "buzz/v1/photos/{userId}/{albumId}",
+        "rpcName": "buzz.photos.insert",
+        "httpMethod": "POST",
+        "methodType": "rest",
+        "parameters": {
+         "albumId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         },
+         "userId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         },
+         "resource": {
+          "parameterType": "query",
+          "required": false
+         },
+         "hl": {
+          "parameterType": "query",
+          "required": false
+         }
+        }
+       }
+      }
+     },
+     "feeds": {
+      "methods": {
+       "insert": {
+        "pathUrl": "buzz/v1/feeds/{userId}/@self",
+        "rpcName": "buzz.feeds.insert",
+        "httpMethod": "POST",
+        "methodType": "rest",
+        "parameters": {
+         "alt": {
+          "parameterType": "query",
+          "required": false
+         },
+         "userId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         },
+         "hl": {
+          "parameterType": "query",
+          "required": false
+         }
+        }
+       },
+       "update": {
+        "pathUrl": "buzz/v1/feeds/{userId}/@self/{siteId}",
+        "rpcName": "buzz.feeds.update",
+        "httpMethod": "PUT",
+        "methodType": "rest",
+        "parameters": {
+         "siteId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         },
+         "max-results": {
+          "parameterType": "query",
+          "required": false
+         },
+         "c": {
+          "parameterType": "query",
+          "required": false
+         },
+         "alt": {
+          "parameterType": "query",
+          "required": false
+         },
+         "userId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         },
+         "hl": {
+          "parameterType": "query",
+          "required": false
+         }
+        }
+       },
+       "list": {
+        "pathUrl": "buzz/v1/feeds/{userId}/{scope}",
+        "rpcName": "buzz.feeds.list",
+        "httpMethod": "GET",
+        "methodType": "rest",
+        "parameters": {
+         "scope": {
+          "parameterType": "path",
+          "pattern": "@.*",
+          "required": true
+         },
+         "alt": {
+          "parameterType": "query",
+          "required": false
+         },
+         "userId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         },
+         "hl": {
+          "parameterType": "query",
+          "required": false
+         }
+        }
+       },
+       "delete": {
+        "pathUrl": "buzz/v1/feeds/{userId}/@self/{siteId}",
+        "rpcName": "buzz.feeds.delete",
+        "httpMethod": "DELETE",
+        "methodType": "rest",
+        "parameters": {
+         "siteId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         },
+         "alt": {
+          "parameterType": "query",
+          "required": false
+         },
+         "userId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         },
+         "hl": {
+          "parameterType": "query",
+          "required": false
+         }
+        }
+       }
+      }
+     },
+     "search": {
+      "methods": {
+       "extractPeople": {
+        "pathUrl": "buzz/v1/activities/search/@people",
+        "rpcName": "buzz.activities.search.extractPeople",
+        "httpMethod": "POST",
+        "methodType": "rest",
+        "parameters": {
+         "lon": {
+          "parameterType": "query",
+          "required": false
+         },
+         "max-results": {
+          "parameterType": "query",
+          "required": false
+         },
+         "c": {
+          "parameterType": "query",
+          "required": false
+         },
+         "bbox": {
+          "parameterType": "query",
+          "required": false
+         },
+         "q": {
+          "parameterType": "query",
+          "required": false
+         },
+         "alt": {
+          "parameterType": "query",
+          "required": false
+         },
+         "pid": {
+          "parameterType": "query",
+          "required": false
+         },
+         "radius": {
+          "parameterType": "query",
+          "required": false
+         },
+         "lat": {
+          "parameterType": "query",
+          "required": false
+         },
+         "hl": {
+          "parameterType": "query",
+          "required": false
+         }
+        }
+       }
+      }
+     },
+     "activities": {
+      "methods": {
+       "update": {
+        "pathUrl": "buzz/v1/activities/{userId}/{scope}/{postId}",
+        "rpcName": "buzz.activities.update",
+        "httpMethod": "PUT",
+        "methodType": "rest",
+        "parameters": {
+         "scope": {
+          "parameterType": "path",
+          "pattern": "@.*",
+          "required": true
+         },
+         "alt": {
+          "parameterType": "query",
+          "required": false
+         },
+         "userId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         },
+         "postId": {
+          "parameterType": "path",
+          "pattern": ".*",
+          "required": true
+         },
+         "hl": {
+          "parameterType": "query",
+          "required": false
+         },
+         "abuseType": {
+          "parameterType": "query",
+          "required": false
+         }
+        }
+       },
+       "delete": {
+        "pathUrl": "buzz/v1/activities/{userId}/{scope}/{postId}",
+        "rpcName": "buzz.activities.delete",
+        "httpMethod": "DELETE",
+        "methodType": "rest",
+        "parameters": {
+         "scope": {
+          "parameterType": "path",
+          "pattern": "@.*",
+          "required": true
+         },
+         "alt": {
+          "parameterType": "query",
+          "required": false
+         },
+         "userId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         },
+         "postId": {
+          "parameterType": "path",
+          "pattern": ".*",
+          "required": true
+         },
+         "hl": {
+          "parameterType": "query",
+          "required": false
+         }
+        }
+       },
+       "insert": {
+        "pathUrl": "buzz/v1/activities/{userId}/@self",
+        "rpcName": "buzz.activities.insert",
+        "httpMethod": "POST",
+        "methodType": "rest",
+        "parameters": {
+         "preview": {
+          "parameterType": "query",
+          "required": false
+         },
+         "alt": {
+          "parameterType": "query",
+          "required": false
+         },
+         "userId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         },
+         "resource": {
+          "parameterType": "query",
+          "required": false
+         },
+         "hl": {
+          "parameterType": "query",
+          "required": false
+         }
+        }
+       },
+       "list": {
+        "pathUrl": "buzz/v1/activities/{userId}/{scope}",
+        "rpcName": "buzz.activities.list",
+        "httpMethod": "GET",
+        "methodType": "rest",
+        "parameters": {
+         "max-comments": {
+          "parameterType": "query",
+          "required": false
+         },
+         "scope": {
+          "parameterType": "path",
+          "pattern": "@(self|public|consumption|liked|comments)*",
+          "required": true
+         },
+         "max-results": {
+          "parameterType": "query",
+          "required": false
+         },
+         "c": {
+          "parameterType": "query",
+          "required": false
+         },
+         "alt": {
+          "parameterType": "query",
+          "required": false
+         },
+         "userId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         },
+         "max-liked": {
+          "parameterType": "query",
+          "required": false
+         },
+         "hl": {
+          "parameterType": "query",
+          "required": false
+         }
+        }
+       },
+       "get": {
+        "pathUrl": "buzz/v1/activities/{userId}/@self/{postId}",
+        "rpcName": "buzz.activities.get",
+        "httpMethod": "GET",
+        "methodType": "rest",
+        "parameters": {
+         "alt": {
+          "parameterType": "query",
+          "required": false
+         },
+         "userId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         },
+         "postId": {
+          "parameterType": "path",
+          "pattern": ".*",
+          "required": true
+         },
+         "hl": {
+          "parameterType": "query",
+          "required": false
+         }
+        }
+       },
+       "search": {
+        "pathUrl": "buzz/v1/activities/search",
+        "rpcName": "buzz.activities.search",
+        "httpMethod": "POST",
+        "methodType": "rest",
+        "parameters": {
+         "lon": {
+          "parameterType": "query",
+          "required": false
+         },
+         "max-results": {
+          "parameterType": "query",
+          "required": false
+         },
+         "c": {
+          "parameterType": "query",
+          "required": false
+         },
+         "bbox": {
+          "parameterType": "query",
+          "required": false
+         },
+         "q": {
+          "parameterType": "query",
+          "required": false
+         },
+         "alt": {
+          "parameterType": "query",
+          "required": false
+         },
+         "pid": {
+          "parameterType": "query",
+          "required": false
+         },
+         "radius": {
+          "parameterType": "query",
+          "required": false
+         },
+         "lat": {
+          "parameterType": "query",
+          "required": false
+         },
+         "hl": {
+          "parameterType": "query",
+          "required": false
+         }
+        }
+       }
+      }
+     },
+     "people": {
+      "methods": {
+       "get": {
+        "pathUrl": "buzz/v1/people/{userId}/@self",
+        "rpcName": "buzz.people.get",
+        "httpMethod": "GET",
+        "methodType": "rest",
+        "parameters": {
+         "alt": {
+          "parameterType": "query",
+          "required": false
+         },
+         "userId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         },
+         "hl": {
+          "parameterType": "query",
+          "required": false
+         }
+        }
+       },
+       "search": {
+        "pathUrl": "buzz/v1/people/search",
+        "rpcName": "buzz.people.search",
+        "httpMethod": "POST",
+        "methodType": "rest",
+        "parameters": {
+         "max-results": {
+          "parameterType": "query",
+          "required": false
+         },
+         "c": {
+          "parameterType": "query",
+          "required": false
+         },
+         "q": {
+          "parameterType": "query",
+          "required": false
+         },
+         "alt": {
+          "parameterType": "query",
+          "required": false
+         },
+         "hl": {
+          "parameterType": "query",
+          "required": false
+         }
+        }
+       },
+       "list": {
+        "pathUrl": "buzz/v1/people/{userId}/@groups/{groupId}",
+        "rpcName": "buzz.people.list",
+        "httpMethod": "GET",
+        "methodType": "rest",
+        "parameters": {
+         "groupId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         },
+         "max-results": {
+          "parameterType": "query",
+          "required": false
+         },
+         "c": {
+          "parameterType": "query",
+          "required": false
+         },
+         "alt": {
+          "parameterType": "query",
+          "required": false
+         },
+         "userId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         },
+         "hl": {
+          "parameterType": "query",
+          "required": false
+         }
+        }
+       },
+       "reshared": {
+        "pathUrl": "buzz/v1/activities/{userId}/{scope}/{postId}/{groupId}",
+        "rpcName": "buzz.people.reshared",
+        "httpMethod": "POST",
+        "methodType": "rest",
+        "parameters": {
+         "groupId": {
+          "parameterType": "path",
+          "pattern": "@reshared",
+          "required": true
+         },
+         "max-results": {
+          "parameterType": "query",
+          "required": false
+         },
+         "c": {
+          "parameterType": "query",
+          "required": false
+         },
+         "alt": {
+          "parameterType": "query",
+          "required": false
+         },
+         "userId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         },
+         "postId": {
+          "parameterType": "path",
+          "pattern": ".*",
+          "required": true
+         },
+         "hl": {
+          "parameterType": "query",
+          "required": false
+         }
+        }
+       },
+       "delete": {
+        "pathUrl": "buzz/v1/people/{userId}/@groups/{groupId}/{personId}",
+        "rpcName": "buzz.people.delete",
+        "httpMethod": "DELETE",
+        "methodType": "rest",
+        "parameters": {
+         "groupId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         },
+         "alt": {
+          "parameterType": "query",
+          "required": false
+         },
+         "userId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         },
+         "personId": {
+          "parameterType": "path",
+          "pattern": "(?!@self).*",
+          "required": true
+         },
+         "hl": {
+          "parameterType": "query",
+          "required": false
+         }
+        }
+       },
+       "update": {
+        "pathUrl": "buzz/v1/people/{userId}/@groups/{groupId}/{personId}",
+        "rpcName": "buzz.people.update",
+        "httpMethod": "PUT",
+        "methodType": "rest",
+        "parameters": {
+         "groupId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         },
+         "alt": {
+          "parameterType": "query",
+          "required": false
+         },
+         "userId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         },
+         "personId": {
+          "parameterType": "path",
+          "pattern": "(?!@self).*",
+          "required": true
+         },
+         "hl": {
+          "parameterType": "query",
+          "required": false
+         }
+        }
+       },
+       "liked": {
+        "pathUrl": "buzz/v1/activities/{userId}/{scope}/{postId}/{groupId}",
+        "rpcName": "buzz.people.liked",
+        "httpMethod": "POST",
+        "methodType": "rest",
+        "parameters": {
+         "groupId": {
+          "parameterType": "path",
+          "pattern": "@liked",
+          "required": true
+         },
+         "max-results": {
+          "parameterType": "query",
+          "required": false
+         },
+         "c": {
+          "parameterType": "query",
+          "required": false
+         },
+         "alt": {
+          "parameterType": "query",
+          "required": false
+         },
+         "userId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         },
+         "postId": {
+          "parameterType": "path",
+          "pattern": ".*",
+          "required": true
+         },
+         "hl": {
+          "parameterType": "query",
+          "required": false
+         }
+        }
+       }
+      }
+     },
+     "groups": {
+      "methods": {
+       "get": {
+        "pathUrl": "buzz/v1/people/{userId}/@groups/{groupId}/@self",
+        "rpcName": "buzz.groups.get",
+        "httpMethod": "GET",
+        "methodType": "rest",
+        "parameters": {
+         "groupId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         },
+         "alt": {
+          "parameterType": "query",
+          "required": false
+         },
+         "userId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         },
+         "hl": {
+          "parameterType": "query",
+          "required": false
+         }
+        }
+       },
+       "list": {
+        "pathUrl": "buzz/v1/people/{userId}/@groups",
+        "rpcName": "buzz.groups.list",
+        "httpMethod": "GET",
+        "methodType": "rest",
+        "parameters": {
+         "max-results": {
+          "parameterType": "query",
+          "required": false
+         },
+         "c": {
+          "parameterType": "query",
+          "required": false
+         },
+         "alt": {
+          "parameterType": "query",
+          "required": false
+         },
+         "userId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         },
+         "hl": {
+          "parameterType": "query",
+          "required": false
+         }
+        }
+       },
+       "delete": {
+        "pathUrl": "buzz/v1/people/{userId}/@groups/{groupId}",
+        "rpcName": "buzz.groups.delete",
+        "httpMethod": "DELETE",
+        "methodType": "rest",
+        "parameters": {
+         "groupId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         },
+         "alt": {
+          "parameterType": "query",
+          "required": false
+         },
+         "userId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         },
+         "hl": {
+          "parameterType": "query",
+          "required": false
+         }
+        }
+       },
+       "insert": {
+        "pathUrl": "buzz/v1/people/{userId}/@groups",
+        "rpcName": "buzz.groups.insert",
+        "httpMethod": "POST",
+        "methodType": "rest",
+        "parameters": {
+         "alt": {
+          "parameterType": "query",
+          "required": false
+         },
+         "userId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         },
+         "hl": {
+          "parameterType": "query",
+          "required": false
+         }
+        }
+       },
+       "update": {
+        "pathUrl": "buzz/v1/people/{userId}/@groups/{groupId}/@self",
+        "rpcName": "buzz.groups.update",
+        "httpMethod": "PUT",
+        "methodType": "rest",
+        "parameters": {
+         "groupId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         },
+         "userId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         },
+         "hl": {
+          "parameterType": "query",
+          "required": false
+         }
+        }
+       }
+      }
+     },
+     "comments": {
+      "methods": {
+       "update": {
+        "pathUrl": "buzz/v1/activities/{userId}/{scope}/{postId}/@comments/{commentId}",
+        "rpcName": "buzz.comments.update",
+        "httpMethod": "PUT",
+        "methodType": "rest",
+        "parameters": {
+         "scope": {
+          "parameterType": "path",
+          "pattern": "@.*",
+          "required": true
+         },
+         "alt": {
+          "parameterType": "query",
+          "required": false
+         },
+         "userId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         },
+         "commentId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         },
+         "postId": {
+          "parameterType": "path",
+          "pattern": ".*",
+          "required": true
+         },
+         "hl": {
+          "parameterType": "query",
+          "required": false
+         },
+         "abuseType": {
+          "parameterType": "query",
+          "required": false
+         }
+        }
+       },
+       "list": {
+        "pathUrl": "buzz/v1/activities/{userId}/{scope}/{postId}/@comments",
+        "rpcName": "buzz.comments.list",
+        "httpMethod": "GET",
+        "methodType": "rest",
+        "parameters": {
+         "scope": {
+          "parameterType": "path",
+          "pattern": "@.*",
+          "required": true
+         },
+         "max-results": {
+          "parameterType": "query",
+          "required": false
+         },
+         "c": {
+          "parameterType": "query",
+          "required": false
+         },
+         "alt": {
+          "parameterType": "query",
+          "required": false
+         },
+         "userId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         },
+         "postId": {
+          "parameterType": "path",
+          "pattern": ".*",
+          "required": true
+         },
+         "hl": {
+          "parameterType": "query",
+          "required": false
+         }
+        }
+       },
+       "delete": {
+        "pathUrl": "buzz/v1/activities/{userId}/@self/{postId}/@comments/{commentId}",
+        "rpcName": "buzz.comments.delete",
+        "httpMethod": "DELETE",
+        "methodType": "rest",
+        "parameters": {
+         "alt": {
+          "parameterType": "query",
+          "required": false
+         },
+         "userId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         },
+         "commentId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         },
+         "postId": {
+          "parameterType": "path",
+          "pattern": ".*",
+          "required": true
+         },
+         "hl": {
+          "parameterType": "query",
+          "required": false
+         }
+        }
+       },
+       "insert": {
+        "pathUrl": "buzz/v1/activities/{userId}/@self/{postId}/@comments",
+        "rpcName": "buzz.comments.insert",
+        "httpMethod": "POST",
+        "methodType": "rest",
+        "parameters": {
+         "alt": {
+          "parameterType": "query",
+          "required": false
+         },
+         "userId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         },
+         "postId": {
+          "parameterType": "path",
+          "pattern": ".*",
+          "required": true
+         },
+         "hl": {
+          "parameterType": "query",
+          "required": false
+         }
+        }
+       },
+       "get": {
+        "pathUrl": "buzz/v1/activities/{userId}/@self/{postId}/@comments/{commentId}",
+        "rpcName": "buzz.comments.get",
+        "httpMethod": "GET",
+        "methodType": "rest",
+        "parameters": {
+         "alt": {
+          "parameterType": "query",
+          "required": false
+         },
+         "userId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         },
+         "commentId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         },
+         "postId": {
+          "parameterType": "path",
+          "pattern": ".*",
+          "required": true
+         },
+         "hl": {
+          "parameterType": "query",
+          "required": false
+         }
+        }
+       }
+      }
+     },
+     "related": {
+      "methods": {
+       "list": {
+        "pathUrl": "buzz/v1/activities/{userId}/{scope}/{postId}/@related",
+        "rpcName": "buzz.related.list",
+        "httpMethod": "GET",
+        "methodType": "rest",
+        "parameters": {
+         "scope": {
+          "parameterType": "path",
+          "pattern": "@.*",
+          "required": true
+         },
+         "max-results": {
+          "parameterType": "query",
+          "required": false
+         },
+         "c": {
+          "parameterType": "query",
+          "required": false
+         },
+         "alt": {
+          "parameterType": "query",
+          "required": false
+         },
+         "userId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         },
+         "hl": {
+          "parameterType": "query",
+          "required": false
+         },
+         "postId": {
+          "parameterType": "path",
+          "pattern": ".*",
+          "required": true
+         }
+        }
+       }
+      }
+     }
+    }
+   }
+  }
+ }
+}
diff --git a/tests/data/latitude.json b/tests/data/latitude.json
new file mode 100644
index 0000000..29dc80f
--- /dev/null
+++ b/tests/data/latitude.json
@@ -0,0 +1,103 @@
+{
+ "data": {
+  "latitude": {
+   "v1": {
+    "baseUrl": "https://www.googleapis.com/",
+    "resources": {
+     "location": {
+      "methods": {
+       "insert": {
+        "pathUrl": "latitude/v1/location",
+        "rpcName": "latitude.location.insert",
+        "httpMethod": "POST",
+        "methodType": "rest"
+       },
+       "list": {
+        "pathUrl": "latitude/v1/location",
+        "rpcName": "latitude.location.list",
+        "httpMethod": "GET",
+        "methodType": "rest",
+        "parameters": {
+         "max-time": {
+          "parameterType": "query",
+          "required": false
+         },
+         "min-time": {
+          "parameterType": "query",
+          "required": false
+         },
+         "max-results": {
+          "parameterType": "query",
+          "required": false
+         },
+         "granularity": {
+          "parameterType": "query",
+          "required": false
+         }
+        }
+       },
+       "get": {
+        "pathUrl": "latitude/v1/location/{locationId}",
+        "rpcName": "latitude.location.get",
+        "httpMethod": "GET",
+        "methodType": "rest",
+        "parameters": {
+         "granularity": {
+          "parameterType": "query",
+          "required": false
+         },
+         "locationId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         }
+        }
+       },
+       "delete": {
+        "pathUrl": "latitude/v1/location/{locationId}",
+        "rpcName": "latitude.location.delete",
+        "httpMethod": "DELETE",
+        "methodType": "rest",
+        "parameters": {
+         "locationId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         }
+        }
+       }
+      }
+     },
+     "currentLocation": {
+      "methods": {
+       "insert": {
+        "pathUrl": "latitude/v1/currentLocation",
+        "rpcName": "latitude.currentLocation.insert",
+        "httpMethod": "POST",
+        "methodType": "rest"
+       },
+       "delete": {
+        "pathUrl": "latitude/v1/currentLocation",
+        "rpcName": "latitude.currentLocation.delete",
+        "httpMethod": "DELETE",
+        "methodType": "rest"
+       },
+       "get": {
+        "pathUrl": "latitude/v1/currentLocation",
+        "rpcName": "latitude.currentLocation.get",
+        "httpMethod": "GET",
+        "methodType": "rest",
+        "parameters": {
+         "granularity": {
+          "parameterType": "query",
+          "required": false
+         }
+        }
+       }
+      }
+     }
+    }
+   }
+  }
+ }
+}
diff --git a/tests/data/moderator.json b/tests/data/moderator.json
new file mode 100644
index 0000000..3ac3f2c
--- /dev/null
+++ b/tests/data/moderator.json
@@ -0,0 +1,528 @@
+{
+ "data": {
+  "moderator": {
+   "v1": {
+    "baseUrl": "https://www.googleapis.com/",
+    "resources": {
+     "tags": {
+      "methods": {
+       "delete": {
+        "pathUrl": "moderator/v1/series/{seriesId}/submissions/{submissionId}/tags/{tagId}",
+        "rpcName": "moderator.tags.delete",
+        "httpMethod": "DELETE",
+        "methodType": "rest",
+        "parameters": {
+         "tagId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         },
+         "seriesId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         },
+         "submissionId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         }
+        }
+       },
+       "list": {
+        "pathUrl": "moderator/v1/series/{seriesId}/submissions/{submissionId}/tags",
+        "rpcName": "moderator.tags.list",
+        "httpMethod": "GET",
+        "methodType": "rest",
+        "parameters": {
+         "seriesId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         },
+         "submissionId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         }
+        }
+       },
+       "insert": {
+        "pathUrl": "moderator/v1/series/{seriesId}/submissions/{submissionId}/tags",
+        "rpcName": "moderator.tags.insert",
+        "httpMethod": "POST",
+        "methodType": "rest",
+        "parameters": {
+         "seriesId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         },
+         "submissionId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         }
+        }
+       }
+      }
+     },
+     "series": {
+      "methods": {
+       "list": {
+        "pathUrl": "moderator/v1/series/@me/mine",
+        "rpcName": "moderator.my.series.list",
+        "httpMethod": "GET",
+        "methodType": "rest"
+       },
+       "list": {
+        "pathUrl": "moderator/v1/series",
+        "rpcName": "moderator.series.list",
+        "httpMethod": "GET",
+        "methodType": "rest",
+        "parameters": {
+         "max-results": {
+          "parameterType": "query",
+          "required": false
+         },
+         "start-index": {
+          "parameterType": "query",
+          "required": false
+         }
+        }
+       },
+       "list": {
+        "pathUrl": "moderator/v1/series/@me/recent",
+        "rpcName": "moderator.myrecent.series.list",
+        "httpMethod": "GET",
+        "methodType": "rest"
+       },
+       "update": {
+        "pathUrl": "moderator/v1/series/{seriesId}",
+        "rpcName": "moderator.series.update",
+        "httpMethod": "PUT",
+        "methodType": "rest",
+        "parameters": {
+         "seriesId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         }
+        }
+       },
+       "insert": {
+        "pathUrl": "moderator/v1/series",
+        "rpcName": "moderator.series.insert",
+        "httpMethod": "POST",
+        "methodType": "rest"
+       },
+       "get": {
+        "pathUrl": "moderator/v1/series/{seriesId}",
+        "rpcName": "moderator.series.get",
+        "httpMethod": "GET",
+        "methodType": "rest",
+        "parameters": {
+         "seriesId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         }
+        }
+       },
+       "list": {
+        "pathUrl": "moderator/v1/series/featured",
+        "rpcName": "moderator.featured.series.list",
+        "httpMethod": "GET",
+        "methodType": "rest"
+       }
+      }
+     },
+     "topics": {
+      "methods": {
+       "list": {
+        "pathUrl": "moderator/v1/series/{seriesId}/topics",
+        "rpcName": "moderator.topics.list",
+        "httpMethod": "GET",
+        "methodType": "rest",
+        "parameters": {
+         "max-results": {
+          "parameterType": "query",
+          "required": false
+         },
+         "start-index": {
+          "parameterType": "query",
+          "required": false
+         },
+         "seriesId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         }
+        }
+       },
+       "insert": {
+        "pathUrl": "moderator/v1/series/{seriesId}/topics",
+        "rpcName": "moderator.topics.insert",
+        "httpMethod": "POST",
+        "methodType": "rest",
+        "parameters": {
+         "seriesId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         }
+        }
+       },
+       "get": {
+        "pathUrl": "moderator/v1/series/{seriesId}/topics/{topicId}",
+        "rpcName": "moderator.topics.get",
+        "httpMethod": "GET",
+        "methodType": "rest",
+        "parameters": {
+         "topicId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         },
+         "seriesId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         }
+        }
+       }
+      }
+     },
+     "votes": {
+      "methods": {
+       "update": {
+        "pathUrl": "moderator/v1/series/{seriesId}/submissions/{submissionId}/votes/@me",
+        "rpcName": "moderator.votes.update",
+        "httpMethod": "PUT",
+        "methodType": "rest",
+        "parameters": {
+         "userId": {
+          "parameterType": "query",
+          "required": false
+         },
+         "seriesId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         },
+         "submissionId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         }
+        }
+       },
+       "list": {
+        "pathUrl": "moderator/v1/series/{seriesId}/votes/@me",
+        "rpcName": "moderator.votes.list",
+        "httpMethod": "GET",
+        "methodType": "rest",
+        "parameters": {
+         "max-results": {
+          "parameterType": "query",
+          "required": false
+         },
+         "start-index": {
+          "parameterType": "query",
+          "required": false
+         },
+         "seriesId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         }
+        }
+       },
+       "get": {
+        "pathUrl": "moderator/v1/series/{seriesId}/submissions/{submissionId}/votes/@me",
+        "rpcName": "moderator.votes.get",
+        "httpMethod": "GET",
+        "methodType": "rest",
+        "parameters": {
+         "userId": {
+          "parameterType": "query",
+          "required": false
+         },
+         "seriesId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         },
+         "submissionId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         }
+        }
+       },
+       "insert": {
+        "pathUrl": "moderator/v1/series/{seriesId}/submissions/{submissionId}/votes/@me",
+        "rpcName": "moderator.votes.insert",
+        "httpMethod": "POST",
+        "methodType": "rest",
+        "parameters": {
+         "seriesId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         },
+         "submissionId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         }
+        }
+       }
+      }
+     },
+     "submissions": {
+      "methods": {
+       "get": {
+        "pathUrl": "moderator/v1/series/{seriesId}/submissions/{submissionId}",
+        "rpcName": "moderator.submissions.get",
+        "httpMethod": "GET",
+        "methodType": "rest",
+        "parameters": {
+         "seriesId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         },
+         "submissionId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         }
+        }
+       },
+       "list": {
+        "pathUrl": "moderator/v1/series/{seriesId}/submissions",
+        "rpcName": "moderator.series.submissions.list",
+        "httpMethod": "GET",
+        "methodType": "rest",
+        "parameters": {
+         "author": {
+          "parameterType": "query",
+          "required": false
+         },
+         "sort": {
+          "parameterType": "query",
+          "required": false
+         },
+         "max-results": {
+          "parameterType": "query",
+          "required": false
+         },
+         "q": {
+          "parameterType": "query",
+          "required": false
+         },
+         "hasAttachmentVideo": {
+          "parameterType": "query",
+          "required": false
+         },
+         "start-index": {
+          "parameterType": "query",
+          "required": false
+         },
+         "seriesId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         }
+        }
+       },
+       "insert": {
+        "pathUrl": "moderator/v1/series/{seriesId}/topics/{topicId}/submissions",
+        "rpcName": "moderator.submissions.insert",
+        "httpMethod": "POST",
+        "methodType": "rest",
+        "parameters": {
+         "topicId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         },
+         "seriesId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         }
+        }
+       },
+       "list": {
+        "pathUrl": "moderator/v1/series/{seriesId}/topics/{topicId}/submissions",
+        "rpcName": "moderator.topics.submissions.list",
+        "httpMethod": "GET",
+        "methodType": "rest",
+        "parameters": {
+         "author": {
+          "parameterType": "query",
+          "required": false
+         },
+         "hasAttachedVideo": {
+          "parameterType": "query",
+          "required": false
+         },
+         "sort": {
+          "parameterType": "query",
+          "required": false
+         },
+         "max-results": {
+          "parameterType": "query",
+          "required": false
+         },
+         "q": {
+          "parameterType": "query",
+          "required": false
+         },
+         "topicId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         },
+         "start-index": {
+          "parameterType": "query",
+          "required": false
+         },
+         "seriesId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         }
+        }
+       }
+      }
+     },
+     "profiles": {
+      "methods": {
+       "get": {
+        "pathUrl": "moderator/v1/profiles/@me",
+        "rpcName": "moderator.profiles.get",
+        "httpMethod": "GET",
+        "methodType": "rest"
+       },
+       "update": {
+        "pathUrl": "moderator/v1/profiles/@me",
+        "rpcName": "moderator.profiles.update",
+        "httpMethod": "PUT",
+        "methodType": "rest"
+       }
+      }
+     },
+     "responses": {
+      "methods": {
+       "insert": {
+        "pathUrl": "moderator/v1/series/{seriesId}/topics/{topicId}/submissions/{parentSubmissionId}/responses",
+        "rpcName": "moderator.responses.insert",
+        "httpMethod": "POST",
+        "methodType": "rest",
+        "parameters": {
+         "parentSubmissionId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         },
+         "topicId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         },
+         "seriesId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         }
+        }
+       },
+       "list": {
+        "pathUrl": "moderator/v1/series/{seriesId}/submissions/{submissionId}/responses",
+        "rpcName": "moderator.responses.list",
+        "httpMethod": "GET",
+        "methodType": "rest",
+        "parameters": {
+         "author": {
+          "parameterType": "query",
+          "required": false
+         },
+         "hasAttachedVideo": {
+          "parameterType": "query",
+          "required": false
+         },
+         "sort": {
+          "parameterType": "query",
+          "required": false
+         },
+         "max-results": {
+          "parameterType": "query",
+          "required": false
+         },
+         "q": {
+          "parameterType": "query",
+          "required": false
+         },
+         "start-index": {
+          "parameterType": "query",
+          "required": false
+         },
+         "seriesId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         },
+         "submissionId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         }
+        }
+       },
+       "list": {
+        "pathUrl": "moderator/v1/series/{seriesId}/responses",
+        "rpcName": "moderator.series.responses.list",
+        "httpMethod": "GET",
+        "methodType": "rest",
+        "parameters": {
+         "author": {
+          "parameterType": "query",
+          "required": false
+         },
+         "hasAttachedVideo": {
+          "parameterType": "query",
+          "required": false
+         },
+         "sort": {
+          "parameterType": "query",
+          "required": false
+         },
+         "max-results": {
+          "parameterType": "query",
+          "required": false
+         },
+         "q": {
+          "parameterType": "query",
+          "required": false
+         },
+         "start-index": {
+          "parameterType": "query",
+          "required": false
+         },
+         "seriesId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
+         }
+        }
+       }
+      }
+     }
+    }
+   }
+  }
+ }
+}
diff --git a/tests/test_discovery.py b/tests/test_discovery.py
new file mode 100644
index 0000000..a217932
--- /dev/null
+++ b/tests/test_discovery.py
@@ -0,0 +1,78 @@
+#!/usr/bin/python2.4
+#
+# Copyright 2010 Google Inc. All Rights Reserved.
+
+"""Discovery document tests
+
+Unit tests for objects created from discovery documents.
+"""
+
+__author__ = 'jcgregorio@google.com (Joe Gregorio)'
+
+from apiclient.discovery import build
+import httplib2
+import os
+import unittest
+
+DATA_DIR = os.path.join(os.path.dirname(__file__), 'data')
+
+class HttpMock(object):
+
+  def __init__(self, filename, headers):
+    f = file(os.path.join(DATA_DIR, filename), 'r')
+    self.data = f.read()
+    f.close()
+    self.headers = headers
+
+  def request(self, uri, method="GET", body=None, headers=None, redirections=1, connection_type=None):
+    return httplib2.Response(self.headers), self.data
+
+
+class Discovery(unittest.TestCase):
+  def test_method_error_checking(self):
+    self.http = HttpMock('buzz.json', {'status': '200'})
+    buzz = build('buzz', 'v1', self.http)
+
+    # Missing required parameters
+    try:
+      buzz.activities().list()
+      self.fail()
+    except TypeError, e:
+      self.assertTrue('Missing' in str(e))
+
+    # Parameter doesn't match regex
+    try:
+      buzz.activities().list(scope='@self', userId='')
+      self.fail()
+    except TypeError, e:
+      self.assertTrue('does not match' in str(e))
+
+    # Parameter doesn't match regex
+    try:
+      buzz.activities().list(scope='not@', userId='foo')
+      self.fail()
+    except TypeError, e:
+      self.assertTrue('does not match' in str(e))
+
+    # Unexpected parameter
+    try:
+      buzz.activities().list(flubber=12)
+      self.fail()
+    except TypeError, e:
+      self.assertTrue('unexpected' in str(e))
+
+  def test_resources(self):
+    self.http = HttpMock('buzz.json', {'status': '200'})
+    buzz = build('buzz', 'v1', self.http)
+    self.assertTrue(getattr(buzz, 'activities'))
+    self.assertTrue(getattr(buzz, 'search'))
+    self.assertTrue(getattr(buzz, 'feeds'))
+    self.assertTrue(getattr(buzz, 'photos'))
+    self.assertTrue(getattr(buzz, 'people'))
+    self.assertTrue(getattr(buzz, 'groups'))
+    self.assertTrue(getattr(buzz, 'comments'))
+    self.assertTrue(getattr(buzz, 'related'))
+
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/tests/test_json_model.py b/tests/test_json_model.py
new file mode 100644
index 0000000..477f88d
--- /dev/null
+++ b/tests/test_json_model.py
@@ -0,0 +1,45 @@
+#!/usr/bin/python2.4
+#
+# Copyright 2010 Google Inc. All Rights Reserved.
+
+"""Discovery document tests
+
+Unit tests for objects created from discovery documents.
+"""
+
+__author__ = 'jcgregorio@google.com (Joe Gregorio)'
+
+from apiclient.discovery import JsonModel
+import os
+import unittest
+
+
+class Model(unittest.TestCase):
+  def test_json_no_body(self):
+    model = JsonModel()
+
+    headers = {}
+    params = {}
+
+    headers, params, query, body = model.request(headers, params)
+
+    self.assertEqual(headers['accept'], 'application/json')
+    self.assertTrue('content-type' not in headers)
+    self.assertNotEqual(query, '')
+    self.assertEqual(body, None)
+
+  def test_json_body(self):
+    model = JsonModel()
+
+    headers = {}
+    params = {'body': {}}
+
+    headers, params, query, body = model.request(headers, params)
+
+    self.assertEqual(headers['accept'], 'application/json')
+    self.assertEqual(headers['content-type'], 'application/json')
+    self.assertNotEqual(query, '')
+    self.assertEqual(body, '{"data": {}}')
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/tests/test_oauth.py b/tests/test_oauth.py
deleted file mode 100644
index e69de29..0000000
--- a/tests/test_oauth.py
+++ /dev/null
diff --git a/tests/test_pagination.py b/tests/test_pagination.py
deleted file mode 100644
index e69de29..0000000
--- a/tests/test_pagination.py
+++ /dev/null