1import { getUserStatePath } from '@expo/config/build/getUserState'; 2import { fs, vol } from 'memfs'; 3import nock from 'nock'; 4 5import { getExpoApiBaseUrl } from '../../endpoint'; 6import UserSettings from '../UserSettings'; 7import { Actor, getActorDisplayName, getUserAsync, loginAsync, logoutAsync } from '../user'; 8 9jest.unmock('../UserSettings'); 10jest.mock('../../graphql/client', () => ({ 11 graphqlClient: { 12 query: () => { 13 return { 14 toPromise: () => 15 Promise.resolve({ data: { viewer: { id: 'USER_ID', username: 'USERNAME' } } }), 16 }; 17 }, 18 }, 19})); 20jest.mock('../../graphql/queries/UserQuery', () => ({ 21 UserQuery: { 22 currentUserAsync: async () => ({ __typename: 'User', username: 'USERNAME', id: 'USER_ID' }), 23 }, 24})); 25 26beforeEach(() => { 27 vol.reset(); 28}); 29 30const userStub: Actor = { 31 __typename: 'User', 32 id: 'userId', 33 username: 'username', 34 accounts: [], 35 isExpoAdmin: false, 36}; 37 38const robotStub: Actor = { 39 __typename: 'Robot', 40 id: 'userId', 41 firstName: 'GLaDOS', 42 accounts: [], 43 isExpoAdmin: false, 44}; 45 46function mockLoginRequest() { 47 nock(getExpoApiBaseUrl()) 48 .post('/v2/auth/loginAsync') 49 .reply(200, { data: { sessionSecret: 'SESSION_SECRET' } }); 50} 51 52describe(getUserAsync, () => { 53 it('skips fetching user without access token or session secret', async () => { 54 expect(await getUserAsync()).toBeUndefined(); 55 }); 56 57 it('fetches user when access token is defined', async () => { 58 process.env.EXPO_TOKEN = 'accesstoken'; 59 expect(await getUserAsync()).toMatchObject({ __typename: 'User' }); 60 }); 61 62 it('fetches user when session secret is defined', async () => { 63 mockLoginRequest(); 64 65 await loginAsync({ username: 'USERNAME', password: 'PASSWORD' }); 66 expect(await getUserAsync()).toMatchObject({ __typename: 'User' }); 67 }); 68}); 69 70describe(loginAsync, () => { 71 it('saves user data to ~/.expo/state.json', async () => { 72 mockLoginRequest(); 73 await loginAsync({ username: 'USERNAME', password: 'PASSWORD' }); 74 75 expect(await fs.promises.readFile(getUserStatePath(), 'utf8')).toMatchInlineSnapshot(` 76 "{ 77 \\"auth\\": { 78 \\"sessionSecret\\": \\"SESSION_SECRET\\", 79 \\"userId\\": \\"USER_ID\\", 80 \\"username\\": \\"USERNAME\\", 81 \\"currentConnection\\": \\"Username-Password-Authentication\\" 82 } 83 } 84 " 85 `); 86 }); 87}); 88 89describe(logoutAsync, () => { 90 it('removes the session secret', async () => { 91 mockLoginRequest(); 92 await loginAsync({ username: 'USERNAME', password: 'PASSWORD' }); 93 expect(UserSettings.getSession()?.sessionSecret).toBe('SESSION_SECRET'); 94 95 await logoutAsync(); 96 expect(UserSettings.getSession()?.sessionSecret).toBeUndefined(); 97 }); 98}); 99 100describe(getActorDisplayName, () => { 101 it('returns anonymous for unauthenticated users', () => { 102 expect(getActorDisplayName()).toBe('anonymous'); 103 }); 104 105 it('returns username for user actors', () => { 106 expect(getActorDisplayName(userStub)).toBe(userStub.username); 107 }); 108 109 it('returns firstName with robot prefix for robot actors', () => { 110 expect(getActorDisplayName(robotStub)).toBe(`${robotStub.firstName} (robot)`); 111 }); 112 113 it('returns robot prefix only for robot actors without firstName', () => { 114 expect(getActorDisplayName({ ...robotStub, firstName: undefined })).toBe('robot'); 115 }); 116}); 117