1import { fs } from 'memfs';
2import path from 'path';
3
4import * as ProjectDevices from '../devices';
5
6describe('devices info', () => {
7  let projectRoot: string;
8
9  beforeAll(() => {
10    projectRoot = path.join('/', 'tmp', 'xdl-project-settings');
11  });
12
13  afterEach(async () => {
14    await ProjectDevices.setDevicesInfoAsync(projectRoot, { devices: [] });
15  });
16
17  afterAll(() => {
18    if (projectRoot) {
19      fs.rmdirSync(projectRoot);
20    }
21  });
22
23  it('should persist device info to disk', async () => {
24    await ProjectDevices.saveDevicesAsync(projectRoot, 'test-device-id');
25
26    const file = path.join(projectRoot, '.expo', 'devices.json');
27    expect(fs.existsSync(file)).toBe(true);
28
29    const { devices } = JSON.parse(fs.readFileSync(file, 'utf8').toString());
30    expect(devices.length).toBe(1);
31    expect(devices[0].installationId).toBe('test-device-id');
32  });
33
34  it('should save an array of devices', async () => {
35    await ProjectDevices.saveDevicesAsync(projectRoot, ['device-id-1', 'device-id-2']);
36    const { devices } = await ProjectDevices.getDevicesInfoAsync(projectRoot);
37    expect(devices.length).toBe(2);
38    expect(devices.some((device) => device.installationId === 'device-id-1')).toBe(true);
39    expect(devices.some((device) => device.installationId === 'device-id-2')).toBe(true);
40  });
41
42  it('should save at most 10 devices', async () => {
43    const deviceIds = [];
44    for (let i = 0; i < 11; i++) {
45      deviceIds.push(`device-id-${i}`);
46    }
47    await ProjectDevices.saveDevicesAsync(projectRoot, deviceIds);
48    const { devices } = await ProjectDevices.getDevicesInfoAsync(projectRoot);
49    expect(devices.length).toBe(10);
50  });
51
52  it('should remove older devices if the total number exceeds 10', async () => {
53    const currentTime = new Date().getTime();
54    const earlierTime = currentTime - 10;
55    const earliestTime = currentTime - 20;
56
57    const devicesInfo = [{ installationId: 'oldest-device', lastUsed: earliestTime }];
58    for (let i = 0; i < 9; i++) {
59      devicesInfo.push({ installationId: `device-id-${i}`, lastUsed: earlierTime });
60    }
61    await ProjectDevices.setDevicesInfoAsync(projectRoot, {
62      devices: devicesInfo,
63    });
64
65    await ProjectDevices.saveDevicesAsync(projectRoot, 'newest-device');
66    const { devices } = await ProjectDevices.getDevicesInfoAsync(projectRoot);
67    expect(devices.length).toBe(10);
68    expect(devices[0].installationId).toBe('newest-device');
69    expect(devices.some((device) => device.installationId === 'oldest-device')).toBe(false);
70  });
71
72  it('should remove any devices last used before 30 days ago', async () => {
73    const currentTime = new Date().getTime();
74    const time30DaysAnd1SecondAgo = currentTime - 30 * 24 * 60 * 60 * 1000 - 1000;
75    await ProjectDevices.setDevicesInfoAsync(projectRoot, {
76      devices: [{ installationId: 'very-old-device-id', lastUsed: time30DaysAnd1SecondAgo }],
77    });
78    await ProjectDevices.saveDevicesAsync(projectRoot, 'new-device-id');
79    const { devices } = await ProjectDevices.getDevicesInfoAsync(projectRoot);
80    expect(devices.length).toBe(1);
81    expect(devices[0].installationId).toBe('new-device-id');
82  });
83
84  it('should remove old devices when reading for the first time from disk', async () => {
85    const currentTime = new Date().getTime();
86    const time30DaysAnd1SecondAgo = currentTime - 30 * 24 * 60 * 60 * 1000 - 1000;
87    await ProjectDevices.setDevicesInfoAsync(projectRoot, {
88      devices: [
89        { installationId: 'very-old-device-id', lastUsed: time30DaysAnd1SecondAgo },
90        { installationId: 'new-device-id', lastUsed: currentTime },
91      ],
92    });
93    // use readDeviceInfoAsync to bypass memoized devices and read from disk
94    const { devices } = await ProjectDevices.readDevicesInfoAsync(projectRoot);
95    expect(devices.length).toBe(1);
96    expect(devices[0].installationId).toBe('new-device-id');
97  });
98});
99