1import fs from 'fs'; 2import { vol } from 'memfs'; 3import path from 'path'; 4 5import { 6 getNativeVersion, 7 getRuntimeVersionAsync, 8 getSDKVersion, 9 getUpdatesCheckOnLaunch, 10 getUpdatesCodeSigningCertificate, 11 getUpdatesCodeSigningMetadata, 12 getUpdatesCodeSigningMetadataStringified, 13 getUpdatesRequestHeaders, 14 getUpdatesRequestHeadersStringified, 15 getUpdatesEnabled, 16 getUpdatesTimeout, 17 getUpdateUrl, 18} from '../Updates'; 19 20const fsReal = jest.requireActual('fs') as typeof fs; 21jest.mock('fs'); 22jest.mock('resolve-from'); 23 24const { silent } = require('resolve-from'); 25 26const fixturesPath = path.resolve(__dirname, 'fixtures'); 27const sampleCodeSigningCertificatePath = path.resolve(fixturesPath, 'codeSigningCertificate.pem'); 28 29console.warn = jest.fn(); 30 31describe('shared config getters', () => { 32 beforeEach(() => { 33 const resolveFrom = require('resolve-from'); 34 resolveFrom.silent = silent; 35 vol.reset(); 36 }); 37 38 it(`returns correct default values from all getters if no value provided`, () => { 39 expect(getSDKVersion({})).toBe(null); 40 expect(getUpdatesCheckOnLaunch({})).toBe('ALWAYS'); 41 expect(getUpdatesTimeout({})).toBe(0); 42 expect(getUpdatesCodeSigningCertificate('/app', {})).toBe(undefined); 43 expect(getUpdatesCodeSigningMetadata({})).toBe(undefined); 44 expect(getUpdatesRequestHeaders({})).toBe(undefined); 45 46 expect(getUpdatesEnabled({})).toBe(false); 47 expect(getUpdatesEnabled({ updates: {} })).toBe(false); 48 }); 49 50 it(`returns correct value from all getters if value provided`, () => { 51 vol.fromJSON({ 52 '/app/hello': fsReal.readFileSync(sampleCodeSigningCertificatePath, 'utf-8'), 53 }); 54 55 expect(getSDKVersion({ sdkVersion: '37.0.0' })).toBe('37.0.0'); 56 expect(getUpdatesCheckOnLaunch({ updates: { checkAutomatically: 'ON_ERROR_RECOVERY' } })).toBe( 57 'NEVER' 58 ); 59 expect( 60 getUpdatesCheckOnLaunch({ updates: { checkAutomatically: 'ON_ERROR_RECOVERY' } }, '0.11.0') 61 ).toBe('ERROR_RECOVERY_ONLY'); 62 expect( 63 getUpdatesCheckOnLaunch({ updates: { checkAutomatically: 'ON_ERROR_RECOVERY' } }, '0.10.15') 64 ).toBe('NEVER'); 65 expect(getUpdatesCheckOnLaunch({ updates: { checkAutomatically: 'ON_LOAD' } })).toBe('ALWAYS'); 66 expect(getUpdatesCheckOnLaunch({ updates: { checkAutomatically: 'WIFI_ONLY' } })).toBe( 67 'WIFI_ONLY' 68 ); 69 expect(getUpdatesCheckOnLaunch({ updates: { checkAutomatically: 'NEVER' } })).toBe('NEVER'); 70 expect(getUpdatesCheckOnLaunch({ updates: {} })).toBe('ALWAYS'); 71 expect(getUpdatesEnabled({ updates: { enabled: false } })).toBe(false); 72 expect(getUpdatesTimeout({ updates: { fallbackToCacheTimeout: 2000 } })).toBe(2000); 73 expect( 74 getUpdatesCodeSigningCertificate('/app', { 75 updates: { 76 codeSigningCertificate: 'hello', 77 }, 78 }) 79 ).toBe(fsReal.readFileSync(sampleCodeSigningCertificatePath, 'utf-8')); 80 expect( 81 getUpdatesCodeSigningMetadataStringified({ 82 updates: { 83 codeSigningMetadata: { 84 alg: 'rsa-v1_5-sha256', 85 keyid: 'test', 86 }, 87 }, 88 }) 89 ).toBe( 90 JSON.stringify({ 91 alg: 'rsa-v1_5-sha256', 92 keyid: 'test', 93 }) 94 ); 95 expect( 96 getUpdatesCodeSigningMetadata({ 97 updates: { 98 codeSigningMetadata: { 99 alg: 'rsa-v1_5-sha256', 100 keyid: 'test', 101 }, 102 }, 103 }) 104 ).toMatchObject({ 105 alg: 'rsa-v1_5-sha256', 106 keyid: 'test', 107 }); 108 expect( 109 getUpdatesRequestHeadersStringified({ 110 updates: { 111 requestHeaders: { 112 'expo-channel-name': 'test', 113 testheader: 'test', 114 }, 115 }, 116 }) 117 ).toBe( 118 JSON.stringify({ 119 'expo-channel-name': 'test', 120 testheader: 'test', 121 }) 122 ); 123 expect( 124 getUpdatesRequestHeaders({ 125 updates: { 126 requestHeaders: { 127 'expo-channel-name': 'test', 128 testheader: 'test', 129 }, 130 }, 131 }) 132 ).toMatchObject({ 133 'expo-channel-name': 'test', 134 testheader: 'test', 135 }); 136 }); 137}); 138 139describe(getUpdateUrl, () => { 140 it(`returns correct default values from all getters if no value provided.`, () => { 141 const url = 'https://u.expo.dev/00000000-0000-0000-0000-000000000000'; 142 expect(getUpdateUrl({ updates: { url } })).toBe(url); 143 }); 144 145 it(`returns correct legacy urls if 'updates.url' is not provided, but 'slug' and ('username'|'owner') are provided and useClassicUpdates is false.`, () => { 146 expect(getUpdateUrl({})).toBe(null); 147 }); 148}); 149 150describe(getNativeVersion, () => { 151 const version = '2.0.0'; 152 const versionCode = 42; 153 const buildNumber = '13'; 154 it('works for android', () => { 155 expect(getNativeVersion({ version, android: { versionCode } }, 'android')).toBe( 156 `${version}(${versionCode})` 157 ); 158 }); 159 it('works for ios', () => { 160 expect(getNativeVersion({ version, ios: { buildNumber } }, 'ios')).toBe( 161 `${version}(${buildNumber})` 162 ); 163 }); 164 it('throws an error if platform is not recognized', () => { 165 const fakePlatform = 'doesnotexist'; 166 expect(() => { 167 getNativeVersion({ version }, fakePlatform as any); 168 }).toThrow(`"${fakePlatform}" is not a supported platform. Choose either "ios" or "android".`); 169 }); 170 it('uses the default version if the version is missing', () => { 171 expect(getNativeVersion({}, 'ios')).toBe('1.0.0(1)'); 172 }); 173 it('uses the default buildNumber if the platform is ios and the buildNumber is missing', () => { 174 expect(getNativeVersion({ version }, 'ios')).toBe(`${version}(1)`); 175 }); 176 it('uses the default versionCode if the platform is android and the versionCode is missing', () => { 177 expect(getNativeVersion({ version }, 'android')).toBe(`${version}(1)`); 178 }); 179}); 180 181describe(getRuntimeVersionAsync, () => { 182 it('works if the top level runtimeVersion is a string', async () => { 183 const runtimeVersion = '42'; 184 expect(await getRuntimeVersionAsync('', { runtimeVersion }, 'ios')).toBe(runtimeVersion); 185 }); 186 it('works if the platform specific runtimeVersion is a string', async () => { 187 const runtimeVersion = '42'; 188 expect(await getRuntimeVersionAsync('', { ios: { runtimeVersion } }, 'ios')).toBe( 189 runtimeVersion 190 ); 191 }); 192 it('works if the runtimeVersion is a nativeVersion policy', async () => { 193 const version = '1'; 194 const buildNumber = '2'; 195 expect( 196 await getRuntimeVersionAsync( 197 '', 198 { version, runtimeVersion: { policy: 'nativeVersion' }, ios: { buildNumber } }, 199 'ios' 200 ) 201 ).toBe(`${version}(${buildNumber})`); 202 }); 203 it('works if the runtimeVersion is an appVersion policy', async () => { 204 const version = '1'; 205 const buildNumber = '2'; 206 expect( 207 await getRuntimeVersionAsync( 208 '', 209 { version, runtimeVersion: { policy: 'appVersion' }, ios: { buildNumber } }, 210 'ios' 211 ) 212 ).toBe(version); 213 }); 214 it('returns null if no runtime version is supplied', async () => { 215 expect(await getRuntimeVersionAsync('', {}, 'ios')).toEqual(null); 216 }); 217 it('throws if runtime version is not parseable', async () => { 218 await expect(getRuntimeVersionAsync('', { runtimeVersion: 1 } as any, 'ios')).rejects.toThrow( 219 `"1" is not a valid runtime version. getRuntimeVersionAsync only supports a string, "sdkVersion", "appVersion", "nativeVersion" or "fingerprintExperimental" policy.` 220 ); 221 await expect( 222 getRuntimeVersionAsync('', { runtimeVersion: { policy: 'unsupportedPlugin' } } as any, 'ios') 223 ).rejects.toThrow( 224 `"{"policy":"unsupportedPlugin"}" is not a valid runtime version. getRuntimeVersionAsync only supports a string, "sdkVersion", "appVersion", "nativeVersion" or "fingerprintExperimental" policy.` 225 ); 226 }); 227}); 228