Files
Client/test/session/account_codec_test.dart
T

163 lines
5.5 KiB
Dart

import 'package:flutter_test/flutter_test.dart';
import 'package:marianum_mobile/session/account_codec.dart';
import 'package:marianum_mobile/session/session.dart';
import 'package:marianum_mobile/session/session_codec.dart';
void main() {
final school = CredentialSession(username: 'max', password: 'pw');
const guardian = GuardianSession(email: 'eltern@x.de');
group('AccountIndex.activate', () {
test('adds an unknown account with its own namespace', () {
final index = AccountIndex.empty.activate(school, newId: 'a', now: 5);
expect(index.activeId, 'a');
final entry = index.active!;
expect(entry.kind, SessionKeys.kindCredential);
expect(entry.label, 'max');
expect(entry.namespace, 'a');
expect(entry.lastUsed, 5);
});
test('legacy account keeps the un-namespaced storage', () {
final index = AccountIndex.empty.activate(
school,
newId: 'a',
namespace: '',
);
expect(index.active!.namespace, '');
});
test('a known account is reused instead of duplicated', () {
final index = AccountIndex.empty
.activate(school, newId: 'a')
.activate(guardian, newId: 'b')
.activate(
CredentialSession(username: 'max', password: 'new'),
newId: 'c',
now: 9,
);
expect(index.accounts.map((e) => e.id), ['a', 'b']);
expect(index.activeId, 'a');
expect(index.active!.lastUsed, 9);
});
test('demo and real account with the same name stay apart', () {
final index = AccountIndex.empty
.activate(school, newId: 'a')
.activate(
CredentialSession(username: 'max', password: 'demo', isDemo: true),
newId: 'b',
);
expect(index.accounts, hasLength(2));
});
});
group('AccountIndex removal', () {
final index = AccountIndex.empty
.activate(school, newId: 'a', now: 1)
.activate(guardian, newId: 'b', now: 3)
.select('a', now: 2);
test('removing the active account leaves none active', () {
final removed = index.remove('a');
expect(removed.activeId, isNull);
expect(removed.accounts.map((e) => e.id), ['b']);
});
test('removing an inactive account keeps the active one', () {
expect(index.remove('b').activeId, 'a');
});
test('takeover picks the most recently used remaining account', () {
final third = index.activate(
const GuardianSession(email: 'z@x.de'),
newId: 'c',
now: 1,
);
expect(third.remove('a').mostRecentExcept(null)?.id, 'b');
expect(index.mostRecentExcept('b')?.id, 'a');
expect(AccountIndex.empty.mostRecentExcept(null), isNull);
});
});
group('AccountIndex codec', () {
test('round trip', () {
final index = AccountIndex.empty
.activate(school, newId: 'a', namespace: '', now: 1)
.activate(guardian, newId: 'b', now: 2);
final decoded = AccountIndex.decode(index.encode());
expect(decoded.activeId, 'b');
expect(decoded.accounts, hasLength(2));
expect(decoded.byId('a')!.namespace, '');
expect(decoded.byId('b')!.isGuardian, isTrue);
expect(decoded.byId('b')!.lastUsed, 2);
});
test('display name survives rename and round trip', () {
final index = AccountIndex.empty
.activate(school, newId: 'a')
.activate(guardian, newId: 'b')
.rename('b', 'Erika Muster');
final decoded = AccountIndex.decode(index.encode());
expect(decoded.byId('b')!.displayName, 'Erika Muster');
expect(decoded.byId('a')!.displayName, isNull);
expect(decoded.activeId, 'b');
});
test('garbage decodes to an empty index', () {
expect(AccountIndex.decode(null).accounts, isEmpty);
expect(AccountIndex.decode('{nope').accounts, isEmpty);
expect(AccountIndex.decode('[]').accounts, isEmpty);
});
test('malformed entries are skipped', () {
final decoded = AccountIndex.decode(
'{"activeId":"a","accounts":[{"id":"a"},'
'{"id":"b","kind":"guardian","label":"x"}]}',
);
expect(decoded.accounts.map((e) => e.id), ['b']);
expect(decoded.active, isNull);
});
});
group('vault', () {
test('credential session survives the vault with app passwords', () {
final session = CredentialSession(
username: 'max',
password: 'pw',
appPassword: 'app',
appPasswordTalk: 'talk',
usesLoginFlow: true,
);
final fields = decodeVault(
encodeVault({...sessionVaultFields(session), 'mc_bearer_token': 't'}),
);
final restored = decodeSession(fields)! as CredentialSession;
expect(restored.username, 'max');
expect(restored.password, 'pw');
expect(restored.nextcloud.appPassword, 'app');
expect(restored.nextcloud.appPasswordTalk, 'talk');
expect(restored.nextcloud.usesLoginFlow, isTrue);
expect(fields['mc_bearer_token'], 't');
});
test('guardian demo session survives the vault', () {
final fields = decodeVault(
encodeVault(
sessionVaultFields(
const GuardianSession(email: 'e@x.de', isDemo: true),
),
),
);
final restored = decodeSession(fields)! as GuardianSession;
expect(restored.email, 'e@x.de');
expect(restored.isDemo, isTrue);
expect(fields.containsKey(SessionKeys.username), isFalse);
});
test('broken vault yields no session', () {
expect(decodeSession(decodeVault('garbage')), isNull);
});
});
}