Несколько ящиков у одного логина, общие ящики, кто ответил, вход ящик*логин
continuous-integration/drone/pr Build is passing
continuous-integration/drone/pr Build is passing
- V7: users.is_shared, mailbox_access (MEMBER от админа/Jambotron, ATTACHED по паролю ящика), messages.answered_by
- /api/mailboxes: мои ящики с непрочитанными, сессия другого ящика (JWT с claim by), подключить по паролю
(LoginGuard), убрать из меню
- общий ящик: без своего входа (веб/SMTP/IMAP), участники открывают; «прочитано» общее; ответ из веба → answered_by
- SMTP/IMAP: ящик*логин + пароль логина (как master user в Dovecot)
- открытый чужой ящик (общий, подключённый, OPEN админа): профиль и пароль не меняются (403)
- админ: создать shared, участники ящика (PUT /{id}/members); служебное API: PUT /shared/{address};
выключение аккаунта убирает и из общих ящиков
- UI: MAILBOXES в левом меню (переключение, ⊕ подключить, × убрать), shared/участники в настройках,
«Answered by» у письма
- 88 тестов; живой прогон на Postgres 17 (апгрейд V6→V7 на данных), скриншоты UI
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -35,8 +35,9 @@ class MailboxAdminControllerTest {
|
||||
private final MailAdmins admins = new MailAdmins("", "", domains);
|
||||
private final com.mailserver.util.JwtUtils jwt = mock(com.mailserver.util.JwtUtils.class);
|
||||
private final com.mailserver.service.MailLists lists = mock(com.mailserver.service.MailLists.class);
|
||||
private final com.mailserver.service.MailDirectory directory = new com.mailserver.service.MailDirectory(users, lists, domains);
|
||||
private final MailboxAdminController controller = new MailboxAdminController(users, mail, domains, admins, jwt, directory, lists);
|
||||
private final com.mailserver.service.MailboxAccess access = mock(com.mailserver.service.MailboxAccess.class);
|
||||
private final com.mailserver.service.MailDirectory directory = new com.mailserver.service.MailDirectory(users, lists, domains, access);
|
||||
private final MailboxAdminController controller = new MailboxAdminController(users, mail, domains, admins, jwt, directory, lists, access);
|
||||
|
||||
private final User admin = user(1L, "admin@jambocheck.com");
|
||||
private final User alice = user(2L, "alice@jambocheck.com");
|
||||
@@ -44,6 +45,7 @@ class MailboxAdminControllerTest {
|
||||
MailboxAdminControllerTest() {
|
||||
when(mail.usedBytes(anyLong())).thenReturn(Mono.just(0L));
|
||||
when(lists.exists(anyString())).thenReturn(Mono.just(false));
|
||||
when(access.members(anyLong())).thenReturn(Flux.empty());
|
||||
when(lists.exists("alerts@jambocheck.com")).thenReturn(Mono.just(true));
|
||||
when(users.findOptional(anyString())).thenReturn(Mono.empty());
|
||||
when(users.findOptional("alice@jambocheck.com")).thenReturn(Mono.just(alice));
|
||||
@@ -89,6 +91,26 @@ class MailboxAdminControllerTest {
|
||||
.isEqualTo(HttpStatus.FORBIDDEN);
|
||||
}
|
||||
|
||||
@Test
|
||||
void sharedMailboxHasNoPasswordShownMembersAreSet() {
|
||||
when(users.setShared(any(), any(Boolean.class))).thenAnswer(i -> {
|
||||
User u = i.getArgument(0);
|
||||
u.setShared(i.getArgument(1));
|
||||
return Mono.just(u);
|
||||
});
|
||||
var created = controller.create(as("admin@jambocheck.com"),
|
||||
Mono.just(new MailboxAdminController.CreateRequest("support@jambocheck.com", "Support", "", true))).block();
|
||||
assertThat(created.password()).isNull();
|
||||
assertThat(created.mailbox().shared()).isTrue();
|
||||
|
||||
when(access.setMembers(anyLong(), any())).thenReturn(Mono.empty());
|
||||
controller.members(as("admin@jambocheck.com"), 2L,
|
||||
Mono.just(new MailboxAdminController.MembersRequest(List.of("Alice@jambocheck.com")))).block();
|
||||
verify(access).setMembers(eq(2L), eq(List.of(2L)));
|
||||
assertThat(status(() -> controller.members(as("admin@jambocheck.com"), 2L,
|
||||
Mono.just(new MailboxAdminController.MembersRequest(List.of("ghost@jambocheck.com")))).block())).isEqualTo(HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
@Test
|
||||
void administratorSwitchesTrapOthersCanNot() {
|
||||
assertThat(controller.trap(as("admin@jambocheck.com"), 2L, Mono.just(new MailboxAdminController.TrapRequest(true))).block().trap()).isTrue();
|
||||
@@ -117,14 +139,14 @@ class MailboxAdminControllerTest {
|
||||
@Test
|
||||
void createsReservedNamesWithAGeneratedPasswordShownOnce() {
|
||||
var created = controller.create(as("admin@jambocheck.com"),
|
||||
Mono.just(new MailboxAdminController.CreateRequest(" No-Reply@JamboCheck.com ", "Jambotron", ""))).block();
|
||||
Mono.just(new MailboxAdminController.CreateRequest(" No-Reply@JamboCheck.com ", "Jambotron", "", null))).block();
|
||||
|
||||
assertThat(created.mailbox().email()).isEqualTo("no-reply@jambocheck.com");
|
||||
assertThat(created.password()).hasSizeGreaterThanOrEqualTo(20);
|
||||
verify(users).register(eq("no-reply@jambocheck.com"), eq(created.password()), eq("Jambotron"));
|
||||
|
||||
var chosen = controller.create(as("admin@jambocheck.com"),
|
||||
Mono.just(new MailboxAdminController.CreateRequest("monitor@jambocheck.com", null, "long-enough-1"))).block();
|
||||
Mono.just(new MailboxAdminController.CreateRequest("monitor@jambocheck.com", null, "long-enough-1", null))).block();
|
||||
assertThat(chosen.password()).isNull();
|
||||
}
|
||||
|
||||
@@ -153,7 +175,7 @@ class MailboxAdminControllerTest {
|
||||
|
||||
@Test
|
||||
void administratorOpensAnyMailboxOthersCanNot() {
|
||||
when(jwt.generateToken("alice@jambocheck.com")).thenReturn("alice-token");
|
||||
when(jwt.generateToken("alice@jambocheck.com", "admin@jambocheck.com")).thenReturn("alice-token");
|
||||
|
||||
var session = controller.open(as("admin@jambocheck.com"), 2L).block();
|
||||
assertThat(session.getEmail()).isEqualTo("alice@jambocheck.com");
|
||||
@@ -164,7 +186,7 @@ class MailboxAdminControllerTest {
|
||||
}
|
||||
|
||||
private MailboxAdminController.Created create(String me, String email, String password) {
|
||||
return controller.create(as(me), Mono.just(new MailboxAdminController.CreateRequest(email, null, password))).block();
|
||||
return controller.create(as(me), Mono.just(new MailboxAdminController.CreateRequest(email, null, password, null))).block();
|
||||
}
|
||||
|
||||
private static Mono<Authentication> as(String email) {
|
||||
|
||||
Reference in New Issue
Block a user