mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-06-25 08:34:06 +00:00
Some checks are pending
Flutter analyze & test / flutter_analyze_and_test (push) Waiting to run
693 lines
24 KiB
Dart
693 lines
24 KiB
Dart
import 'dart:io';
|
||
|
||
import 'package:drift/drift.dart' hide isNotNull, isNull;
|
||
import 'package:drift/native.dart';
|
||
import 'package:flutter_test/flutter_test.dart';
|
||
import 'package:twonly/globals.dart';
|
||
import 'package:twonly/locator.dart';
|
||
import 'package:twonly/src/database/daos/key_verification.dao.dart';
|
||
import 'package:twonly/src/database/tables/contacts.table.dart';
|
||
import 'package:twonly/src/database/twonly.db.dart';
|
||
import 'package:twonly/src/model/json/userdata.model.dart';
|
||
import 'package:twonly/src/services/api.service.dart';
|
||
import 'package:twonly/src/services/user.service.dart';
|
||
|
||
void main() {
|
||
if (!Platform.isMacOS) {
|
||
return;
|
||
}
|
||
|
||
TestWidgetsFlutterBinding.ensureInitialized();
|
||
|
||
setUp(() async {
|
||
await locator.reset();
|
||
locator
|
||
..registerSingleton<TwonlyDB>(
|
||
TwonlyDB.forTesting(
|
||
DatabaseConnection(
|
||
NativeDatabase.memory(),
|
||
closeStreamsSynchronously: true,
|
||
),
|
||
),
|
||
)
|
||
..registerSingleton<UserService>(UserService())
|
||
..registerSingleton<ApiService>(ApiService());
|
||
|
||
// isUserDiscoveryEnabled defaults to false, so no Rust bridge calls happen
|
||
// in addKeyVerification / deleteKeyVerification.
|
||
userService.currentUser = UserData(
|
||
userId: 1,
|
||
username: 'me',
|
||
displayName: 'Me',
|
||
subscriptionPlan: 'Free',
|
||
currentSetupPage: null,
|
||
appVersion: 100,
|
||
);
|
||
userService.isUserCreated = true;
|
||
AppEnvironment.initTesting();
|
||
});
|
||
|
||
tearDown(() async {
|
||
await twonlyDB.close();
|
||
});
|
||
|
||
// ─── Helpers ────────────────────────────────────────────────────────────────
|
||
|
||
Future<void> insertContact(int userId, {String? username}) async {
|
||
await twonlyDB.contactsDao.insertContact(
|
||
ContactsCompanion.insert(
|
||
userId: Value(userId),
|
||
username: username ?? 'user$userId',
|
||
),
|
||
);
|
||
}
|
||
|
||
Future<void> addDirectVerification(
|
||
int contactId,
|
||
VerificationType type,
|
||
) async {
|
||
await twonlyDB.keyVerificationDao.addKeyVerification(contactId, type);
|
||
}
|
||
|
||
Future<void> addSharedVerification(
|
||
int contactId,
|
||
int sharedByContactId,
|
||
) async {
|
||
await twonlyDB.keyVerificationDao.addKeyVerification(
|
||
contactId,
|
||
VerificationType.contactSharedByVerified,
|
||
verifiedBy: sharedByContactId,
|
||
);
|
||
}
|
||
|
||
// ─── Verification Tokens ────────────────────────────────────────────────────
|
||
|
||
group('KeyVerificationDao – Verification Tokens', () {
|
||
test(
|
||
'insertVerificationToken stores token; getRecentVerificationTokens returns it',
|
||
() async {
|
||
final token = Uint8List.fromList([1, 2, 3, 4, 5, 6, 7, 8]);
|
||
await twonlyDB.keyVerificationDao.insertVerificationToken(token);
|
||
|
||
final tokens = await twonlyDB.keyVerificationDao
|
||
.getRecentVerificationTokens();
|
||
expect(tokens.length, 1);
|
||
expect(tokens.first.token, token);
|
||
},
|
||
);
|
||
|
||
test('multiple tokens are all returned when recent', () async {
|
||
final t1 = Uint8List.fromList(List.generate(16, (i) => i));
|
||
final t2 = Uint8List.fromList(List.generate(16, (i) => i + 16));
|
||
await twonlyDB.keyVerificationDao.insertVerificationToken(t1);
|
||
await twonlyDB.keyVerificationDao.insertVerificationToken(t2);
|
||
|
||
final tokens = await twonlyDB.keyVerificationDao
|
||
.getRecentVerificationTokens();
|
||
expect(tokens.length, 2);
|
||
});
|
||
});
|
||
|
||
// ─── Direct Verification ────────────────────────────────────────────────────
|
||
|
||
group('KeyVerificationDao – Direct Verification', () {
|
||
test(
|
||
'addKeyVerification stores an entry; getContactVerification returns it',
|
||
() async {
|
||
await insertContact(10);
|
||
await addDirectVerification(10, VerificationType.secretQrToken);
|
||
|
||
final verifications = await twonlyDB.keyVerificationDao
|
||
.getContactVerification(10);
|
||
expect(verifications.length, 1);
|
||
expect(verifications.first.type, VerificationType.secretQrToken);
|
||
expect(verifications.first.contactId, 10);
|
||
expect(verifications.first.verifiedBy, isNull);
|
||
},
|
||
);
|
||
|
||
test('isContactVerified returns true for secretQrToken', () async {
|
||
await insertContact(10);
|
||
await addDirectVerification(10, VerificationType.secretQrToken);
|
||
|
||
expect(await twonlyDB.keyVerificationDao.isContactVerified(10), true);
|
||
});
|
||
|
||
test('isContactVerified returns true for qrScanned', () async {
|
||
await insertContact(10);
|
||
await addDirectVerification(10, VerificationType.qrScanned);
|
||
|
||
expect(await twonlyDB.keyVerificationDao.isContactVerified(10), true);
|
||
});
|
||
|
||
test('isContactVerified returns true for link', () async {
|
||
await insertContact(10);
|
||
await addDirectVerification(10, VerificationType.link);
|
||
|
||
expect(await twonlyDB.keyVerificationDao.isContactVerified(10), true);
|
||
});
|
||
|
||
test(
|
||
'isContactVerified returns false when no verification exists',
|
||
() async {
|
||
await insertContact(10);
|
||
|
||
expect(await twonlyDB.keyVerificationDao.isContactVerified(10), false);
|
||
},
|
||
);
|
||
|
||
test('multiple direct verifications are stored independently', () async {
|
||
await insertContact(10);
|
||
await addDirectVerification(10, VerificationType.secretQrToken);
|
||
await addDirectVerification(10, VerificationType.link);
|
||
|
||
final verifications = await twonlyDB.keyVerificationDao
|
||
.getContactVerification(10);
|
||
expect(verifications.length, 2);
|
||
});
|
||
});
|
||
|
||
// ─── Shared / Transitive Verification (Blue Badge) ──────────────────────────
|
||
|
||
group('KeyVerificationDao – Transitive Trust (Blue Badge)', () {
|
||
// Scenario setup:
|
||
// alice (userId=2) – may or may not be directly verified
|
||
// bob (userId=3) – shared by alice (contactSharedByVerified, verifiedBy=2)
|
||
//
|
||
// Rule: bob's shared verification only "counts" if alice is herself verified.
|
||
|
||
test(
|
||
'isContactVerified is false when sharer (alice) is NOT verified',
|
||
() async {
|
||
await insertContact(2, username: 'alice');
|
||
await insertContact(3, username: 'bob');
|
||
|
||
// Bob shared by Alice, but Alice is not verified
|
||
await addSharedVerification(3, 2);
|
||
|
||
expect(await twonlyDB.keyVerificationDao.isContactVerified(3), false);
|
||
},
|
||
);
|
||
|
||
test(
|
||
'isContactVerified is true (blue badge) when sharer (alice) IS verified',
|
||
() async {
|
||
await insertContact(2, username: 'alice');
|
||
await insertContact(3, username: 'bob');
|
||
|
||
// Alice verified directly
|
||
await addDirectVerification(2, VerificationType.secretQrToken);
|
||
// Bob shared by verified Alice
|
||
await addSharedVerification(3, 2);
|
||
|
||
expect(await twonlyDB.keyVerificationDao.isContactVerified(3), true);
|
||
},
|
||
);
|
||
|
||
test(
|
||
'isContactVerified updates reactively when sharer becomes verified',
|
||
() async {
|
||
await insertContact(2, username: 'alice');
|
||
await insertContact(3, username: 'bob');
|
||
|
||
await addSharedVerification(3, 2);
|
||
// Bob not yet verified (alice not verified)
|
||
expect(await twonlyDB.keyVerificationDao.isContactVerified(3), false);
|
||
|
||
// Alice gets verified
|
||
await addDirectVerification(2, VerificationType.qrScanned);
|
||
// Now Bob is transitively verified
|
||
expect(await twonlyDB.keyVerificationDao.isContactVerified(3), true);
|
||
},
|
||
);
|
||
|
||
test(
|
||
'direct verification takes precedence regardless of sharer state',
|
||
() async {
|
||
await insertContact(2, username: 'alice');
|
||
await insertContact(3, username: 'bob');
|
||
|
||
// Bob has both a direct and a shared (unverified sharer) verification
|
||
await addDirectVerification(3, VerificationType.link);
|
||
await addSharedVerification(3, 2); // alice not verified
|
||
|
||
// Direct verification makes bob verified
|
||
expect(await twonlyDB.keyVerificationDao.isContactVerified(3), true);
|
||
},
|
||
);
|
||
});
|
||
|
||
// ─── watchContactVerification ───────────────────────────────────────────────
|
||
|
||
group('KeyVerificationDao – watchContactVerification', () {
|
||
test(
|
||
'emits the direct verification entry with no verifier contact',
|
||
() async {
|
||
await insertContact(10, username: 'alice');
|
||
await addDirectVerification(10, VerificationType.secretQrToken);
|
||
|
||
final entries = await twonlyDB.keyVerificationDao
|
||
.watchContactVerification(10)
|
||
.first;
|
||
|
||
expect(entries.length, 1);
|
||
final (kv, verifierContact) = entries.first;
|
||
expect(kv.type, VerificationType.secretQrToken);
|
||
expect(verifierContact, isNull);
|
||
},
|
||
);
|
||
|
||
test(
|
||
'filters out shared verification when sharer is NOT verified',
|
||
() async {
|
||
await insertContact(2, username: 'alice');
|
||
await insertContact(3, username: 'bob');
|
||
|
||
await addSharedVerification(3, 2); // alice not verified
|
||
|
||
final entries = await twonlyDB.keyVerificationDao
|
||
.watchContactVerification(3)
|
||
.first;
|
||
|
||
expect(entries, isEmpty);
|
||
},
|
||
);
|
||
|
||
test(
|
||
'returns shared verification entry with verifier contact when sharer IS verified',
|
||
() async {
|
||
await insertContact(2, username: 'alice');
|
||
await insertContact(3, username: 'bob');
|
||
|
||
await addDirectVerification(2, VerificationType.secretQrToken);
|
||
await addSharedVerification(3, 2);
|
||
|
||
final entries = await twonlyDB.keyVerificationDao
|
||
.watchContactVerification(3)
|
||
.first;
|
||
|
||
expect(entries.length, 1);
|
||
final (kv, verifierContact) = entries.first;
|
||
expect(kv.type, VerificationType.contactSharedByVerified);
|
||
expect(verifierContact, isNotNull);
|
||
expect(verifierContact!.username, 'alice');
|
||
},
|
||
);
|
||
|
||
test(
|
||
'emits mixed entries: direct (no filter) + shared (filtered by verifier state)',
|
||
() async {
|
||
await insertContact(2, username: 'alice');
|
||
await insertContact(3, username: 'charlie');
|
||
await insertContact(4, username: 'bob');
|
||
|
||
// Bob has a direct verification
|
||
await addDirectVerification(4, VerificationType.link);
|
||
// Bob is also shared by Alice (unverified) – should NOT appear
|
||
await addSharedVerification(4, 2);
|
||
// Bob is also shared by Charlie (verified) – SHOULD appear
|
||
await addDirectVerification(3, VerificationType.qrScanned);
|
||
await addSharedVerification(4, 3);
|
||
|
||
final entries = await twonlyDB.keyVerificationDao
|
||
.watchContactVerification(4)
|
||
.first;
|
||
|
||
// Expect: direct + shared-by-charlie (2 entries, shared-by-alice filtered)
|
||
expect(entries.length, 2);
|
||
final types = entries.map((e) => e.$1.type).toSet();
|
||
expect(types, contains(VerificationType.link));
|
||
expect(types, contains(VerificationType.contactSharedByVerified));
|
||
},
|
||
);
|
||
|
||
test('emits empty list for contact with no verifications', () async {
|
||
await insertContact(10);
|
||
|
||
final entries = await twonlyDB.keyVerificationDao
|
||
.watchContactVerification(10)
|
||
.first;
|
||
|
||
expect(entries, isEmpty);
|
||
});
|
||
});
|
||
|
||
// ─── getFirstVerificationTypeByContacts ─────────────────────────────────────
|
||
|
||
group('KeyVerificationDao – getFirstVerificationTypeByContacts', () {
|
||
test(
|
||
'returns a map with the earliest verification type per contact',
|
||
() async {
|
||
await insertContact(10);
|
||
await insertContact(20);
|
||
|
||
await addDirectVerification(10, VerificationType.secretQrToken);
|
||
await addDirectVerification(20, VerificationType.link);
|
||
|
||
final map = await twonlyDB.keyVerificationDao
|
||
.getFirstVerificationTypeByContacts();
|
||
|
||
expect(map[10], VerificationType.secretQrToken);
|
||
expect(map[20], VerificationType.link);
|
||
},
|
||
);
|
||
|
||
test('returns an empty map when no verifications exist', () async {
|
||
final map = await twonlyDB.keyVerificationDao
|
||
.getFirstVerificationTypeByContacts();
|
||
expect(map, isEmpty);
|
||
});
|
||
|
||
test(
|
||
'returns only the first verification type when multiple exist',
|
||
() async {
|
||
await insertContact(10);
|
||
|
||
// Insert two types for the same contact
|
||
await addDirectVerification(10, VerificationType.secretQrToken);
|
||
await addDirectVerification(10, VerificationType.link);
|
||
|
||
final map = await twonlyDB.keyVerificationDao
|
||
.getFirstVerificationTypeByContacts();
|
||
|
||
// Only the first-inserted (earliest createdAt) should be in the map
|
||
expect(map.length, 1);
|
||
expect(map.containsKey(10), true);
|
||
// The first inserted was secretQrToken
|
||
expect(map[10], VerificationType.secretQrToken);
|
||
},
|
||
);
|
||
});
|
||
|
||
// ─── deleteKeyVerification ───────────────────────────────────────────────────
|
||
|
||
group('KeyVerificationDao – deleteKeyVerification', () {
|
||
test('removes all verifications for a contact', () async {
|
||
await insertContact(10);
|
||
await addDirectVerification(10, VerificationType.secretQrToken);
|
||
await addDirectVerification(10, VerificationType.link);
|
||
|
||
expect(
|
||
await twonlyDB.keyVerificationDao.getContactVerification(10),
|
||
hasLength(2),
|
||
);
|
||
|
||
await twonlyDB.keyVerificationDao.deleteKeyVerification(10);
|
||
|
||
expect(
|
||
await twonlyDB.keyVerificationDao.getContactVerification(10),
|
||
isEmpty,
|
||
);
|
||
});
|
||
|
||
test(
|
||
'isContactVerified returns false after deleteKeyVerification',
|
||
() async {
|
||
await insertContact(10);
|
||
await addDirectVerification(10, VerificationType.secretQrToken);
|
||
expect(await twonlyDB.keyVerificationDao.isContactVerified(10), true);
|
||
|
||
await twonlyDB.keyVerificationDao.deleteKeyVerification(10);
|
||
expect(await twonlyDB.keyVerificationDao.isContactVerified(10), false);
|
||
},
|
||
);
|
||
|
||
test('deleting one contact does not affect other contacts', () async {
|
||
await insertContact(10);
|
||
await insertContact(20);
|
||
await addDirectVerification(10, VerificationType.secretQrToken);
|
||
await addDirectVerification(20, VerificationType.qrScanned);
|
||
|
||
await twonlyDB.keyVerificationDao.deleteKeyVerification(10);
|
||
|
||
expect(await twonlyDB.keyVerificationDao.isContactVerified(10), false);
|
||
expect(await twonlyDB.keyVerificationDao.isContactVerified(20), true);
|
||
});
|
||
|
||
test(
|
||
'deleting sharer invalidates transitive trust for shared contacts',
|
||
() async {
|
||
await insertContact(2, username: 'alice');
|
||
await insertContact(3, username: 'bob');
|
||
|
||
await addDirectVerification(2, VerificationType.secretQrToken);
|
||
await addSharedVerification(3, 2);
|
||
|
||
expect(await twonlyDB.keyVerificationDao.isContactVerified(3), true);
|
||
|
||
// Remove Alice's verification
|
||
await twonlyDB.keyVerificationDao.deleteKeyVerification(2);
|
||
|
||
// Bob's transitive trust should now be invalid
|
||
expect(await twonlyDB.keyVerificationDao.isContactVerified(3), false);
|
||
},
|
||
);
|
||
});
|
||
|
||
// ─── deleteKeyVerificationById ───────────────────────────────────────────────
|
||
|
||
group('KeyVerificationDao – deleteKeyVerificationById', () {
|
||
test('removes only the specified verification entry', () async {
|
||
await insertContact(10);
|
||
await addDirectVerification(10, VerificationType.secretQrToken);
|
||
await addDirectVerification(10, VerificationType.link);
|
||
|
||
var verifications = await twonlyDB.keyVerificationDao
|
||
.getContactVerification(10);
|
||
expect(verifications.length, 2);
|
||
|
||
final idToDelete = verifications.first.verificationId;
|
||
await twonlyDB.keyVerificationDao.deleteKeyVerificationById(
|
||
idToDelete,
|
||
10,
|
||
);
|
||
|
||
verifications = await twonlyDB.keyVerificationDao.getContactVerification(
|
||
10,
|
||
);
|
||
expect(verifications.length, 1);
|
||
expect(verifications.first.verificationId, isNot(idToDelete));
|
||
});
|
||
|
||
test(
|
||
'isContactVerified remains true if another direct verification exists',
|
||
() async {
|
||
await insertContact(10);
|
||
await addDirectVerification(10, VerificationType.secretQrToken);
|
||
await addDirectVerification(10, VerificationType.link);
|
||
|
||
final verifications = await twonlyDB.keyVerificationDao
|
||
.getContactVerification(10);
|
||
await twonlyDB.keyVerificationDao.deleteKeyVerificationById(
|
||
verifications.first.verificationId,
|
||
10,
|
||
);
|
||
|
||
expect(await twonlyDB.keyVerificationDao.isContactVerified(10), true);
|
||
},
|
||
);
|
||
|
||
test(
|
||
'isContactVerified returns false after deleting the last verification',
|
||
() async {
|
||
await insertContact(10);
|
||
await addDirectVerification(10, VerificationType.secretQrToken);
|
||
|
||
final verifications = await twonlyDB.keyVerificationDao
|
||
.getContactVerification(10);
|
||
await twonlyDB.keyVerificationDao.deleteKeyVerificationById(
|
||
verifications.first.verificationId,
|
||
10,
|
||
);
|
||
|
||
expect(await twonlyDB.keyVerificationDao.isContactVerified(10), false);
|
||
},
|
||
);
|
||
});
|
||
|
||
// ─── watchAllGroupMembersVerified ───────────────────────────────────────────
|
||
|
||
group('KeyVerificationDao – watchAllGroupMembersVerified', () {
|
||
const groupId = 'test-group-abc';
|
||
|
||
Future<void> setupGroup(List<int> memberIds) async {
|
||
await twonlyDB.groupsDao.createNewGroup(
|
||
GroupsCompanion.insert(
|
||
groupId: groupId,
|
||
groupName: 'Trust Test Group',
|
||
),
|
||
);
|
||
for (final id in memberIds) {
|
||
await twonlyDB.groupsDao.insertOrUpdateGroupMember(
|
||
GroupMembersCompanion.insert(groupId: groupId, contactId: id),
|
||
);
|
||
}
|
||
}
|
||
|
||
test('notTrusted when no member is verified', () async {
|
||
await insertContact(10);
|
||
await insertContact(20);
|
||
await setupGroup([10, 20]);
|
||
|
||
final status = await twonlyDB.keyVerificationDao
|
||
.watchAllGroupMembersVerified(groupId)
|
||
.first;
|
||
|
||
expect(status, VerificationStatus.notTrusted);
|
||
});
|
||
|
||
test('trusted when all members are directly verified', () async {
|
||
await insertContact(10);
|
||
await insertContact(20);
|
||
await addDirectVerification(10, VerificationType.secretQrToken);
|
||
await addDirectVerification(20, VerificationType.qrScanned);
|
||
await setupGroup([10, 20]);
|
||
|
||
final status = await twonlyDB.keyVerificationDao
|
||
.watchAllGroupMembersVerified(groupId)
|
||
.first;
|
||
|
||
expect(status, VerificationStatus.trusted);
|
||
});
|
||
|
||
test('notTrusted for empty group', () async {
|
||
await twonlyDB.groupsDao.createNewGroup(
|
||
GroupsCompanion.insert(groupId: groupId, groupName: 'Empty Group'),
|
||
);
|
||
|
||
final status = await twonlyDB.keyVerificationDao
|
||
.watchAllGroupMembersVerified(groupId)
|
||
.first;
|
||
|
||
expect(status, VerificationStatus.notTrusted);
|
||
});
|
||
|
||
test(
|
||
'single member group – trusted when that member is verified',
|
||
() async {
|
||
await insertContact(10);
|
||
await addDirectVerification(10, VerificationType.link);
|
||
await setupGroup([10]);
|
||
|
||
final status = await twonlyDB.keyVerificationDao
|
||
.watchAllGroupMembersVerified(groupId)
|
||
.first;
|
||
|
||
expect(status, VerificationStatus.trusted);
|
||
},
|
||
);
|
||
});
|
||
|
||
// ─── Full Transitive Trust Scenario ─────────────────────────────────────────
|
||
|
||
group('KeyVerificationService – Full Transitive Trust Scenario', () {
|
||
// Simulates the complete "blue badge" flow:
|
||
// 1. Alice (2) is directly verified by me via QR code.
|
||
// 2. Alice shares Bob (3) – addKeyVerification(3, contactSharedByVerified, verifiedBy: 2)
|
||
// 3. Bob should receive the blue verification badge (isContactVerified = true)
|
||
// because Alice (the sharer) is herself verified.
|
||
//
|
||
// 4. Charlie (4) is shared by Dave (5) who is NOT verified.
|
||
// 5. Charlie should NOT receive a badge.
|
||
//
|
||
// 6. Eve (6) is shared by both Dave (5, unverified) and Alice (2, verified).
|
||
// 7. Eve should receive a badge (at least one verified sharer).
|
||
|
||
test('bob gets blue badge because alice (sharer) is verified', () async {
|
||
await insertContact(2, username: 'alice');
|
||
await insertContact(3, username: 'bob');
|
||
|
||
await addDirectVerification(2, VerificationType.secretQrToken);
|
||
await addSharedVerification(3, 2);
|
||
|
||
expect(await twonlyDB.keyVerificationDao.isContactVerified(3), true);
|
||
});
|
||
|
||
test(
|
||
'charlie gets no badge because dave (sharer) is NOT verified',
|
||
() async {
|
||
await insertContact(4, username: 'charlie');
|
||
await insertContact(5, username: 'dave');
|
||
|
||
await addSharedVerification(4, 5);
|
||
|
||
expect(await twonlyDB.keyVerificationDao.isContactVerified(4), false);
|
||
},
|
||
);
|
||
|
||
test(
|
||
'eve gets blue badge because alice (one of her sharers) is verified',
|
||
() async {
|
||
await insertContact(2, username: 'alice');
|
||
await insertContact(5, username: 'dave');
|
||
await insertContact(6, username: 'eve');
|
||
|
||
await addDirectVerification(2, VerificationType.secretQrToken);
|
||
// Dave is NOT verified
|
||
await addSharedVerification(6, 5); // dave shares eve (does not count)
|
||
await addSharedVerification(6, 2); // alice shares eve (counts!)
|
||
|
||
expect(await twonlyDB.keyVerificationDao.isContactVerified(6), true);
|
||
},
|
||
);
|
||
|
||
test(
|
||
'watchContactVerification shows alice as verifier for bob (blue badge)',
|
||
() async {
|
||
await insertContact(2, username: 'alice');
|
||
await insertContact(3, username: 'bob');
|
||
|
||
await addDirectVerification(2, VerificationType.secretQrToken);
|
||
await addSharedVerification(3, 2);
|
||
|
||
final entries = await twonlyDB.keyVerificationDao
|
||
.watchContactVerification(3)
|
||
.first;
|
||
|
||
expect(entries.length, 1);
|
||
final (kv, verifierContact) = entries.first;
|
||
expect(kv.type, VerificationType.contactSharedByVerified);
|
||
expect(verifierContact?.username, 'alice');
|
||
},
|
||
);
|
||
|
||
test(
|
||
'watchContactVerification shows no entries for charlie (sharer unverified)',
|
||
() async {
|
||
await insertContact(4, username: 'charlie');
|
||
await insertContact(5, username: 'dave');
|
||
|
||
await addSharedVerification(4, 5);
|
||
|
||
final entries = await twonlyDB.keyVerificationDao
|
||
.watchContactVerification(4)
|
||
.first;
|
||
|
||
expect(entries, isEmpty);
|
||
},
|
||
);
|
||
|
||
test('removing alice revokes bob blue badge transitively', () async {
|
||
await insertContact(2, username: 'alice');
|
||
await insertContact(3, username: 'bob');
|
||
|
||
await addDirectVerification(2, VerificationType.secretQrToken);
|
||
await addSharedVerification(3, 2);
|
||
|
||
// Confirm bob is verified
|
||
expect(await twonlyDB.keyVerificationDao.isContactVerified(3), true);
|
||
|
||
// Revoke alice's verification
|
||
await twonlyDB.keyVerificationDao.deleteKeyVerification(2);
|
||
|
||
// Bob should lose the blue badge
|
||
expect(await twonlyDB.keyVerificationDao.isContactVerified(3), false);
|
||
|
||
// watchContactVerification should also be empty for bob
|
||
final entries = await twonlyDB.keyVerificationDao
|
||
.watchContactVerification(3)
|
||
.first;
|
||
expect(entries, isEmpty);
|
||
});
|
||
});
|
||
}
|