d3ro-voice/apps/mobile-rn/__tests__/pending-invite.test.ts
2026-08-29 18:33:45 +09:00

72 lines
2.5 KiB
TypeScript

jest.mock('react-native-keychain', () => ({
ACCESSIBLE: { AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY: 'device-only' },
SECURITY_LEVEL: { SECURE_SOFTWARE: 1 },
STORAGE_TYPE: { AES_GCM_NO_AUTH: 'aes-gcm' },
getGenericPassword: jest.fn().mockResolvedValue(false),
setGenericPassword: jest.fn(),
resetGenericPassword: jest.fn(),
}))
import * as Keychain from 'react-native-keychain'
import {
loadPendingInviteToken,
savePendingInviteToken,
} from '../src/features/teams/pending-invite'
const TOKEN = 'Abcdefghijklmnopqrstuvwxyz_12345'
const PENDING_SERVICE = 'com.d3ro.voice.pending-team-invite.v1'
describe('pending invitation secure storage', () => {
beforeEach(() => {
jest.clearAllMocks()
;(Keychain.getGenericPassword as jest.Mock).mockReset().mockResolvedValue(false)
})
test('stores only a validated token in device-bound secure storage', async () => {
;(Keychain.setGenericPassword as jest.Mock).mockResolvedValue({ service: 'stored' })
await savePendingInviteToken(TOKEN)
expect(Keychain.setGenericPassword).toHaveBeenCalledWith(
'pending-invite',
TOKEN,
expect.objectContaining({
service: 'com.d3ro.voice.pending-team-invite.v1',
accessible: 'device-only',
}),
)
})
test('rejects malformed tokens before touching secure storage', async () => {
await expect(savePendingInviteToken('short')).rejects.toMatchObject({
code: 'invalid-token',
})
expect(Keychain.setGenericPassword).not.toHaveBeenCalled()
})
test('clears corrupt or foreign secure-storage records', async () => {
const pendingResponses = [
{ username: 'other', password: TOKEN },
{ username: 'pending-invite', password: 'short' },
]
;(Keychain.getGenericPassword as jest.Mock).mockImplementation(
async ({ service }: { service?: string } = {}) => (
service === PENDING_SERVICE ? pendingResponses.shift() ?? false : false
),
)
;(Keychain.resetGenericPassword as jest.Mock).mockResolvedValue(true)
await expect(loadPendingInviteToken()).resolves.toBeNull()
await expect(loadPendingInviteToken()).resolves.toBeNull()
expect(Keychain.resetGenericPassword).toHaveBeenCalledTimes(2)
})
test('returns a valid stored invitation token', async () => {
;(Keychain.getGenericPassword as jest.Mock).mockImplementation(
async ({ service }: { service?: string } = {}) => service === PENDING_SERVICE
? { username: 'pending-invite', password: TOKEN }
: false,
)
await expect(loadPendingInviteToken()).resolves.toBe(TOKEN)
})
})