From c9de20f3a7fc7d8471449396f366e94ebc385e0b Mon Sep 17 00:00:00 2001 From: Jonathan Bernard Date: Sat, 28 Mar 2026 09:58:12 -0500 Subject: [PATCH] WIP: Test cases covering findings surfaced by ChatGPT. --- tests/tvcard3.nim | 147 ++++++++++++++++++++++++++++++++++++++++++++++ tests/tvcard4.nim | 10 +++- 2 files changed, 155 insertions(+), 2 deletions(-) diff --git a/tests/tvcard3.nim b/tests/tvcard3.nim index 9d59d49..99ac7b6 100644 --- a/tests/tvcard3.nim +++ b/tests/tvcard3.nim @@ -4,6 +4,19 @@ import zero_functional import vcard import vcard/vcard3 +template vcard3Doc(lines: varargs[string]): string = + "BEGIN:VCARD\r\n" & + lines.join("\r\n") & + "\r\nEND:VCARD\r\n" + +proc parseSingleVCard3(content: string): VCard3 = + cast[VCard3](parseVCards(content)[0]) + +proc newMinimalVCard3(): VCard3 = + result = VCard3(parsedVersion: VCardV3) + result.add(newVC3_Fn("John Smith")) + result.add(newVC3_N(family = @["Smith"], given = @["John"])) + suite "vcard/vcard3": test "vcard3/private tests": @@ -72,3 +85,137 @@ suite "vcard/vcard3": cast[VCard3](vcards[0]).fn.value == "Frank Dawson" cast[VCard3](vcards[0]).email.len == 2 (cast[VCard3](vcards[0]).email --> find(it.emailType.contains("PREF"))).isSome + + test "spec: parser rejects cards missing VERSION": + expect(VCardParsingError): + discard parseVCards(vcard3Doc( + "FN:John Smith", + "N:Smith;John;;;")) + + test "spec: parser rejects cards missing FN": + expect(VCardParsingError): + discard parseVCards(vcard3Doc( + "VERSION:3.0", + "N:Smith;John;;;")) + + test "spec: parser rejects cards missing N": + expect(VCardParsingError): + discard parseVCards(vcard3Doc( + "VERSION:3.0", + "FN:John Smith")) + + test "spec: simple text values decode RFC 2426 escapes when parsing": + let parsed = parseSingleVCard3(vcard3Doc( + "VERSION:3.0", + r"FN:Jane\, Smith\; Esq.\\Office\nSecond line", + "N:Smith;Jane;;;")) + + check parsed.fn.value == "Jane, Smith; Esq.\\Office\nSecond line" + + test "spec: simple text values escape special characters when serializing": + let vc = newMinimalVCard3() + vc.set(newVC3_Fn("Jane, Smith; Esq.\\Office\nSecond line")) + + check ($vc).contains(r"FN:Jane\, Smith\; Esq.\\Office\nSecond line") + + test "spec: structured text values escape special characters when serializing": + let vc = VCard3(parsedVersion: VCardV3) + vc.add(newVC3_Fn("John Smith")) + vc.add(newVC3_N( + family = @["Smith, Sr."], + given = @["John;Jack"], + additional = @["Back\\Slash"], + prefixes = @["Dr.\nProf."], + suffixes = @["III"])) + + check ($vc).contains(r"N:Smith\, Sr.;John\;Jack;Back\\Slash;Dr.\nProf.;III") + + test "spec: list text values escape special characters when serializing": + let vc = newMinimalVCard3() + vc.add(newVC3_Categories(@["alpha,beta", "semi;colon", "slash\\value"])) + + check ($vc).contains(r"CATEGORIES:alpha\,beta,semi\;colon,slash\\value") + + test "spec: inline binary values round-trip without double encoding": + let payload = "aGVsbG8=" + let serialized = $parseSingleVCard3(vcard3Doc( + "VERSION:3.0", + "FN:John Smith", + "N:Smith;John;;;", + "PHOTO;ENCODING=b;TYPE=JPEG:" & payload, + "LOGO;ENCODING=b;TYPE=GIF:" & payload, + "SOUND;ENCODING=b;TYPE=WAVE:" & payload, + "KEY;ENCODING=b;TYPE=PGP:" & payload)) + + check: + serialized.contains("PHOTO;ENCODING=b;TYPE=JPEG:" & payload) + serialized.contains("LOGO;ENCODING=b;TYPE=GIF:" & payload) + serialized.contains("SOUND;ENCODING=b;TYPE=WAVE:" & payload) + serialized.contains("KEY;ENCODING=b;TYPE=PGP:" & payload) + + test "spec: quoted parameter values are accepted": + let parsed = parseSingleVCard3(vcard3Doc( + "VERSION:3.0", + "FN;LANGUAGE=\"en\":John Smith", + "N:Smith;John;;;")) + + check parsed.fn.language == some("en") + + test "spec: PROFILE is exposed as the standard property type": + let parsed = parseSingleVCard3(vcard3Doc( + "VERSION:3.0", + "PROFILE:VCARD", + "FN:John Smith", + "N:Smith;John;;;")) + + check parsed.profile.isSome + + test "spec: AGENT uri values survive parse and serialize": + let serialized = $parseSingleVCard3(vcard3Doc( + "VERSION:3.0", + "FN:John Smith", + "N:Smith;John;;;", + "AGENT;VALUE=uri:mailto:assistant@example.com")) + + check serialized.contains("AGENT;VALUE=uri:mailto:assistant@example.com") + + test "spec: folded lines may continue with horizontal tab": + let parsed = parseSingleVCard3( + "BEGIN:VCARD\r\n" & + "VERSION:3.0\r\n" & + "FN:John Smith\r\n" & + "N:Smith;John;;;\r\n" & + "NOTE:one two \r\n" & + "\tthree four\r\n" & + "END:VCARD\r\n") + + check: + parsed.note.len == 1 + parsed.note[0].value == "one two three four" + + test "spec: group names may contain hyphens": + let parsed = parseSingleVCard3(vcard3Doc( + "VERSION:3.0", + "FN:John Smith", + "N:Smith;John;;;", + "item-1.EMAIL:test@example.com")) + + check: + parsed.email.len == 1 + parsed.email[0].group == some("item-1") + + test "spec: REV with VALUE=date serializes as a date": + let vc = newMinimalVCard3() + vc.add(newVC3_Rev( + value = parse("2000-01-02", "yyyy-MM-dd"), + valueType = some("date"))) + + check ($vc).contains("REV;VALUE=date:2000-01-02") + + test "spec: KEY defaults to text rather than uri": + let vc = newMinimalVCard3() + vc.add(newVC3_Key("ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC")) + + check: + ($vc).contains("KEY:ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC") + not ($vc).contains("KEY;VALUE=uri:") diff --git a/tests/tvcard4.nim b/tests/tvcard4.nim index 17d572f..a63ee41 100644 --- a/tests/tvcard4.nim +++ b/tests/tvcard4.nim @@ -68,8 +68,14 @@ suite "vcard/vcard4": types = @["work", "internet"], params = @[("PREF", @["1"]), ("X-ATTACHMENT-LIMIT", @["25MB"])]) - check serialize(email) == - "EMAIL;X-ATTACHMENT-LIMIT=25MB;TYPE=work,internet;PREF=1:john.smith@testco.test" + let serialized = serialize(email) + check: + serialized.startsWith("EMAIL;") + serialized.endsWith(":john.smith@testco.test") + serialized.contains(";PREF=1") + serialized.contains(";TYPE=work,internet") + serialized.contains(";X-ATTACHMENT-LIMIT=25MB") + serialized.count(';') == 3 test "can parse properties with escaped characters": check v4Ex.note.len == 1