// Proves the Phase-0 vault root recording: the path persists through // SharedPreferences, vaultRootValid() is true ONLY when a path is set AND its // directory exists, and clearVaultRoot() forgets it. Uses a real temp dir for // existence checks and a mock SharedPreferences for persistence. import 'dart:io'; import 'package:flutter_test/flutter_test.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:badnote/services/vault_service.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); late Directory tempDir; setUp(() async { SharedPreferences.setMockInitialValues({}); tempDir = await Directory.systemTemp.createTemp('vault_service_test'); }); tearDown(() async { if (await tempDir.exists()) { await tempDir.delete(recursive: true); } }); Future makeService() async { final prefs = await SharedPreferences.getInstance(); return VaultService.forTest(prefs); } test('starts with no vault root and is invalid', () async { final vault = await makeService(); expect(vault.vaultRoot, isNull); expect(await vault.vaultRootValid(), isFalse); }); test('setVaultRoot persists the path', () async { final vault = await makeService(); await vault.setVaultRoot(tempDir.path); expect(vault.vaultRoot, tempDir.path); // Re-read through a fresh service over the SAME prefs store: persisted. final prefs = await SharedPreferences.getInstance(); expect(prefs.getString(VaultService.vaultRootKey), tempDir.path); final reopened = VaultService.forTest(prefs); expect(reopened.vaultRoot, tempDir.path); }); test('vaultRootValid is true only when set AND the dir exists', () async { final vault = await makeService(); // Set to an existing dir → valid. await vault.setVaultRoot(tempDir.path); expect(await vault.vaultRootValid(), isTrue); // Point at a path that does not exist → invalid (folder went missing). final missing = '${tempDir.path}${Platform.pathSeparator}gone'; await vault.setVaultRoot(missing); expect(vault.vaultRoot, missing); expect(await vault.vaultRootValid(), isFalse); }); test('clearVaultRoot forgets the path and becomes invalid', () async { final vault = await makeService(); await vault.setVaultRoot(tempDir.path); expect(await vault.vaultRootValid(), isTrue); await vault.clearVaultRoot(); expect(vault.vaultRoot, isNull); expect(await vault.vaultRootValid(), isFalse); }); test('empty stored path is treated as invalid', () async { final vault = await makeService(); await vault.setVaultRoot(''); expect(await vault.vaultRootValid(), isFalse); }); group('createNotebook', () { late Directory srcDir; setUp(() async { srcDir = await Directory.systemTemp.createTemp('vault_src'); }); tearDown(() async { if (await srcDir.exists()) await srcDir.delete(recursive: true); }); Future makeSource(String name, [String content = 'pdf-bytes']) async { final f = File('${srcDir.path}${Platform.pathSeparator}$name'); await f.writeAsString(content); return f.path; } test('creates folder, copies file, returns the in-vault path', () async { final vault = await makeService(); await vault.setVaultRoot(tempDir.path); final src = await makeSource('Calculus Lecture 3.pdf', 'hello'); final vaultPath = await vault.createNotebook(src); // Returned path is inside the vault, in a folder named from the file. final expectedFolder = '${tempDir.path}${Platform.pathSeparator}Calculus Lecture 3'; expect(Directory(expectedFolder).existsSync(), isTrue); expect( vaultPath, '$expectedFolder${Platform.pathSeparator}Calculus Lecture 3.pdf', ); // The file was copied with its contents intact. expect(File(vaultPath).existsSync(), isTrue); expect(File(vaultPath).readAsStringSync(), 'hello'); // The original is untouched (copy, not move). expect(File(src).existsSync(), isTrue); }); test('de-duplicates the folder name on collision', () async { final vault = await makeService(); await vault.setVaultRoot(tempDir.path); final a = await makeSource('Notes.pdf', 'A'); final firstPath = await vault.createNotebook(a); expect( firstPath, '${tempDir.path}${Platform.pathSeparator}Notes${Platform.pathSeparator}Notes.pdf', ); // Re-import a file with the same basename → suffixed folder. final b = await makeSource('Notes.pdf', 'B'); final secondPath = await vault.createNotebook(b); expect( secondPath, '${tempDir.path}${Platform.pathSeparator}Notes 2${Platform.pathSeparator}Notes.pdf', ); expect(File(secondPath).readAsStringSync(), 'B'); // First copy still intact. expect(File(firstPath).readAsStringSync(), 'A'); }); test('throws when no vault root is set', () async { final vault = await makeService(); final src = await makeSource('x.pdf'); expect(() => vault.createNotebook(src), throwsStateError); }); }); group('scanNotebooks', () { Future writeFile(String path, [String content = 'x']) async { final f = File(path); await f.create(recursive: true); await f.writeAsString(content); return f.path; } test('empty / missing vault yields an empty list', () async { final vault = await makeService(); // No root set. expect(await vault.scanNotebooks(), isEmpty); // Root set but empty. await vault.setVaultRoot(tempDir.path); expect(await vault.scanNotebooks(), isEmpty); }); test('lists notebook folders that contain a source file', () async { final vault = await makeService(); await vault.setVaultRoot(tempDir.path); final sep = Platform.pathSeparator; // A PDF notebook with a sidecar. await writeFile('${tempDir.path}${sep}Lecture${sep}Lecture.pdf'); await writeFile( '${tempDir.path}${sep}Lecture${sep}Lecture.pdf.badnote.json', '{}', ); // A PPTX notebook without a sidecar. await writeFile('${tempDir.path}${sep}Deck${sep}Deck.pptx'); // A hidden vault-metadata folder is skipped. await writeFile('${tempDir.path}$sep.badnote${sep}vault.json', '{}'); // A folder with no importable file is skipped. await writeFile('${tempDir.path}${sep}Empty${sep}readme.txt'); final notebooks = await vault.scanNotebooks(); final byName = {for (final n in notebooks) n.filename: n}; expect(byName.keys, containsAll(['Lecture.pdf', 'Deck.pptx'])); expect(notebooks.length, 2); expect(byName['Lecture.pdf']!.docType, 'pdf'); expect(byName['Lecture.pdf']!.hasSidecar, isTrue); expect(byName['Deck.pptx']!.docType, 'pptx'); expect(byName['Deck.pptx']!.hasSidecar, isFalse); }); test('a created notebook is discoverable by the scan', () async { final vault = await makeService(); await vault.setVaultRoot(tempDir.path); final srcDir = await Directory.systemTemp.createTemp('vault_scan_src'); try { final src = '${srcDir.path}${Platform.pathSeparator}Doc.pdf'; await File(src).writeAsString('bytes'); await vault.createNotebook(src); final notebooks = await vault.scanNotebooks(); expect(notebooks.length, 1); expect(notebooks.first.filename, 'Doc.pdf'); expect(notebooks.first.docType, 'pdf'); } finally { await srcDir.delete(recursive: true); } }); }); }