Files
sepot/app/test/storage/groups_test.dart
T
Julius Joska 92eb316a83 tests: make design-rationale comments self-contained
Test comments referenced internal design documents that are not part
of this repository. The relevant invariants are now stated inline.
2026-07-11 20:02:56 +02:00

133 lines
4.4 KiB
Dart

import 'dart:io';
import 'dart:math';
import 'dart:typed_data';
import 'package:flutter_test/flutter_test.dart';
import 'package:sepot/core/storage/crypto_db.dart';
import 'package:sepot/core/storage/secure_storage.dart';
import 'package:sepot/models/conversation.dart';
import 'package:sepot/models/group.dart';
import 'package:sepot/models/message.dart';
/// Skupiny v1 — perzistence a model. Bezpečnostní pravidla
/// příjmové cesty (roster anti-spoof, membership guard) mají vlastní test
/// v `test/groups_security_test.dart`, protože nepotřebují DB.
void main() {
late Directory tmp;
late CryptoDb db;
late SecureStorageService storage;
final rng = Random.secure();
Uint8List randomDek() =>
Uint8List.fromList(List.generate(32, (_) => rng.nextInt(256)));
setUp(() {
tmp = Directory.systemTemp.createTempSync('sepot_groups_test');
db = CryptoDb.open(path: '${tmp.path}/app.db', dek: randomDek());
storage = SecureStorageService(dbPath: '${tmp.path}/app.db')
..debugAttachDb(db);
});
tearDown(() {
db.close();
tmp.deleteSync(recursive: true);
});
Group vzorovaSkupina({String id = 'g-1', List<String>? members}) => Group(
id: id,
name: 'Rodina',
memberIds: members ?? ['ME', 'ALICE', 'BOB'],
createdBy: 'ME',
);
test('skupina přežije uložení a načtení (JSON roundtrip přes DB)', () async {
final g = vzorovaSkupina();
await storage.saveGroup(g);
final nactena = await storage.loadGroup('g-1');
expect(nactena, isNotNull);
expect(nactena!.name, 'Rodina');
expect(nactena.memberIds, ['ME', 'ALICE', 'BOB']);
expect(nactena.createdBy, 'ME');
expect(nactena.left, isFalse);
expect(nactena.createdAt.toIso8601String(), g.createdAt.toIso8601String());
});
test('loadGroup vrátí null pro id, které patří 1:1 kontaktu', () async {
await storage.saveGroup(vzorovaSkupina());
expect(await storage.loadGroup('ALICE'), isNull,
reason: 'rozlišení skupina vs kontakt stojí na tomhle null');
});
test('loadGroups vrátí všechny skupiny včetně opuštěných', () async {
await storage.saveGroup(vzorovaSkupina(id: 'g-1'));
final opustena = vzorovaSkupina(id: 'g-2')
..left = true
..memberIds = ['ALICE', 'BOB'];
await storage.saveGroup(opustena);
final vsechny = await storage.loadGroups();
expect(vsechny.map((g) => g.id).toSet(), {'g-1', 'g-2'});
expect(vsechny.firstWhere((g) => g.id == 'g-2').left, isTrue,
reason: 'opuštěná skupina zůstává kvůli historii');
});
test('uložení stejného id skupinu přepíše, nezdvojí', () async {
await storage.saveGroup(vzorovaSkupina());
final zmenena = vzorovaSkupina()
..name = 'Rodina a přátelé'
..memberIds = ['ME', 'ALICE', 'BOB', 'CILKA'];
await storage.saveGroup(zmenena);
final vsechny = await storage.loadGroups();
expect(vsechny.length, 1);
expect(vsechny.single.name, 'Rodina a přátelé');
expect(vsechny.single.memberIds.length, 4);
});
test('deleteGroup skupinu odstraní', () async {
await storage.saveGroup(vzorovaSkupina());
await storage.deleteGroup('g-1');
expect(await storage.loadGroup('g-1'), isNull);
expect(await storage.loadGroups(), isEmpty);
});
test('skupinová konverzace žije pod contact_id = gid', () async {
final g = vzorovaSkupina();
await storage.saveGroup(g);
await storage.saveConversation(Conversation(
contactId: g.id,
messages: [
Message.text(senderId: 'ALICE', recipientId: g.id, content: 'ahoj'),
],
));
final conv = await storage.loadConversation(g.id);
expect(conv, isNotNull);
expect(conv!.messages.single.content, 'ahoj');
expect(conv.messages.single.senderId, 'ALICE',
reason: 'autor zprávy = fingerprint člena, ne gid');
});
test('Group.isMember rozhoduje o membership guardu', () {
final g = vzorovaSkupina();
expect(g.isMember('ALICE'), isTrue);
expect(g.isMember('MALLORY'), isFalse);
});
test('strop členů je 128 (párový fanout = N obálek na zprávu)', () {
expect(Group.maxMembers, 128);
});
test('fromJson snese chybějící "left" (starší záznam)', () {
final g = Group.fromJson({
'id': 'g-old',
'name': 'Staré',
'memberIds': ['ME'],
'createdBy': 'ME',
'createdAt': DateTime(2026, 7, 1).toIso8601String(),
});
expect(g.left, isFalse);
});
}