package addressparsing_test import ( "slices" "strings" "testing" "pkg.jfrech.com/brief/internal/addressparsing" ) type CaseDeleteComments struct { S string Z string } func CasesDeleteComments() []CaseDeleteComments { return []CaseDeleteComments{ CaseDeleteComments{ "", "", }, CaseDeleteComments{ "hello world", "hello world", }, CaseDeleteComments{ "hello wo(rl)d", "hello wod", }, CaseDeleteComments{ "hello \"wo(rl)d\"", "hello \"wo(rl)d\"", }, CaseDeleteComments{ "this is (a nested (comment), you see?) no longer\there", "this is no longer\there", }, CaseDeleteComments{ "I am called parenthesis \"(\" ", "I am called parenthesis \"(\" ", }, CaseDeleteComments{ "I am called parenthesis “(” ", "I am called parenthesis “", }, CaseDeleteComments{ "\"I am called parenthesis “(”\" ", "\"I am called parenthesis “(”\" ", }, CaseDeleteComments{ `"I am called parenthesis \"(\"" `, `"I am called parenthesis \"(\"" `, }, CaseDeleteComments{ "()<>", "<>", }, CaseDeleteComments{ "(,)\t5", "\t5", }, CaseDeleteComments{ "(,)\t5\t<=?>", "\t5\t<=?>", }, CaseDeleteComments{ "(,,,)", "", }, CaseDeleteComments{ "(really not) real <\"\\\"\"@quote>", " real <\"\\\"\"@quote>", }, CaseDeleteComments{ "<()>", "<>", }, CaseDeleteComments{ "<3,(,>)4>", "<3,4>", }, CaseDeleteComments{ "<\">\"@example.com (not a real address)>", "<\">\"@example.com >", }, CaseDeleteComments{ "<\tello\t \t(the hello) @f .>", "<\tello\t \t @f .>", }, CaseDeleteComments{ "", "", }, CaseDeleteComments{ "John Doe ", "John Doe ", }, CaseDeleteComments{ "\"< :\t()()\" ()", "\"< :\t()()\" ", }, CaseDeleteComments{ "ign \"(or\" (don't invite (really!))", "ign \"(or\" ", }, CaseDeleteComments{ "mana()ger?", "manager?", }, CaseDeleteComments{ "manag((((()(())((())(()()(())()(()()))())))))er?", "manager?", }, CaseDeleteComments{ "manage()r?", "manager?", }, CaseDeleteComments{ "a", "a", }, } } func TestDeleteComments(t *testing.T) { for j, tc := range CasesDeleteComments() { z := addressparsing.DeleteComments(tc.S) if z != tc.Z { t.Errorf("CasesDeleteComments()[%d]: addressparsing.DeleteComments(%q)\n == %q,\n != %q", j, tc.S, z, tc.Z) } } } // type CaseList struct { S string L []string } func CasesList() []CaseList { testcase := func(s string, l ...string) CaseList { return CaseList{s, l} } return []CaseList{ testcase( `"x \" y" `, `"x \" y" `, ), testcase( "\tx,:;::,,;,;ay", "x", "ay", ), testcase( "\tx:;::,,;,;ay", "ay", ), testcase( "\t;,:;::,,;,;a;", ";", "a;", ), testcase( "\t;:;::,,;,;a;", ";", "a;", ), testcase( "nested: <1>, <2>, <3>,,, groups: <4> are allowed: ,,<5>,<6>;, <7>;;", "<1>", "<2>", "<3>", "<4>", "<5>", "<6>", "<7>", ), testcase( "", // (empty) ), testcase( `"" <>`, `"" <>`, ), testcase( `"a" `, `"a" `, ), testcase( `"a b" `, `"a b" `, ), testcase( "", "", ), testcase( "No One ", "No One ", ), testcase( "No One ,, and\tyou", "No One ", "and\tyou", ), testcase( "x\ty,\n,y z ", "x\ty", "y z", ), testcase( " some gr\r\noup: , ign \"(or\" (don't invite (really!)); and, the,,manage()r?", "", "ign \"(or\" (don't invite (really!))", "and", "the", "manage()r?", ), testcase( " some gr\r\noup: , ign \"(or\" (don't invite (really!));, and, the,,mana()ger?", "", "ign \"(or\" (don't invite (really!))", "and", "the", "mana()ger?", ), testcase( " some gr\r\noup: , ign \"(or\" (don't invite (really!));\t, and, the,,manag((((()(())((())(()()(())()(()()))())))))er?", "", "ign \"(or\" (don't invite (really!))", "and", "the", "manag((((()(())((())(()()(())()(()()))())))))er?", ), testcase( "1, 2, <3, 4>(,)\t5, (,,,),<3,(,>)4>", "1", "2", "<3, 4>", "(,)\t5", "(,,,)", "<3,(,>)4>", ), testcase( "1, 2, <3, 4>(,)\t5\t<=?>, (,,,),<3,(,>)4>", "1", "2", "<3, 4>", "(,)\t5\t<=?>", "(,,,)", "<3,(,>)4>", ), testcase( "", "", ), testcase( "group:1 1@1, 2 2@2, 3@3;", "1 1@1", "2 2@2", "3@3", ), testcase( "group:1 <1@1>, 2 <2@2, 3@3>;", "1 <1@1>", "2 <2@2, 3@3>", ), testcase( "\"< :\t()()\" () , a:b,c,d,,,;()<>", "\"< :\t()()\" ()", "b", "c", "d", "()<>", ), testcase( "<,,>,,<()>", "<,,>", "<()>", ), testcase( "<,,>,,", "<,,>", "", ), testcase( "<,,>,,<\tello\t \t(the hello) @f .> ", "<,,>", "<\tello\t \t(the hello) @f .>", ), testcase( "Jayne E. Doe Alfred M. Müller <ü@+<\t>", "Jayne E. Doe ", "Alfred M. Müller <ü@+<\t>", ), testcase( "Jayne E. DoeAlfred M. Müller <ü@+<\t>", "Jayne E. Doe", "Alfred M. Müller <ü@+<\t>", ), testcase( "a > b", "a > b", ), testcase( " b", "", "b", ), testcase( "<><<<>", "<>", "<<<>", ), testcase( "\"\tb", "\"\tb", ), testcase( `<">"@example.com (not a real address)> (really not) real <"\""@quote>`, `<">"@example.com (not a real address)>`, "(really not) real <\"\\\"\"@quote>", ), testcase( "<><><>", "<>", "<>", "<>", ), testcase( "<<<<<<<", "<<<<<<<", ), testcase( "<", "<", ), testcase( "some nested: groups are: allowed, why not: ;;;", "allowed", "", ), testcase( "this \",group\" is: \t; ;,;,:,,", "", ";", ";", ), testcase( "(muted :) ;,x:y", "(muted :) ;", "y", ), testcase( " ", // (empty) ), testcase( " .. \t", "..", ), testcase( "", // (empty) ), testcase( "wakka", "wakka", ), testcase( `"\\" `, `"\\"`, ), testcase( `1 23 " " \\ "\\" "\"" ?"""`, `1 23 " " \\ "\\" "\"" ?"""`, ), testcase( `1 23 " " \\ "\\" "\"" ?"`, `1 23 " " \\ "\\" "\"" ?"`, ), testcase( `1 23 " " \\ "\\" `, `1 23 " " \\ "\\"`, ), testcase( `1 23 " " \\ "\\"`, `1 23 " " \\ "\\"`, ), testcase( `1 23 " " \\ `, `1 23 " " \\`, ), testcase( `1 23 " " \ `, `1 23 " " \`, ), testcase( "\t1 5 \r\n 8 ", "1 5 \r\n 8", ), testcase( "a", "a", ), testcase( "b", "", "b", ), testcase( "a", "a", ), testcase( "a c", "a", "c", ), testcase( "a", "a", ), testcase( "<1 2 3> 4", "<1 2 3>", "4", ), testcase( "\"\tb", "\"\tb", ), testcase( "<<<<<<<", "<<<<<<<", ), testcase( "", // (empty) ), testcase( " ", // (empty) ), testcase( ",", // (empty) ), testcase( " , ", // (empty) ), testcase( " ,", // (empty) ), testcase( ", ,", // (empty) ), testcase( ", x,", "x", ), testcase( ", x,,,,y", "x", "y", ), testcase( "5145a6c7-dd7f-4cb8-9aea-5c2dab8b09e3@example.com", "5145a6c7-dd7f-4cb8-9aea-5c2dab8b09e3@example.com", ), testcase( ",5145a6c7-dd7f-4cb8-9aea-5c2dab8b09e3@example.com", "5145a6c7-dd7f-4cb8-9aea-5c2dab8b09e3@example.com", ), testcase( " ,, \t\t,5145a6c7-dd7f-4cb8-9aea-5c2dab8b09e3@example.com", "5145a6c7-dd7f-4cb8-9aea-5c2dab8b09e3@example.com", ), testcase( " ,, \t\t,5145a6c7-dd7f-4cb8-9aea-5c2dab8b09e3@example.com\r\n ,", "5145a6c7-dd7f-4cb8-9aea-5c2dab8b09e3@example.com", ), testcase( `"," comma@example.com`, `"," comma@example.com`, ), testcase( `",", comma@example.com`, `","`, "comma@example.com", ), testcase( `comma,com ma,"," comma , coo mmm a?`, "comma", "com ma", `"," comma`, "coo mmm a?", ), testcase( `"You'll find yourself lost" ","@example.com, a1f2699d-0ed2-443a-9425-610f538d550d@example.com`, `"You'll find yourself lost" ","@example.com`, "a1f2699d-0ed2-443a-9425-610f538d550d@example.com", ), testcase( ",@Valid", "@Valid", ), testcase( ":: :@", "@", ), // testcase( "A Group:Ed Jones ,joe@where.test,John ;", "Ed Jones ", "joe@where.test", "John ", ), // Cf. https://www.rfc-editor.org/rfc/rfc5322#appendix-A.1.3 [2024-05-27] testcase( "Undisclosed recipients:;", // (empty) ), // Cf. https://www.rfc-editor.org/rfc/rfc5322#appendix-A.1.3 [2024-05-27] testcase( `"Mary Smith: Personal Account" `, `"Mary Smith: Personal Account" `, ), // Cf. https://www.rfc-editor.org/rfc/rfc5322#appendix-A.2 [2024-05-27] testcase( `"Mary Smith, M.A.: Personal Account" `, `"Mary Smith, M.A.: Personal Account" `, ), testcase( "John Doe \r\n", "John Doe ", ), // Cf. https://www.rfc-editor.org/rfc/rfc5322#appendix-A.6.3 [2024-05-27] testcase( "Mary Smith\r\n \r\n", "Mary Smith\r\n ", ), // Cf. https://www.rfc-editor.org/rfc/rfc5322#appendix-A.6.3 [2024-05-27] testcase( "<1234@local.machine.example> <3456@example.net>", "<1234@local.machine.example>", "<3456@example.net>", ), // Cf. https://www.rfc-editor.org/rfc/rfc5322#appendix-A.2 [2024-05-27] } } func TestList(t *testing.T) { for j, tc := range CasesList() { l := addressparsing.List(tc.S) if !slices.Equal(l, tc.L) { t.Errorf("CasesList()[%d]: \naddressparsing.List(%q)\n == %q\n != %q", j, tc.S, l, tc.L) //t.Logf("\n%s\n", addressparsing.Pretty(addressparsing.Paint(tc.S))) } } } func FuzzList(f *testing.F) { for _, tc := range CasesList() { f.Add(tc.S) } f.Fuzz(func(t *testing.T, s string) { l := addressparsing.List(s) z := s for _, a := range l { k := strings.Index(z, a) if k < 0 { t.Errorf("addressparsing.List(%q)\n == %q\n cannot find substring %q", s, l, a) return } z = z[k+len(a):] } }) }