added guardian login with views for their assigned childs

This commit is contained in:
2026-09-20 11:11:58 +02:00
parent e4e2b1a4fb
commit 67c935c05b
117 changed files with 4784 additions and 1514 deletions
+134
View File
@@ -0,0 +1,134 @@
import 'dart:convert';
import 'package:flutter_test/flutter_test.dart';
import 'package:marianum_mobile/session/nextcloud_credentials.dart';
import 'package:marianum_mobile/session/session.dart';
import 'package:marianum_mobile/session/session_codec.dart';
String _basic(String user, String secret) =>
'Basic ${base64Encode(utf8.encode('$user:$secret'))}';
void main() {
group('decodeSession installs from before guardian accounts', () {
test('username + password without a kind is a credential session', () {
final session = decodeSession({
'username': 'max',
'password': 'pw',
'nextcloud_app_password': 'app',
'nextcloud_app_password_talk': 'talk',
});
expect(session, isA<CredentialSession>());
final credential = session! as CredentialSession;
expect(credential.username, 'max');
expect(credential.password, 'pw');
expect(credential.isDemo, isFalse);
expect(credential.nextcloud.appPassword, 'app');
expect(credential.nextcloud.appPasswordTalk, 'talk');
expect(credential.nextcloud.usesLoginFlow, isFalse);
});
test('login-flow and demo flags are carried over', () {
final session =
decodeSession({
'username': 'demo@x',
'password': 'demo',
'is_demo': 'true',
'nextcloud_login_flow': 'true',
})!
as CredentialSession;
expect(session.isDemo, isTrue);
expect(session.nextcloud.usesLoginFlow, isTrue);
});
test('missing password means signed out', () {
expect(decodeSession({'username': 'max'}), isNull);
expect(decodeSession({}), isNull);
});
});
group('decodeSession guardian', () {
test('guardian kind with e-mail', () {
final session = decodeSession({
'session_kind': 'guardian',
'guardian_email': 'eltern@example.org',
});
expect(session, isA<GuardianSession>());
expect(session!.nextcloud, isNull);
expect((session as GuardianSession).email, 'eltern@example.org');
});
test('guardian kind without e-mail is invalid', () {
expect(decodeSession({'session_kind': 'guardian'}), isNull);
});
test('an unknown kind from a newer app version reads as signed out', () {
expect(
decodeSession({
'session_kind': 'something-new',
'username': 'max',
'password': 'pw',
}),
isNull,
);
});
});
group('encodeSessionFields', () {
test('round-trips a credential session', () {
final original = CredentialSession(
username: 'max',
password: 'pw',
usesLoginFlow: true,
);
final decoded =
decodeSession(encodeSessionFields(original))! as CredentialSession;
expect(decoded.username, 'max');
expect(decoded.password, 'pw');
expect(decoded.nextcloud.usesLoginFlow, isTrue);
});
test('guardian clears credential fields', () {
final fields = encodeSessionFields(
const GuardianSession(email: 'e@x.de'),
);
expect(fields['username'], isNull);
expect(fields['password'], isNull);
expect(fields.containsKey('username'), isTrue);
expect(decodeSession(fields), isA<GuardianSession>());
});
});
group('NextcloudCredentials', () {
const base = NextcloudCredentials(username: 'max', password: 'pw');
test('prefers the app password once available', () {
expect(base.basicAuthHeader, _basic('max', 'pw'));
final withApp = base.copyWith(appPassword: () => 'app');
expect(withApp.basicAuthHeader, _basic('max', 'app'));
expect(withApp.secret, 'app');
expect(withApp.realPasswordBasicAuthHeader, _basic('max', 'pw'));
});
test('talk header needs its own app password', () {
expect(() => base.talkBasicAuthHeader, throwsStateError);
final withTalk = base.copyWith(appPasswordTalk: () => 't');
expect(withTalk.talkBasicAuthHeader, _basic('max', 't'));
});
test('login-flow accounts share the flow password for talk', () {
final flow = base.copyWith(
appPassword: () => 'flow',
usesLoginFlow: true,
);
expect(flow.talkBasicAuthHeader, _basic('max', 'flow'));
});
test('copyWith can clear an app password', () {
final cleared = base
.copyWith(appPassword: () => 'app')
.copyWith(appPassword: () => null);
expect(cleared.hasAppPassword, isFalse);
expect(cleared.secret, 'pw');
});
});
}