@@ -1345,7 +1345,7 @@ describe("getBookingResponsesPartialSchema - Prefill validation", () => {
13451345
13461346describe ( "excluded email/domain validation" , ( ) => {
13471347 test ( "should fail if the email is present in excluded emails" , async ( ) => {
1348- const excludedEmails = "spammer@cal.com, hotmail.com, yahoo, @ gmail.com" ;
1348+ const excludedEmails = "spammer@cal.com, hotmail.com, yahoo.com, gmail.com" ;
13491349
13501350 const schema = getBookingResponsesSchema ( {
13511351 bookingFields : [
@@ -1382,7 +1382,7 @@ describe("excluded email/domain validation", () => {
13821382 } ) ;
13831383
13841384 test ( "should pass if the email is not present in excluded emails" , async ( ) => {
1385- const excludedEmails = "spammer@cal.com, hotmail.com, yahoo, @ gmail.com" ;
1385+ const excludedEmails = "spammer@cal.com, hotmail.com, yahoo.com, gmail.com" ;
13861386
13871387 const schema = getBookingResponsesSchema ( {
13881388 bookingFields : [
@@ -1417,11 +1417,130 @@ describe("excluded email/domain validation", () => {
14171417 email : "harry@workmail.com" ,
14181418 } ) ;
14191419 } ) ;
1420+
1421+ test ( "should not block email domains that contain excluded domain as substring" , async ( ) => {
1422+ // This tests the fix for the bug where `includes` was used instead of `endsWith`
1423+ // e.g., blocking "test.co" should not block "test.com"
1424+ const excludedEmails = "test.co" ;
1425+
1426+ const schema = getBookingResponsesSchema ( {
1427+ bookingFields : [
1428+ {
1429+ name : "name" ,
1430+ type : "name" ,
1431+ required : true ,
1432+ } ,
1433+ {
1434+ name : "email" ,
1435+ type : "email" ,
1436+ required : true ,
1437+ excludeEmails : excludedEmails ,
1438+ } ,
1439+ ] as z . infer < typeof eventTypeBookingFields > & z . BRAND < "HAS_SYSTEM_FIELDS" > ,
1440+ view : "ALL_VIEWS" ,
1441+ } ) ;
1442+
1443+ // test.com should NOT be blocked when test.co is excluded
1444+ const parsedResponses = await schema . safeParseAsync ( {
1445+ name : "test" ,
1446+ email : "user@test.com" ,
1447+ } ) ;
1448+
1449+ expect ( parsedResponses . success ) . toBe ( true ) ;
1450+
1451+ if ( ! parsedResponses . success ) {
1452+ throw new Error ( "Should not reach here" ) ;
1453+ }
1454+
1455+ expect ( parsedResponses . data ) . toEqual ( {
1456+ name : "test" ,
1457+ email : "user@test.com" ,
1458+ } ) ;
1459+ } ) ;
1460+
1461+ test ( "should block exact domain match when using endsWith" , async ( ) => {
1462+ const excludedEmails = "test.co" ;
1463+
1464+ const schema = getBookingResponsesSchema ( {
1465+ bookingFields : [
1466+ {
1467+ name : "name" ,
1468+ type : "name" ,
1469+ required : true ,
1470+ } ,
1471+ {
1472+ name : "email" ,
1473+ type : "email" ,
1474+ required : true ,
1475+ excludeEmails : excludedEmails ,
1476+ } ,
1477+ ] as z . infer < typeof eventTypeBookingFields > & z . BRAND < "HAS_SYSTEM_FIELDS" > ,
1478+ view : "ALL_VIEWS" ,
1479+ } ) ;
1480+
1481+ // test.co should be blocked
1482+ const parsedResponses = await schema . safeParseAsync ( {
1483+ name : "test" ,
1484+ email : "user@test.co" ,
1485+ } ) ;
1486+
1487+ expect ( parsedResponses . success ) . toBe ( false ) ;
1488+
1489+ if ( parsedResponses . success ) {
1490+ throw new Error ( "Should not reach here" ) ;
1491+ }
1492+
1493+ expect ( parsedResponses . error . issues [ 0 ] ) . toEqual (
1494+ expect . objectContaining ( {
1495+ code : "custom" ,
1496+ message : `{email}${ CUSTOM_EMAIL_EXCLUDED_ERROR_MSG } ` ,
1497+ } )
1498+ ) ;
1499+ } ) ;
1500+
1501+ test ( "should not block emails where excluded domain appears in local part" , async ( ) => {
1502+ // Ensures that the @ anchor prevents matching domains in the local part of email
1503+ const excludedEmails = "blocked.com" ;
1504+
1505+ const schema = getBookingResponsesSchema ( {
1506+ bookingFields : [
1507+ {
1508+ name : "name" ,
1509+ type : "name" ,
1510+ required : true ,
1511+ } ,
1512+ {
1513+ name : "email" ,
1514+ type : "email" ,
1515+ required : true ,
1516+ excludeEmails : excludedEmails ,
1517+ } ,
1518+ ] as z . infer < typeof eventTypeBookingFields > & z . BRAND < "HAS_SYSTEM_FIELDS" > ,
1519+ view : "ALL_VIEWS" ,
1520+ } ) ;
1521+
1522+ // Email with blocked.com in local part should not be blocked
1523+ const parsedResponses = await schema . safeParseAsync ( {
1524+ name : "test" ,
1525+ email : "blocked.com@allowed.com" ,
1526+ } ) ;
1527+
1528+ expect ( parsedResponses . success ) . toBe ( true ) ;
1529+
1530+ if ( ! parsedResponses . success ) {
1531+ throw new Error ( "Should not reach here" ) ;
1532+ }
1533+
1534+ expect ( parsedResponses . data ) . toEqual ( {
1535+ name : "test" ,
1536+ email : "blocked.com@allowed.com" ,
1537+ } ) ;
1538+ } ) ;
14201539} ) ;
14211540
14221541describe ( "require email/domain validation" , ( ) => {
14231542 test ( "should fail if the required email/domain is not present" , async ( ) => {
1424- const requiredEmails = "gmail.com, user@hotmail.com, @ yahoo.com" ;
1543+ const requiredEmails = "gmail.com, user@hotmail.com, yahoo.com" ;
14251544 const schema = getBookingResponsesSchema ( {
14261545 bookingFields : [
14271546 {
@@ -1459,7 +1578,7 @@ describe("require email/domain validation", () => {
14591578 } ) ;
14601579
14611580 test ( "should pass if the required email/domain is present" , async ( ) => {
1462- const requiredEmails = "gmail.com, user@hotmail.com, @ yahoo.com" ;
1581+ const requiredEmails = "gmail.com, user@hotmail.com, yahoo.com" ;
14631582
14641583 const schema = getBookingResponsesSchema ( {
14651584 bookingFields : [
@@ -1494,4 +1613,125 @@ describe("require email/domain validation", () => {
14941613 email : "test@gmail.com" ,
14951614 } ) ;
14961615 } ) ;
1616+
1617+ test ( "should not match email domains that contain required domain as substring" , async ( ) => {
1618+ // This tests the fix for the bug where `includes` was used instead of `endsWith`
1619+ // e.g., requiring "test.co" should not match "test.com"
1620+ const requiredEmails = "test.co" ;
1621+
1622+ const schema = getBookingResponsesSchema ( {
1623+ bookingFields : [
1624+ {
1625+ name : "name" ,
1626+ type : "name" ,
1627+ required : true ,
1628+ } ,
1629+ {
1630+ name : "email" ,
1631+ type : "email" ,
1632+ required : true ,
1633+ requireEmails : requiredEmails ,
1634+ } ,
1635+ ] as z . infer < typeof eventTypeBookingFields > & z . BRAND < "HAS_SYSTEM_FIELDS" > ,
1636+ view : "ALL_VIEWS" ,
1637+ } ) ;
1638+
1639+ // test.com should NOT match when test.co is required
1640+ const parsedResponses = await schema . safeParseAsync ( {
1641+ name : "test" ,
1642+ email : "user@test.com" ,
1643+ } ) ;
1644+
1645+ expect ( parsedResponses . success ) . toBe ( false ) ;
1646+
1647+ if ( parsedResponses . success ) {
1648+ throw new Error ( "Should not reach here" ) ;
1649+ }
1650+
1651+ expect ( parsedResponses . error . issues [ 0 ] ) . toEqual (
1652+ expect . objectContaining ( {
1653+ code : "custom" ,
1654+ message : `{email}${ CUSTOM_EMAIL_REQUIRED_ERROR_MSG } ` ,
1655+ } )
1656+ ) ;
1657+ } ) ;
1658+
1659+ test ( "should match exact domain when using endsWith" , async ( ) => {
1660+ const requiredEmails = "test.co" ;
1661+
1662+ const schema = getBookingResponsesSchema ( {
1663+ bookingFields : [
1664+ {
1665+ name : "name" ,
1666+ type : "name" ,
1667+ required : true ,
1668+ } ,
1669+ {
1670+ name : "email" ,
1671+ type : "email" ,
1672+ required : true ,
1673+ requireEmails : requiredEmails ,
1674+ } ,
1675+ ] as z . infer < typeof eventTypeBookingFields > & z . BRAND < "HAS_SYSTEM_FIELDS" > ,
1676+ view : "ALL_VIEWS" ,
1677+ } ) ;
1678+
1679+ // test.co should match
1680+ const parsedResponses = await schema . safeParseAsync ( {
1681+ name : "test" ,
1682+ email : "user@test.co" ,
1683+ } ) ;
1684+
1685+ expect ( parsedResponses . success ) . toBe ( true ) ;
1686+
1687+ if ( ! parsedResponses . success ) {
1688+ throw new Error ( "Should not reach here" ) ;
1689+ }
1690+
1691+ expect ( parsedResponses . data ) . toEqual ( {
1692+ name : "test" ,
1693+ email : "user@test.co" ,
1694+ } ) ;
1695+ } ) ;
1696+
1697+ test ( "should not match emails where required domain appears in local part" , async ( ) => {
1698+ // Ensures that the @ anchor prevents matching domains in the local part of email
1699+ const requiredEmails = "required.com" ;
1700+
1701+ const schema = getBookingResponsesSchema ( {
1702+ bookingFields : [
1703+ {
1704+ name : "name" ,
1705+ type : "name" ,
1706+ required : true ,
1707+ } ,
1708+ {
1709+ name : "email" ,
1710+ type : "email" ,
1711+ required : true ,
1712+ requireEmails : requiredEmails ,
1713+ } ,
1714+ ] as z . infer < typeof eventTypeBookingFields > & z . BRAND < "HAS_SYSTEM_FIELDS" > ,
1715+ view : "ALL_VIEWS" ,
1716+ } ) ;
1717+
1718+ // Email with required.com in local part should not match
1719+ const parsedResponses = await schema . safeParseAsync ( {
1720+ name : "test" ,
1721+ email : "required.com@other.com" ,
1722+ } ) ;
1723+
1724+ expect ( parsedResponses . success ) . toBe ( false ) ;
1725+
1726+ if ( parsedResponses . success ) {
1727+ throw new Error ( "Should not reach here" ) ;
1728+ }
1729+
1730+ expect ( parsedResponses . error . issues [ 0 ] ) . toEqual (
1731+ expect . objectContaining ( {
1732+ code : "custom" ,
1733+ message : `{email}${ CUSTOM_EMAIL_REQUIRED_ERROR_MSG } ` ,
1734+ } )
1735+ ) ;
1736+ } ) ;
14971737} ) ;
0 commit comments