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