/** * Tests for WiFi Password Secure Storage Service */ import * as SecureStore from 'expo-secure-store'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { saveWiFiPassword, getWiFiPassword, getAllWiFiPasswords, removeWiFiPassword, clearAllWiFiPasswords, migrateFromAsyncStorage, } from '../wifiPasswordStore'; // Mock SecureStore jest.mock('expo-secure-store', () => ({ setItemAsync: jest.fn(), getItemAsync: jest.fn(), deleteItemAsync: jest.fn(), })); // Mock AsyncStorage jest.mock('@react-native-async-storage/async-storage', () => ({ default: { getItem: jest.fn(), removeItem: jest.fn(), }, })); describe('wifiPasswordStore', () => { beforeEach(() => { jest.clearAllMocks(); }); describe('saveWiFiPassword', () => { it('should save a new WiFi password', async () => { (SecureStore.getItemAsync as jest.Mock).mockResolvedValueOnce(null); await saveWiFiPassword('MyNetwork', 'password123'); expect(SecureStore.setItemAsync).toHaveBeenCalledWith( 'WIFI_PASSWORDS', JSON.stringify({ MyNetwork: 'password123' }) ); }); it('should update an existing WiFi password', async () => { const existingPasswords = { Network1: 'pass1', Network2: 'pass2' }; (SecureStore.getItemAsync as jest.Mock).mockResolvedValueOnce( JSON.stringify(existingPasswords) ); await saveWiFiPassword('Network1', 'newpass1'); expect(SecureStore.setItemAsync).toHaveBeenCalledWith( 'WIFI_PASSWORDS', JSON.stringify({ Network1: 'newpass1', Network2: 'pass2' }) ); }); it('should add password to existing map', async () => { const existingPasswords = { Network1: 'pass1' }; (SecureStore.getItemAsync as jest.Mock).mockResolvedValueOnce( JSON.stringify(existingPasswords) ); await saveWiFiPassword('Network2', 'pass2'); expect(SecureStore.setItemAsync).toHaveBeenCalledWith( 'WIFI_PASSWORDS', JSON.stringify({ Network1: 'pass1', Network2: 'pass2' }) ); }); }); describe('getWiFiPassword', () => { it('should return password for existing network', async () => { const passwords = { MyNetwork: 'password123', OtherNetwork: 'pass456' }; (SecureStore.getItemAsync as jest.Mock).mockResolvedValueOnce( JSON.stringify(passwords) ); const password = await getWiFiPassword('MyNetwork'); expect(password).toBe('password123'); }); it('should return undefined for non-existing network', async () => { const passwords = { MyNetwork: 'password123' }; (SecureStore.getItemAsync as jest.Mock).mockResolvedValueOnce( JSON.stringify(passwords) ); const password = await getWiFiPassword('NonExistent'); expect(password).toBeUndefined(); }); it('should return undefined when no passwords saved', async () => { (SecureStore.getItemAsync as jest.Mock).mockResolvedValueOnce(null); const password = await getWiFiPassword('MyNetwork'); expect(password).toBeUndefined(); }); it('should handle SecureStore errors gracefully', async () => { (SecureStore.getItemAsync as jest.Mock).mockRejectedValueOnce( new Error('SecureStore error') ); const password = await getWiFiPassword('MyNetwork'); expect(password).toBeUndefined(); }); }); describe('getAllWiFiPasswords', () => { it('should return all saved passwords', async () => { const passwords = { Network1: 'pass1', Network2: 'pass2', Network3: 'pass3' }; (SecureStore.getItemAsync as jest.Mock).mockResolvedValueOnce( JSON.stringify(passwords) ); const result = await getAllWiFiPasswords(); expect(result).toEqual(passwords); }); it('should return empty object when no passwords saved', async () => { (SecureStore.getItemAsync as jest.Mock).mockResolvedValueOnce(null); const result = await getAllWiFiPasswords(); expect(result).toEqual({}); }); it('should handle SecureStore errors gracefully', async () => { (SecureStore.getItemAsync as jest.Mock).mockRejectedValueOnce( new Error('SecureStore error') ); const result = await getAllWiFiPasswords(); expect(result).toEqual({}); }); }); describe('removeWiFiPassword', () => { it('should remove specific network password', async () => { const passwords = { Network1: 'pass1', Network2: 'pass2', Network3: 'pass3' }; (SecureStore.getItemAsync as jest.Mock).mockResolvedValueOnce( JSON.stringify(passwords) ); await removeWiFiPassword('Network2'); expect(SecureStore.setItemAsync).toHaveBeenCalledWith( 'WIFI_PASSWORDS', JSON.stringify({ Network1: 'pass1', Network3: 'pass3' }) ); }); it('should delete key when removing last password', async () => { const passwords = { Network1: 'pass1' }; (SecureStore.getItemAsync as jest.Mock).mockResolvedValueOnce( JSON.stringify(passwords) ); await removeWiFiPassword('Network1'); expect(SecureStore.deleteItemAsync).toHaveBeenCalledWith('WIFI_PASSWORDS'); expect(SecureStore.setItemAsync).not.toHaveBeenCalled(); }); it('should handle removal of non-existent network', async () => { const passwords = { Network1: 'pass1', Network2: 'pass2' }; (SecureStore.getItemAsync as jest.Mock).mockResolvedValueOnce( JSON.stringify(passwords) ); await removeWiFiPassword('NonExistent'); // Should still update the store with unchanged passwords expect(SecureStore.setItemAsync).toHaveBeenCalledWith( 'WIFI_PASSWORDS', JSON.stringify(passwords) ); }); }); describe('clearAllWiFiPasswords', () => { it('should delete all WiFi passwords', async () => { await clearAllWiFiPasswords(); expect(SecureStore.deleteItemAsync).toHaveBeenCalledWith('WIFI_PASSWORDS'); }); it('should handle errors gracefully', async () => { (SecureStore.deleteItemAsync as jest.Mock).mockRejectedValueOnce( new Error('Delete failed') ); await expect(clearAllWiFiPasswords()).rejects.toThrow('Delete failed'); }); }); describe('migrateFromAsyncStorage', () => { it('should migrate passwords from AsyncStorage to SecureStore', async () => { const oldPasswords = { Network1: 'pass1', Network2: 'pass2' }; // No existing data in SecureStore (SecureStore.getItemAsync as jest.Mock).mockResolvedValueOnce(null); // Old data in AsyncStorage (AsyncStorage.getItem as jest.Mock).mockResolvedValueOnce( JSON.stringify(oldPasswords) ); await migrateFromAsyncStorage(); // Should save to SecureStore expect(SecureStore.setItemAsync).toHaveBeenCalledWith( 'WIFI_PASSWORDS', JSON.stringify(oldPasswords) ); // Should remove from AsyncStorage expect(AsyncStorage.removeItem).toHaveBeenCalledWith('WIFI_PASSWORDS'); expect(AsyncStorage.removeItem).toHaveBeenCalledWith('LAST_WIFI_PASSWORD'); }); it('should skip migration if data already exists in SecureStore', async () => { const existingPasswords = { Network1: 'pass1' }; // Data already exists in SecureStore (SecureStore.getItemAsync as jest.Mock).mockResolvedValueOnce( JSON.stringify(existingPasswords) ); await migrateFromAsyncStorage(); // Should not call AsyncStorage expect(AsyncStorage.getItem).not.toHaveBeenCalled(); expect(SecureStore.setItemAsync).not.toHaveBeenCalled(); }); it('should skip migration if no data in AsyncStorage', async () => { // No data in SecureStore (SecureStore.getItemAsync as jest.Mock).mockResolvedValueOnce(null); // No data in AsyncStorage (AsyncStorage.getItem as jest.Mock).mockResolvedValueOnce(null); await migrateFromAsyncStorage(); // Should not migrate anything expect(SecureStore.setItemAsync).not.toHaveBeenCalled(); expect(AsyncStorage.removeItem).not.toHaveBeenCalled(); }); it('should not throw error on migration failure', async () => { (SecureStore.getItemAsync as jest.Mock).mockResolvedValueOnce(null); (AsyncStorage.getItem as jest.Mock).mockRejectedValueOnce( new Error('AsyncStorage error') ); // Should not throw await expect(migrateFromAsyncStorage()).resolves.toBeUndefined(); }); }); describe('Special characters in SSID and password', () => { it('should handle special characters in SSID', async () => { (SecureStore.getItemAsync as jest.Mock).mockResolvedValueOnce(null); const ssid = "Network's-Name!@#$%^&*()"; const password = 'password123'; await saveWiFiPassword(ssid, password); expect(SecureStore.setItemAsync).toHaveBeenCalledWith( 'WIFI_PASSWORDS', JSON.stringify({ [ssid]: password }) ); }); it('should handle special characters in password', async () => { (SecureStore.getItemAsync as jest.Mock).mockResolvedValueOnce(null); const ssid = 'MyNetwork'; const password = "P@ssw0rd!#$%^&*()_+-=[]{}|;':,.<>?"; await saveWiFiPassword(ssid, password); expect(SecureStore.setItemAsync).toHaveBeenCalledWith( 'WIFI_PASSWORDS', JSON.stringify({ [ssid]: password }) ); }); it('should handle unicode characters', async () => { (SecureStore.getItemAsync as jest.Mock).mockResolvedValueOnce(null); const ssid = '我的网络'; const password = 'пароль123'; await saveWiFiPassword(ssid, password); expect(SecureStore.setItemAsync).toHaveBeenCalledWith( 'WIFI_PASSWORDS', JSON.stringify({ [ssid]: password }) ); }); }); });