115 lines
4.1 KiB
Dart
115 lines
4.1 KiB
Dart
import 'dart:async';
|
|
import 'dart:convert';
|
|
import 'dart:io';
|
|
|
|
import 'package:flutter_secure_storage_platform_interface/flutter_secure_storage_platform_interface.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:fuel_tax_tracker/services/cloud/webdav_provider.dart';
|
|
|
|
const _multistatusBody = '''<?xml version="1.0"?>
|
|
<D:multistatus xmlns:D="DAV:">
|
|
<D:response>
|
|
<D:href>/</D:href>
|
|
<D:propstat>
|
|
<D:prop><D:resourcetype><D:collection/></D:resourcetype></D:prop>
|
|
<D:status>HTTP/1.1 200 OK</D:status>
|
|
</D:propstat>
|
|
</D:response>
|
|
</D:multistatus>''';
|
|
|
|
/// Confirms sign-in survives a cold app restart: [WebDavProvider] persists
|
|
/// verified credentials to secure storage on [signInWithCredentials], and a
|
|
/// *fresh* instance (an in-memory field reset, standing in for a relaunched
|
|
/// app) can restore the session from them via [attemptSilentSignIn] — the
|
|
/// gap flagged in README's "Known limitations" and fixed here.
|
|
void main() {
|
|
late HttpServer server;
|
|
late String serverUrl;
|
|
const username = 'alice';
|
|
const password = 'hunter2';
|
|
|
|
setUp(() async {
|
|
FlutterSecureStoragePlatform.instance = _FakeSecureStoragePlatform();
|
|
|
|
server = await HttpServer.bind(InternetAddress.loopbackIPv4, 0);
|
|
serverUrl = 'http://127.0.0.1:${server.port}/';
|
|
unawaited(server.forEach((request) async {
|
|
final auth = request.headers.value('authorization');
|
|
final expected = 'Basic ${base64Encode(utf8.encode('$username:$password'))}';
|
|
if (request.method == 'PROPFIND' && auth == expected) {
|
|
request.response.statusCode = 207;
|
|
request.response.headers.contentType = ContentType('application', 'xml');
|
|
request.response.write(_multistatusBody);
|
|
} else if (request.method == 'PROPFIND') {
|
|
request.response.statusCode = 401;
|
|
} else {
|
|
request.response.statusCode = 404;
|
|
}
|
|
await request.response.close();
|
|
}));
|
|
});
|
|
|
|
tearDown(() async {
|
|
await server.close(force: true);
|
|
});
|
|
|
|
test('signInWithCredentials persists credentials that a fresh instance can restore', () async {
|
|
final first = WebDavProvider();
|
|
await first.signInWithCredentials(serverUrl: serverUrl, username: username, password: password);
|
|
expect(first.isSignedIn, isTrue);
|
|
|
|
// A brand new instance has no in-memory session — simulates the app
|
|
// process having been killed and relaunched.
|
|
final afterRestart = WebDavProvider();
|
|
expect(afterRestart.isSignedIn, isFalse);
|
|
|
|
final restored = await afterRestart.attemptSilentSignIn();
|
|
|
|
expect(restored, isTrue);
|
|
expect(afterRestart.isSignedIn, isTrue);
|
|
expect(afterRestart.accountLabel, first.accountLabel);
|
|
});
|
|
|
|
test('attemptSilentSignIn returns false, not throws, when nothing was ever stored', () async {
|
|
final provider = WebDavProvider();
|
|
|
|
expect(await provider.attemptSilentSignIn(), isFalse);
|
|
expect(provider.isSignedIn, isFalse);
|
|
});
|
|
|
|
test('signOut clears stored credentials so a later restart no longer restores', () async {
|
|
final first = WebDavProvider();
|
|
await first.signInWithCredentials(serverUrl: serverUrl, username: username, password: password);
|
|
await first.signOut();
|
|
|
|
final afterRestart = WebDavProvider();
|
|
expect(await afterRestart.attemptSilentSignIn(), isFalse);
|
|
});
|
|
}
|
|
|
|
class _FakeSecureStoragePlatform extends FlutterSecureStoragePlatform {
|
|
final Map<String, String> _store = {};
|
|
|
|
@override
|
|
Future<void> write({required String key, required String value, required Map<String, String> options}) async {
|
|
_store[key] = value;
|
|
}
|
|
|
|
@override
|
|
Future<String?> read({required String key, required Map<String, String> options}) async => _store[key];
|
|
|
|
@override
|
|
Future<bool> containsKey({required String key, required Map<String, String> options}) async =>
|
|
_store.containsKey(key);
|
|
|
|
@override
|
|
Future<void> delete({required String key, required Map<String, String> options}) async {
|
|
_store.remove(key);
|
|
}
|
|
|
|
@override
|
|
Future<Map<String, String>> readAll({required Map<String, String> options}) async => Map.of(_store);
|
|
|
|
@override
|
|
Future<void> deleteAll({required Map<String, String> options}) async => _store.clear();
|
|
}
|