Files
sharedinbox/test/unit/fake_imap.dart
Thomas SharedInboxandClaude Sonnet 4.6 c649ee3414 fix(snooze): create Snoozed folder automatically on first use (#75)
Two bugs prevented snoozing in a brand-new IMAP/JMAP account:

- IMAP flush read `payload['mailboxPath']` which doesn't exist in snooze
  payloads (they use 'src'); selecting the wrong (null) mailbox caused the
  operation to fail.  Now uses `payload['mailboxPath'] ?? payload['src']`.

- JMAP flush had no path to create the Snoozed mailbox when the folder
  didn't already exist on the server.  Flush now calls `Mailbox/set` to
  create it whenever `dest == 'Snoozed'` (the sentinel used when the folder
  was absent at enqueue time), then substitutes the real JMAP mailbox ID.

Tests added for both code paths using a spy IMAP client and a mock JMAP
HTTP client respectively.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 17:35:36 +02:00

86 lines
2.3 KiB
Dart

import 'package:enough_mail/enough_mail.dart' as imap;
// ignore: implementation_imports
import 'package:enough_mail/src/private/util/client_base.dart'
show ConnectionInfo;
/// Minimal fake IMAP client used by connection_test_service_test.dart.
/// Only overrides what is strictly needed to avoid real network calls.
class FakeImapClient extends imap.ImapClient {
FakeImapClient() : super();
@override
final imap.ImapServerInfo serverInfo = imap.ImapServerInfo(
const ConnectionInfo('fake.host', 993, isSecure: true),
)..capabilities = [];
@override
Future<dynamic> logout() async {}
}
/// Spy IMAP client that records snooze-related operations and succeeds silently.
class SnoozeSpyImapClient extends FakeImapClient {
String? selectedMailbox;
String? createdMailbox;
String? movedToMailbox;
imap.Mailbox _fakeMailbox(String path) => imap.Mailbox(
encodedName: path,
encodedPath: path,
pathSeparator: '/',
flags: [],
);
@override
Future<imap.Mailbox> selectMailboxByPath(
String path, {
bool enableCondStore = false,
imap.QResyncParameters? qresync,
}) async {
selectedMailbox = path;
return _fakeMailbox(path);
}
@override
Future<imap.Mailbox> createMailbox(String path) async {
createdMailbox = path;
return _fakeMailbox(path);
}
@override
Future<imap.StoreImapResult> uidStore(
imap.MessageSequence sequence,
List<String> flags, {
imap.StoreAction? action,
bool? silent,
int? unchangedSinceModSequence,
}) async =>
imap.StoreImapResult();
@override
Future<imap.GenericImapResult> uidMove(
imap.MessageSequence sequence, {
imap.Mailbox? targetMailbox,
String? targetMailboxPath,
}) async {
movedToMailbox = targetMailboxPath;
return imap.GenericImapResult();
}
@override
Future<imap.FetchImapResult> uidFetchMessages(
imap.MessageSequence sequence,
String? fetchContentDefinition, {
int? changedSinceModSequence,
Duration? responseTimeout,
}) async =>
const imap.FetchImapResult([], null);
}
/// Minimal fake SMTP client; only `quit` is exercised by ConnectionTestService.
class FakeSmtpClient extends imap.SmtpClient {
FakeSmtpClient() : super('fake.host');
@override
Future<imap.SmtpResponse> quit() async => imap.SmtpResponse(const []);
}