- Create wifiPasswordStore service for encrypted password storage - Replace AsyncStorage with SecureStore for WiFi credentials - Add automatic migration from AsyncStorage to SecureStore - Integrate WiFi password cleanup into logout process - Add comprehensive test suite for password storage operations - Update setup-wifi screen to use secure storage Security improvements: - WiFi passwords now stored encrypted via expo-secure-store - Passwords automatically cleared on user logout - Seamless migration for existing users 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
135 lines
4.8 KiB
TypeScript
135 lines
4.8 KiB
TypeScript
/**
|
|
* API Logout Tests
|
|
* Tests for logout functionality including BLE cleanup and WiFi password cleanup
|
|
*/
|
|
|
|
import { api, setOnLogoutBLECleanupCallback } from '../api';
|
|
import * as SecureStore from 'expo-secure-store';
|
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
import * as wifiPasswordStore from '../wifiPasswordStore';
|
|
|
|
// Mock dependencies
|
|
jest.mock('expo-secure-store');
|
|
jest.mock('@react-native-async-storage/async-storage');
|
|
jest.mock('../wifiPasswordStore');
|
|
|
|
describe('API logout with BLE cleanup', () => {
|
|
let bleCleanupCallback: jest.Mock;
|
|
|
|
beforeEach(() => {
|
|
// Reset mocks
|
|
jest.clearAllMocks();
|
|
|
|
// Create mock BLE cleanup callback
|
|
bleCleanupCallback = jest.fn().mockResolvedValue(undefined);
|
|
});
|
|
|
|
describe('logout without BLE cleanup callback', () => {
|
|
it('should clear all auth tokens and data', async () => {
|
|
await api.logout();
|
|
|
|
// Verify WiFi passwords are cleared
|
|
expect(wifiPasswordStore.clearAllWiFiPasswords).toHaveBeenCalledTimes(1);
|
|
|
|
// Verify SecureStore items are deleted
|
|
expect(SecureStore.deleteItemAsync).toHaveBeenCalledWith('accessToken');
|
|
expect(SecureStore.deleteItemAsync).toHaveBeenCalledWith('userId');
|
|
expect(SecureStore.deleteItemAsync).toHaveBeenCalledWith('userEmail');
|
|
expect(SecureStore.deleteItemAsync).toHaveBeenCalledWith('onboardingCompleted');
|
|
expect(SecureStore.deleteItemAsync).toHaveBeenCalledWith('legacyAccessToken');
|
|
expect(SecureStore.deleteItemAsync).toHaveBeenCalledWith('privileges');
|
|
expect(SecureStore.deleteItemAsync).toHaveBeenCalledWith('maxRole');
|
|
expect(SecureStore.deleteItemAsync).toHaveBeenCalledWith('userAvatar');
|
|
|
|
// Verify AsyncStorage items are removed
|
|
expect(AsyncStorage.removeItem).toHaveBeenCalledWith('wellnuo_local_beneficiaries');
|
|
});
|
|
|
|
it('should complete logout even if no BLE callback is set', async () => {
|
|
// Should not throw
|
|
await expect(api.logout()).resolves.not.toThrow();
|
|
});
|
|
|
|
it('should continue logout even if WiFi password cleanup fails', async () => {
|
|
// Make WiFi password cleanup fail
|
|
(wifiPasswordStore.clearAllWiFiPasswords as jest.Mock).mockRejectedValue(
|
|
new Error('WiFi cleanup failed')
|
|
);
|
|
|
|
// Logout should still complete
|
|
await expect(api.logout()).resolves.not.toThrow();
|
|
|
|
// Verify WiFi cleanup was attempted
|
|
expect(wifiPasswordStore.clearAllWiFiPasswords).toHaveBeenCalled();
|
|
|
|
// Verify auth data was still cleared despite WiFi cleanup failure
|
|
expect(SecureStore.deleteItemAsync).toHaveBeenCalledWith('accessToken');
|
|
expect(SecureStore.deleteItemAsync).toHaveBeenCalledWith('userId');
|
|
});
|
|
});
|
|
|
|
describe('logout with BLE cleanup callback', () => {
|
|
beforeEach(() => {
|
|
// Set the BLE cleanup callback
|
|
setOnLogoutBLECleanupCallback(bleCleanupCallback);
|
|
});
|
|
|
|
it('should call BLE cleanup callback before clearing data', async () => {
|
|
await api.logout();
|
|
|
|
// Verify BLE cleanup was called
|
|
expect(bleCleanupCallback).toHaveBeenCalledTimes(1);
|
|
|
|
// Verify auth data was still cleared
|
|
expect(SecureStore.deleteItemAsync).toHaveBeenCalledWith('accessToken');
|
|
});
|
|
|
|
it('should continue logout even if BLE cleanup fails', async () => {
|
|
// Make BLE cleanup fail
|
|
bleCleanupCallback.mockRejectedValue(new Error('BLE cleanup failed'));
|
|
|
|
// Logout should still complete
|
|
await expect(api.logout()).resolves.not.toThrow();
|
|
|
|
// Verify BLE cleanup was attempted
|
|
expect(bleCleanupCallback).toHaveBeenCalled();
|
|
|
|
// Verify auth data was still cleared despite BLE failure
|
|
expect(SecureStore.deleteItemAsync).toHaveBeenCalledWith('accessToken');
|
|
expect(SecureStore.deleteItemAsync).toHaveBeenCalledWith('userId');
|
|
});
|
|
|
|
it('should log error but not throw if BLE cleanup fails', async () => {
|
|
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
|
|
bleCleanupCallback.mockRejectedValue(new Error('BLE error'));
|
|
|
|
await api.logout();
|
|
|
|
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
'[API] BLE cleanup failed during logout:',
|
|
expect.any(Error)
|
|
);
|
|
|
|
consoleErrorSpy.mockRestore();
|
|
});
|
|
});
|
|
|
|
describe('BLE cleanup callback registration', () => {
|
|
it('should allow setting BLE cleanup callback', () => {
|
|
const callback = jest.fn();
|
|
expect(() => setOnLogoutBLECleanupCallback(callback)).not.toThrow();
|
|
});
|
|
|
|
it('should allow replacing BLE cleanup callback', () => {
|
|
const callback1 = jest.fn().mockResolvedValue(undefined);
|
|
const callback2 = jest.fn().mockResolvedValue(undefined);
|
|
|
|
setOnLogoutBLECleanupCallback(callback1);
|
|
setOnLogoutBLECleanupCallback(callback2);
|
|
|
|
// Only callback2 should be called
|
|
expect(callback1).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
});
|