import 'dart:convert'; import 'package:crypto/crypto.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; import 'package:kooltab2/services/pin_lock_service.dart'; class MockFlutterSecureStorage extends Mock implements FlutterSecureStorage {} String _computeHash(String pin, String salt) { final bytes = utf8.encode('$salt:$pin'); return sha256.convert(bytes).toString(); } void main() { late PinLockService service; late MockFlutterSecureStorage mockStorage; setUp(() { mockStorage = MockFlutterSecureStorage(); service = PinLockService(storage: mockStorage); }); group('PinLockService', () { group('hasPin', () { test('returns false when no hash stored', () async { when(() => mockStorage.read(key: 'pin_hash')) .thenAnswer((_) async => null); final result = await service.hasPin(); expect(result, false); }); test('returns false when hash is empty', () async { when(() => mockStorage.read(key: 'pin_hash')) .thenAnswer((_) async => ''); final result = await service.hasPin(); expect(result, false); }); test('returns true when hash exists', () async { when(() => mockStorage.read(key: 'pin_hash')) .thenAnswer((_) async => 'somehash'); final result = await service.hasPin(); expect(result, true); }); }); group('setPin', () { test('stores salt and hash', () async { when(() => mockStorage.write(key: any(named: 'key'), value: any(named: 'value'))) .thenAnswer((_) async {}); await service.setPin('1234'); verify(() => mockStorage.write(key: 'pin_salt', value: any(named: 'value'))).called(1); verify(() => mockStorage.write(key: 'pin_hash', value: any(named: 'value'))).called(1); }); test('generates unique salts for different pins', () async { when(() => mockStorage.write(key: any(named: 'key'), value: any(named: 'value'))) .thenAnswer((_) async {}); await service.setPin('1234'); final firstSaltCapture = verify(() => mockStorage.write( key: 'pin_salt', value: captureAny(named: 'value'), )).captured.first; await service.setPin('5678'); final secondSaltCapture = verify(() => mockStorage.write( key: 'pin_salt', value: captureAny(named: 'value'), )).captured.last; expect(firstSaltCapture, isNot(equals(secondSaltCapture))); }); }); group('verifyPin', () { test('returns false when salt is missing', () async { when(() => mockStorage.read(key: 'pin_salt')) .thenAnswer((_) async => null); when(() => mockStorage.read(key: 'pin_hash')) .thenAnswer((_) async => 'somehash'); final result = await service.verifyPin('1234'); expect(result, false); }); test('returns false when hash is missing', () async { when(() => mockStorage.read(key: 'pin_salt')) .thenAnswer((_) async => 'somesalt'); when(() => mockStorage.read(key: 'pin_hash')) .thenAnswer((_) async => null); final result = await service.verifyPin('1234'); expect(result, false); }); test('returns true for correct pin', () async { const salt = 'test_salt'; const pin = '1234'; final expectedHash = _computeHash(pin, salt); when(() => mockStorage.read(key: 'pin_salt')) .thenAnswer((_) async => salt); when(() => mockStorage.read(key: 'pin_hash')) .thenAnswer((_) async => expectedHash); final result = await service.verifyPin('1234'); expect(result, true); }); test('returns false for incorrect pin', () async { const salt = 'test_salt'; final correctHash = _computeHash('1234', salt); when(() => mockStorage.read(key: 'pin_salt')) .thenAnswer((_) async => salt); when(() => mockStorage.read(key: 'pin_hash')) .thenAnswer((_) async => correctHash); final result = await service.verifyPin('5678'); expect(result, false); }); }); group('clearPin', () { test('deletes both salt and hash', () async { when(() => mockStorage.delete(key: any(named: 'key'))) .thenAnswer((_) async {}); await service.clearPin(); verify(() => mockStorage.delete(key: 'pin_salt')).called(1); verify(() => mockStorage.delete(key: 'pin_hash')).called(1); }); }); }); }