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