test: verify sync errors always appear in the sync log
Add _CapturingSyncLogRepository and two tests (IMAP + JMAP) that assert a failed sync cycle produces an error entry in the sync log. Also replace .ignore() in the catch blocks with a proper try-catch so the sync log write is genuinely attempted and any secondary failure is logged to stdout rather than silently dropped. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
fca9e1aecf
commit
2e869194e9
@@ -140,21 +140,23 @@ class _AccountSync implements _SyncLoop {
|
||||
await _idle();
|
||||
_backoffSeconds = 5;
|
||||
} catch (e, st) {
|
||||
_syncLog
|
||||
.log(
|
||||
accountId: account.id,
|
||||
success: false,
|
||||
errorMessage: e.toString(),
|
||||
protocol: 'imap',
|
||||
emailsFetched: 0,
|
||||
emailsSkipped: 0,
|
||||
mailboxesSynced: 0,
|
||||
pendingFlushed: 0,
|
||||
bytesTransferred: 0,
|
||||
startedAt: startedAt,
|
||||
finishedAt: DateTime.now(),
|
||||
)
|
||||
.ignore();
|
||||
try {
|
||||
await _syncLog.log(
|
||||
accountId: account.id,
|
||||
success: false,
|
||||
errorMessage: e.toString(),
|
||||
protocol: 'imap',
|
||||
emailsFetched: 0,
|
||||
emailsSkipped: 0,
|
||||
mailboxesSynced: 0,
|
||||
pendingFlushed: 0,
|
||||
bytesTransferred: 0,
|
||||
startedAt: startedAt,
|
||||
finishedAt: DateTime.now(),
|
||||
);
|
||||
} catch (logErr) {
|
||||
log('Failed to write IMAP sync log entry: $logErr');
|
||||
}
|
||||
log(
|
||||
'Sync failed for ${account.email}, retrying in ${_backoffSeconds}s',
|
||||
error: e,
|
||||
@@ -286,21 +288,23 @@ class _JmapAccountSync implements _SyncLoop {
|
||||
_backoffSeconds = 5;
|
||||
await _wait();
|
||||
} catch (e, st) {
|
||||
_syncLog
|
||||
.log(
|
||||
accountId: account.id,
|
||||
success: false,
|
||||
errorMessage: e.toString(),
|
||||
protocol: 'jmap',
|
||||
emailsFetched: 0,
|
||||
emailsSkipped: 0,
|
||||
mailboxesSynced: 0,
|
||||
pendingFlushed: 0,
|
||||
bytesTransferred: 0,
|
||||
startedAt: startedAt,
|
||||
finishedAt: DateTime.now(),
|
||||
)
|
||||
.ignore();
|
||||
try {
|
||||
await _syncLog.log(
|
||||
accountId: account.id,
|
||||
success: false,
|
||||
errorMessage: e.toString(),
|
||||
protocol: 'jmap',
|
||||
emailsFetched: 0,
|
||||
emailsSkipped: 0,
|
||||
mailboxesSynced: 0,
|
||||
pendingFlushed: 0,
|
||||
bytesTransferred: 0,
|
||||
startedAt: startedAt,
|
||||
finishedAt: DateTime.now(),
|
||||
);
|
||||
} catch (logErr) {
|
||||
log('Failed to write JMAP sync log entry: $logErr');
|
||||
}
|
||||
log(
|
||||
'JMAP sync failed for ${account.email}, retrying in ${_backoffSeconds}s',
|
||||
error: e,
|
||||
|
||||
@@ -9,6 +9,7 @@ import 'package:sharedinbox/core/models/mailbox.dart';
|
||||
import 'package:sharedinbox/core/repositories/account_repository.dart';
|
||||
import 'package:sharedinbox/core/repositories/email_repository.dart';
|
||||
import 'package:sharedinbox/core/repositories/mailbox_repository.dart';
|
||||
import 'package:sharedinbox/core/repositories/sync_log_repository.dart';
|
||||
import 'package:sharedinbox/core/sync/account_sync_manager.dart';
|
||||
|
||||
// ── Fakes ─────────────────────────────────────────────────────────────────────
|
||||
@@ -184,6 +185,45 @@ class FailingJmapEmailRepository extends FakeEmailRepository {
|
||||
}
|
||||
}
|
||||
|
||||
class _CapturingSyncLogRepository implements SyncLogRepository {
|
||||
final List<SyncLogEntry> entries = [];
|
||||
|
||||
@override
|
||||
Future<void> log({
|
||||
required String accountId,
|
||||
required bool success,
|
||||
String? errorMessage,
|
||||
required String protocol,
|
||||
required int emailsFetched,
|
||||
required int emailsSkipped,
|
||||
required int mailboxesSynced,
|
||||
required int pendingFlushed,
|
||||
required int bytesTransferred,
|
||||
required DateTime startedAt,
|
||||
required DateTime finishedAt,
|
||||
}) async {
|
||||
entries.add(
|
||||
SyncLogEntry(
|
||||
id: entries.length,
|
||||
result: success ? 'ok' : 'error',
|
||||
errorMessage: errorMessage,
|
||||
protocol: protocol,
|
||||
emailsFetched: emailsFetched,
|
||||
emailsSkipped: emailsSkipped,
|
||||
mailboxesSynced: mailboxesSynced,
|
||||
pendingFlushed: pendingFlushed,
|
||||
bytesTransferred: bytesTransferred,
|
||||
startedAt: startedAt,
|
||||
finishedAt: finishedAt,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<List<SyncLogEntry>> observeSyncLogs(String accountId) =>
|
||||
Stream.value(List.of(entries));
|
||||
}
|
||||
|
||||
// ── Tests ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
void main() {
|
||||
@@ -349,5 +389,57 @@ void main() {
|
||||
async.flushMicrotasks();
|
||||
});
|
||||
});
|
||||
|
||||
group('sync errors are visible in the sync log', () {
|
||||
test('IMAP sync failure writes an error entry to the sync log', () {
|
||||
fakeAsync((async) {
|
||||
final accounts = FakeAccountRepository();
|
||||
final syncLog = _CapturingSyncLogRepository();
|
||||
final mgr = AccountSyncManager(
|
||||
accounts,
|
||||
FailingMailboxRepository(),
|
||||
FakeEmailRepository(),
|
||||
syncLog: syncLog,
|
||||
);
|
||||
mgr.start();
|
||||
accounts.push([_account]);
|
||||
async.flushMicrotasks();
|
||||
|
||||
expect(syncLog.entries, hasLength(1));
|
||||
expect(syncLog.entries.first.isOk, isFalse);
|
||||
expect(syncLog.entries.first.errorMessage, isNotEmpty);
|
||||
expect(syncLog.entries.first.protocol, 'imap');
|
||||
|
||||
mgr.dispose();
|
||||
async.elapse(const Duration(seconds: 10));
|
||||
async.flushMicrotasks();
|
||||
});
|
||||
});
|
||||
|
||||
test('JMAP sync failure writes an error entry to the sync log', () {
|
||||
fakeAsync((async) {
|
||||
final accounts = FakeAccountRepository();
|
||||
final syncLog = _CapturingSyncLogRepository();
|
||||
final mgr = AccountSyncManager(
|
||||
accounts,
|
||||
FakeMailboxRepositoryWithInbox(),
|
||||
FailingJmapEmailRepository(),
|
||||
syncLog: syncLog,
|
||||
);
|
||||
mgr.start();
|
||||
accounts.push([_jmapAccount]);
|
||||
async.flushMicrotasks();
|
||||
|
||||
expect(syncLog.entries, hasLength(1));
|
||||
expect(syncLog.entries.first.isOk, isFalse);
|
||||
expect(syncLog.entries.first.errorMessage, isNotEmpty);
|
||||
expect(syncLog.entries.first.protocol, 'jmap');
|
||||
|
||||
mgr.dispose();
|
||||
async.elapse(const Duration(seconds: 10));
|
||||
async.flushMicrotasks();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user