1import fs from 'fs'; 2import { vol } from 'memfs'; 3import path from 'path'; 4 5import { 6 getNativeVersion, 7 getRuntimeVersion, 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(getRuntimeVersion, () => { 182 it('works if the top level runtimeVersion is a string', () => { 183 const runtimeVersion = '42'; 184 expect(getRuntimeVersion({ runtimeVersion }, 'ios')).toBe(runtimeVersion); 185 }); 186 it('works if the platform specific runtimeVersion is a string', () => { 187 const runtimeVersion = '42'; 188 expect(getRuntimeVersion({ ios: { runtimeVersion } }, 'ios')).toBe(runtimeVersion); 189 }); 190 it('works if the runtimeVersion is a nativeVersion policy', () => { 191 const version = '1'; 192 const buildNumber = '2'; 193 expect( 194 getRuntimeVersion( 195 { version, runtimeVersion: { policy: 'nativeVersion' }, ios: { buildNumber } }, 196 'ios' 197 ) 198 ).toBe(`${version}(${buildNumber})`); 199 }); 200 it('works if the runtimeVersion is an appVersion policy', () => { 201 const version = '1'; 202 const buildNumber = '2'; 203 expect( 204 getRuntimeVersion( 205 { version, runtimeVersion: { policy: 'appVersion' }, ios: { buildNumber } }, 206 'ios' 207 ) 208 ).toBe(version); 209 }); 210 it('returns null if no runtime version is supplied', () => { 211 expect(getRuntimeVersion({}, 'ios')).toEqual(null); 212 }); 213 it('throws if runtime version is not parseable', () => { 214 expect(() => { 215 getRuntimeVersion({ runtimeVersion: 1 } as any, 'ios'); 216 }).toThrow( 217 `"1" is not a valid runtime version. getRuntimeVersion only supports a string, "sdkVersion", "appVersion", or "nativeVersion" policy.` 218 ); 219 expect(() => { 220 getRuntimeVersion({ runtimeVersion: { policy: 'unsupportedPlugin' } } as any, 'ios'); 221 }).toThrow( 222 `"{"policy":"unsupportedPlugin"}" is not a valid runtime version. getRuntimeVersion only supports a string, "sdkVersion", "appVersion", or "nativeVersion" policy.` 223 ); 224 }); 225}); 226