Files
sharedinbox/test/unit/mailbox_repository_impl_test.dart
T

222 lines
6.9 KiB
Dart

import 'package:drift/drift.dart' show Value;
import 'package:enough_mail/enough_mail.dart' as imap;
import 'package:flutter_test/flutter_test.dart';
import 'package:sharedinbox/core/models/account.dart';
import 'package:sharedinbox/data/db/database.dart' hide Account;
import 'package:sharedinbox/data/repositories/account_repository_impl.dart';
import 'package:sharedinbox/data/repositories/mailbox_repository_impl.dart';
import 'account_repository_impl_test.dart' show MapSecureStorage;
import 'db_test_helper.dart';
import 'fake_imap.dart';
// ── Helpers ───────────────────────────────────────────────────────────────────
const _account = Account(
id: 'acc-1',
displayName: 'Alice',
email: 'alice@example.com',
imapHost: 'imap.example.com',
imapPort: 993,
imapSsl: true,
smtpHost: 'smtp.example.com',
smtpPort: 587,
smtpSsl: false,
);
Future<imap.ImapClient> _noImapConnect(Account a, String p) =>
Future.error(UnsupportedError('IMAP unavailable in unit tests'));
({
AppDatabase db,
AccountRepositoryImpl accounts,
MailboxRepositoryImpl mailboxes,
}) _makeRepos() {
final db = openTestDatabase();
final accounts = AccountRepositoryImpl(db, MapSecureStorage());
final mailboxes = MailboxRepositoryImpl(
db,
accounts,
imapConnect: _noImapConnect,
);
return (db: db, accounts: accounts, mailboxes: mailboxes);
}
({
AppDatabase db,
AccountRepositoryImpl accounts,
MailboxRepositoryImpl mailboxes,
FakeImapClient fakeImap,
}) _makeReposWithFake() {
final db = openTestDatabase();
final accounts = AccountRepositoryImpl(db, MapSecureStorage());
final fakeImap = FakeImapClient();
final mailboxes = MailboxRepositoryImpl(
db,
accounts,
imapConnect: (_, __) async => fakeImap,
);
return (db: db, accounts: accounts, mailboxes: mailboxes, fakeImap: fakeImap);
}
// ── Tests ─────────────────────────────────────────────────────────────────────
void main() {
setUpAll(configureSqliteForTests);
group('MailboxRepositoryImpl', () {
test('observeMailboxes emits empty list initially', () async {
final r = _makeRepos();
await r.accounts.addAccount(_account, 'pw');
final mailboxes = await r.mailboxes.observeMailboxes('acc-1').first;
expect(mailboxes, isEmpty);
});
test('observeMailboxes reflects inserted rows ordered by path', () async {
final r = _makeRepos();
await r.accounts.addAccount(_account, 'pw');
for (final (path, name) in [
('Sent', 'Sent'),
('INBOX', 'Inbox'),
('Drafts', 'Drafts'),
]) {
await r.db.into(r.db.mailboxes).insert(
MailboxesCompanion.insert(
id: 'acc-1:$path',
accountId: 'acc-1',
path: path,
name: name,
),
);
}
final mailboxes = await r.mailboxes.observeMailboxes('acc-1').first;
expect(mailboxes.map((m) => m.path).toList(), ['Drafts', 'INBOX', 'Sent']);
});
test('observeMailboxes only returns mailboxes for the given account',
() async {
final r = _makeRepos();
await r.accounts.addAccount(_account, 'pw');
const other = Account(
id: 'acc-2',
displayName: 'Bob',
email: 'bob@example.com',
imapHost: 'imap.example.com',
imapPort: 993,
imapSsl: true,
smtpHost: 'smtp.example.com',
smtpPort: 587,
smtpSsl: false,
);
await r.accounts.addAccount(other, 'pw2');
await r.db.into(r.db.mailboxes).insert(
MailboxesCompanion.insert(
id: 'acc-1:INBOX',
accountId: 'acc-1',
path: 'INBOX',
name: 'Inbox',
),
);
await r.db.into(r.db.mailboxes).insert(
MailboxesCompanion.insert(
id: 'acc-2:INBOX',
accountId: 'acc-2',
path: 'INBOX',
name: 'Inbox',
),
);
final mailboxes = await r.mailboxes.observeMailboxes('acc-1').first;
expect(mailboxes, hasLength(1));
expect(mailboxes.first.id, 'acc-1:INBOX');
});
test('observeMailboxes maps unread/total counts', () async {
final r = _makeRepos();
await r.accounts.addAccount(_account, 'pw');
await r.db.into(r.db.mailboxes).insert(
MailboxesCompanion.insert(
id: 'acc-1:INBOX',
accountId: 'acc-1',
path: 'INBOX',
name: 'Inbox',
unreadCount: const Value(5),
totalCount: const Value(42),
),
);
final mailboxes = await r.mailboxes.observeMailboxes('acc-1').first;
expect(mailboxes.first.unreadCount, 5);
expect(mailboxes.first.totalCount, 42);
});
test('syncMailboxes propagates IMAP error', () async {
final r = _makeRepos();
await r.accounts.addAccount(_account, 'pw');
expect(
() => r.mailboxes.syncMailboxes('acc-1'),
throwsA(isA<UnsupportedError>()),
);
});
test('syncMailboxes stores mailboxes from IMAP in DB', () async {
final r = _makeReposWithFake();
await r.accounts.addAccount(_account, 'pw');
r.fakeImap.listMailboxesResult = [
imap.Mailbox(
encodedName: 'INBOX',
encodedPath: 'INBOX',
flags: [],
pathSeparator: '/',
),
imap.Mailbox(
encodedName: 'Sent',
encodedPath: 'Sent',
flags: [],
pathSeparator: '/',
),
];
await r.mailboxes.syncMailboxes('acc-1');
final mailboxes =
await r.mailboxes.observeMailboxes('acc-1').first;
expect(mailboxes, hasLength(2));
expect(mailboxes.map((m) => m.path).toSet(), {'INBOX', 'Sent'});
// statusMailbox fake returns 3 unread / 10 total for all mailboxes
expect(mailboxes.first.unreadCount, 3);
expect(mailboxes.first.totalCount, 10);
expect(r.fakeImap.logoutCalled, isTrue);
});
test('syncMailboxes still stores mailbox when statusMailbox throws', () async {
final r = _makeReposWithFake();
await r.accounts.addAccount(_account, 'pw');
r.fakeImap.throwOnStatus = true;
r.fakeImap.listMailboxesResult = [
imap.Mailbox(
encodedName: 'INBOX',
encodedPath: 'INBOX',
flags: [],
pathSeparator: '/',
),
];
await r.mailboxes.syncMailboxes('acc-1');
final mailboxes =
await r.mailboxes.observeMailboxes('acc-1').first;
// Mailbox is stored even though STATUS failed; counts default to 0.
expect(mailboxes, hasLength(1));
expect(mailboxes.first.unreadCount, 0);
expect(mailboxes.first.totalCount, 0);
});
});
}