Files
SpotiFLAC-Mobile/test/network_metadata_service_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

175 lines
5.5 KiB
Dart

import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:spotiflac_android/services/network_storage_service.dart';
import 'package:spotiflac_android/services/network_metadata_service.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
late NetworkStorageService storage;
late Directory directory;
setUp(() async {
storage = NetworkStorageService(
read: () async => null,
write: (_) async {},
);
await storage.save(
const NetworkConnection(
id: 'nas',
name: 'NAS',
protocol: NetworkProtocol.http,
address: 'https://nas.test/music/',
),
);
directory = await Directory.systemTemp.createTemp('network-tag-test-');
});
tearDown(() async {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(
const MethodChannel('com.zarz.spotiflac/backend'),
null,
);
await storage.dispose();
await directory.delete(recursive: true);
});
test(
'native cache returns a file path and excludes oversized tags from memory',
() async {
var calls = 0;
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(
const MethodChannel('com.zarz.spotiflac/backend'),
(call) async {
expect(call.method, 'runNativeDataJob');
final args = call.arguments as Map;
final request = jsonDecode(args['request_json'] as String) as Map;
expect(request['operation'], 'network_metadata');
expect(request['url'], startsWith('http://127.0.0.1:'));
expect(
request['cache_directory'],
'${directory.path}/network_metadata',
);
expect(args['request_id'], isA<String>());
calls++;
return jsonEncode({
'metadata': {
'title': 'Native tags',
'cover_path': '/native/cover.image',
},
'cacheable': calls != 1,
});
},
);
final reader = NetworkMetadataService(
storage: storage,
cacheDirectory: () async => directory,
);
final source = NetworkStorageService.source('nas', 'song.flac');
expect((await reader.read(source))['cover_path'], '/native/cover.image');
await reader.read(source);
await reader.read(source);
expect(calls, 2);
},
);
test(
'playback and Lyrics share one read and embedded cover is cached as a file',
() async {
var calls = 0;
final gate = Completer<Map<String, dynamic>>();
final reader = NetworkMetadataService(
storage: storage,
cacheDirectory: () async => directory,
reader: (url, name) {
calls++;
expect(url, startsWith('http://127.0.0.1:'));
expect(name, 'Song #1.flac');
return gate.future;
},
);
final source = NetworkStorageService.source('nas', 'Song #1.flac');
final a = reader.read(source);
final b = reader.read(source);
gate.complete({
'title': 'Title',
'lyrics': '[00:01.00]Hello',
'cover_base64': base64Encode([1, 2, 3]),
});
final tags = await a;
expect(await b, tags);
expect(calls, 1);
expect(tags['lyrics'], '[00:01.00]Hello');
expect(tags.containsKey('cover_base64'), false);
expect(await File(tags['cover_path'] as String).readAsBytes(), [1, 2, 3]);
await reader.read(source);
expect(calls, 1);
},
);
test('rescans can refresh tags without reusing the playback cache', () async {
var calls = 0;
final reader = NetworkMetadataService(
storage: storage,
reader: (_, _) async => {'title': 'Revision ${++calls}'},
);
final source = NetworkStorageService.source('nas', 'song.flac');
expect((await reader.read(source))['title'], 'Revision 1');
expect((await reader.read(source))['title'], 'Revision 1');
expect(
(await reader.read(source, forceRefresh: true))['title'],
'Revision 2',
);
expect(calls, 2);
});
test(
'a failed read is retryable and corrupt artwork does not discard lyrics',
() async {
var calls = 0;
final reader = NetworkMetadataService(
storage: storage,
cacheDirectory: () async => directory,
reader: (_, name) async {
if (++calls == 1) return {'error': 'offline'};
return {'lyrics': 'Plain lyrics', 'cover_base64': 'invalid base64!'};
},
);
final source = NetworkStorageService.source('nas', 'song.mp3');
await expectLater(reader.read(source), throwsStateError);
expect((await reader.read(source))['lyrics'], 'Plain lyrics');
expect(calls, 2);
},
);
test(
'direct HTTP connection supplies the real filename as a native hint',
() async {
await storage.save(
const NetworkConnection(
id: 'direct',
name: 'Single',
protocol: NetworkProtocol.http,
address: 'https://nas.test/song.m4a',
),
);
final reader = NetworkMetadataService(
storage: storage,
reader: (_, name) async {
expect(name, 'song.m4a');
return {'title': 'Song'};
},
);
expect(
(await reader.read(
NetworkStorageService.source('direct', ''),
))['title'],
'Song',
);
},
);
}