import sdk from '@farcaster/miniapp-sdk'; import { renderHook } from '@testing-library/react'; import { act } from 'react'; import { type Mock, beforeEach, describe, expect, it, vi } from 'vitest'; import { parseSignInMessage, useAuthenticate } from './useAuthenticate'; import { useMiniKit } from './useMiniKit'; const signInMock = { message: 'test.frames.app wants you to sign in with your Ethereum account:\n0x123\n\nFarcaster Auth\n\nURI: https://test.frames.app/\nVersion: 1\nChain ID: 10\nNonce: z72pa2nz\nIssued At: 2025-03-25T15:45:27.312Z\nResources:\n- farcaster://fid/123456', signature: '0x123', }; vi.mock('@farcaster/miniapp-sdk', () => ({ default: { actions: { signIn: vi.fn(), }, }, })); vi.mock('./useMiniKit', () => ({ useMiniKit: vi.fn(), })); describe('useAuthenticate', () => { beforeEach(() => { (sdk.actions.signIn as Mock).mockResolvedValue(signInMock); (useMiniKit as Mock).mockReturnValue({ context: { client: { clientFid: 123456, }, }, }); vi.clearAllMocks(); }); it('should return a function', () => { const { result } = renderHook(() => useAuthenticate()); expect(typeof result.current.signIn).toBe('function'); }); it('should call sdk.actions.signIn when executed', async () => { const { result } = renderHook(() => useAuthenticate()); await act(async () => { await result.current.signIn({ nonce: 'z72pa2nz' }); }); expect(sdk.actions.signIn).toHaveBeenCalled(); }); it('should return the auth details on success', async () => { const { result } = renderHook(() => useAuthenticate()); const response = await result.current.signIn({ nonce: 'z72pa2nz' }); expect(response).toBe(signInMock); }); it('should return false if authentication fails', async () => { vi.spyOn(console, 'error').mockImplementation(vi.fn()); (sdk.actions.signIn as Mock).mockRejectedValue(new Error('Auth failed')); const { result } = renderHook(() => useAuthenticate()); const response = await result.current.signIn(); expect(response).toBe(false); }); it('should throw an error if the domain of the message does not match the passed in domain', async () => { const errorSpy = vi.spyOn(console, 'error').mockImplementation(vi.fn()); const { result } = renderHook(() => useAuthenticate('https://other.frames.app'), ); const response = await result.current.signIn({ nonce: 'z72pa2nz' }); expect(response).toBe(false); expect(errorSpy).toHaveBeenCalledWith(new Error('Domain mismatch')); }); it('should throw an error if the nonce of the message does not match the passed in nonce', async () => { const errorSpy = vi.spyOn(console, 'error').mockImplementation(vi.fn()); const { result } = renderHook(() => useAuthenticate()); const response = await result.current.signIn({ nonce: '123' }); expect(response).toBe(false); expect(errorSpy).toHaveBeenCalledWith(new Error('Nonce mismatch')); }); it('should throw an error if the fid of the message does not match the users fid', async () => { (useMiniKit as Mock).mockReturnValue({ context: { user: { fid: 123, }, }, }); const errorSpy = vi.spyOn(console, 'error').mockImplementation(vi.fn()); const { result } = renderHook(() => useAuthenticate()); const response = await result.current.signIn({ nonce: 'z72pa2nz' }); expect(response).toBe(false); expect(errorSpy).toHaveBeenCalledWith(new Error('Fid mismatch')); }); it('should skip validation if skipValidation is true', async () => { (useMiniKit as Mock).mockReturnValue({ context: { user: { fid: 123, }, }, }); const errorSpy = vi.spyOn(console, 'error').mockImplementation(vi.fn()); const { result } = renderHook(() => useAuthenticate(undefined, true)); const response = await result.current.signIn({ nonce: 'z72pa2nz', }); expect(response).toBe(signInMock); expect(errorSpy).not.toHaveBeenCalled(); }); it('should parse the message correctly', () => { const message = parseSignInMessage(signInMock.message); expect(message).toEqual({ domain: 'test.frames.app', address: '0x123', chainID: '10', issuedAt: '2025-03-25T15:45:27.312Z', nonce: 'z72pa2nz', resources: ['farcaster://fid/123456'], uri: 'https://test.frames.app/', version: '1', }); }); });