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': '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
82describe(addBlockedPermissions, () => {
83  it(`restricts an existing permission`, () => {
84    expect(
85      addBlockedPermissions(
86        {
87          manifest: {
88            $: {
89              'xmlns:android': '...',
90            },
91            'uses-permission': [
92              {
93                $: { 'android:name': 'foobar' },
94              },
95              {
96                $: { 'android:name': 'foobar-2' },
97              },
98            ],
99          },
100        },
101        ['foobar']
102      ).manifest['uses-permission']
103    ).toStrictEqual([
104      {
105        $: { 'android:name': 'foobar-2' },
106      },
107      {
108        $: { 'android:name': 'foobar', 'tools:node': 'remove' },
109      },
110    ]);
111  });
112
113  it(`restricts a new permission`, () => {
114    expect(
115      addBlockedPermissions(
116        {
117          manifest: {
118            $: {
119              'xmlns:android': '...',
120            },
121            'uses-permission': [],
122          },
123        },
124        ['foobar']
125      ).manifest['uses-permission']
126    ).toStrictEqual([
127      {
128        $: { 'android:name': 'foobar', 'tools:node': 'remove' },
129      },
130    ]);
131  });
132});
133
134describe('Android permissions', () => {
135  it(`returns empty array if no android permissions key is provided`, () => {
136    expect(getAndroidPermissions({})).toMatchObject([]);
137  });
138
139  it(`returns android permissions if array is provided`, () => {
140    expect(
141      getAndroidPermissions({ android: { permissions: ['CAMERA', 'RECORD_AUDIO'] } })
142    ).toMatchObject(['CAMERA', 'RECORD_AUDIO']);
143  });
144
145  it('adds permissions if not present, does not duplicate permissions', async () => {
146    const givenPermissions = [
147      'android.permission.READ_CONTACTS',
148      'com.android.launcher.permission.INSTALL_SHORTCUT',
149      'com.android.launcher.permission.INSTALL_SHORTCUT',
150    ];
151    let androidManifestJson = await readAndroidManifestAsync(sampleManifestPath);
152    androidManifestJson = await setAndroidPermissions(
153      { android: { permissions: givenPermissions } },
154      androidManifestJson
155    );
156
157    const manifestPermissionsJSON = androidManifestJson.manifest['uses-permission'];
158    const manifestPermissions = manifestPermissionsJSON.map((e) => e.$['android:name']);
159
160    // Account for INTERNET permission in fixture
161    // No duplicates
162    expect(manifestPermissions).toStrictEqual([
163      'android.permission.INTERNET',
164      'android.permission.READ_CONTACTS',
165      'com.android.launcher.permission.INSTALL_SHORTCUT',
166    ]);
167    expect(
168      manifestPermissions.filter((e) => e === 'com.android.launcher.permission.INSTALL_SHORTCUT')
169    ).toHaveLength(1);
170  });
171});
172
173describe('Permissions', () => {
174  it(`adds a new permission`, async () => {
175    const manifest = await readAndroidManifestAsync(sampleManifestPath);
176    const didAdd = ensurePermission(manifest, 'EXPO_TEST_PERMISSION');
177    const permissions = getPermissions(manifest);
178    expect(didAdd).toBe(true);
179    expect(permissions).toContain('android.permission.EXPO_TEST_PERMISSION');
180    expect(permissions.length).toBe(2);
181  });
182
183  it(`prevents adding a duplicate permission`, async () => {
184    const manifest = await readAndroidManifestAsync(sampleManifestPath);
185    const didAdd = ensurePermission(manifest, 'INTERNET');
186    const permissions = getPermissions(manifest);
187    expect(didAdd).toBe(false);
188    expect(permissions).toContain('android.permission.INTERNET');
189    expect(permissions.length).toBe(1);
190  });
191
192  it(`ensures multiple permissions`, async () => {
193    const manifest = await readAndroidManifestAsync(sampleManifestPath);
194    const permissionsToAdd = ['VALUE_1', 'VALUE_2'];
195    const results = ensurePermissions(manifest, permissionsToAdd);
196    expect(results).toMatchSnapshot();
197    expect(Object.values(results)).toStrictEqual([true, true]);
198
199    expect(getPermissions(manifest).length).toBe(3);
200  });
201
202  it(`removes permissions by name`, async () => {
203    const manifest = await readAndroidManifestAsync(sampleManifestPath);
204    expect(ensurePermission(manifest, 'VALUE_TO_REMOVE_1')).toBe(true);
205    expect(ensurePermission(manifest, 'VALUE_TO_REMOVE_2')).toBe(true);
206    expect(getPermissions(manifest).length).toBe(3);
207
208    removePermissions(manifest, ['VALUE_TO_REMOVE_1', 'VALUE_TO_REMOVE_2']);
209    expect(getPermissions(manifest).length).toBe(1);
210  });
211
212  it(`removes all permissions`, async () => {
213    const manifest = await readAndroidManifestAsync(sampleManifestPath);
214    expect(ensurePermission(manifest, 'VALUE_TO_REMOVE_1')).toBe(true);
215    expect(ensurePermission(manifest, 'VALUE_TO_REMOVE_2')).toBe(true);
216    expect(getPermissions(manifest).length).toBe(3);
217
218    removePermissions(manifest);
219
220    expect(getPermissions(manifest).length).toBe(0);
221  });
222
223  it(`can write with a pretty format`, async () => {
224    const manifest = await readAndroidManifestAsync(sampleManifestPath);
225    expect(ensurePermission(manifest, 'NEW_PERMISSION_1')).toBe(true);
226    expect(ensurePermission(manifest, 'NEW_PERMISSION_2')).toBe(true);
227    expect(getPermissions(manifest).length).toBe(3);
228
229    expect(format(manifest)).toMatchSnapshot();
230  });
231});
232