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