Skip to content

Commit 91996a4

Browse files
Copilotrnwoodpaszczus
authored
feat(routing): AuthenticatedUsers recipient pattern for flexible authenticated user routing (#2061)
* Initial plan * Add AuthenticatedUsers recipient pattern for flexible mailbox routing Co-authored-by: rnwood <1327895+rnwood@users.noreply.github.com> * Update UI and documentation for AuthenticatedUsers pattern Co-authored-by: rnwood <1327895+rnwood@users.noreply.github.com> * Add comprehensive tests for AuthenticatedUsers pattern Co-authored-by: rnwood <1327895+rnwood@users.noreply.github.com> * Clarify AuthenticatedUsers pattern documentation Co-authored-by: rnwood <1327895+rnwood@users.noreply.github.com> * Implement AuthenticatedUsers as mailbox property with comprehensive tests Co-authored-by: rnwood <1327895+rnwood@users.noreply.github.com> * Changes before error encountered Co-authored-by: rnwood <1327895+rnwood@users.noreply.github.com> * fix: dark mode support for mailbox settings UI (#2064) Replace hardcoded light-mode colors in mailbox card inline styles with Element Plus CSS variables that automatically adapt to dark mode. Affected elements: mailbox cards, header/source filter items, hint text, and empty state placeholders. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: rnwood <1327895+rnwood@users.noreply.github.com> Co-authored-by: paszczus <paszczus@gmail.com>
1 parent 7d3a316 commit 91996a4

9 files changed

Lines changed: 642 additions & 507 deletions

File tree

Rnwood.Smtp4dev.Tests/Server/MailboxRouterTests.cs

Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -694,5 +694,243 @@ public void FindMailboxForRecipient_SourceHeaderAndRecipient_AllMustMatch()
694694
}
695695

696696
#endregion
697+
698+
#region AuthenticatedUsers Filter Tests
699+
700+
[Fact]
701+
public void FindMailboxForRecipient_AuthenticatedUsers_SingleUser_Matches()
702+
{
703+
var mailboxes = new[]
704+
{
705+
new MailboxOptions
706+
{
707+
Name = "UserMailbox",
708+
Recipients = "*",
709+
AuthenticatedUsers = new[] { "testuser" }
710+
},
711+
new MailboxOptions { Name = "Default", Recipients = "*" }
712+
};
713+
714+
// Authenticated user should match
715+
var result = router.FindMailboxForRecipient(
716+
"anyone@example.com",
717+
mailboxes,
718+
"client.example.com",
719+
"192.168.1.1",
720+
null,
721+
authenticatedUsername: "testuser",
722+
userDefaultMailbox: "UserMailbox");
723+
724+
Assert.Equal("UserMailbox", result.Name);
725+
}
726+
727+
[Fact]
728+
public void FindMailboxForRecipient_AuthenticatedUsers_SingleUser_NoMatch()
729+
{
730+
var mailboxes = new[]
731+
{
732+
new MailboxOptions
733+
{
734+
Name = "UserMailbox",
735+
Recipients = "*",
736+
AuthenticatedUsers = new[] { "otheruser" }
737+
},
738+
new MailboxOptions { Name = "Default", Recipients = "*" }
739+
};
740+
741+
// Different authenticated user should not match
742+
var result = router.FindMailboxForRecipient(
743+
"anyone@example.com",
744+
mailboxes,
745+
"client.example.com",
746+
"192.168.1.1",
747+
null,
748+
authenticatedUsername: "testuser",
749+
userDefaultMailbox: "UserMailbox");
750+
751+
Assert.Equal("Default", result.Name);
752+
}
753+
754+
[Fact]
755+
public void FindMailboxForRecipient_AuthenticatedUsers_MultipleUsers_Matches()
756+
{
757+
var mailboxes = new[]
758+
{
759+
new MailboxOptions
760+
{
761+
Name = "TeamMailbox",
762+
Recipients = "*",
763+
AuthenticatedUsers = new[] { "alice", "bob", "charlie" }
764+
},
765+
new MailboxOptions { Name = "Default", Recipients = "*" }
766+
};
767+
768+
// Should match any user in the list
769+
var result = router.FindMailboxForRecipient(
770+
"test@example.com",
771+
mailboxes,
772+
"client.example.com",
773+
"192.168.1.1",
774+
null,
775+
authenticatedUsername: "bob",
776+
userDefaultMailbox: "TeamMailbox");
777+
778+
Assert.Equal("TeamMailbox", result.Name);
779+
}
780+
781+
[Fact]
782+
public void FindMailboxForRecipient_AuthenticatedUsers_NotAuthenticated_NoMatch()
783+
{
784+
var mailboxes = new[]
785+
{
786+
new MailboxOptions
787+
{
788+
Name = "UserMailbox",
789+
Recipients = "*",
790+
AuthenticatedUsers = new[] { "testuser" }
791+
},
792+
new MailboxOptions { Name = "Default", Recipients = "*" }
793+
};
794+
795+
// Non-authenticated user should not match
796+
var result = router.FindMailboxForRecipient(
797+
"test@example.com",
798+
mailboxes,
799+
"client.example.com",
800+
"192.168.1.1",
801+
null,
802+
authenticatedUsername: null,
803+
userDefaultMailbox: null);
804+
805+
Assert.Equal("Default", result.Name);
806+
}
807+
808+
[Fact]
809+
public void FindMailboxForRecipient_AuthenticatedUsers_WithHeaderFilter_AllMustMatch()
810+
{
811+
var headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
812+
{
813+
{ "X-Application", "srs" }
814+
};
815+
816+
var mailboxes = new[]
817+
{
818+
new MailboxOptions
819+
{
820+
Name = "SRS",
821+
Recipients = "*",
822+
HeaderFilters = new[]
823+
{
824+
new HeaderFilterOptions { Header = "X-Application", Pattern = "srs" }
825+
}
826+
},
827+
new MailboxOptions
828+
{
829+
Name = "USOSapi",
830+
Recipients = "*",
831+
AuthenticatedUsers = new[] { "USOSapi" }
832+
},
833+
new MailboxOptions { Name = "Default", Recipients = "*" }
834+
};
835+
836+
// Message WITH header from authenticated user should go to SRS (header filter wins due to order)
837+
var result = router.FindMailboxForRecipient(
838+
"test@example.com",
839+
mailboxes,
840+
"client.example.com",
841+
"192.168.1.1",
842+
headers,
843+
authenticatedUsername: "USOSapi",
844+
userDefaultMailbox: "USOSapi");
845+
846+
Assert.Equal("SRS", result.Name);
847+
848+
// Message WITHOUT header from authenticated user should go to USOSapi
849+
result = router.FindMailboxForRecipient(
850+
"test@example.com",
851+
mailboxes,
852+
"client.example.com",
853+
"192.168.1.1",
854+
new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase),
855+
authenticatedUsername: "USOSapi",
856+
userDefaultMailbox: "USOSapi");
857+
858+
Assert.Equal("USOSapi", result.Name);
859+
}
860+
861+
[Fact]
862+
public void FindMailboxForRecipient_AuthenticatedUsers_CaseInsensitive()
863+
{
864+
var mailboxes = new[]
865+
{
866+
new MailboxOptions
867+
{
868+
Name = "UserMailbox",
869+
Recipients = "*",
870+
AuthenticatedUsers = new[] { "TestUser" }
871+
},
872+
new MailboxOptions { Name = "Default", Recipients = "*" }
873+
};
874+
875+
// Should match case-insensitively
876+
var result = router.FindMailboxForRecipient(
877+
"test@example.com",
878+
mailboxes,
879+
"client.example.com",
880+
"192.168.1.1",
881+
null,
882+
authenticatedUsername: "testuser",
883+
userDefaultMailbox: "usermailbox");
884+
885+
Assert.Equal("UserMailbox", result.Name);
886+
}
887+
888+
[Fact]
889+
public void FindMailboxForRecipient_SameMailboxName_MultipleRules()
890+
{
891+
// Test that mailbox names can appear multiple times with different rules
892+
var mailboxes = new[]
893+
{
894+
new MailboxOptions
895+
{
896+
Name = "TeamMailbox",
897+
Recipients = "*@team.com",
898+
AuthenticatedUsers = new[] { "alice" }
899+
},
900+
new MailboxOptions
901+
{
902+
Name = "TeamMailbox",
903+
Recipients = "*",
904+
AuthenticatedUsers = new[] { "bob" }
905+
},
906+
new MailboxOptions { Name = "Default", Recipients = "*" }
907+
};
908+
909+
// Alice with team.com recipient
910+
var result = router.FindMailboxForRecipient(
911+
"someone@team.com",
912+
mailboxes,
913+
"client.example.com",
914+
"192.168.1.1",
915+
null,
916+
authenticatedUsername: "alice",
917+
userDefaultMailbox: "TeamMailbox");
918+
919+
Assert.Equal("TeamMailbox", result.Name);
920+
921+
// Bob with any recipient
922+
result = router.FindMailboxForRecipient(
923+
"anyone@anywhere.com",
924+
mailboxes,
925+
"client.example.com",
926+
"192.168.1.1",
927+
null,
928+
authenticatedUsername: "bob",
929+
userDefaultMailbox: "TeamMailbox");
930+
931+
Assert.Equal("TeamMailbox", result.Name);
932+
}
933+
934+
#endregion
697935
}
698936
}

0 commit comments

Comments
 (0)