1import rnFixture from '../../plugins/__tests__/fixtures/react-native-project'; 2import { format } from '../../utils/XML'; 3import * as XML from '../../utils/XML'; 4import { AndroidManifest } from '../Manifest'; 5import { 6 addBlockedPermissions, 7 ensurePermission, 8 ensurePermissions, 9 getAndroidPermissions, 10 getPermissions, 11 removePermissions, 12 setAndroidPermissions, 13 withInternalBlockedPermissions, 14} from '../Permissions'; 15 16async function getFixtureManifestAsync() { 17 const manifest = (await XML.parseXMLAsync( 18 rnFixture['android/app/src/main/AndroidManifest.xml'] 19 )) as AndroidManifest; 20 21 removePermissions(manifest, [ 22 'android.permission.SYSTEM_ALERT_WINDOW', 23 'android.permission.VIBRATE', 24 'android.permission.READ_EXTERNAL_STORAGE', 25 'android.permission.WRITE_EXTERNAL_STORAGE', 26 ]); 27 return manifest; 28} 29 30describe(withInternalBlockedPermissions, () => { 31 it(`adds blocked permissions`, async () => { 32 const config = withInternalBlockedPermissions({ 33 slug: '', 34 name: '', 35 android: { 36 blockedPermissions: ['android.permission.ACCESS_FINE_LOCATION', 'OTHER'], 37 }, 38 }); 39 40 const { modResults } = await (config as any).mods.android.manifest({ 41 modRequest: {}, 42 modResults: await getFixtureManifestAsync(), 43 }); 44 45 expect(modResults).toEqual({ 46 manifest: { 47 $: { 48 'xmlns:android': expect.any(String), 49 // Added tools 50 'xmlns:tools': 'http://schemas.android.com/tools', 51 }, 52 'uses-permission': [ 53 expect.anything(), 54 // Added two blocked permissions 55 { 56 $: { 57 'android:name': 'android.permission.ACCESS_FINE_LOCATION', 58 'tools:node': 'remove', 59 }, 60 }, 61 { 62 $: { 63 'android:name': 'android.permission.OTHER', 64 'tools:node': 'remove', 65 }, 66 }, 67 ], 68 queries: expect.anything(), 69 application: expect.anything(), 70 }, 71 }); 72 }); 73 74 it(`does not add tools if there are no blocked permissions`, async () => { 75 const config = withInternalBlockedPermissions({ 76 slug: '', 77 name: '', 78 android: { 79 blockedPermissions: [], 80 }, 81 }); 82 83 // Doesn't even add the mod 84 expect((config as any).mods).not.toBeDefined(); 85 }); 86 87 it(`adds blocked permission when using short notation`, async () => { 88 const config = withInternalBlockedPermissions({ 89 slug: '', 90 name: '', 91 android: { 92 permissions: ['android.permission.ACCESS_FINE_LOCATION'], 93 blockedPermissions: ['ACCESS_FINE_LOCATION'], 94 }, 95 }); 96 97 const { modResults } = await (config as any).mods.android.manifest({ 98 modRequest: {}, 99 modResults: await getFixtureManifestAsync(), 100 }); 101 102 expect(modResults).toEqual({ 103 manifest: { 104 $: { 105 'xmlns:android': expect.any(String), 106 // Added tools 107 'xmlns:tools': 'http://schemas.android.com/tools', 108 }, 109 'uses-permission': [ 110 expect.anything(), 111 112 // Added two blocked permissions 113 { 114 $: { 115 'android:name': 'android.permission.ACCESS_FINE_LOCATION', 116 'tools:node': 'remove', 117 }, 118 }, 119 ], 120 queries: expect.anything(), 121 application: expect.anything(), 122 }, 123 }); 124 }); 125 126 it(`adds blocked permission when using long notation`, async () => { 127 const config = withInternalBlockedPermissions({ 128 slug: '', 129 name: '', 130 android: { 131 permissions: ['ACCESS_FINE_LOCATION'], 132 blockedPermissions: ['android.permission.ACCESS_FINE_LOCATION'], 133 }, 134 }); 135 136 const { modResults } = await (config as any).mods.android.manifest({ 137 modRequest: {}, 138 modResults: await getFixtureManifestAsync(), 139 }); 140 141 expect(modResults).toEqual({ 142 manifest: { 143 $: { 144 'xmlns:android': expect.any(String), 145 // Added tools 146 'xmlns:tools': 'http://schemas.android.com/tools', 147 }, 148 'uses-permission': [ 149 expect.anything(), 150 // Added two blocked permissions 151 { 152 $: { 153 'android:name': 'android.permission.ACCESS_FINE_LOCATION', 154 'tools:node': 'remove', 155 }, 156 }, 157 ], 158 queries: expect.anything(), 159 application: expect.anything(), 160 }, 161 }); 162 }); 163}); 164 165describe(addBlockedPermissions, () => { 166 it(`restricts an existing permission`, () => { 167 expect( 168 addBlockedPermissions( 169 { 170 manifest: { 171 $: { 172 'xmlns:android': '...', 173 }, 174 'uses-permission': [ 175 { 176 $: { 'android:name': 'dev.expo.foobar' }, 177 }, 178 { 179 $: { 'android:name': 'dev.expo.foobar-2' }, 180 }, 181 ], 182 }, 183 }, 184 ['dev.expo.foobar'] 185 ).manifest['uses-permission'] 186 ).toStrictEqual([ 187 { 188 $: { 'android:name': 'dev.expo.foobar-2' }, 189 }, 190 { 191 $: { 'android:name': 'dev.expo.foobar', 'tools:node': 'remove' }, 192 }, 193 ]); 194 }); 195 196 it(`restricts a new permission`, () => { 197 expect( 198 addBlockedPermissions( 199 { 200 manifest: { 201 $: { 202 'xmlns:android': '...', 203 }, 204 'uses-permission': [], 205 }, 206 }, 207 ['dev.expo.foobar'] 208 ).manifest['uses-permission'] 209 ).toStrictEqual([ 210 { 211 $: { 'android:name': 'dev.expo.foobar', 'tools:node': 'remove' }, 212 }, 213 ]); 214 }); 215}); 216 217describe('Android permissions', () => { 218 it(`returns empty array if no android permissions key is provided`, () => { 219 expect(getAndroidPermissions({})).toMatchObject([]); 220 }); 221 222 it(`returns android permissions if array is provided`, () => { 223 expect( 224 getAndroidPermissions({ android: { permissions: ['CAMERA', 'RECORD_AUDIO'] } }) 225 ).toMatchObject(['CAMERA', 'RECORD_AUDIO']); 226 }); 227 228 it('adds permissions if not present, does not duplicate permissions', async () => { 229 const givenPermissions = [ 230 'android.permission.READ_CONTACTS', 231 'com.android.launcher.permission.INSTALL_SHORTCUT', 232 'com.android.launcher.permission.INSTALL_SHORTCUT', 233 ]; 234 let androidManifestJson = await getFixtureManifestAsync(); 235 androidManifestJson = await setAndroidPermissions( 236 { android: { permissions: givenPermissions } }, 237 androidManifestJson 238 ); 239 240 const manifestPermissionsJSON = androidManifestJson.manifest['uses-permission']; 241 const manifestPermissions = manifestPermissionsJSON!.map((e) => e.$['android:name']); 242 243 // Account for INTERNET permission in fixture 244 // No duplicates 245 expect(manifestPermissions).toStrictEqual([ 246 'android.permission.INTERNET', 247 248 'android.permission.READ_CONTACTS', 249 'com.android.launcher.permission.INSTALL_SHORTCUT', 250 ]); 251 expect( 252 manifestPermissions.filter((e) => e === 'com.android.launcher.permission.INSTALL_SHORTCUT') 253 ).toHaveLength(1); 254 }); 255}); 256 257describe('Permissions', () => { 258 it(`adds a new permission`, async () => { 259 const manifest = await getFixtureManifestAsync(); 260 const didAdd = ensurePermission(manifest, 'EXPO_TEST_PERMISSION'); 261 const permissions = getPermissions(manifest); 262 expect(didAdd).toBe(true); 263 expect(permissions).toContain('android.permission.EXPO_TEST_PERMISSION'); 264 expect(permissions.length).toBe(2); 265 }); 266 267 it(`prevents adding a duplicate permission`, async () => { 268 const manifest = await getFixtureManifestAsync(); 269 const didAdd = ensurePermission(manifest, 'INTERNET'); 270 const permissions = getPermissions(manifest); 271 expect(didAdd).toBe(false); 272 expect(permissions).toContain('android.permission.INTERNET'); 273 expect(permissions.length).toBe(1); 274 }); 275 276 it(`ensures multiple permissions`, async () => { 277 const manifest = await getFixtureManifestAsync(); 278 const permissionsToAdd = ['VALUE_1', 'VALUE_2']; 279 const results = ensurePermissions(manifest, permissionsToAdd); 280 expect(results).toMatchSnapshot(); 281 expect(Object.values(results)).toStrictEqual([true, true]); 282 283 expect(getPermissions(manifest).length).toBe(3); 284 }); 285 286 it(`removes permissions by name`, async () => { 287 const manifest = await getFixtureManifestAsync(); 288 expect(ensurePermission(manifest, 'VALUE_TO_REMOVE_1')).toBe(true); 289 expect(ensurePermission(manifest, 'VALUE_TO_REMOVE_2')).toBe(true); 290 expect(getPermissions(manifest).length).toBe(3); 291 292 removePermissions(manifest, ['VALUE_TO_REMOVE_1', 'VALUE_TO_REMOVE_2']); 293 expect(getPermissions(manifest).length).toBe(1); 294 }); 295 296 it(`removes all permissions`, async () => { 297 const manifest = await getFixtureManifestAsync(); 298 expect(ensurePermission(manifest, 'VALUE_TO_REMOVE_1')).toBe(true); 299 expect(ensurePermission(manifest, 'VALUE_TO_REMOVE_2')).toBe(true); 300 expect(getPermissions(manifest).length).toBe(3); 301 302 removePermissions(manifest); 303 304 expect(getPermissions(manifest).length).toBe(0); 305 }); 306 307 it(`can write with a pretty format`, async () => { 308 const manifest = await getFixtureManifestAsync(); 309 expect(ensurePermission(manifest, 'NEW_PERMISSION_1')).toBe(true); 310 expect(ensurePermission(manifest, 'NEW_PERMISSION_2')).toBe(true); 311 expect(getPermissions(manifest).length).toBe(3); 312 313 expect(format(manifest)).toMatchSnapshot(); 314 }); 315}); 316