|
| 1 | +// GSTACK_IOS_LAUNCH_ENV (split from #1796, credit @Bmathews721): a JSON object |
| 2 | +// of string to string forwarded to `devicectl device process launch |
| 3 | +// --environment-variables`. An invalid value refuses the launch with |
| 4 | +// IOS_LAUNCH_ENV_INVALID instead of launching the app without it. |
| 5 | + |
| 6 | +import { afterEach, beforeEach, describe, expect, test } from 'bun:test'; |
| 7 | +import type { SpawnSyncReturns } from 'child_process'; |
| 8 | +import { launchApp, launchEnvArgs, parseLaunchEnv, type SpawnImpl } from '../src/devicectl'; |
| 9 | + |
| 10 | +function recorder(status = 0): { spawn: SpawnImpl; calls: string[][] } { |
| 11 | + const calls: string[][] = []; |
| 12 | + const spawn: SpawnImpl = (cmd, args) => { |
| 13 | + calls.push([cmd, ...args]); |
| 14 | + return { status, stdout: Buffer.from(''), stderr: Buffer.from(''), pid: 1, output: [], signal: null } as unknown as SpawnSyncReturns<Buffer>; |
| 15 | + }; |
| 16 | + return { spawn, calls }; |
| 17 | +} |
| 18 | + |
| 19 | +let saved: string | undefined; |
| 20 | +beforeEach(() => { saved = process.env.GSTACK_IOS_LAUNCH_ENV; delete process.env.GSTACK_IOS_LAUNCH_ENV; }); |
| 21 | +afterEach(() => { if (saved === undefined) delete process.env.GSTACK_IOS_LAUNCH_ENV; else process.env.GSTACK_IOS_LAUNCH_ENV = saved; }); |
| 22 | + |
| 23 | +describe('GSTACK_IOS_LAUNCH_ENV', () => { |
| 24 | + test('unset or empty leaves the launch argv unchanged', () => { |
| 25 | + for (const value of [undefined, '', ' ', '{}']) { |
| 26 | + if (value === undefined) delete process.env.GSTACK_IOS_LAUNCH_ENV; else process.env.GSTACK_IOS_LAUNCH_ENV = value; |
| 27 | + const { spawn, calls } = recorder(); |
| 28 | + expect(launchApp('UDID', 'com.test', spawn)).toEqual({ ok: true }); |
| 29 | + expect(calls).toEqual([['xcrun', 'devicectl', 'device', 'process', 'launch', '--device', 'UDID', 'com.test']]); |
| 30 | + } |
| 31 | + }); |
| 32 | + |
| 33 | + test('a JSON object is forwarded before the bundle id', () => { |
| 34 | + process.env.GSTACK_IOS_LAUNCH_ENV = '{"DEBUG_BRIDGE":"1","API_BASE":"http://localhost:8080"}'; |
| 35 | + const launch = recorder(); |
| 36 | + expect(launchApp('UDID', 'com.test', launch.spawn)).toEqual({ ok: true }); |
| 37 | + expect(launch.calls[0]).toEqual(['xcrun', 'devicectl', 'device', 'process', 'launch', '--device', 'UDID', |
| 38 | + '--environment-variables', '{"DEBUG_BRIDGE":"1","API_BASE":"http://localhost:8080"}', 'com.test']); |
| 39 | + expect(launchEnvArgs()).toEqual({ ok: true, args: ['--environment-variables', '{"DEBUG_BRIDGE":"1","API_BASE":"http://localhost:8080"}'] }); |
| 40 | + expect(launchEnvArgs('')).toEqual({ ok: true, args: [] }); |
| 41 | + }); |
| 42 | + |
| 43 | + test('invalid JSON, a non-object, a non-string value, a bad name or a bridge-reserved key refuses without launching', () => { |
| 44 | + const cases: Array<[string, string]> = [ |
| 45 | + ['{"A":', 'is not valid JSON'], |
| 46 | + ['["A=1"]', 'must be a JSON object of string to string'], |
| 47 | + ['null', 'must be a JSON object of string to string'], |
| 48 | + ['{"PORT":8080}', 'PORT is a number, not a string'], |
| 49 | + ['{"BAD-NAME":"x"}', '"BAD-NAME" is not an environment variable name'], |
| 50 | + ['{"GSTACK_IOS_QA_PORT":"1"}', 'GSTACK_IOS_QA_PORT collides with the debug bridge\'s reserved GSTACK_ variables'], |
| 51 | + ]; |
| 52 | + for (const [value, detail] of cases) { |
| 53 | + process.env.GSTACK_IOS_LAUNCH_ENV = value; |
| 54 | + const { spawn, calls } = recorder(); |
| 55 | + const r = launchApp('UDID', 'com.test', spawn); |
| 56 | + expect(r.ok).toBe(false); |
| 57 | + expect(r.error).toContain(detail); |
| 58 | + expect(r.error).toEndWith('(IOS_LAUNCH_ENV_INVALID)'); |
| 59 | + expect(calls).toEqual([]); |
| 60 | + } |
| 61 | + }); |
| 62 | + |
| 63 | + test('a refusal never echoes a value', () => { |
| 64 | + process.env.GSTACK_IOS_LAUNCH_ENV = '{"TOKEN":"sk-secret-value","N":1}'; |
| 65 | + const r = launchApp('UDID', 'com.test', recorder().spawn); |
| 66 | + expect(r.error).not.toContain('sk-secret-value'); |
| 67 | + expect(parseLaunchEnv('{"TOKEN": "sk-secret-value"')).toEqual({ ok: false, detail: expect.not.stringContaining('sk-secret-value') }); |
| 68 | + }); |
| 69 | +}); |
0 commit comments