JJ_MKVMaker/test/settings_json_contract_test...

85 lines
4.5 KiB
Dart

import 'dart:convert';
import 'dart:io';
import 'package:flutter_test/flutter_test.dart';
import 'package:jj_mkvmaker/app/settings.dart';
import 'package:path/path.dart' as p;
/// 설정 A 약속 시험: 설정 파일 (settings.json) 을 읽고 다시 쓴 결과가 나누기 전 판과 같다 (키 순서를 뺀 바이트 - 키를 정렬해 견줌).
/// - 합성: 모든 키의 값을 바꾼 입력 (test/fixtures/settings_golden_in.json) → 나누기 전 판의 출력 (…_out.json)
/// - 사용자 파일: JJ_USER_SETTINGS_EXPECT 가 있으면 사용자 설정 파일 (com.jj\jj_mkvmaker\settings.json) 을 시험 폴더에 복사해 읽기만 하고,
/// 나누기 전 판이 같은 파일로 낸 출력 (scratch, 커밋하지 않음) 과 견준다. 사용자 파일에는 쓰지 않는다
/// JJ_SETTINGS_GOLDEN_WRITE=1 이면 견주지 않고 기준을 만든다 (나누기 전 판에서 한 번)
void main() {
final write = Platform.environment['JJ_SETTINGS_GOLDEN_WRITE'] == '1';
const fixtures = 'test/fixtures';
/// 키를 정렬한 JSON (키 순서는 분야를 옮기면 바뀐다 - 값 · 키 집합만 견줌)
String canonical(Object? v) {
Object? sort(Object? x) => x is Map
? {for (final k in (x.keys.map((e) => '$e').toList()..sort())) k: sort(x[k])}
: x is List
? [for (final e in x) sort(e)]
: x;
return const JsonEncoder.withIndent(' ').convert(sort(v));
}
/// 읽고 다시 쓴 것 + 읽을 때 정한 "바뀐 기본값 알림" 목록 (파일에는 없지만 읽기의 결과)
String roundTrip(String text) {
final s = AppSettings.fromJson(jsonDecode(text) as Map<String, dynamic>);
return canonical({...s.toJson(), '__migrated': s.migrated});
}
/// 모든 키의 값을 바꾼 입력 (bool 은 반대, 수는 +7, 글은 앞에 x_, 안쪽 묶음도) - 읽기의 검사 · 고침도 함께 지나게
Object? change(Object? v) => switch (v) {
final bool b => !b,
final int n => n + 7,
final double d => d + 0.5,
final String s => 'x_$s',
final Map<dynamic, dynamic> m => {for (final e in m.entries) '${e.key}': change(e.value)},
_ => v,
};
Map<String, Object?> mutated() => {for (final e in AppSettings().toJson(plainSecrets: true).entries) e.key: change(e.value)};
test('합성: 모든 키를 바꾼 설정을 읽고 쓰면 나누기 전 판과 같다 · 처음 값도', () {
final inFile = File(p.join(fixtures, 'settings_golden_in.json'));
final outFile = File(p.join(fixtures, 'settings_golden_out.json'));
final defFile = File(p.join(fixtures, 'settings_golden_default.json'));
if (write) {
Directory(fixtures).createSync(recursive: true);
inFile.writeAsStringSync(const JsonEncoder.withIndent(' ').convert(mutated()));
outFile.writeAsStringSync(roundTrip(inFile.readAsStringSync()));
defFile.writeAsStringSync(canonical(AppSettings().toJson()));
return;
}
// git 이 줄 끝을 CRLF 로 바꿔 꺼낼 수 있다 - LF 로 맞춰 견줌
String read(File f) => f.readAsStringSync().replaceAll('\r\n', '\n');
expect(roundTrip(read(inFile)), read(outFile));
expect(canonical(AppSettings().toJson()), read(defFile));
});
test('옛 판 설정 (옮김 표시 없음 · 예전 처음 값): 읽은 값과 바뀐 기본값 알림이 나누기 전 판과 같다', () {
const legacy = {'explorerLayout': 'auto', 'explorerClick': 'open', 'explorerOrientation': 'auto', 'screenOrientation': 'landscape'};
final outFile = File(p.join(fixtures, 'settings_golden_legacy_out.json'));
if (write) {
outFile.writeAsStringSync(roundTrip(jsonEncode(legacy)));
return;
}
expect(roundTrip(jsonEncode(legacy)), outFile.readAsStringSync().replaceAll('\r\n', '\n'));
});
final expectPath = Platform.environment['JJ_USER_SETTINGS_EXPECT'];
test('사용자 설정 파일 (복사본): 읽고 쓰면 나누기 전 판과 같다', () {
final user = File(p.join(Platform.environment['APPDATA'] ?? '', 'com.jj', 'jj_mkvmaker', 'settings.json'));
final tmp = Directory.systemTemp.createTempSync('jj_settings_user_');
addTearDown(() => tmp.deleteSync(recursive: true));
final copy = user.copySync(p.join(tmp.path, 'settings.json'));
final got = roundTrip(copy.readAsStringSync());
if (write) {
File(expectPath!).writeAsStringSync(got);
return;
}
expect(got, File(expectPath!).readAsStringSync().replaceAll('\r\n', '\n'));
}, skip: expectPath == null ? '사용자 파일 기준 (JJ_USER_SETTINGS_EXPECT) 이 없으면 건너뜀 - 이 PC 에서만' : null);
}