WellNuo/services/__tests__/api.logout.test.ts
Sergei bbc45ddb5f Implement secure WiFi password storage using SecureStore
- 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>
2026-01-29 11:13:37 -08:00

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();
});
});
});