143 lines
7.5 KiB
Dart
143 lines
7.5 KiB
Dart
import 'dart:io';
|
|
import 'dart:typed_data';
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:jj_mkvmaker/app/app_controller.dart';
|
|
import 'package:jj_mkvmaker/core/sqlite_reader.dart';
|
|
import 'package:jj_mkvmaker/platform/windows/desktop_storage_service.dart';
|
|
import 'package:jj_mkvmaker/platform/windows/process_media_tool.dart';
|
|
import 'package:jj_mkvmaker/services/platform_services.dart';
|
|
import 'package:jj_mkvmaker/ui/sqlite_viewer.dart';
|
|
import 'package:path/path.dart' as p;
|
|
|
|
/// X-plore 비교 5-1 ④ (관리자 지시 10/21): SQLite 읽기 전용 - 시험 파일은 진짜 SQLite (python sqlite3) 가 만든 것
|
|
/// (tool/fixtures/make_sqlite_fixtures.py → test/fixtures/sqlite)
|
|
void main() {
|
|
String fx(String name) => p.join('test', 'fixtures', 'sqlite', name);
|
|
|
|
test('basic.db: 테이블 · 열 이름 (INTEGER PRIMARY KEY · 따옴표 · 제약 줄 뺌) · WITHOUT ROWID 표시', () async {
|
|
final db = await SqliteFile.open(fx('basic.db'));
|
|
addTearDown(db.close);
|
|
final ts = await db.tables();
|
|
expect(ts.map((t) => t.name), ['people', 'odd name', 'kv']);
|
|
expect(ts[0].displayColumns, ['id', 'name', 'age', 'score', 'photo', 'note']);
|
|
expect(ts[1].displayColumns, ['a b', 'c', 'd', 'e'], reason: '따옴표 · [ ] · ` 이름, CONSTRAINT 줄은 빼고, DEFAULT (1+2) 의 쉼표에 속지 않음');
|
|
expect(ts[2].withoutRowid, isTrue);
|
|
await expectLater(db.rows(ts[2]), throwsA(isA<SqliteFormatException>()));
|
|
});
|
|
|
|
test('basic.db: 값 - 한글 · 일본어 · NULL · 음수 · 64비트 정수 · 실수 · BLOB · 넘친 페이지의 긴 글 · rowid = INTEGER PRIMARY KEY', () async {
|
|
final db = await SqliteFile.open(fx('basic.db'));
|
|
addTearDown(db.close);
|
|
final people = (await db.tables()).first;
|
|
final rows = await db.rows(people);
|
|
expect(rows.map((r) => r.rowid), [1, 2, 5, 7]);
|
|
expect(rows[0].values.take(4), [1, '홍길동', 30, 91.5]);
|
|
expect(rows[0].values[4], Uint8List.fromList([0, 1, 2]));
|
|
expect(rows[0].values[5], isNull);
|
|
expect(rows[1].values.sublist(2, 4), [-5, -0.25]);
|
|
expect(rows[1].values[5], 'x' * 5000, reason: '넘친 페이지 (페이지 1024 바이트) 를 이어 읽음');
|
|
expect(rows[2].values.sublist(0, 4), [5, '日本語', 9007199254740993, 1e100]);
|
|
expect(rows[2].values[4], Uint8List(0));
|
|
expect(rows[3].values.sublist(1, 3), [null, 0]);
|
|
expect(rows[3].values[5], '줄\n바꿈');
|
|
expect(await db.count(people), 4);
|
|
final odd = (await db.tables())[1];
|
|
expect((await db.rows(odd)).single.values, ['v', 1, 2, 3]);
|
|
});
|
|
|
|
test('big.db: 2000 행 (안쪽 페이지 여러 단계) - 차례대로 · 건너뛰기 · 개수', () async {
|
|
final db = await SqliteFile.open(fx('big.db'));
|
|
addTearDown(db.close);
|
|
final t = (await db.tables()).single;
|
|
expect(await db.count(t), 2000);
|
|
final first = await db.rows(t, limit: 3);
|
|
expect(first.map((r) => r.values[1]), ['row 0001 y', 'row 0002 yy', 'row 0003 yyy']);
|
|
final page = await db.rows(t, offset: 1500, limit: 2);
|
|
expect(page.map((r) => r.rowid), [1501, 1502]);
|
|
final all = await db.rows(t, limit: 5000);
|
|
expect(all.length, 2000);
|
|
expect([for (final r in all) r.rowid], [for (var i = 1; i <= 2000; i++) i], reason: 'rowid 차례');
|
|
});
|
|
|
|
test('utf16.db: UTF-16LE 글', () async {
|
|
final db = await SqliteFile.open(fx('utf16.db'));
|
|
addTearDown(db.close);
|
|
final t = (await db.tables()).single;
|
|
expect(db.encoding, 2);
|
|
expect((await db.rows(t)).map((r) => r.values.single), ['가나다', 'abc']);
|
|
});
|
|
|
|
test('SQLite 가 아닌 파일 · 잘린 파일은 SqliteFormatException (파일을 바꾸지 않음)', () async {
|
|
final tmp = Directory.systemTemp.createTempSync('jj_sqlite_');
|
|
addTearDown(() => tmp.deleteSync(recursive: true));
|
|
final txt = File(p.join(tmp.path, 'a.db'))..writeAsStringSync('hello');
|
|
await expectLater(SqliteFile.open(txt.path), throwsA(isA<SqliteFormatException>()));
|
|
// 머리만 남기고 자른 파일: 열 수는 있어도 페이지를 읽을 때 오류
|
|
final cut = File(p.join(tmp.path, 'cut.db'))..writeAsBytesSync(File(fx('big.db')).readAsBytesSync().sublist(0, 1024));
|
|
final db = await SqliteFile.open(cut.path);
|
|
addTearDown(db.close);
|
|
final t = (await db.tables()).single;
|
|
await expectLater(db.rows(t, limit: 5000), throwsA(isA<SqliteFormatException>()));
|
|
final before = File(fx('basic.db')).readAsBytesSync();
|
|
final b = await SqliteFile.open(fx('basic.db'));
|
|
await b.rows((await b.tables()).first);
|
|
await b.close();
|
|
expect(File(fx('basic.db')).readAsBytesSync(), before, reason: '읽기만');
|
|
});
|
|
Future<void> openPage(WidgetTester tester, String path, bool Function() ready) async {
|
|
tester.view.physicalSize = const Size(1200, 900);
|
|
tester.view.devicePixelRatio = 1;
|
|
addTearDown(tester.view.reset);
|
|
final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService()));
|
|
await tester.pumpWidget(MaterialApp(home: SqliteViewerPage(c: c, path: path)));
|
|
for (var i = 0; i < 500 && !ready(); i++) {
|
|
await tester.runAsync(() => Future<void>.delayed(const Duration(milliseconds: 30)));
|
|
await tester.pump();
|
|
}
|
|
}
|
|
|
|
testWidgets('보기 화면: 첫 테이블 · 행 수 · 값 (NULL · BLOB) · 칸을 누르면 전체 값 · 다른 테이블 고르기', (tester) async {
|
|
await openPage(tester, fx('basic.db'), () => find.text('홍길동').evaluate().isNotEmpty);
|
|
expect(find.text('홍길동'), findsOneWidget);
|
|
expect(find.text('4행'), findsOneWidget);
|
|
expect(find.text('NULL'), findsWidgets);
|
|
expect(find.text('(BLOB 3바이트)'), findsOneWidget);
|
|
expect(find.textContaining('읽기 전용'), findsOneWidget);
|
|
await tester.tap(find.text('홍길동'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.widgetWithText(AlertDialog, 'name'), findsOneWidget);
|
|
await tester.tap(find.text('닫기'));
|
|
await tester.pumpAndSettle();
|
|
// WITHOUT ROWID 테이블은 안내
|
|
await tester.tap(find.byType(DropdownButton<SqliteTable>));
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.text('kv').last);
|
|
for (var i = 0; i < 20; i++) {
|
|
await tester.runAsync(() => Future<void>.delayed(const Duration(milliseconds: 20)));
|
|
await tester.pump();
|
|
}
|
|
expect(find.text('WITHOUT ROWID 테이블은 아직 볼 수 없습니다'), findsOneWidget);
|
|
await tester.pumpWidget(const SizedBox());
|
|
});
|
|
|
|
testWidgets('보기 화면: 2000 행은 200 씩 [더 보기] · SQLite 가 아니면 알림', (tester) async {
|
|
await openPage(tester, fx('big.db'), () => find.textContaining('더 보기').evaluate().isNotEmpty);
|
|
expect(find.text('더 보기 (200 / 2000)'), findsOneWidget);
|
|
await tester.tap(find.text('더 보기 (200 / 2000)'));
|
|
for (var i = 0; i < 50 && find.text('더 보기 (400 / 2000)').evaluate().isEmpty; i++) {
|
|
await tester.runAsync(() => Future<void>.delayed(const Duration(milliseconds: 20)));
|
|
await tester.pump();
|
|
}
|
|
expect(find.text('더 보기 (400 / 2000)'), findsOneWidget);
|
|
await tester.pumpWidget(const SizedBox());
|
|
final tmp = Directory.systemTemp.createTempSync('jj_sqlv_');
|
|
addTearDown(() => tmp.deleteSync(recursive: true));
|
|
final txt = File(p.join(tmp.path, 'x.db'))..writeAsStringSync('not sqlite');
|
|
await openPage(tester, txt.path, () => find.text('SQLite 파일이 아니거나 깨졌습니다').evaluate().isNotEmpty);
|
|
expect(find.text('SQLite 파일이 아니거나 깨졌습니다'), findsOneWidget);
|
|
await tester.pumpWidget(const SizedBox());
|
|
});
|
|
}
|