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(getUpdatesEnabled({})).toBe(true); 42 expect(getUpdatesTimeout({})).toBe(0); 43 expect(getUpdatesCodeSigningCertificate('/app', {})).toBe(undefined); 44 expect(getUpdatesCodeSigningMetadata({})).toBe(undefined); 45 expect(getUpdatesRequestHeaders({})).toBe(undefined); 46 }); 47 48 it(`returns correct value from all getters if value provided`, () => { 49 vol.fromJSON({ 50 '/app/hello': fsReal.readFileSync(sampleCodeSigningCertificatePath, 'utf-8'), 51 }); 52 53 expect(getSDKVersion({ sdkVersion: '37.0.0' })).toBe('37.0.0'); 54 expect(getUpdatesCheckOnLaunch({ updates: { checkAutomatically: 'ON_ERROR_RECOVERY' } })).toBe( 55 'NEVER' 56 ); 57 expect( 58 getUpdatesCheckOnLaunch({ updates: { checkAutomatically: 'ON_ERROR_RECOVERY' } }, '0.11.0') 59 ).toBe('ERROR_RECOVERY_ONLY'); 60 expect( 61 getUpdatesCheckOnLaunch({ updates: { checkAutomatically: 'ON_ERROR_RECOVERY' } }, '0.10.15') 62 ).toBe('NEVER'); 63 expect(getUpdatesCheckOnLaunch({ updates: { checkAutomatically: 'ON_LOAD' } })).toBe('ALWAYS'); 64 expect(getUpdatesCheckOnLaunch({ updates: { checkAutomatically: 'WIFI_ONLY' } })).toBe( 65 'WIFI_ONLY' 66 ); 67 expect(getUpdatesCheckOnLaunch({ updates: { checkAutomatically: 'NEVER' } })).toBe('NEVER'); 68 expect(getUpdatesCheckOnLaunch({ updates: {} })).toBe('ALWAYS'); 69 expect(getUpdatesEnabled({ updates: { enabled: false } })).toBe(false); 70 expect(getUpdatesTimeout({ updates: { fallbackToCacheTimeout: 2000 } })).toBe(2000); 71 expect( 72 getUpdatesCodeSigningCertificate('/app', { 73 updates: { 74 codeSigningCertificate: 'hello', 75 }, 76 }) 77 ).toBe(fsReal.readFileSync(sampleCodeSigningCertificatePath, 'utf-8')); 78 expect( 79 getUpdatesCodeSigningMetadataStringified({ 80 updates: { 81 codeSigningMetadata: { 82 alg: 'rsa-v1_5-sha256', 83 keyid: 'test', 84 }, 85 }, 86 }) 87 ).toBe( 88 JSON.stringify({ 89 alg: 'rsa-v1_5-sha256', 90 keyid: 'test', 91 }) 92 ); 93 expect( 94 getUpdatesCodeSigningMetadata({ 95 updates: { 96 codeSigningMetadata: { 97 alg: 'rsa-v1_5-sha256', 98 keyid: 'test', 99 }, 100 }, 101 }) 102 ).toMatchObject({ 103 alg: 'rsa-v1_5-sha256', 104 keyid: 'test', 105 }); 106 expect( 107 getUpdatesRequestHeadersStringified({ 108 updates: { 109 requestHeaders: { 110 'expo-channel-name': 'test', 111 testheader: 'test', 112 }, 113 }, 114 }) 115 ).toBe( 116 JSON.stringify({ 117 'expo-channel-name': 'test', 118 testheader: 'test', 119 }) 120 ); 121 expect( 122 getUpdatesRequestHeaders({ 123 updates: { 124 requestHeaders: { 125 'expo-channel-name': 'test', 126 testheader: 'test', 127 }, 128 }, 129 }) 130 ).toMatchObject({ 131 'expo-channel-name': 'test', 132 testheader: 'test', 133 }); 134 }); 135}); 136 137describe(getUpdateUrl, () => { 138 it(`returns correct default values from all getters if no value provided.`, () => { 139 const url = 'https://u.expo.dev/00000000-0000-0000-0000-000000000000'; 140 expect(getUpdateUrl({ updates: { url }, slug: 'foo' }, 'user')).toBe(url); 141 }); 142 143 it(`returns null if neither 'updates.url' or 'user' is supplied.`, () => { 144 expect(getUpdateUrl({ slug: 'foo' }, null)).toBe(null); 145 }); 146 147 it(`returns correct legacy urls if 'updates.url' is not provided, but 'slug' and ('username'|'owner') are provided.`, () => { 148 expect(getUpdateUrl({ slug: 'my-app' }, 'user')).toBe('https://exp.host/@user/my-app'); 149 expect(getUpdateUrl({ slug: 'my-app', owner: 'owner' }, 'user')).toBe( 150 'https://exp.host/@owner/my-app' 151 ); 152 expect(getUpdateUrl({ slug: 'my-app', owner: 'owner' }, null)).toBe( 153 'https://exp.host/@owner/my-app' 154 ); 155 }); 156}); 157 158describe(getNativeVersion, () => { 159 const version = '2.0.0'; 160 const versionCode = 42; 161 const buildNumber = '13'; 162 it('works for android', () => { 163 expect(getNativeVersion({ version, android: { versionCode } }, 'android')).toBe( 164 `${version}(${versionCode})` 165 ); 166 }); 167 it('works for ios', () => { 168 expect(getNativeVersion({ version, ios: { buildNumber } }, 'ios')).toBe( 169 `${version}(${buildNumber})` 170 ); 171 }); 172 it('throws an error if platform is not recognized', () => { 173 const fakePlatform = 'doesnotexist'; 174 expect(() => { 175 getNativeVersion({ version }, fakePlatform as any); 176 }).toThrow(`"${fakePlatform}" is not a supported platform. Choose either "ios" or "android".`); 177 }); 178 it('uses the default version if the version is missing', () => { 179 expect(getNativeVersion({}, 'ios')).toBe('1.0.0(1)'); 180 }); 181 it('uses the default buildNumber if the platform is ios and the buildNumber is missing', () => { 182 expect(getNativeVersion({ version }, 'ios')).toBe(`${version}(1)`); 183 }); 184 it('uses the default versionCode if the platform is android and the versionCode is missing', () => { 185 expect(getNativeVersion({ version }, 'android')).toBe(`${version}(1)`); 186 }); 187}); 188 189describe(getRuntimeVersion, () => { 190 it('works if the top level runtimeVersion is a string', () => { 191 const runtimeVersion = '42'; 192 expect(getRuntimeVersion({ runtimeVersion }, 'ios')).toBe(runtimeVersion); 193 }); 194 it('works if the platform specific runtimeVersion is a string', () => { 195 const runtimeVersion = '42'; 196 expect(getRuntimeVersion({ ios: { runtimeVersion } }, 'ios')).toBe(runtimeVersion); 197 }); 198 it('works if the runtimeVersion is a nativeVersion policy', () => { 199 const version = '1'; 200 const buildNumber = '2'; 201 expect( 202 getRuntimeVersion( 203 { version, runtimeVersion: { policy: 'nativeVersion' }, ios: { buildNumber } }, 204 'ios' 205 ) 206 ).toBe(`${version}(${buildNumber})`); 207 }); 208 it('works if the runtimeVersion is an appVersion policy', () => { 209 const version = '1'; 210 const buildNumber = '2'; 211 expect( 212 getRuntimeVersion( 213 { version, runtimeVersion: { policy: 'appVersion' }, ios: { buildNumber } }, 214 'ios' 215 ) 216 ).toBe(version); 217 }); 218 it('returns null if no runtime version is supplied', () => { 219 expect(getRuntimeVersion({}, 'ios')).toEqual(null); 220 }); 221 it('throws if runtime version is not parseable', () => { 222 expect(() => { 223 getRuntimeVersion({ runtimeVersion: 1 } as any, 'ios'); 224 }).toThrow( 225 `"1" is not a valid runtime version. getRuntimeVersion only supports a string, "sdkVersion", "appVersion", or "nativeVersion" policy.` 226 ); 227 expect(() => { 228 getRuntimeVersion({ runtimeVersion: { policy: 'unsupportedPlugin' } } as any, 'ios'); 229 }).toThrow( 230 `"{"policy":"unsupportedPlugin"}" is not a valid runtime version. getRuntimeVersion only supports a string, "sdkVersion", "appVersion", or "nativeVersion" policy.` 231 ); 232 }); 233}); 234