Files
SpotiFLAC-Mobile/test/network_webdav_tls_test.dart
T
zarzet 927fd033cd perf(nas): process listings, artwork caches, and upload recovery natively
Move WebDAV/HTML parsing, content-addressed artwork caching, cover promotion, and streamed upload verification to native workers. Preserve proxy authentication, cancellation, cache limits, and deep XML fallback behavior.
2026-10-06 23:27:07 +07:00

119 lines
4.5 KiB
Dart

import 'dart:convert';
import 'dart:io';
import 'package:flutter_test/flutter_test.dart';
import 'package:spotiflac_android/services/network_certificate.dart';
import 'package:spotiflac_android/services/network_storage_error.dart';
import 'package:spotiflac_android/services/network_storage_service.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
// The native listing fallback needs a binding while TLS must use real I/O.
HttpOverrides.global = null;
test(
'self-signed WebDAV requires explicit trust for listing and streaming',
() async {
final context = SecurityContext()
..useCertificateChain('test/fixtures/network_tls/cert.pem')
..usePrivateKey('test/fixtures/network_tls/key.pem');
final server = await HttpServer.bindSecure(
InternetAddress.loopbackIPv4,
0,
context,
);
addTearDown(() => server.close(force: true));
final requests = <String>[];
server.listen((request) async {
requests.add(request.method);
expect(
request.headers.value('authorization'),
'Basic ${base64Encode(utf8.encode('test:secret'))}',
);
if (request.uri.path == '/music') {
request.response.statusCode = 301;
request.response.headers.set('location', '/music/');
} else if (request.method == 'PROPFIND') {
expect(request.headers.value('depth'), '1');
expect(await utf8.decoder.bind(request).join(), contains('propfind'));
request.response.statusCode = 207;
request.response.write('''<d:multistatus xmlns:d="DAV:">
<d:response><d:href>/music/A%20B.flac</d:href><d:propstat>
<d:prop><d:getcontentlength>4</d:getcontentlength></d:prop>
<d:status>HTTP/1.1 200 OK</d:status></d:propstat></d:response>
</d:multistatus>''');
} else {
expect(request.headers.value('range'), 'bytes=1-2');
request.response.statusCode = 206;
request.response.headers.set('content-range', 'bytes 1-2/4');
request.response.contentLength = 2;
request.response.add([1, 2]);
}
await request.response.close();
}, onError: (Object _) {}); // Expected TLS rejection before approval.
String? stored;
final service = NetworkStorageService(
read: () async => stored,
write: (value) async => stored = value,
);
addTearDown(service.dispose);
NetworkConnection connection([String? fingerprint]) => NetworkConnection(
id: 'fixture',
name: 'NAS',
protocol: NetworkProtocol.webdav,
address: 'https://127.0.0.1:${server.port}/music',
username: 'test',
password: 'secret',
trustedCertificateSha256: fingerprint,
);
NetworkCertificateException? rejected;
try {
await service.list(connection());
fail('An untrusted certificate must be rejected');
} on NetworkCertificateException catch (error) {
rejected = error;
expect(
classifyNetworkStorageError(error),
NetworkStorageError.certificate,
);
expect(error.origin, 'https://127.0.0.1:${server.port}');
expect(error.fingerprint.length, 64);
}
expect(
requests,
isEmpty,
); // Credentials have not reached the untrusted server.
final trusted = connection(rejected.fingerprint);
expect((await service.list(trusted)).single.path, 'A B.flac');
await service.save(trusted);
final restored = NetworkStorageService(
read: () async => stored,
write: (_) async {},
);
addTearDown(restored.dispose);
expect(
(await restored.connection('fixture')).trustedCertificateSha256,
rejected.fingerprint,
);
final url = await restored.resolve(
NetworkStorageService.source('fixture', 'A B.flac'),
);
final client = HttpClient();
addTearDown(() => client.close(force: true));
final request = await client.getUrl(Uri.parse(url));
request.headers.set('range', 'bytes=1-2');
final response = await request.close();
expect(response.statusCode, 206);
expect(await response.expand((chunk) => chunk).toList(), [1, 2]);
await expectLater(
service.list(connection()),
throwsA(isA<NetworkCertificateException>()),
);
await expectLater(
service.list(connection('0' * 64)),
throwsA(isA<NetworkCertificateException>()),
);
},
);
}