added support for multiple accounts, guardian login bugfixes, ui changes
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user