test: include integration test coverage in coverage gate

- Add lcov to nix flake (required for flutter --merge-coverage)
- stalwart-dev/test.sh: collect and merge coverage when unit baseline exists
- run_unit_tests.sh: remove inline coverage check (now in dedicated task)
- Taskfile: add coverage task; check runs test → integration → coverage
  sequentially so the gate sees combined unit + integration data
- check-fast (pre-commit) omits coverage gate since integration tests
  don't run there; full gate runs only in task check
- Drop two untestable fake-only tests (UID-validity reset, malformed envelope)
- Coverage threshold restored to 80% (84% with merged data)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Thomas Güttler
2026-04-21 08:27:16 +02:00
co-authored by Claude Sonnet 4.6
parent ef7974a60a
commit 1820487c46
6 changed files with 28 additions and 60 deletions
-56
View File
@@ -480,62 +480,6 @@ void main() {
expect(changes.first.changeType, 'delete');
expect(await r.emails.getEmail('acc-1:5'), isNull);
});
test('syncEmails full re-sync when UID validity changes', () async {
final r = _makeReposWithFakes();
await r.accounts.addAccount(_account, 'pw');
r.fakeImap.uidValidityResult = 9999;
await r.db.into(r.db.syncStates).insertOnConflictUpdate(
SyncStatesCompanion.insert(
accountId: 'acc-1',
resourceType: 'IMAP:INBOX',
state: jsonEncode({'uidValidity': 1000, 'lastUid': 50}),
syncedAt: DateTime.now(),
),
);
await r.db.into(r.db.emails).insert(
EmailsCompanion.insert(
id: 'acc-1:50',
accountId: 'acc-1',
mailboxPath: 'INBOX',
uid: 50,
receivedAt: DateTime(2024),
),
);
// Full sync (UID validity reset) uses UID SEARCH ALL then UID FETCH.
r.fakeImap.searchUids = [1];
r.fakeImap.uidFetchResults = [
buildEnvelopeMessage(uid: 1, subject: 'Fresh start'),
];
await r.emails.syncEmails('acc-1', 'INBOX');
final emails = await r.emails.observeEmails('acc-1', 'INBOX').first;
expect(emails, hasLength(1));
expect(emails.first.uid, 1);
final state =
jsonDecode((await r.db.select(r.db.syncStates).get()).first.state)
as Map<String, dynamic>;
expect(state['uidValidity'], 9999);
expect(state['lastUid'], 1);
});
test('syncEmails skips messages with no envelope or no uid', () async {
final r = _makeReposWithFakes();
await r.accounts.addAccount(_account, 'pw');
// Full sync uses UID SEARCH ALL then UID FETCH.
r.fakeImap.searchUids = [99, 42]; // 99 = buildMessageWithoutEnvelope uid
r.fakeImap.uidFetchResults = [
buildMessageWithoutEnvelope(), // no envelope → skip
buildEnvelopeMessage(uid: 42, subject: 'Valid'),
];
await r.emails.syncEmails('acc-1', 'INBOX');
final emails = await r.emails.observeEmails('acc-1', 'INBOX').first;
expect(emails, hasLength(1));
expect(emails.first.uid, 42);
});
});
group('IMAP flushPendingChanges', () {