def test_annotations_orphaned_example_tags(self): """Tests to ensure orphaned examples are found. Orphaned examples are example tags that have no matching non-example tag: E.g. param/param-example. The name field is used to determine matches. """ docstring = self.sample_api_annotated_docstring api_docstring_parser = APIDocstringParser() docstring = docstring.replace("@param-example \"param_name\"", "@param-example \"param_name_bad\"") api_docstring_parser.parse(docstring, uri=self.test_uri_singular) d = api_docstring_parser.get_dict() self.assert_has_api_warning(d) docstring = docstring.replace("@error-example \"error_name\"", "@error-example \"error_name_bad\"") api_docstring_parser.parse(docstring, uri=self.test_uri_singular) d = api_docstring_parser.get_dict() self.assert_has_api_warning(d) docstring = docstring.replace("@success-example \"success_name\"", "@success-example \"success_name_bad\"") api_docstring_parser.parse(docstring, uri=self.test_uri_singular) d = api_docstring_parser.get_dict() self.assert_has_api_warning(d)
def test_annotations_bad_tag(self): """Replace a good tag with a bad one and get a syntax error.""" docstring = self.sample_api_annotated_docstring api_docstring_parser = APIDocstringParser() api_docstring_parser.parse(docstring.replace("@param", "@bad")) d = api_docstring_parser.get_dict() self.assert_has_syntax_error(d)
def test_find_examples_db(self): """Ensure parser correctly finds example databases.""" ds = self.sample_api_annotated_docstring api_docstring_parser = APIDocstringParser() api_docstring_parser.parse(ds, uri=self.test_uri_singular) d = api_docstring_parser.get_dict() s = d['successes'][1] self.assertEqual(" ".join(s['example'].split()), '{ "name": "value" }') api_docstring_parser.parse(ds, uri=self.test_uri_plural) d = api_docstring_parser.get_dict() s = d['successes'][1] self.assertEqual(" ".join(s['example'].split()), '{ "name": "value" }')
def describe_actions(handler): """Describe the actions that `handler` supports. For each action, which could be a CRUD operation or a custom (piggybacked) operation, a dict with the following entries is generated: method: string, HTTP method. name: string, a human-friendly name for the action. doc: string, documentation about the action. op: string or None, the op parameter to pass in requests for non-CRUD/ReSTful requests. restful: Indicates if this is a CRUD/ReSTful action. """ from maasserver.api import support # Circular import. getname = support.OperationsResource.crudmap.get for signature, function in handler.exports.items(): http_method, operation = signature name = getname(http_method) if operation is None else operation ap = APIDocstringParser() doc = getdoc(function) if doc is not None: if APIDocstringParser.is_annotated_docstring(doc): # Because the docstring contains annotations, we # need to construct a string suitable for output # to stdout that matches the style used for # non-annotated docstrings in the CLI. ap.parse(doc) d = ap.get_dict() if d['description_title'] != "": doc = d['description_title'] + "\n\n" doc += d['description'] + "\n\n" else: doc = d['description'] + "\n\n" # Here, we add the params, but we skip params # surrounded by curly brackets (e.g. {foo}) # because these indicate params that appear in # the URI (e.g. /zone/{name}/). I.e. positional # arguments. These already appear in the CLI # help command output so we don't want duplicates. for p in d['params']: if p['name'].find("{") == -1 and p['name'].find("}") == -1: required = "Required. " if p['options']['required'] == "false": required = "Optional. " doc += (":param %s: %s%s" % (p['name'], required, p['description'])) doc += (":type %s: %s\n " % (p['name'], p['type'])) yield dict( method=http_method, name=name, doc=doc, op=operation, restful=(operation is None))
def test_warn_on_missing_example_db_entry(self): """Ensure we see a warning if there is a missing examples db entry.""" ds_orig = self.sample_api_annotated_docstring ds_bad_exkey = ds_orig.replace('"success_with_exdb" [exkey=key1]', '"success_with_exdb" [exkey=badkey]') api_docstring_parser = APIDocstringParser() api_docstring_parser.parse(ds_bad_exkey, uri=self.test_uri_singular) d = api_docstring_parser.get_dict() self.assert_has_api_warning(d)
def test_parse_annotations_indent_descriptions(self): """Indentation should be kept when present in descriptions.""" docstring = self.sample_api_annotated_docstring ref_string = "Longer description with\n" " multiple lines.\n\n " api_docstring_parser = APIDocstringParser() api_docstring_parser.parse(docstring, uri=self.test_uri_singular) d = api_docstring_parser.get_dict() # Note that we only test one description here because the # same code is used to gather all description areas of the # tags. E.g. @tag (type) "name" [options] description self.assertEqual(d["description"], ref_string)
def test_parse_annotations(self): """Tests whether we can parse the sample.""" docstring = self.sample_api_annotated_docstring api_docstring_parser = APIDocstringParser() api_docstring_parser.parse(docstring, "method", "uri", "operation") d = api_docstring_parser.get_dict() params = d['params'] successes = d['successes'] errors = d['errors'] self.assertEqual(d['http_method'], "method") self.assertEqual(d['uri'], "uri") self.assertEqual(d['operation'], "operation") self.assertEqual(d['description_title'], "Docstring title") self.assertEqual(" ".join(d['description'].split()), "Longer description with multiple lines.") p = params[0] self.assertEqual(p['type'], "String") self.assertEqual(p['name'], "param_name") self.assertEqual(" ".join(p['description'].split()), "param description") self.assertEqual(" ".join(p['example'].split()), "param-ex") p = params[1] self.assertEqual(p['type'], "Int") self.assertEqual(p['name'], "param_name2") self.assertEqual(" ".join(p['description'].split()), "param2 description") self.assertEqual(" ".join(p['example'].split()), "param2-ex") p = params[2] self.assertEqual(p['type'], "URL String") self.assertEqual(p['name'], "param_name3") self.assertEqual(" ".join(p['description'].split()), "param3 description") self.assertEqual(" ".join(p['example'].split()), "param3-ex") s = successes[0] self.assertEqual(s['type'], "Content") self.assertEqual(s['name'], "success_name") self.assertEqual(" ".join(s['description'].split()), "success description") self.assertEqual(" ".join(s['example'].split()), "success content") e = errors[0] self.assertEqual(e['type'], "HTTP Status Code") self.assertEqual(e['name'], "error_name") self.assertEqual(" ".join(e['description'].split()), "error description") self.assertEqual(" ".join(e['example'].split()), "error content")
def test_parse_annotations_indent_example(self): """Indentation should be kept when present in examples.""" docstring = self.sample_api_annotated_docstring ref_string = ("{\n" " \"id\": 1,\n" " \"foo\": \"bar\"\n" " }\n\n ") api_docstring_parser = APIDocstringParser() api_docstring_parser.parse(docstring, uri=self.test_uri_singular) d = api_docstring_parser.get_dict() # Note that we only test one example here because the # same code is used to gather all description areas of the # tags. E.g. @tag-example (type) "name" [options] description params = d['params'] self.assertEqual(params[3]['example'], ref_string)
def test_warn_on_missing_example_db_when_entry_referenced(self): """Missing examples db. If an examples db does not exist for some given URI (like when it simply hasn't been created yet) and a key from that missing DB is referenced by the API, we should see a warning. Note that _not_ having an examples db for a particular operation is a normal and acceptable condition (it takes a while to create one). It only becomes an error condition when the API tries to reference something inside a non-existent examples database. """ ds = self.sample_api_annotated_docstring api_docstring_parser = APIDocstringParser() api_docstring_parser.parse(ds, uri="bad_uri") d = api_docstring_parser.get_dict() self.assert_has_api_warning(d)
def test_load_nodes_examples_by_default(self): """Nodes examples should be loading by default. Some API objects like machines and devices inherit operations from nodes, so when we load the examples database, we always start with nodes and add on object-specific examples. """ ds = self.sample_api_annotated_docstring api_docstring_parser = APIDocstringParser() api_docstring_parser.parse(ds, uri=self.test_uri_plural) d = api_docstring_parser.get_dict() # index=2 contains the example with inherited examples from # example/nodes.json s = d['successes'][2] # The presence of the 'resource-uri' string is a good indicator # that the 'read-node' key has picked up the JSON object and # converted it to a string for output in API docs. self.assertTrue(s['example'].find("resource_uri") != -1)
def test_parse_annotations(self): """Tests whether we can parse the sample.""" docstring = self.sample_api_annotated_docstring api_docstring_parser = APIDocstringParser() api_docstring_parser.parse(docstring, http_method="mymethod", uri=self.test_uri_singular, operation="myoperation") d = api_docstring_parser.get_dict() params = d['params'] successes = d['successes'] errors = d['errors'] self.assertEqual(d['http_method'], "mymethod") self.assertEqual(d['uri'], self.test_uri_singular) self.assertEqual(d['operation'], "myoperation") self.assertEqual(d['description_title'], "Docstring title") self.assertEqual(" ".join(d['description'].split()), "Longer description with multiple lines.") p = params[0] self.assertEqual(p['type'], "String") self.assertEqual(p['name'], "param_name") self.assertEqual(" ".join(p['description'].split()), "param description") self.assertEqual(" ".join(p['example'].split()), "param-ex") p = params[1] self.assertEqual(p['type'], "Int") self.assertEqual(p['name'], "param_name2") self.assertEqual(" ".join(p['description'].split()), "param2 description") self.assertEqual(" ".join(p['example'].split()), "param2-ex") p = params[2] self.assertEqual(p['type'], "URL String") self.assertEqual(p['name'], "param_name3") self.assertEqual(" ".join(p['description'].split()), "param3 description") self.assertEqual(" ".join(p['example'].split()), "param3-ex") p = params[3] self.assertEqual(p['type'], "JSON") self.assertEqual(p['name'], "param_name4") self.assertEqual(" ".join(p['description'].split()), "param4 description") self.assertEqual(" ".join(p['example'].split()), "{ \"id\": 1, \"foo\": \"bar\" }") p = params[4] self.assertEqual(p['type'], "Boolean") self.assertEqual(p['name'], "param_name5") self.assertEqual(" ".join(p['description'].split()), "param5 description") self.assertEqual(" ".join(p['example'].split()), "True") p = params[5] self.assertEqual(p['type'], "Float") self.assertEqual(p['name'], "param_name6") self.assertEqual(" ".join(p['description'].split()), "param6 description") self.assertEqual(" ".join(p['example'].split()), "1.5") s = successes[0] self.assertEqual(s['type'], "Content") self.assertEqual(s['name'], "success_name") self.assertEqual(" ".join(s['description'].split()), "success description") self.assertEqual(" ".join(s['example'].split()), "success content") e = errors[0] self.assertEqual(e['type'], "HTTP Status Code") self.assertEqual(e['name'], "error_name") self.assertEqual(" ".join(e['description'].split()), "error description") self.assertEqual(" ".join(e['example'].split()), "error content")
def test_parse_annotations(self): """Tests whether we can parse the sample.""" docstring = self.sample_api_annotated_docstring api_docstring_parser = APIDocstringParser() api_docstring_parser.parse( docstring, http_method="mymethod", uri=self.test_uri_singular, operation="myoperation", ) d = api_docstring_parser.get_dict() params = d["params"] successes = d["successes"] errors = d["errors"] self.assertEqual(d["http_method"], "mymethod") self.assertEqual(d["uri"], self.test_uri_singular) self.assertEqual(d["operation"], "myoperation") self.assertEqual(d["description_title"], "Docstring title") self.assertEqual( " ".join(d["description"].split()), "Longer description with multiple lines.", ) p = params[0] self.assertEqual(p["type"], "String") self.assertEqual(p["name"], "param_name") self.assertEqual( " ".join(p["description"].split()), "param description" ) self.assertEqual(" ".join(p["example"].split()), "param-ex") p = params[1] self.assertEqual(p["type"], "Int") self.assertEqual(p["name"], "param_name2") self.assertEqual( " ".join(p["description"].split()), "param2 description" ) self.assertEqual(" ".join(p["example"].split()), "param2-ex") p = params[2] self.assertEqual(p["type"], "URL String") self.assertEqual(p["name"], "param_name3") self.assertEqual( " ".join(p["description"].split()), "param3 description" ) self.assertEqual(" ".join(p["example"].split()), "param3-ex") p = params[3] self.assertEqual(p["type"], "JSON") self.assertEqual(p["name"], "param_name4") self.assertEqual( " ".join(p["description"].split()), "param4 description" ) self.assertEqual( " ".join(p["example"].split()), '{ "id": 1, "foo": "bar" }' ) p = params[4] self.assertEqual(p["type"], "Boolean") self.assertEqual(p["name"], "param_name5") self.assertEqual( " ".join(p["description"].split()), "param5 description" ) self.assertEqual(" ".join(p["example"].split()), "True") p = params[5] self.assertEqual(p["type"], "Float") self.assertEqual(p["name"], "param_name6") self.assertEqual( " ".join(p["description"].split()), "param6 description" ) self.assertEqual(" ".join(p["example"].split()), "1.5") s = successes[0] self.assertEqual(s["type"], "Content") self.assertEqual(s["name"], "success_name") self.assertEqual( " ".join(s["description"].split()), "success description" ) self.assertEqual(" ".join(s["example"].split()), "success content") e = errors[0] self.assertEqual(e["type"], "HTTP Status Code") self.assertEqual(e["name"], "error_name") self.assertEqual( " ".join(e["description"].split()), "error description" ) self.assertEqual(" ".join(e["example"].split()), "error content")