Skip to content

Commit d53f19d

Browse files
authored
End point unit tests (Closes #34 through #44 and #46) (#54)
* Initial pass endpoint test framework * Refactor to use test runners, more bug fixes * Add runner tests to remaining objects, improve translations and add tests * Add rules parm checks * Refactor tests to use temporary GRAMPSHOME * Emit Gramps types using xml_str()
1 parent 9a7563b commit d53f19d

33 files changed

+2739
-103
lines changed

gramps_webapi/api/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ def register_endpt(resource: Type[Resource], url: str, name: str):
7070
# Filter
7171
register_endpt(FilterResource, "/filters/<string:namespace>", "filter")
7272
# Translate
73-
register_endpt(TranslationResource, "/translations/<string:code>", "translation")
73+
register_endpt(TranslationResource, "/translations/<string:isocode>", "translation")
7474
register_endpt(TranslationsResource, "/translations/", "translations")
7575
# Relation
7676
register_endpt(

gramps_webapi/api/resources/base.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,10 @@ class GrampsObjectResource(GrampsObjectResourceHelper, Resource):
5757
@use_args(
5858
{
5959
"strip": fields.Str(validate=validate.Length(equal=0)),
60-
"keys": fields.DelimitedList(fields.Str(), missing=[]),
61-
"skipkeys": fields.DelimitedList(fields.Str(), missing=[]),
60+
"keys": fields.DelimitedList(fields.Str(validate=validate.Length(min=1))),
61+
"skipkeys": fields.DelimitedList(
62+
fields.Str(validate=validate.Length(min=1))
63+
),
6264
"profile": fields.Str(validate=validate.Length(equal=0)),
6365
"extend": fields.DelimitedList(fields.Str(validate=validate.Length(min=1))),
6466
},
@@ -80,8 +82,10 @@ class GrampsObjectsResource(GrampsObjectResourceHelper, Resource):
8082
{
8183
"gramps_id": fields.Str(validate=validate.Length(min=1)),
8284
"strip": fields.Str(validate=validate.Length(equal=0)),
83-
"keys": fields.DelimitedList(fields.Str(), missing=[]),
84-
"skipkeys": fields.DelimitedList(fields.Str(), missing=[]),
85+
"keys": fields.DelimitedList(fields.Str(validate=validate.Length(min=1))),
86+
"skipkeys": fields.DelimitedList(
87+
fields.Str(validate=validate.Length(min=1))
88+
),
8589
"profile": fields.Str(validate=validate.Length(equal=0)),
8690
"extend": fields.DelimitedList(fields.Str(validate=validate.Length(min=1))),
8791
"filter": fields.Str(validate=validate.Length(min=1)),

gramps_webapi/api/resources/citation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ def object_extend(self, obj: Citation, args: Dict) -> Citation:
2222
if "extend" in args:
2323
db_handle = self.db_handle
2424
obj.extended = get_extended_attributes(db_handle, obj, args)
25-
if "all" in args["extend"] or "source" in args["extend"]:
25+
if "all" in args["extend"] or "source_handle" in args["extend"]:
2626
obj.extended["source"] = get_source_by_handle(
2727
db_handle, obj.source_handle, args
2828
)

gramps_webapi/api/resources/emit.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,29 +76,41 @@ def is_null(value: Any) -> bool:
7676
):
7777
apply_filter = True
7878
for key, value in obj.__class__.__dict__.items():
79+
if key.startswith("_"):
80+
key = key[2 + key.find("__") :]
7981
if apply_filter:
8082
if self.filter_only_keys and key not in self.filter_only_keys:
8183
continue
8284
if self.filter_skip_keys and key in self.filter_skip_keys:
8385
continue
8486
if isinstance(value, property):
85-
data[key] = getattr(obj, key)
87+
value = getattr(obj, key)
88+
if not self.strip_empty_keys or not is_null(value):
89+
data[key] = value
8690
for key, value in obj.__dict__.items():
91+
if key.startswith("_"):
92+
key = key[2 + key.find("__") :]
93+
# Values we always filter out, data presented through different endpoint
94+
if key in ["thumb"]:
95+
continue
8796
if apply_filter:
8897
if self.filter_only_keys and key not in self.filter_only_keys:
8998
continue
9099
if self.filter_skip_keys and key in self.filter_skip_keys:
91100
continue
92-
if key.startswith("_"):
93-
key = key[2 + key.find("__") :]
101+
# Values we normalize for schema consistency
102+
if key == "rect" and value is None:
103+
value = []
104+
if key in ["mother_handle", "father_handle", "famc"] and value is None:
105+
value = ""
94106
if not self.strip_empty_keys or not is_null(value):
95107
data[key] = value
96108
return data
97109

98110
def default(self, obj: Any):
99111
"""Our default handler."""
100112
if isinstance(obj, lib.GrampsType):
101-
return str(obj)
113+
return obj.xml_str()
102114

103115
for gramps_class in self.gramps_classes:
104116
if isinstance(obj, gramps_class):

gramps_webapi/api/resources/family.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,11 @@ def object_extend(self, obj: Family, args: Dict) -> Family:
3030
)
3131
if "extend" in args:
3232
obj.extended = get_extended_attributes(db_handle, obj, args)
33-
if "all" in args["extend"] or "father" in args["extend"]:
33+
if "all" in args["extend"] or "father_handle" in args["extend"]:
3434
obj.extended["father"] = get_person_by_handle(
3535
db_handle, obj.father_handle
3636
)
37-
if "all" in args["extend"] or "mother" in args["extend"]:
37+
if "all" in args["extend"] or "mother_handle" in args["extend"]:
3838
obj.extended["mother"] = get_person_by_handle(
3939
db_handle, obj.mother_handle
4040
)

gramps_webapi/api/resources/filters.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ def build_filter(filter_parms: Dict, namespace: str) -> GenericFilter:
9595
rule_instance = rule_class
9696
break
9797
if rule_instance is None:
98-
abort(400)
98+
abort(404)
9999
filter_args = []
100100
if "values" in filter_rule:
101101
filter_args = filter_rule["values"]
@@ -120,7 +120,7 @@ def apply_filter(db_handle: DbReadBase, args: Dict, namespace: str) -> List[Hand
120120
except json.JSONDecodeError:
121121
abort(400)
122122
except ValidationError:
123-
abort(400)
123+
abort(422)
124124

125125
filter_object = build_filter(filter_parms, namespace)
126126
return filter_object.apply(db_handle)
@@ -165,7 +165,7 @@ def get(self, args: Dict[str, str], namespace: str) -> Response:
165165
try:
166166
namespace = GRAMPS_NAMESPACES[namespace]
167167
except KeyError:
168-
abort(400)
168+
abort(404)
169169

170170
rule_list = get_filter_rules(args, namespace)
171171
if args.get("rule") and not args.get("filter"):

gramps_webapi/api/resources/metadata.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ def get(self, args: Dict, datatype: str) -> Response:
4848
elif args["type"] == "family_attribute":
4949
result = db_handle.get_family_attribute_types()
5050
elif args["type"] == "media_attribute":
51-
result = db_handle.get_person_attribute_types()
51+
result = db_handle.get_media_attribute_types()
5252
elif args["type"] == "family_relation":
5353
result = db_handle.get_family_relation_types()
5454
elif args["type"] == "child_reference":

gramps_webapi/api/resources/translate.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,23 @@ class TranslationResource(ProtectedResource, GrampsJSONEncoder):
2020
{"strings": fields.Str(required=True)},
2121
location="query",
2222
)
23-
def get(self, args: Dict, code: str) -> Response:
23+
def get(self, args: Dict, isocode: str) -> Response:
2424
"""Get translation."""
2525
try:
2626
strings = json.loads(args["strings"])
2727
except json.JSONDecodeError:
2828
abort(400)
2929

30-
gramps_locale = GrampsLocale(lang=code)
30+
language_code = isocode.replace("-", "_")
31+
catalog = GRAMPS_LOCALE.get_language_dict()
32+
found = False
33+
for language in catalog:
34+
if catalog[language] == language_code:
35+
found = True
36+
if not found:
37+
abort(404)
38+
39+
gramps_locale = GrampsLocale(lang=language_code)
3140
return self.response(
3241
200,
3342
[
@@ -45,5 +54,8 @@ def get(self) -> Response:
4554
catalog = GRAMPS_LOCALE.get_language_dict()
4655
return self.response(
4756
200,
48-
[{"language": language, "code": catalog[language]} for language in catalog],
57+
[
58+
{"language": language, "isocode": catalog[language].replace("_", "-")}
59+
for language in catalog
60+
],
4961
)

gramps_webapi/api/resources/util.py

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Gramps utility functions."""
22

3-
from typing import Dict, Optional
3+
from typing import Dict, Optional, Union
44

55
from gramps.gen.const import GRAMPS_LOCALE
66
from gramps.gen.db.base import DbReadBase
@@ -24,30 +24,30 @@
2424
dd = GRAMPS_LOCALE.date_displayer
2525

2626

27-
def get_person_by_handle(db_handle: DbReadBase, handle: Handle) -> Optional[Person]:
27+
def get_person_by_handle(db_handle: DbReadBase, handle: Handle) -> Union[Person, Dict]:
2828
"""Safe get person by handle."""
2929
try:
3030
return db_handle.get_person_from_handle(handle)
3131
except HandleError:
32-
return None
32+
return {}
3333

3434

35-
def get_place_by_handle(db_handle: DbReadBase, handle: Handle) -> Optional[Place]:
35+
def get_place_by_handle(db_handle: DbReadBase, handle: Handle) -> Union[Place, Dict]:
3636
"""Safe get place by handle."""
3737
try:
3838
return db_handle.get_place_from_handle(handle)
3939
except HandleError:
40-
return None
40+
return {}
4141

4242

4343
def get_family_by_handle(
4444
db_handle: DbReadBase, handle: Handle, args: Optional[Dict] = None
45-
) -> Optional[Family]:
45+
) -> Union[Family, Dict]:
4646
"""Get a family and optional extended attributes."""
4747
try:
4848
obj = db_handle.get_family_from_handle(handle)
4949
except HandleError:
50-
return None
50+
return {}
5151
args = args or {}
5252
if "extend" in args:
5353
obj.extended = get_extended_attributes(db_handle, obj, args)
@@ -81,20 +81,18 @@ def get_sex_profile(person: Person) -> str:
8181
def get_event_profile_for_object(db_handle: DbReadBase, event: Event) -> Dict:
8282
"""Get event profile given an Event."""
8383
return {
84-
"type": str(event.type),
84+
"type": event.type.xml_str(),
8585
"date": dd.display(event.date),
8686
"place": pd.display_event(db_handle, event),
8787
}
8888

8989

90-
def get_event_profile_for_handle(
91-
db_handle: DbReadBase, handle: Handle
92-
) -> Optional[Dict]:
90+
def get_event_profile_for_handle(db_handle: DbReadBase, handle: Handle) -> Dict:
9391
"""Get event profile given a handle."""
9492
try:
9593
obj = db_handle.get_event_from_handle(handle)
9694
except HandleError:
97-
return None
95+
return {}
9896
return get_event_profile_for_object(db_handle, obj)
9997

10098

@@ -173,12 +171,12 @@ def get_person_profile_for_handle(
173171
handle: Handle,
174172
with_family: bool = True,
175173
with_events: bool = True,
176-
) -> Optional[Person]:
174+
) -> Union[Person, Dict]:
177175
"""Get person profile given a handle."""
178176
try:
179177
obj = db_handle.get_person_from_handle(handle)
180178
except HandleError:
181-
return None
179+
return {}
182180
return get_person_profile_for_object(db_handle, obj, with_family, with_events)
183181

184182

@@ -214,12 +212,12 @@ def get_family_profile_for_object(
214212

215213
def get_family_profile_for_handle(
216214
db_handle: DbReadBase, handle: Handle, with_events: bool = True
217-
) -> Optional[Family]:
215+
) -> Union[Family, Dict]:
218216
"""Get family profile given a handle."""
219217
try:
220218
obj = db_handle.get_family_from_handle(handle)
221219
except HandleError:
222-
return None
220+
return {}
223221
return get_family_profile_for_object(db_handle, obj, with_events)
224222

225223

gramps_webapi/app.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from .dbmanager import WebDbManager
1616

1717

18-
def create_app():
18+
def create_app(db_manager=None):
1919
"""Flask application factory."""
2020
app = Flask(__name__)
2121
app.logger.setLevel(logging.INFO)
@@ -27,7 +27,10 @@ def create_app():
2727
app.config.from_envvar(ENV_CONFIG_FILE)
2828

2929
# instantiate DB manager
30-
app.config["DB_MANAGER"] = WebDbManager(name=app.config["TREE"])
30+
if db_manager is None:
31+
app.config["DB_MANAGER"] = WebDbManager(name=app.config["TREE"])
32+
else:
33+
app.config["DB_MANAGER"] = db_manager
3134

3235
if app.config.get("DISABLE_AUTH"):
3336
pass

0 commit comments

Comments
 (0)