mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-09-29 21:02:09 +02:00
272 lines
7.9 KiB
Dart
272 lines
7.9 KiB
Dart
import 'dart:convert';
|
|
import 'dart:io';
|
|
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:spotiflac_android/services/ffmpeg_service.dart';
|
|
import 'package:spotiflac_android/services/replaygain_service.dart';
|
|
|
|
void main() {
|
|
TestWidgetsFlutterBinding.ensureInitialized();
|
|
const channel = MethodChannel('com.zarz.spotiflac/backend');
|
|
final messenger =
|
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger;
|
|
late Directory directory;
|
|
late String tempPath;
|
|
late List<String> calls;
|
|
late Map<String, dynamic> metadata;
|
|
late Map<String, String> editedFields;
|
|
var method = 'native_ogg';
|
|
var applyEdits = true;
|
|
var saveSaf = true;
|
|
|
|
setUp(() async {
|
|
directory = await Directory.systemTemp.createTemp('replaygain-test-');
|
|
tempPath = '${directory.path}/saf_song.opus';
|
|
calls = [];
|
|
editedFields = {};
|
|
metadata = {
|
|
'audio_codec': 'opus',
|
|
'replaygain_track_gain': '-4.00 dB',
|
|
'replaygain_album_gain': '-3.00 dB',
|
|
};
|
|
method = 'native_ogg';
|
|
applyEdits = true;
|
|
saveSaf = true;
|
|
messenger.setMockMethodCallHandler(channel, (call) async {
|
|
calls.add(call.method);
|
|
final args = Map<String, dynamic>.from(call.arguments as Map);
|
|
switch (call.method) {
|
|
case 'safCopyToTemp':
|
|
await File(tempPath).writeAsString('original audio');
|
|
return tempPath;
|
|
case 'editFileMetadata':
|
|
editedFields = Map<String, String>.from(
|
|
jsonDecode(args['metadata_json'] as String) as Map,
|
|
);
|
|
if (applyEdits) {
|
|
metadata.addAll(editedFields);
|
|
if (metadata['audio_codec'] == 'opus') {
|
|
metadata.removeWhere((key, _) => key.endsWith('_peak'));
|
|
}
|
|
}
|
|
return jsonEncode({'success': true, 'method': method});
|
|
case 'readFileMetadata':
|
|
return jsonEncode(metadata);
|
|
case 'writeTempToSaf':
|
|
expect(args['temp_path'], tempPath);
|
|
expect(await File(tempPath).exists(), isTrue);
|
|
return jsonEncode({'success': saveSaf});
|
|
default:
|
|
fail('Unexpected method: ${call.method}');
|
|
}
|
|
});
|
|
});
|
|
|
|
tearDown(() async {
|
|
messenger.setMockMethodCallHandler(channel, null);
|
|
await directory.delete(recursive: true);
|
|
});
|
|
|
|
test(
|
|
'manual SAF scan writes and verifies Opus before copying back',
|
|
() async {
|
|
final result = await ReplayGainService.applyToFile(
|
|
'content://music/document/42',
|
|
scan: (path) async {
|
|
expect(path, tempPath);
|
|
expect(await File(path).exists(), isTrue);
|
|
calls.add('scan');
|
|
return ReplayGainResult(
|
|
trackGain: '-12.20 dB',
|
|
trackPeak: '1.258925',
|
|
integratedLufs: -5.8,
|
|
truePeakLinear: 1.258925,
|
|
);
|
|
},
|
|
);
|
|
expect(result, isTrue);
|
|
expect(calls, [
|
|
'safCopyToTemp',
|
|
'scan',
|
|
'editFileMetadata',
|
|
'readFileMetadata',
|
|
'writeTempToSaf',
|
|
]);
|
|
expect(editedFields['replaygain_track_gain'], '-12.20 dB');
|
|
expect(await File(tempPath).exists(), isFalse);
|
|
},
|
|
);
|
|
|
|
test(
|
|
'album gain on an extensionless SAF URI uses the native writer',
|
|
() async {
|
|
expect(
|
|
await ReplayGainService.writeAlbumTags(
|
|
'content://music/document/42',
|
|
'-10.00 dB',
|
|
'1.300000',
|
|
),
|
|
isTrue,
|
|
);
|
|
expect(editedFields, {
|
|
'replaygain_album_gain': '-10.00 dB',
|
|
'replaygain_album_peak': '1.300000',
|
|
});
|
|
expect(metadata['replaygain_track_gain'], '-4.00 dB');
|
|
expect(calls.last, 'writeTempToSaf');
|
|
expect(await File(tempPath).exists(), isFalse);
|
|
},
|
|
);
|
|
|
|
test('native fallback instruction is not a successful Opus write', () async {
|
|
method = 'ffmpeg';
|
|
applyEdits = false;
|
|
expect(
|
|
await ReplayGainService.writeTrackTags(
|
|
'content://music/document/42',
|
|
'-12.20 dB',
|
|
'1.258925',
|
|
),
|
|
isFalse,
|
|
);
|
|
expect(calls, ['safCopyToTemp', 'editFileMetadata']);
|
|
expect(await File(tempPath).exists(), isFalse);
|
|
});
|
|
|
|
test('unchanged gain fails verification and never overwrites SAF', () async {
|
|
applyEdits = false;
|
|
expect(
|
|
await ReplayGainService.writeTrackTags(
|
|
'content://music/document/42',
|
|
'-12.20 dB',
|
|
'1.258925',
|
|
),
|
|
isFalse,
|
|
);
|
|
expect(calls, ['safCopyToTemp', 'editFileMetadata', 'readFileMetadata']);
|
|
expect(await File(tempPath).exists(), isFalse);
|
|
});
|
|
|
|
test(
|
|
'SAF save failure is reported and the temporary copy is removed',
|
|
() async {
|
|
saveSaf = false;
|
|
expect(
|
|
await ReplayGainService.writeTrackTags(
|
|
'content://music/document/42',
|
|
'-12.20 dB',
|
|
'1.258925',
|
|
),
|
|
isFalse,
|
|
);
|
|
expect(calls.last, 'writeTempToSaf');
|
|
expect(await File(tempPath).exists(), isFalse);
|
|
},
|
|
);
|
|
|
|
test(
|
|
'scan failure leaves SAF untouched and removes its temporary copy',
|
|
() async {
|
|
expect(
|
|
await ReplayGainService.applyToFile(
|
|
'content://music/document/42',
|
|
scan: (_) async => null,
|
|
),
|
|
isFalse,
|
|
);
|
|
expect(calls, ['safCopyToTemp']);
|
|
expect(await File(tempPath).exists(), isFalse);
|
|
},
|
|
);
|
|
|
|
test('FLAC native method and peak verification remain supported', () async {
|
|
method = 'native';
|
|
metadata['audio_codec'] = 'flac';
|
|
expect(
|
|
await ReplayGainService.writeTrackTags(
|
|
'${directory.path}/song.flac',
|
|
'-12.20 dB',
|
|
'1.258925',
|
|
),
|
|
isTrue,
|
|
);
|
|
expect(metadata['replaygain_track_peak'], '1.258925');
|
|
expect(calls, ['editFileMetadata', 'readFileMetadata']);
|
|
});
|
|
|
|
test('removal clears both scopes and verifies before saving SAF', () async {
|
|
metadata['title'] = 'Preserved title';
|
|
expect(
|
|
await ReplayGainService.removeFromFile('content://music/document/42'),
|
|
isTrue,
|
|
);
|
|
expect(editedFields, {
|
|
'replaygain_track_gain': '',
|
|
'replaygain_track_peak': '',
|
|
'replaygain_album_gain': '',
|
|
'replaygain_album_peak': '',
|
|
});
|
|
expect(metadata['title'], 'Preserved title');
|
|
expect(calls, [
|
|
'safCopyToTemp',
|
|
'editFileMetadata',
|
|
'readFileMetadata',
|
|
'writeTempToSaf',
|
|
]);
|
|
expect(await File(tempPath).exists(), isFalse);
|
|
});
|
|
|
|
test('removal refuses to save when the gain tags remain', () async {
|
|
applyEdits = false;
|
|
expect(
|
|
await ReplayGainService.removeFromFile('content://music/document/42'),
|
|
isFalse,
|
|
);
|
|
expect(calls, ['safCopyToTemp', 'editFileMetadata', 'readFileMetadata']);
|
|
expect(await File(tempPath).exists(), isFalse);
|
|
});
|
|
|
|
test('removal does not treat a fallback instruction as success', () async {
|
|
method = 'ffmpeg';
|
|
expect(
|
|
await ReplayGainService.removeFromFile('content://music/document/42'),
|
|
isFalse,
|
|
);
|
|
expect(calls, ['safCopyToTemp', 'editFileMetadata']);
|
|
expect(await File(tempPath).exists(), isFalse);
|
|
});
|
|
|
|
test(
|
|
'removal reports SAF save failure and cleans its temporary copy',
|
|
() async {
|
|
saveSaf = false;
|
|
expect(
|
|
await ReplayGainService.removeFromFile('content://music/document/42'),
|
|
isFalse,
|
|
);
|
|
expect(calls.last, 'writeTempToSaf');
|
|
expect(await File(tempPath).exists(), isFalse);
|
|
},
|
|
);
|
|
|
|
test('removal rejects an empty metadata response', () async {
|
|
metadata = {};
|
|
expect(
|
|
await ReplayGainService.removeFromFile('content://music/document/42'),
|
|
isFalse,
|
|
);
|
|
expect(calls, ['safCopyToTemp', 'editFileMetadata', 'readFileMetadata']);
|
|
});
|
|
|
|
test('removal is idempotent for an untagged local file', () async {
|
|
method = 'native';
|
|
metadata = {'audio_codec': 'flac'};
|
|
expect(
|
|
await ReplayGainService.removeFromFile('${directory.path}/song.flac'),
|
|
isTrue,
|
|
);
|
|
expect(calls, ['editFileMetadata', 'readFileMetadata']);
|
|
});
|
|
}
|