Merge pull request #2158 from pbiering/sharing-bday-template-age-check
Sharing bday template age check
This commit is contained in:
@@ -2324,7 +2324,7 @@ Supported placeholders (data used from VCARD)
|
||||
* `{nickname}`: nick name (RFC6350#6.2.3)
|
||||
|
||||
Supported extra placeholders
|
||||
* `{age}`: age, toggles to creation of single events instead using RRULE
|
||||
* `{age}`: age, toggles to creation of single events instead using RRULE (only active if year + age_max >= current year)
|
||||
|
||||
Fallback is supported if placeholders inside `[...|...]` (first successful resolved one is used)
|
||||
|
||||
@@ -2346,7 +2346,7 @@ Global template for alarm trigger of conversion "bday"
|
||||
|
||||
Default: ``
|
||||
|
||||
Supported format: `TIMEDELTA;DESCRIPTION` (separated by `|` if more alarms should be generated)
|
||||
Supported format: `TIMEDELTA;DESCRIPTION` (separated by `$` if more alarms should be generated)
|
||||
|
||||
Supported format for `TIMEDELTA`: `[+-]?[0-9]+[WDHM]`
|
||||
|
||||
|
||||
2
config
2
config
@@ -363,7 +363,7 @@
|
||||
#conversion_bday_description_template = BDAY={year}-{month}-{day}
|
||||
|
||||
# Global template of alarm trigger of conversion "bday"
|
||||
# Example: "-15H;BDAY tomorrow|9H;BDAY today"
|
||||
# Example: "-15H;BDAY tomorrow$9H;BDAY today"
|
||||
#conversion_bday_alarm_trigger_template = ""
|
||||
|
||||
# Global categories of conversion "bday"
|
||||
|
||||
@@ -49,6 +49,9 @@ UID_SUFFIX = "-auto-converted-by-Radicale"
|
||||
|
||||
VCF_TO_ICS_SUPPORTED_PLACEHOLDERS: list = ["fn", "n:f", "n:g", "n:a", "age", "nickname", "year", "month", "day"]
|
||||
|
||||
# List of BDAY years acting as flag for "no year specified"
|
||||
VCF_TO_ICS_BDAY_NO_YEAR: list = ["1604"]
|
||||
|
||||
|
||||
def read_components(s: str) -> List[vobject.base.Component]:
|
||||
"""Wrapper for vobject.readComponents"""
|
||||
@@ -379,27 +382,37 @@ def verify(file: str, encoding: str):
|
||||
|
||||
|
||||
def replace_placeholders(text: str, placeholder_mapping: dict) -> str:
|
||||
logger.trace("item/convert_vcf_to_ics: resolve placeholders: %r", text)
|
||||
|
||||
for placeholder in placeholder_mapping:
|
||||
text = text.replace(placeholder, placeholder_mapping[placeholder])
|
||||
|
||||
# resolve {..|..} recursive
|
||||
pattern = re.compile('(.*)(\\[)([^|]+)\\|(.+)(\\])(.*)')
|
||||
logger.trace("item/convert_vcf_to_ics: resolve [..|..] starting with: %r", text)
|
||||
logger.trace("item/convert_vcf_to_ics: resolve [..|..] in : %r", text)
|
||||
|
||||
# resolve [..|..] recursive
|
||||
pattern = re.compile('(.*)(\\[)([^|]+)\\|([^\\]]*)(\\])(.*)')
|
||||
while True:
|
||||
match = pattern.match(text)
|
||||
if not match:
|
||||
# nothing more todo
|
||||
break
|
||||
else:
|
||||
if match[3].startswith('!') and match[3].endswith('!'):
|
||||
logger.trace("item/convert_vcf_to_ics: resolve match : %r", match[0])
|
||||
# check for still unresolved placeholders
|
||||
unresolved = False
|
||||
for placeholder in VCF_TO_ICS_SUPPORTED_PLACEHOLDERS:
|
||||
if "!" + placeholder + "!" in match[3]:
|
||||
unresolved = True
|
||||
break
|
||||
if unresolved:
|
||||
# not resolved variable
|
||||
if '|' in match[4]:
|
||||
# further recursion required
|
||||
text = match[1] + match[2] + match[4] + match[5] + match[6]
|
||||
logger.trace("item/convert_vcf_to_ics: resolve [..|..] match/replace/continue result: %r", text)
|
||||
logger.trace("item/convert_vcf_to_ics: resolve continue : %r", text)
|
||||
else:
|
||||
text = match[1] + match[4] + match[6]
|
||||
logger.trace("item/convert_vcf_to_ics: resolve [..|..] match/replace/final result: %r", text)
|
||||
logger.trace("item/convert_vcf_to_ics: resolve final result: %r", text)
|
||||
break
|
||||
else:
|
||||
# resolved variable
|
||||
@@ -605,6 +618,13 @@ class Item:
|
||||
else:
|
||||
pass
|
||||
|
||||
vcard_has_year = True # default
|
||||
vcard_age_exceed_max = False # default
|
||||
|
||||
if str(match[1]) in VCF_TO_ICS_BDAY_NO_YEAR:
|
||||
logger.trace("item/convert_vcf_to_ics: has 'no year' bday: %r -> %r", self.href, bday.value)
|
||||
vcard_has_year = False
|
||||
|
||||
placeholder_mapping: dict = {}
|
||||
|
||||
bdayS = match[1] + match[2] + match[3]
|
||||
@@ -616,6 +636,9 @@ class Item:
|
||||
placeholder_mapping['{month}'] = match[2]
|
||||
placeholder_mapping['{day}'] = match[3]
|
||||
|
||||
if not vcard_has_year:
|
||||
placeholder_mapping['{year}'] = "????"
|
||||
|
||||
# create ICS
|
||||
if hasattr(self.vobject_item, "fn"):
|
||||
name = self.vobject_item.fn.value
|
||||
@@ -669,7 +692,6 @@ class Item:
|
||||
summary = ShareActions['config_default']['conversion_bday_summary_template']
|
||||
else:
|
||||
summary = sharing.SHARING_BDAY_SUMMARY_TEMPLATE_DEFAULT # fallback
|
||||
summary = replace_placeholders(summary, placeholder_mapping)
|
||||
|
||||
# prepare DESCRIPTION
|
||||
if ShareActions is not None and 'config' in ShareActions and 'conversion_bday_description_template' in ShareActions['config']:
|
||||
@@ -678,7 +700,6 @@ class Item:
|
||||
description = ShareActions['config_default']['conversion_bday_description_template']
|
||||
else:
|
||||
description = sharing.SHARING_BDAY_DESCRIPTION_TEMPLATE_DEFAULT # fallback
|
||||
description = replace_placeholders(description, placeholder_mapping)
|
||||
|
||||
# create CATEGORIES
|
||||
if ShareActions is not None and 'config' in ShareActions and 'conversion_bday_categories' in ShareActions['config']:
|
||||
@@ -698,14 +719,23 @@ class Item:
|
||||
|
||||
vevent_enable_age = False
|
||||
age_max = 0
|
||||
if "{age}" in summary or "{age}" in description or "age" in alarm_trigger:
|
||||
if vcard_has_year and ("{age}" in summary or "{age}" in description or "age" in alarm_trigger):
|
||||
if ShareActions is not None and 'config' in ShareActions and 'conversion_bday_age_max' in ShareActions['config']:
|
||||
age_max = ShareActions['config']['conversion_bday_age_max']
|
||||
elif ShareActions is not None and 'config_default' in ShareActions and 'conversion_bday_age_max' in ShareActions['config_default']:
|
||||
age_max = ShareActions['config_default']['conversion_bday_age_max']
|
||||
else:
|
||||
age_max = sharing.SHARING_BDAY_AGE_MAX_DEFAULT # fallback
|
||||
vevent_enable_age = True
|
||||
|
||||
# check for age_max in the past
|
||||
currentDateTime = datetime.datetime.now()
|
||||
date = currentDateTime.date()
|
||||
if bdayY + age_max < date.year:
|
||||
logger.trace("item/convert_vcf_to_ics: bdayY=%d + age_max=%d < current year %d -> disable age support", bdayY, age_max, date.year)
|
||||
vcard_age_exceed_max = True
|
||||
age_max = 0
|
||||
else:
|
||||
vevent_enable_age = True
|
||||
|
||||
# create UID
|
||||
if hasattr(self.vobject_item, "uid"):
|
||||
@@ -742,12 +772,16 @@ class Item:
|
||||
uid_value = uid
|
||||
vevent.add('uid').value = uid_value
|
||||
|
||||
# set SUMMARY
|
||||
# set placehoder for "age"
|
||||
if vevent_enable_age:
|
||||
summary_value = summary.replace("{age}", str(age))
|
||||
else:
|
||||
summary_value = summary
|
||||
vevent.add('summary').value = summary_value
|
||||
placeholder_mapping['{age}'] = str(age)
|
||||
elif not vcard_has_year:
|
||||
placeholder_mapping['{age}'] = "??"
|
||||
elif vcard_age_exceed_max:
|
||||
placeholder_mapping['{age}'] = "!age!"
|
||||
|
||||
# set SUMMARY
|
||||
vevent.add('summary').value = replace_placeholders(summary, placeholder_mapping)
|
||||
|
||||
# set CATEGORIES
|
||||
if categories is not None and categories != []:
|
||||
@@ -761,16 +795,14 @@ class Item:
|
||||
|
||||
# set VALARM
|
||||
if alarm_trigger is not None and alarm_trigger != "":
|
||||
for entry in alarm_trigger.split('|'):
|
||||
for entry in alarm_trigger.split('$'):
|
||||
(trigger, alarm_description) = entry.split(';')
|
||||
logger.trace("item/convert_vcf_to_ics: alarm trigger entry: %r (trigger=%r description=%r)", entry, trigger, description)
|
||||
logger.trace("item/convert_vcf_to_ics: alarm trigger entry: %r (trigger=%r description=%r)", entry, trigger, alarm_description)
|
||||
td = trigger_to_timedelta(trigger)
|
||||
if td is not None:
|
||||
alarm_description = replace_placeholders(alarm_description, placeholder_mapping)
|
||||
alarm_description_value = alarm_description.replace("{age}", str(age))
|
||||
valarm = vevent.add('valarm')
|
||||
valarm.add('action').value = "DISPLAY"
|
||||
valarm.add('description').value = alarm_description_value
|
||||
valarm.add('description').value = replace_placeholders(alarm_description, placeholder_mapping)
|
||||
valarm.add('trigger').value = td
|
||||
|
||||
# set RRULE
|
||||
@@ -781,12 +813,8 @@ class Item:
|
||||
vevent.add('transp').value = "TRANSPARENT"
|
||||
|
||||
# set DESCRIPTION
|
||||
if vevent_enable_age:
|
||||
description_value = description.replace("{age}", str(age))
|
||||
else:
|
||||
description_value = description
|
||||
if description != "":
|
||||
vevent.add('description').value = description_value
|
||||
vevent.add('description').value = replace_placeholders(description, placeholder_mapping)
|
||||
|
||||
# increase age
|
||||
age = age + 1
|
||||
|
||||
@@ -147,7 +147,7 @@ def check_template(data: Any) -> str:
|
||||
placeholder_mapping["{" + placeholder + "}"] = '!' + placeholder + '!'
|
||||
|
||||
result = item.replace_placeholders(data, placeholder_mapping)
|
||||
logger.trace("replace placeholders: %r -> %r", data, result)
|
||||
logger.trace("replace placeholders result: %r -> %r", data, result)
|
||||
pattern = re.compile('.*{.*}.*')
|
||||
if pattern.search(result):
|
||||
raise ValueError("template contains unsupported placeholder {..}: %r" % result)
|
||||
@@ -163,7 +163,7 @@ def check_template_not_empty(data: Any) -> str:
|
||||
|
||||
def check_template_alarm_trigger(data: Any) -> str:
|
||||
if data is not None and data != '':
|
||||
for entry in data.split('|'):
|
||||
for entry in data.split('$'):
|
||||
try:
|
||||
(trigger, alarm_description) = entry.split(';')
|
||||
except ValueError:
|
||||
|
||||
@@ -444,6 +444,7 @@ class Sharing(sharing.BaseSharing):
|
||||
field = field.replace("': {", '": {') # replace for JSON entry/value ': { -> ": {
|
||||
field = field.replace("': ", '": ') # replace for JSON entry/value ': -> ":(whitespace)
|
||||
field = field.replace("', '", '", "') # replace for JSON delimiter ', ' -> ", "
|
||||
field = field.replace(", '", ', "') # replace for JSON delimiter , ' -> , "
|
||||
logger.trace("json prep quote replacer match (after) : %s", field)
|
||||
try:
|
||||
row[fieldname] = json.loads(field)
|
||||
|
||||
9
radicale/tests/static/contact4-with-bday-no-year.vcf
Normal file
9
radicale/tests/static/contact4-with-bday-no-year.vcf
Normal file
@@ -0,0 +1,9 @@
|
||||
BEGIN:VCARD
|
||||
VERSION:3.0
|
||||
PRODID:Thunderbird CardBook V105.3//DE
|
||||
UID:d0bd27f0-5c7c-4540-b9b6-91314b8361f1
|
||||
BDAY:16040101
|
||||
FN:Test bday without year Thunderbird CardBook
|
||||
N:;;;;
|
||||
REV:2026-06-10T04:26:53Z
|
||||
END:VCARD
|
||||
@@ -5020,7 +5020,7 @@ permissions: RrWw""")
|
||||
self.configure({"sharing": {
|
||||
"conversion_bday_summary_template": "[{fn}|{n:f} {n:g} {n:a}|{nickname}] (BDAY)",
|
||||
"conversion_bday_description_template": "BDAY={year}-{month}-{day}",
|
||||
"conversion_bday_alarm_trigger_template": "-15H;BDAY tomorrow|9H;BDAY today",
|
||||
"conversion_bday_alarm_trigger_template": "-15H;BDAY tomorrow$9H;BDAY today",
|
||||
}})
|
||||
|
||||
# verify content as user
|
||||
@@ -5083,7 +5083,7 @@ permissions: RrWw""")
|
||||
assert "TRIGGER:-PT15H" in answer
|
||||
assert "TRIGGER:PT9H" in answer
|
||||
|
||||
self.configure({"sharing": {"conversion_bday_alarm_trigger_template": "-12H;Birthday tomorrow of {fn}|12H;Birthday today of {n:g} {n:f}"}})
|
||||
self.configure({"sharing": {"conversion_bday_alarm_trigger_template": "-12H;Birthday tomorrow of {fn}$12H;Birthday today of {n:g} {n:f}"}})
|
||||
logging.info("\n*** GET collection user format: description -> ok")
|
||||
_, headers, answer = self.request("GET", path_shared_3, login="user:userpw")
|
||||
assert "DESCRIPTION:Birthday tomorrow of Test-FN-C3" in answer
|
||||
@@ -5300,14 +5300,15 @@ permissions: RrWw""")
|
||||
json_dict['PathOrToken'] = path_shared_r
|
||||
json_dict['Actions'] = {"config": {
|
||||
"conversion_bday_age_max": 5,
|
||||
"conversion_bday_summary_template": "{fn} ({year}/{age})",
|
||||
"conversion_bday_summary_template": "{fn} ({year}/[{age}|MAX-in-the-past])",
|
||||
}}
|
||||
_, headers, answer = self._sharing_api_json("map", "update", check=200, login="owner:ownerpw", json_dict=json_dict)
|
||||
|
||||
logging.info("\n*** GET collection user format: summary with age -> ok")
|
||||
logging.info("\n*** GET collection user format: summary with age:5 -> ok")
|
||||
_, headers, answer = self.request("GET", path_shared_3, login="user:userpw")
|
||||
assert "Test-FN-C3 (1990/0)" in answer
|
||||
assert "Test-FN-C3 (1990/5)" in answer
|
||||
assert "Test-FN-C3 (1990/MAX-in-the-past)" in answer
|
||||
assert "Test-FN-C3 (1990/0)" not in answer
|
||||
assert "Test-FN-C3 (1990/5)" not in answer
|
||||
assert "Test-FN-C3 (1990/6)" not in answer
|
||||
|
||||
# update template
|
||||
@@ -5321,7 +5322,7 @@ permissions: RrWw""")
|
||||
}}
|
||||
_, headers, answer = self._sharing_api_json("map", "update", check=200, login="owner:ownerpw", json_dict=json_dict)
|
||||
|
||||
logging.info("\n*** GET collection user format: summary with age -> ok")
|
||||
logging.info("\n*** GET collection user format: summary with age default -> ok")
|
||||
_, headers, answer = self.request("GET", path_shared_3, login="user:userpw")
|
||||
assert "Test-FN-C3 (1990/0)" in answer
|
||||
assert "Test-FN-C3 (1990/5)" in answer
|
||||
@@ -5416,20 +5417,21 @@ permissions: RrWw""")
|
||||
assert "SUMMARY:Test-FN (BDAY)" in answer
|
||||
|
||||
self.configure({"sharing": {
|
||||
"conversion_bday_summary_template": "[{fn}|{n:f} {n:g} {n:a}|{nickname}] ({age}. Birthday)",
|
||||
"conversion_bday_summary_template": "[{fn}|{n:f} {n:g} {n:a}|{nickname}] ([{age}. |]Birthday)",
|
||||
"conversion_bday_age_max": 99,
|
||||
}})
|
||||
logging.info("\n*** GET collection user format:text -> ok")
|
||||
logging.info("\n*** GET collection user format:text (summary) (age:99) -> ok")
|
||||
_, headers, answer = self.request("GET", path_shared_2, login="user:userpw")
|
||||
assert "SUMMARY:Test-FN (0. Birthday)" in answer
|
||||
assert "SUMMARY:Test-FN (1. Birthday)" in answer
|
||||
assert "SUMMARY:Test-FN (99. Birthday)" in answer
|
||||
assert "SUMMARY:Test-FN (100. Birthday)" not in answer
|
||||
|
||||
logging.info("\n*** GET collection user format:text (no trigger) (age:99) -> ok")
|
||||
self.configure({"sharing": {
|
||||
"conversion_bday_summary_template": "[{fn}|{n:f} {n:g} {n:a}|{nickname}] (BDAY)",
|
||||
"conversion_bday_description_template": "AGE={age}",
|
||||
}})
|
||||
logging.info("\n*** GET collection user format:text -> ok")
|
||||
_, headers, answer = self.request("GET", path_shared_2, login="user:userpw")
|
||||
assert "SUMMARY:Test-FN (BDAY)" in answer
|
||||
assert "DESCRIPTION:AGE=0" in answer
|
||||
@@ -5437,12 +5439,12 @@ permissions: RrWw""")
|
||||
assert "DESCRIPTION:AGE=99" in answer
|
||||
assert "DESCRIPTION:AGE=100" not in answer
|
||||
|
||||
logging.info("\n*** GET collection user format:text (with trigger) -> ok")
|
||||
self.configure({"sharing": {
|
||||
"conversion_bday_summary_template": "[{fn}|{n:f} {n:g} {n:a}|{nickname}] (BDAY)",
|
||||
"conversion_bday_description_template": "BDAY={year}-{month}-{day}",
|
||||
"conversion_bday_alarm_trigger_template": "-15H;alarm {fn} {age}. birthday",
|
||||
"conversion_bday_alarm_trigger_template": "-15H;alarm {fn} [{age}. |]birthday",
|
||||
}})
|
||||
logging.info("\n*** GET collection user format:text -> ok")
|
||||
_, headers, answer = self.request("GET", path_shared_2, login="user:userpw")
|
||||
assert "SUMMARY:Test-FN (BDAY)" in answer
|
||||
assert "DESCRIPTION:BDAY=1970-01-01" in answer
|
||||
@@ -5452,7 +5454,44 @@ permissions: RrWw""")
|
||||
assert "DESCRIPTION:alarm Test-FN 100. birthday" not in answer
|
||||
|
||||
# update template
|
||||
logging.info("\n*** update map(bday) user/owner:r -> ok")
|
||||
logging.info("\n*** update map(bday) user/owner:r (age:98) -> ok")
|
||||
json_dict = {}
|
||||
json_dict['User'] = "user"
|
||||
json_dict['PathMapped'] = path_mapped
|
||||
json_dict['PathOrToken'] = path_shared_r
|
||||
json_dict['Actions'] = {"config": {
|
||||
"conversion_bday_age_max": 98,
|
||||
}}
|
||||
_, headers, answer = self._sharing_api_json("map", "update", check=200, login="owner:ownerpw", json_dict=json_dict)
|
||||
answer_dict = json.loads(answer)
|
||||
assert answer_dict['Status'] == "success"
|
||||
|
||||
logging.info("\n*** GET collection user format:text (age:98) -> ok")
|
||||
_, headers, answer = self.request("GET", path_shared_2, login="user:userpw")
|
||||
assert "SUMMARY:Test-FN (BDAY)" in answer
|
||||
assert "DESCRIPTION:BDAY=1970-01-01" in answer
|
||||
assert "DESCRIPTION:alarm Test-FN 0. birthday" in answer
|
||||
assert "DESCRIPTION:alarm Test-FN 1. birthday" in answer
|
||||
assert "DESCRIPTION:alarm Test-FN 98. birthday" in answer
|
||||
assert "DESCRIPTION:alarm Test-FN 99. birthday" not in answer
|
||||
assert "DESCRIPTION:alarm Test-FN 100. birthday" not in answer
|
||||
|
||||
logging.info("\n*** GET collection user limit age (age:98) -> ok")
|
||||
self.configure({"sharing": {
|
||||
"conversion_bday_summary_template": "[{fn}|{n:f} {n:g} {n:a}|{nickname}] (BDAY)",
|
||||
"conversion_bday_description_template": "AGE=[{age}|MAX-in-the-past]",
|
||||
"conversion_bday_alarm_trigger_template": "",
|
||||
}})
|
||||
_, headers, answer = self.request("GET", path_shared_2, login="user:userpw")
|
||||
assert "SUMMARY:Test-FN (BDAY)" in answer
|
||||
assert "DESCRIPTION:AGE=0" in answer
|
||||
assert "DESCRIPTION:AGE=1" in answer
|
||||
assert "DESCRIPTION:AGE=98" in answer
|
||||
assert "DESCRIPTION:AGE=99" not in answer
|
||||
assert "DESCRIPTION:AGE=100" not in answer
|
||||
|
||||
# update template
|
||||
logging.info("\n*** update map(bday) user/owner:r (age:5) -> ok")
|
||||
json_dict = {}
|
||||
json_dict['User'] = "user"
|
||||
json_dict['PathMapped'] = path_mapped
|
||||
@@ -5464,29 +5503,28 @@ permissions: RrWw""")
|
||||
answer_dict = json.loads(answer)
|
||||
assert answer_dict['Status'] == "success"
|
||||
|
||||
logging.info("\n*** GET collection user format:text -> ok")
|
||||
logging.info("\n*** GET collection user format:text (age:5) -> ok")
|
||||
_, headers, answer = self.request("GET", path_shared_2, login="user:userpw")
|
||||
assert "SUMMARY:Test-FN (BDAY)" in answer
|
||||
assert "DESCRIPTION:BDAY=1970-01-01" in answer
|
||||
assert "DESCRIPTION:alarm Test-FN 0. birthday" in answer
|
||||
assert "DESCRIPTION:alarm Test-FN 1. birthday" in answer
|
||||
assert "DESCRIPTION:alarm Test-FN 5. birthday" in answer
|
||||
assert "DESCRIPTION:alarm Test-FN 6. birthday" not in answer
|
||||
assert "DESCRIPTION:AGE=MAX-in-the-past" in answer
|
||||
assert "DESCRIPTION:alarm Test-FN 0. birthday" not in answer
|
||||
assert "DESCRIPTION:alarm Test-FN 1. birthday" not in answer
|
||||
assert "DESCRIPTION:alarm Test-FN 98. birthday" not in answer
|
||||
assert "DESCRIPTION:alarm Test-FN 99. birthday" not in answer
|
||||
assert "DESCRIPTION:alarm Test-FN 100. birthday" not in answer
|
||||
|
||||
logging.info("\n*** GET collection user limit age (age:5) -> ok")
|
||||
self.configure({"sharing": {
|
||||
"conversion_bday_summary_template": "[{fn}|{n:f} {n:g} {n:a}|{nickname}] (BDAY)",
|
||||
"conversion_bday_description_template": "AGE={age}",
|
||||
"conversion_bday_description_template": "AGE=[{age}|MAX-in-the-past|MAX-in-the-past]",
|
||||
"conversion_bday_alarm_trigger_template": "",
|
||||
}})
|
||||
logging.info("\n*** GET collection user limit age to 5 -> ok")
|
||||
_, headers, answer = self.request("GET", path_shared_2, login="user:userpw")
|
||||
assert "SUMMARY:Test-FN (BDAY)" in answer
|
||||
assert "DESCRIPTION:AGE=0" in answer
|
||||
assert "DESCRIPTION:AGE=1" in answer
|
||||
assert "DESCRIPTION:AGE=5" in answer
|
||||
assert "DESCRIPTION:AGE=6" not in answer
|
||||
assert "DESCRIPTION:AGE=MAX-in-the-past" in answer
|
||||
assert "DESCRIPTION:AGE=0" not in answer
|
||||
assert "DESCRIPTION:AGE=1" not in answer
|
||||
assert "DESCRIPTION:AGE=98" not in answer
|
||||
assert "DESCRIPTION:AGE=99" not in answer
|
||||
assert "DESCRIPTION:AGE=100" not in answer
|
||||
|
||||
@@ -5554,6 +5592,103 @@ permissions: RrWw""")
|
||||
}}
|
||||
_, headers, answer = self._sharing_api_json("map", "update", check=400, login="owner:ownerpw", json_dict=json_dict)
|
||||
|
||||
def test_sharing_api_map_vcf_no_year_bday_age_template(self) -> None:
|
||||
"""share-by-map with conversion=bday template tests with age and VCF without year."""
|
||||
self.configure({"auth": {"type": "htpasswd",
|
||||
"htpasswd_filename": self.htpasswd_file_path,
|
||||
"htpasswd_encryption": "plain"},
|
||||
"sharing": {
|
||||
"type": "csv",
|
||||
"permit_create_map": True,
|
||||
"permit_properties_overlay": "True",
|
||||
"enforce_properties_overlay": "True",
|
||||
"collection_by_map": "True"},
|
||||
"logging": {"request_header_on_debug": "False",
|
||||
"response_content_on_debug": "True",
|
||||
"response_header_on_debug": "True",
|
||||
"request_content_on_debug": "True"},
|
||||
"rights": {"type": "owner_only"}})
|
||||
|
||||
json_dict: dict
|
||||
|
||||
logging.info("\n*** prepare and test access")
|
||||
|
||||
for db_type in list(filter(lambda item: item != "none", sharing.INTERNAL_TYPES)):
|
||||
logging.info("\n*** test: %s", db_type)
|
||||
self.configure({"sharing": {"type": db_type}})
|
||||
|
||||
path_mapped = "/owner/adressbook-" + db_type + ".vcf/"
|
||||
path_shared_r = "/user/calendar-bday-abook-shared-by-owner-r-" + db_type + ".ics/"
|
||||
self.create_addressbook(path_mapped, login="owner:ownerpw")
|
||||
|
||||
contact2 = get_file_content("contact4-with-bday-no-year.vcf")
|
||||
path2 = path_mapped + "/contact4-with-bday-no-year.vcf"
|
||||
path_shared_2 = path_shared_r + "/contact4-with-bday-no-year.vcf"
|
||||
self.put(path2, contact2, login="owner:ownerpw")
|
||||
|
||||
# create map
|
||||
logging.info("\n*** create map(bday) user/owner:r -> ok")
|
||||
json_dict = {}
|
||||
json_dict['User'] = "user"
|
||||
json_dict['PathMapped'] = path_mapped
|
||||
json_dict['PathOrToken'] = path_shared_r
|
||||
json_dict['Conversion'] = "bday"
|
||||
json_dict['Permissions'] = "rP"
|
||||
json_dict['Enabled'] = True
|
||||
json_dict['Enabled'] = True
|
||||
json_dict['Hidden'] = False
|
||||
json_dict['Properties'] = {"D:displayname": "Test-BDAY"}
|
||||
_, headers, answer = self._sharing_api_json("map", "create", check=200, login="owner:ownerpw", json_dict=json_dict)
|
||||
answer_dict = json.loads(answer)
|
||||
assert answer_dict['Status'] == "success"
|
||||
|
||||
# enable map by user
|
||||
logging.info("\n*** enable map(bday) by user")
|
||||
json_dict = {}
|
||||
json_dict['User'] = "user"
|
||||
json_dict['PathMapped'] = path_mapped
|
||||
json_dict['PathOrToken'] = path_shared_r
|
||||
_, headers, answer = self._sharing_api_json("map", "enable", check=200, login="user:userpw", json_dict=json_dict)
|
||||
|
||||
self.configure({"sharing": {
|
||||
"conversion_bday_summary_template": "[{fn}|{n:f} {n:g} {n:a}|{nickname}] (BDAY)",
|
||||
"conversion_bday_description_template": "BDAY={year}-{month}-{day}",
|
||||
}})
|
||||
|
||||
# verify content as user
|
||||
logging.info("\n*** GET collection user format:default -> ok")
|
||||
_, headers, answer = self.request("GET", path_shared_2, login="user:userpw")
|
||||
assert "SUMMARY:Test bday without year Thunderbird CardBook (BDAY)" in answer
|
||||
|
||||
self.configure({"sharing": {
|
||||
"conversion_bday_summary_template": "[{fn}|{n:f} {n:g} {n:a}|{nickname}] ([{age}. |]Birthday)",
|
||||
}})
|
||||
logging.info("\n*** GET collection user format:text -> ok")
|
||||
_, headers, answer = self.request("GET", path_shared_2, login="user:userpw")
|
||||
assert "(0. Birthday)" not in answer
|
||||
|
||||
self.configure({"sharing": {
|
||||
"conversion_bday_summary_template": "[{fn}|{n:f} {n:g} {n:a}|{nickname}] (BDAY)",
|
||||
"conversion_bday_description_template": "AGE={age} BDAY={year}-{month}-{day}",
|
||||
}})
|
||||
logging.info("\n*** GET collection user format:text -> ok")
|
||||
_, headers, answer = self.request("GET", path_shared_2, login="user:userpw")
|
||||
assert "SUMMARY:Test bday without year Thunderbird CardBook (BDAY)" in answer
|
||||
assert "DESCRIPTION:AGE=0" not in answer
|
||||
assert "DESCRIPTION:AGE=??" in answer
|
||||
|
||||
self.configure({"sharing": {
|
||||
"conversion_bday_summary_template": "[{fn}|{n:f} {n:g} {n:a}|{nickname}] (BDAY)",
|
||||
"conversion_bday_description_template": "BDAY={year}-{month}-{day}",
|
||||
"conversion_bday_alarm_trigger_template": "-15H;alarm {fn} [{age}. |]birthday",
|
||||
}})
|
||||
logging.info("\n*** GET collection user format:text -> ok")
|
||||
_, headers, answer = self.request("GET", path_shared_2, login="user:userpw")
|
||||
assert "SUMMARY:Test bday without year Thunderbird CardBook (BDAY)" in answer
|
||||
assert "DESCRIPTION:BDAY=????-01-01" in answer
|
||||
assert "DESCRIPTION:alarm Test bday without year Thunderbird CardBook 0. birthday" not in answer
|
||||
assert "DESCRIPTION:alarm Test bday without year Thunderbird CardBook ??. birthday" in answer
|
||||
|
||||
def test_sharing_api_map_vcf_bday_per_share_template(self) -> None:
|
||||
"""share-by-map with conversion=bday template per share tests."""
|
||||
self.configure({"auth": {"type": "htpasswd",
|
||||
|
||||
Reference in New Issue
Block a user