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()); 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()); 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()); }); }); 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'); }); }); }